[automerger skipped] Merge "Import translations. DO NOT MERGE ANYWHERE" into rvc-qpr-dev am: edb1a79ae7 -s ours am: 1b064c226b -s ours am: bd69265146 -s ours

am skip reason: subject contains skip directive

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/services/Car/+/15804718

Change-Id: I9bc43d437566285cc54d4ea1aa79d2d76afed03a
diff --git a/Android.mk b/Android.mk
index 8b1efbf..0d59692 100644
--- a/Android.mk
+++ b/Android.mk
@@ -15,5 +15,8 @@
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
+# Include car_ui_portrait
+include $(LOCAL_PATH)/car_product/car_ui_portrait/Android.mk
+
 # Include the sub-makefiles
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java
index aa98215..23ed24d 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -904,6 +904,25 @@
     public static final String CAR_EXTRA_BROWSE_SERVICE_FOR_SESSION =
             "android.media.session.BROWSE_SERVICE";
 
+    /**
+     * If some specific Activity should be launched on the designated TDA all the time, include this
+     * integer extra in the first launching Intent and ActivityOption with the launch TDA.
+     * If the value is {@link #LAUNCH_PERSISTENT_ADD}, CarLaunchParamsModifier will memorize
+     * the Activity and the TDA pair, and assign the TDA in the following Intents for the Activity.
+     * If there is any assigned Activity on the TDA, it'll be replaced with the new Activity.
+     * If the value is {@Link #LAUNCH_PERSISTENT_DELETE}, it'll remove the stored info for the given
+     * Activity.
+     *
+     * @hide
+     */
+    public static final String CAR_EXTRA_LAUNCH_PERSISTENT =
+            "android.car.intent.extra.launchparams.PERSISTENT";
+
+    /** @hide */
+    public static final int LAUNCH_PERSISTENT_DELETE = 0;
+    /** @hide */
+    public static final int LAUNCH_PERSISTENT_ADD = 1;
+
     /** @hide */
     public static final String CAR_SERVICE_INTERFACE_NAME = CommonConstants.CAR_SERVICE_INTERFACE;
 
diff --git a/car-lib/src/android/car/CarAppFocusManager.java b/car-lib/src/android/car/CarAppFocusManager.java
index cc0d10b..7a6d1f3 100644
--- a/car-lib/src/android/car/CarAppFocusManager.java
+++ b/car-lib/src/android/car/CarAppFocusManager.java
@@ -17,6 +17,7 @@
 package android.car;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -28,6 +29,7 @@
 import java.lang.ref.WeakReference;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -221,6 +223,22 @@
     }
 
     /**
+     * Returns the package names of the current owner of a given application type, or {@code null}
+     * if there is no owner. This method might return more than one package name if the current
+     * owner uses the "android:sharedUserId" feature.
+     *
+     * @hide
+     */
+    @Nullable
+    public List<String> getAppTypeOwner(@AppFocusType int appType) {
+        try {
+            return mService.getAppTypeOwner(appType);
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, null);
+        }
+    }
+
+    /**
      * Checks if listener is associated with active a focus
      * @param callback
      * @param appType
diff --git a/car-lib/src/android/car/IAppFocus.aidl b/car-lib/src/android/car/IAppFocus.aidl
index f3d6d1f..118ad3b 100644
--- a/car-lib/src/android/car/IAppFocus.aidl
+++ b/car-lib/src/android/car/IAppFocus.aidl
@@ -30,4 +30,5 @@
     int requestAppFocus(IAppFocusOwnershipCallback callback, int appType) = 4;
     /** callback used as a token */
     void abandonAppFocus(IAppFocusOwnershipCallback callback, int appType) = 5;
+    List<String> getAppTypeOwner(int appType) = 6;
 }
diff --git a/car-lib/src/android/car/ICarUserService.aidl b/car-lib/src/android/car/ICarUserService.aidl
index 267044d..bee3bba 100644
--- a/car-lib/src/android/car/ICarUserService.aidl
+++ b/car-lib/src/android/car/ICarUserService.aidl
@@ -40,8 +40,8 @@
     List<UserInfo> getPassengers(int driverId);
     boolean startPassenger(int passengerId, int zoneId);
     boolean stopPassenger(int passengerId);
-    void setLifecycleListenerForUid(in IResultReceiver listener);
-    void resetLifecycleListenerForUid();
+    void setLifecycleListenerForApp(String pkgName, in IResultReceiver listener);
+    void resetLifecycleListenerForApp(in IResultReceiver listener);
     UserIdentificationAssociationResponse getUserIdentificationAssociation(in int[] types);
     void setUserIdentificationAssociation(int timeoutMs, in int[] types, in int[] values,
       in AndroidFuture<UserIdentificationAssociationResponse> result);
diff --git a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
index fe697b1..008fc4b 100644
--- a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
+++ b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.Service;
 import android.car.Car;
@@ -152,7 +153,7 @@
                 String packageName) {
             try {
                 ProviderInfo[] providers = packageManager.getPackageInfo(packageName,
-                        PackageManager.GET_PROVIDERS).providers;
+                        PackageManager.GET_PROVIDERS | PackageManager.MATCH_ANY_USER).providers;
                 if (providers == null) {
                     return Collections.emptyList();
                 }
@@ -370,8 +371,16 @@
         }
     }
 
+    /**
+     * Returns the cluster activity from the application given by its package name.
+     *
+     * @return the {@link ComponentName} of the cluster activity, or null if the given application
+     * doesn't have a cluster activity.
+     *
+     * @hide
+     */
     @Nullable
-    private ComponentName getComponentFromPackage(@NonNull String packageName) {
+    public ComponentName getComponentFromPackage(@NonNull String packageName) {
         PackageManager packageManager = getPackageManager();
 
         // Check package permission.
@@ -385,8 +394,8 @@
         Intent intent = new Intent(Intent.ACTION_MAIN)
                 .addCategory(Car.CAR_CATEGORY_NAVIGATION)
                 .setPackage(packageName);
-        List<ResolveInfo> resolveList = packageManager.queryIntentActivities(intent,
-                PackageManager.GET_RESOLVED_FILTER);
+        List<ResolveInfo> resolveList = packageManager.queryIntentActivitiesAsUser(intent,
+                PackageManager.GET_RESOLVED_FILTER, ActivityManager.getCurrentUser());
         if (resolveList == null || resolveList.isEmpty()
                 || resolveList.get(0).getComponentInfo() == null) {
             Log.i(TAG, "Failed to resolve an intent: " + intent);
diff --git a/car-lib/src/android/car/input/CarInputManager.java b/car-lib/src/android/car/input/CarInputManager.java
index 8199bd0..7eddcf8 100644
--- a/car-lib/src/android/car/input/CarInputManager.java
+++ b/car-lib/src/android/car/input/CarInputManager.java
@@ -264,8 +264,10 @@
      * same {@link CarInputManager} instance, then only the last registered callback will receive
      * events, even if they were registered for different input event types.
      *
-     * @throws SecurityException is caller doesn't have android.car.permission.CAR_MONITOR_INPUT
-     *                           permission granted
+     * @throws SecurityException if caller doesn't have
+     *                           {@code android.car.permission.CAR_MONITOR_INPUT} permission
+     *                           granted. Currently this method also accept
+     *                           {@code android.permission.MONITOR_INPUT}
      * @throws IllegalArgumentException if targetDisplayType parameter correspond to a non supported
      *                                  display type
      * @throws IllegalArgumentException if inputTypes parameter contains invalid or non supported
@@ -292,6 +294,10 @@
      * CarInputCaptureCallback)} except that callbacks are invoked using
      * the executor passed as parameter.
      *
+     * @throws SecurityException if caller doesn't have
+     *                           {@code android.permission.MONITOR_INPUT} permission
+     *                           granted. Currently this method also accept
+     *                           {@code android.car.permission.CAR_MONITOR_INPUT}
      * @param targetDisplayType the display type to register callback against
      * @param inputTypes the input type to register callback against
      * @param requestFlags the capture request flag
diff --git a/car-lib/src/android/car/telemetry/CarTelemetryManager.java b/car-lib/src/android/car/telemetry/CarTelemetryManager.java
index 3d3cf50..3c4c187 100644
--- a/car-lib/src/android/car/telemetry/CarTelemetryManager.java
+++ b/car-lib/src/android/car/telemetry/CarTelemetryManager.java
@@ -45,7 +45,7 @@
 
     private static final boolean DEBUG = false;
     private static final String TAG = CarTelemetryManager.class.getSimpleName();
-    private static final int MANIFEST_MAX_SIZE_BYTES = 10 * 1024; // 10 kb
+    private static final int METRICS_CONFIG_MAX_SIZE_BYTES = 10 * 1024; // 10 kb
 
     private final CarTelemetryServiceListener mCarTelemetryServiceListener =
             new CarTelemetryServiceListener(this);
@@ -58,49 +58,51 @@
     private Executor mExecutor;
 
     /**
-     * Status to indicate that manifest was added successfully.
+     * Status to indicate that MetricsConfig was added successfully.
      */
-    public static final int ERROR_NONE = 0;
+    public static final int ERROR_METRICS_CONFIG_NONE = 0;
 
     /**
-     * Status to indicate that add manifest failed because the same manifest based on the
+     * Status to indicate that add MetricsConfig failed because the same MetricsConfig based on the
      * ManifestKey already exists.
      */
-    public static final int ERROR_SAME_MANIFEST_EXISTS = 1;
+    public static final int ERROR_METRICS_CONFIG_ALREADY_EXISTS = 1;
 
     /**
-     * Status to indicate that add manifest failed because a newer version of the manifest exists.
+     * Status to indicate that add MetricsConfig failed because a newer version of the MetricsConfig
+     * exists.
      */
-    public static final int ERROR_NEWER_MANIFEST_EXISTS = 2;
+    public static final int ERROR_METRICS_CONFIG_VERSION_TOO_OLD = 2;
 
     /**
-     * Status to indicate that add manifest failed because CarTelemetryService is unable to parse
-     * the given byte array into a Manifest.
+     * Status to indicate that add MetricsConfig failed because CarTelemetryService is unable to
+     * parse the given byte array into a MetricsConfig.
      */
-    public static final int ERROR_PARSE_MANIFEST_FAILED = 3;
+    public static final int ERROR_METRICS_CONFIG_PARSE_FAILED = 3;
 
     /**
-     * Status to indicate that add manifest failed because of failure to verify the signature of
-     * the manifest.
+     * Status to indicate that add MetricsConfig failed because of failure to verify the signature
+     * of the MetricsConfig.
      */
-    public static final int ERROR_SIGNATURE_VERIFICATION_FAILED = 4;
+    public static final int ERROR_METRICS_CONFIG_SIGNATURE_VERIFICATION_FAILED = 4;
 
     /**
-     * Status to indicate that add manifest failed because of a general error in cars.
+     * Status to indicate that add MetricsConfig failed because of a general error in cars.
      */
-    public static final int ERROR_UNKNOWN = 5;
+    public static final int ERROR_METRICS_CONFIG_UNKNOWN = 5;
 
     /** @hide */
-    @IntDef(prefix = {"ERROR_"}, value = {
-            ERROR_NONE,
-            ERROR_SAME_MANIFEST_EXISTS,
-            ERROR_NEWER_MANIFEST_EXISTS,
-            ERROR_PARSE_MANIFEST_FAILED,
-            ERROR_SIGNATURE_VERIFICATION_FAILED,
-            ERROR_UNKNOWN
+    @IntDef(prefix = {"ERROR_METRICS_CONFIG_"}, value = {
+            ERROR_METRICS_CONFIG_NONE,
+            ERROR_METRICS_CONFIG_ALREADY_EXISTS,
+            ERROR_METRICS_CONFIG_VERSION_TOO_OLD,
+            ERROR_METRICS_CONFIG_PARSE_FAILED,
+            ERROR_METRICS_CONFIG_SIGNATURE_VERIFICATION_FAILED,
+            ERROR_METRICS_CONFIG_UNKNOWN
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface AddManifestError {}
+    public @interface MetricsConfigError {
+    }
 
     /**
      * Application registers {@link CarTelemetryResultsListener} object to receive data from
@@ -110,22 +112,39 @@
      */
     public interface CarTelemetryResultsListener {
         /**
-         * Called by {@link com.android.car.telemetry.CarTelemetryService} to send script result to
-         * the client.
+         * Sends script results to the client. Called by {@link CarTelemetryServiceListener}.
+         *
          * TODO(b/184964661): Publish the documentation for the format of the results.
          *
-         * @param key the {@link ManifestKey} that the result is associated with.
-         * @param result the serialized car telemetry result.
+         * @param key    the {@link MetricsConfigKey} that the result is associated with.
+         * @param result the car telemetry result as serialized bytes.
          */
-        void onResult(@NonNull ManifestKey key, @NonNull byte[] result);
+        void onResult(@NonNull MetricsConfigKey key, @NonNull byte[] result);
 
         /**
-         * Called by {@link com.android.car.telemetry.CarTelemetryService} to send error message to
-         * the client.
+         * Sends script execution errors to the client.
          *
+         * @param key   the {@link MetricsConfigKey} that the error is associated with
          * @param error the serialized car telemetry error.
          */
-        void onError(@NonNull byte[] error);
+        void onError(@NonNull MetricsConfigKey key, @NonNull byte[] error);
+
+        /**
+         * Sends the {@link #addMetricsConfig(MetricsConfigKey, byte[])} status to the client.
+         *
+         * @param key        the {@link MetricsConfigKey} that the status is associated with
+         * @param statusCode See {@link MetricsConfigError}.
+         */
+        void onAddMetricsConfigStatus(@NonNull MetricsConfigKey key,
+                @MetricsConfigError int statusCode);
+
+        /**
+         * Sends the {@link #removeMetricsConfig(MetricsConfigKey)} status to the client.
+         *
+         * @param key     the {@link MetricsConfigKey} that the status is associated with
+         * @param success true for successful removal, false otherwise.
+         */
+        void onRemoveMetricsConfigStatus(@NonNull MetricsConfigKey key, boolean success);
     }
 
     /**
@@ -141,7 +160,7 @@
         }
 
         @Override
-        public void onResult(@NonNull ManifestKey key, @NonNull byte[] result) {
+        public void onResult(@NonNull MetricsConfigKey key, @NonNull byte[] result) {
             CarTelemetryManager manager = mManager.get();
             if (manager == null) {
                 return;
@@ -150,27 +169,66 @@
         }
 
         @Override
-        public void onError(@NonNull byte[] error) {
+        public void onError(@NonNull MetricsConfigKey key, @NonNull byte[] error) {
             CarTelemetryManager manager = mManager.get();
             if (manager == null) {
                 return;
             }
-            manager.onError(error);
+            manager.onError(key, error);
+        }
+
+        @Override
+        public void onAddMetricsConfigStatus(@NonNull MetricsConfigKey key,
+                @MetricsConfigError int statusCode) {
+            CarTelemetryManager manager = mManager.get();
+            if (manager == null) {
+                return;
+            }
+            manager.onAddMetricsConfigStatus(key, statusCode);
+        }
+
+        @Override
+        public void onRemoveMetricsConfigStatus(@NonNull MetricsConfigKey key, boolean success) {
+            CarTelemetryManager manager = mManager.get();
+            if (manager == null) {
+                return;
+            }
+            manager.onRemoveMetricsConfigStatus(key, success);
         }
     }
 
-    private void onResult(ManifestKey key, byte[] result) {
+    private void onResult(MetricsConfigKey key, byte[] result) {
         long token = Binder.clearCallingIdentity();
         synchronized (mLock) {
+            // TODO(b/198824696): listener should be nonnull
             mExecutor.execute(() -> mResultsListener.onResult(key, result));
         }
         Binder.restoreCallingIdentity(token);
     }
 
-    private void onError(byte[] error) {
+    private void onError(MetricsConfigKey key, byte[] error) {
         long token = Binder.clearCallingIdentity();
         synchronized (mLock) {
-            mExecutor.execute(() -> mResultsListener.onError(error));
+            // TODO(b/198824696): listener should be nonnull
+            mExecutor.execute(() -> mResultsListener.onError(key, error));
+        }
+        Binder.restoreCallingIdentity(token);
+    }
+
+    private void onAddMetricsConfigStatus(MetricsConfigKey key, int statusCode) {
+        long token = Binder.clearCallingIdentity();
+        synchronized (mLock) {
+            // TODO(b/198824696): listener should be nonnull
+            mExecutor.execute(() -> mResultsListener.onAddMetricsConfigStatus(key, statusCode));
+        }
+        Binder.restoreCallingIdentity(token);
+    }
+
+    private void onRemoveMetricsConfigStatus(MetricsConfigKey key, boolean success) {
+        long token = Binder.clearCallingIdentity();
+        synchronized (mLock) {
+            // TODO(b/198824696): listener should be nonnull
+            mExecutor.execute(() -> mResultsListener.onRemoveMetricsConfigStatus(key, success));
         }
         Binder.restoreCallingIdentity(token);
     }
@@ -209,7 +267,6 @@
      *
      * @param listener to received data from {@link com.android.car.telemetry.CarTelemetryService}.
      * @throws IllegalStateException if the listener is already set.
-     *
      * @hide
      */
     @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
@@ -249,77 +306,75 @@
     }
 
     /**
-     * Called by client to send telemetry manifest. The size of the manifest cannot exceed a
-     * predefined size. Otherwise an exception is thrown.
-     * 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. All cache and intermediate results will be cleared if replaced.
-     * TODO(b/185420981): Update javadoc after CarTelemetryService has concrete implementation.
+     * Sends a telemetry MetricsConfig to CarTelemetryService. The size of the MetricsConfig cannot
+     * exceed a predefined size, otherwise an exception is thrown.
+     * The {@link MetricsConfigKey} is used to uniquely identify a MetricsConfig. If a MetricsConfig
+     * of the same name already exists in {@link com.android.car.telemetry.CarTelemetryService},
+     * the config version will be compared. If the version is strictly higher, the existing
+     * MetricsConfig will be replaced by the new one. All cache and intermediate results will be
+     * cleared if replaced.
+     * The status of this API is sent back asynchronously via {@link CarTelemetryResultsListener}.
      *
-     * @param key      the unique key to identify the manifest.
-     * @param manifest the serialized bytes of a Manifest object.
-     * @return {@link #AddManifestError} to tell the result of the request.
-     * @throws IllegalArgumentException if the manifest size exceeds limit.
-     *
+     * @param key           the unique key to identify the MetricsConfig.
+     * @param metricsConfig the serialized bytes of a MetricsConfig object.
+     * @throws IllegalArgumentException if the MetricsConfig size exceeds limit.
      * @hide
      */
     @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
-    public @AddManifestError int addManifest(@NonNull ManifestKey key, @NonNull byte[] manifest) {
-        if (manifest.length > MANIFEST_MAX_SIZE_BYTES) {
-            throw new IllegalArgumentException("Manifest size exceeds limit.");
+    public void addMetricsConfig(@NonNull MetricsConfigKey key, @NonNull byte[] metricsConfig) {
+        if (metricsConfig.length > METRICS_CONFIG_MAX_SIZE_BYTES) {
+            throw new IllegalArgumentException("MetricsConfig size exceeds limit.");
         }
         try {
-            return mService.addManifest(key, manifest);
+            mService.addMetricsConfig(key, metricsConfig);
         } catch (RemoteException e) {
             handleRemoteExceptionFromCarService(e);
         }
-        return ERROR_UNKNOWN;
     }
 
     /**
-     * Removes a manifest from {@link com.android.car.telemetry.CarTelemetryService}. If the
-     * manifest does not exist, nothing will be removed but the status will be indicated in the
-     * return value.
+     * Removes a MetricsConfig from {@link com.android.car.telemetry.CarTelemetryService}. If the
+     * MetricsConfig does not exist, nothing will be removed.
+     * The status of this API is sent back asynchronously via {@link CarTelemetryResultsListener}.
      *
-     * @param key the unique key to identify the manifest. Name and version must be exact.
+     * @param key the unique key to identify the MetricsConfig. Name and version must be exact.
      * @return true for success, false otherwise.
      * @hide
      */
     @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
-    public boolean removeManifest(@NonNull ManifestKey key) {
+    public void removeMetricsConfig(@NonNull MetricsConfigKey key) {
         try {
-            return mService.removeManifest(key);
+            mService.removeMetricsConfig(key);
         } catch (RemoteException e) {
             handleRemoteExceptionFromCarService(e);
         }
-        return false;
     }
 
     /**
-     * Removes all manifests from {@link com.android.car.telemetry.CarTelemetryService}.
+     * Removes all MetricsConfigs from {@link com.android.car.telemetry.CarTelemetryService}.
      *
      * @hide
      */
     @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
-    public void removeAllManifests() {
+    public void removeAllMetricsConfigs() {
         try {
-            mService.removeAllManifests();
+            mService.removeAllMetricsConfigs();
         } catch (RemoteException e) {
             handleRemoteExceptionFromCarService(e);
         }
     }
 
     /**
-     * An asynchronous API for the client to get script execution results of a specific manifest
-     * from the {@link com.android.car.telemetry.CarTelemetryService} through the listener.
+     * Gets script execution results of a MetricsConfig as from the
+     * {@link com.android.car.telemetry.CarTelemetryService}. This API is asynchronous and the
+     * result is sent back asynchronously via the {@link CarTelemetryResultsListener}.
      * This call is destructive. The returned results will be deleted from CarTelemetryService.
      *
-     * @param key the unique key to identify the manifest.
+     * @param key the unique key to identify the MetricsConfig.
      * @hide
      */
     @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
-    public void sendFinishedReports(@NonNull ManifestKey key) {
+    public void sendFinishedReports(@NonNull MetricsConfigKey key) {
         try {
             mService.sendFinishedReports(key);
         } catch (RemoteException e) {
@@ -328,8 +383,8 @@
     }
 
     /**
-     * An asynchronous API for the client to get all script execution results
-     * from the {@link com.android.car.telemetry.CarTelemetryService} through the listener.
+     * Gets all script execution results from {@link com.android.car.telemetry.CarTelemetryService}
+     * asynchronously via the {@link CarTelemetryResultsListener}.
      * This call is destructive. The returned results will be deleted from CarTelemetryService.
      *
      * @hide
@@ -342,20 +397,4 @@
             handleRemoteExceptionFromCarService(e);
         }
     }
-
-    /**
-     * An asynchronous API for the client to get all script execution errors
-     * from the {@link com.android.car.telemetry.CarTelemetryService} through the listener.
-     * This call is destructive. The returned results will be deleted from CarTelemetryService.
-     *
-     * @hide
-     */
-    @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
-    public void sendScriptExecutionErrors() {
-        try {
-            mService.sendScriptExecutionErrors();
-        } catch (RemoteException e) {
-            handleRemoteExceptionFromCarService(e);
-        }
-    }
 }
diff --git a/car-lib/src/android/car/telemetry/ICarTelemetryService.aidl b/car-lib/src/android/car/telemetry/ICarTelemetryService.aidl
index 09743d8..49472f4 100644
--- a/car-lib/src/android/car/telemetry/ICarTelemetryService.aidl
+++ b/car-lib/src/android/car/telemetry/ICarTelemetryService.aidl
@@ -1,7 +1,7 @@
 package android.car.telemetry;
 
 import android.car.telemetry.ICarTelemetryServiceListener;
-import android.car.telemetry.ManifestKey;
+import android.car.telemetry.MetricsConfigKey;
 
 /**
  * Internal binder interface for {@code CarTelemetryService}, used by {@code CarTelemetryManager}.
@@ -21,33 +21,28 @@
     void clearListener();
 
     /**
-     * Sends telemetry manifests to CarTelemetryService.
+     * Sends telemetry MetricsConfigs to CarTelemetryService.
      */
-    int addManifest(in ManifestKey key, in byte[] manifest);
+    void addMetricsConfig(in MetricsConfigKey key, in byte[] metricsConfig);
 
     /**
-     * Removes a manifest based on the key.
+     * Removes a MetricsConfig based on the key.
      */
-    boolean removeManifest(in ManifestKey key);
+    void removeMetricsConfig(in MetricsConfigKey key);
 
     /**
-     * Removes all manifests.
+     * Removes all MetricsConfigs.
      */
-    void removeAllManifests();
+    void removeAllMetricsConfigs();
 
     /**
-     * Sends script results associated with the given key using the
+     * Sends script results or errors associated with the given key using the
      * {@code ICarTelemetryServiceListener}.
      */
-    void sendFinishedReports(in ManifestKey key);
+    void sendFinishedReports(in MetricsConfigKey key);
 
     /**
-     * Sends all script results associated using the {@code ICarTelemetryServiceListener}.
+     * Sends all script results or errors using the {@code ICarTelemetryServiceListener}.
      */
     void sendAllFinishedReports();
-
-    /**
-     * Sends all errors using the {@code ICarTelemetryServiceListener}.
-     */
-    void sendScriptExecutionErrors();
 }
\ No newline at end of file
diff --git a/car-lib/src/android/car/telemetry/ICarTelemetryServiceListener.aidl b/car-lib/src/android/car/telemetry/ICarTelemetryServiceListener.aidl
index ba4ca2d..4bd61fd 100644
--- a/car-lib/src/android/car/telemetry/ICarTelemetryServiceListener.aidl
+++ b/car-lib/src/android/car/telemetry/ICarTelemetryServiceListener.aidl
@@ -16,7 +16,7 @@
 
 package android.car.telemetry;
 
-import android.car.telemetry.ManifestKey;
+import android.car.telemetry.MetricsConfigKey;
 import java.util.List;
 
 /**
@@ -34,13 +34,29 @@
      * @param key the key that the result is associated with.
      * @param result the serialized bytes of the script execution result message.
      */
-    void onResult(in ManifestKey key, in byte[] result);
+    void onResult(in MetricsConfigKey key, in byte[] result);
 
     /**
-     * Called by {@code CarTelemetryService} to provide telemetry errors. This call is destrutive.
+     * Called by {@code CarTelemetryService} to provide telemetry errors. This call is destructive.
      * The parameter will no longer be stored in {@code CarTelemetryService}.
      *
      * @param error the serialized bytes of an error message.
      */
-    void onError(in byte[] error);
+    void onError(in MetricsConfigKey key, in byte[] error);
+
+    /**
+     * Sends the {@link #addMetricsConfig(MetricsConfigKey, byte[])} status to the client.
+     *
+     * @param key the {@link MetricsConfigKey} that the status is associated with.
+     * @param statusCode indicating add status.
+     */
+     void onAddMetricsConfigStatus(in MetricsConfigKey key, in int statusCode);
+
+    /**
+     * Sends the {@link #remove(MetricsConfigKey)} status to the client.
+     *
+     * @param key the {@link MetricsConfigKey} that the status is associated with.
+     * @param success true for successful removal, false otherwise.
+     */
+     void onRemoveMetricsConfigStatus(in MetricsConfigKey key, in boolean success);
 }
\ No newline at end of file
diff --git a/car-lib/src/android/car/telemetry/IScriptExecutor.aidl b/car-lib/src/android/car/telemetry/IScriptExecutor.aidl
deleted file mode 100644
index 83ea3f0..0000000
--- a/car-lib/src/android/car/telemetry/IScriptExecutor.aidl
+++ /dev/null
@@ -1,43 +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 android.car.telemetry;
-
-import android.car.telemetry.IScriptExecutorListener;
-import android.os.Bundle;
-
-/**
- * An internal API provided by isolated Script Executor process
- * for executing Lua scripts in a sandboxed environment
- *
- * @hide
- */
-interface IScriptExecutor {
-  /**
-   * Executes a specified function in provided Lua script with given input arguments.
-   *
-   * @param scriptBody complete body of Lua script that also contains the function to be invoked
-   * @param functionName the name of the function to execute
-   * @param publishedData input data provided by the source which the function handles
-   * @param savedState key-value pairs preserved from the previous invocation of the function
-   * @param listener callback for the sandboxed environent to report back script execution results, errors, and logs
-   */
-  void invokeScript(String scriptBody,
-                    String functionName,
-                    in Bundle publishedData,
-                    in @nullable Bundle savedState,
-                    in IScriptExecutorListener listener);
-}
diff --git a/car-lib/src/android/car/telemetry/IScriptExecutorListener.aidl b/car-lib/src/android/car/telemetry/IScriptExecutorListener.aidl
deleted file mode 100644
index d751a61..0000000
--- a/car-lib/src/android/car/telemetry/IScriptExecutorListener.aidl
+++ /dev/null
@@ -1,73 +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 android.car.telemetry;
-
-import android.os.Bundle;
-
-/**
- * Listener for {@code IScriptExecutor#invokeScript}.
- *
- * An invocation of a script by Script Executor will result in a call of only one
- * of the three methods below. If a script fully completes its objective, onScriptFinished
- * is called. If a script's invocation completes normally, onSuccess is called.
- * onError is called if any error happens before or during script execution and we
- * should abandon this run of the script.
- */
-interface IScriptExecutorListener {
-  /**
-   * Called by ScriptExecutor when the script declares itself as "finished".
-   *
-   * @param result final results of the script that will be uploaded.
-   */
-  void onScriptFinished(in byte[] result);
-
-  /**
-   * Called by ScriptExecutor when a function completes successfully and also provides
-   * optional state that the script wants CarTelemetryService to persist.
-   *
-   * @param stateToPersist key-value pairs to persist
-   */
-  void onSuccess(in @nullable Bundle stateToPersist);
-
-  /**
-   * Default error type.
-   */
-  const int ERROR_TYPE_UNSPECIFIED = 0;
-
-  /**
-   * Used when an error occurs in the ScriptExecutor code.
-   */
-  const int ERROR_TYPE_SCRIPT_EXECUTOR_ERROR = 1;
-
-  /**
-   * Used when an error occurs while executing the Lua script (such as
-   * errors returned by lua_pcall)
-   */
-  const int ERROR_TYPE_LUA_RUNTIME_ERROR = 2;
-
-
-  /**
-   * Called by ScriptExecutor to report errors that prevented the script
-   * from running or completing execution successfully.
-   *
-   * @param errorType type of the error message as defined in this aidl file.
-   * @param messsage the human-readable message containing information helpful for analysis or debugging.
-   * @param stackTrace the stack trace of the error if available.
-   */
-  void onError(int errorType, String message, @nullable String stackTrace);
-}
-
diff --git a/car-lib/src/android/car/telemetry/ManifestKey.aidl b/car-lib/src/android/car/telemetry/ManifestKey.aidl
deleted file mode 100644
index 25097df..0000000
--- a/car-lib/src/android/car/telemetry/ManifestKey.aidl
+++ /dev/null
@@ -1,22 +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 android.car.telemetry;
-
-/**
- * @hide
- */
-parcelable ManifestKey;
\ No newline at end of file
diff --git a/car-lib/src/android/car/telemetry/ManifestKey.java b/car-lib/src/android/car/telemetry/ManifestKey.java
deleted file mode 100644
index b0a69c2..0000000
--- a/car-lib/src/android/car/telemetry/ManifestKey.java
+++ /dev/null
@@ -1,76 +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 android.car.telemetry;
-
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * A parcelable that wraps around the Manifest name and version.
- *
- * @hide
- */
-public final class ManifestKey implements Parcelable {
-
-    @NonNull
-    private String mName;
-    private int mVersion;
-
-    @NonNull
-    public String getName() {
-        return mName;
-    }
-
-    public int getVersion() {
-        return mVersion;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel out, int flags) {
-        out.writeString(mName);
-        out.writeInt(mVersion);
-    }
-
-    private ManifestKey(Parcel in) {
-        mName = in.readString();
-        mVersion = in.readInt();
-    }
-
-    public ManifestKey(@NonNull String name, int version) {
-        mName = name;
-        mVersion = version;
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    public static final @NonNull Parcelable.Creator<ManifestKey> CREATOR =
-            new Parcelable.Creator<ManifestKey>() {
-                @Override
-                public ManifestKey createFromParcel(Parcel in) {
-                    return new ManifestKey(in);
-                }
-
-                @Override
-                public ManifestKey[] newArray(int size) {
-                    return new ManifestKey[size];
-                }
-            };
-}
diff --git a/car-lib/src/android/car/telemetry/MetricsConfigKey.aidl b/car-lib/src/android/car/telemetry/MetricsConfigKey.aidl
new file mode 100644
index 0000000..2c00127
--- /dev/null
+++ b/car-lib/src/android/car/telemetry/MetricsConfigKey.aidl
@@ -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.
+ */
+
+package android.car.telemetry;
+
+/**
+ * @hide
+ */
+parcelable MetricsConfigKey;
\ No newline at end of file
diff --git a/car-lib/src/android/car/telemetry/MetricsConfigKey.java b/car-lib/src/android/car/telemetry/MetricsConfigKey.java
new file mode 100644
index 0000000..6e4614b
--- /dev/null
+++ b/car-lib/src/android/car/telemetry/MetricsConfigKey.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 android.car.telemetry;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A parcelable that wraps around the Manifest name and version.
+ *
+ * @hide
+ */
+public final class MetricsConfigKey implements Parcelable {
+
+    @NonNull
+    private String mName;
+    private int mVersion;
+
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    public int getVersion() {
+        return mVersion;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeString(mName);
+        out.writeInt(mVersion);
+    }
+
+    private MetricsConfigKey(Parcel in) {
+        mName = in.readString();
+        mVersion = in.readInt();
+    }
+
+    public MetricsConfigKey(@NonNull String name, int version) {
+        mName = name;
+        mVersion = version;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Parcelable.Creator<MetricsConfigKey> CREATOR =
+            new Parcelable.Creator<MetricsConfigKey>() {
+                @Override
+                public MetricsConfigKey createFromParcel(Parcel in) {
+                    return new MetricsConfigKey(in);
+                }
+
+                @Override
+                public MetricsConfigKey[] newArray(int size) {
+                    return new MetricsConfigKey[size];
+                }
+            };
+}
diff --git a/car-lib/src/android/car/user/CarUserManager.java b/car-lib/src/android/car/user/CarUserManager.java
index dace5a8..f42da45 100644
--- a/car-lib/src/android/car/user/CarUserManager.java
+++ b/car-lib/src/android/car/user/CarUserManager.java
@@ -307,13 +307,20 @@
     public @interface UserIdentificationAssociationValue{}
 
     private final Object mLock = new Object();
+
     private final ICarUserService mService;
     private final UserManager mUserManager;
 
+    /**
+     * Map of listeners registers by the app.
+     */
     @Nullable
     @GuardedBy("mLock")
     private ArrayMap<UserLifecycleListener, Executor> mListeners;
 
+    /**
+     * Receiver used to receive user-lifecycle callbacks from the service.
+     */
     @Nullable
     @GuardedBy("mLock")
     private LifecycleResultReceiver mReceiver;
@@ -332,6 +339,7 @@
     public CarUserManager(@NonNull Car car, @NonNull ICarUserService service,
             @NonNull UserManager userManager) {
         super(car);
+
         mService = service;
         mUserManager = userManager;
     }
@@ -524,20 +532,31 @@
         Objects.requireNonNull(listener, "listener cannot be null");
 
         int uid = myUid();
+        String packageName = getContext().getPackageName();
+        if (DBG) {
+            Log.d(TAG, "addListener(): uid=" + uid + ", pkg=" + packageName
+                    + ", listener=" + listener);
+        }
         synchronized (mLock) {
             Preconditions.checkState(mListeners == null || !mListeners.containsKey(listener),
                     "already called for this listener");
             if (mReceiver == null) {
                 mReceiver = new LifecycleResultReceiver();
                 try {
-                    EventLog.writeEvent(EventLogTags.CAR_USER_MGR_ADD_LISTENER, uid);
-                    if (DBG) Log.d(TAG, "Setting lifecycle receiver for uid " + uid);
-                    mService.setLifecycleListenerForUid(mReceiver);
+                    EventLog.writeEvent(EventLogTags.CAR_USER_MGR_ADD_LISTENER, uid, packageName);
+                    if (DBG) {
+                        Log.d(TAG, "Setting lifecycle receiver for uid " + uid + " and package "
+                                + packageName);
+                    }
+                    mService.setLifecycleListenerForApp(packageName, mReceiver);
                 } catch (RemoteException e) {
                     handleRemoteExceptionFromCarService(e);
                 }
             } else {
-                if (DBG) Log.d(TAG, "Already set receiver for uid " + uid);
+                if (DBG) {
+                    Log.d(TAG, "Already set receiver for uid " + uid + " and package "
+                            + packageName);
+                }
             }
 
             if (mListeners == null) {
@@ -547,7 +566,7 @@
                         + " already has " + mListeners.size() + " listeners: "
                         + mListeners.keySet().stream()
                                 .map((l) -> getLambdaName(l))
-                                .collect(Collectors.toList()), new Exception());
+                                .collect(Collectors.toList()), new Exception("caller's stack"));
             }
             if (DBG) Log.d(TAG, "Adding listener: " + listener);
             mListeners.put(listener, executor);
@@ -568,6 +587,11 @@
         Objects.requireNonNull(listener, "listener cannot be null");
 
         int uid = myUid();
+        String packageName = getContext().getPackageName();
+        if (DBG) {
+            Log.d(TAG, "removeListener(): uid=" + uid + ", pkg=" + packageName
+                    + ", listener=" + listener);
+        }
         synchronized (mLock) {
             Preconditions.checkState(mListeners != null && mListeners.containsKey(listener),
                     "not called for this listener yet");
@@ -584,10 +608,13 @@
                 return;
             }
 
-            EventLog.writeEvent(EventLogTags.CAR_USER_MGR_REMOVE_LISTENER, uid);
-            if (DBG) Log.d(TAG, "Removing lifecycle receiver for uid=" + uid);
+            EventLog.writeEvent(EventLogTags.CAR_USER_MGR_REMOVE_LISTENER, uid, packageName);
+            if (DBG) {
+                Log.d(TAG, "Removing lifecycle receiver for uid=" + uid + " and package "
+                        + packageName);
+            }
             try {
-                mService.resetLifecycleListenerForUid();
+                mService.resetLifecycleListenerForApp(mReceiver);
                 mReceiver = null;
             } catch (RemoteException e) {
                 handleRemoteExceptionFromCarService(e);
diff --git a/car-lib/src/android/car/watchdog/CarWatchdogManager.java b/car-lib/src/android/car/watchdog/CarWatchdogManager.java
index aa930a0..007bb22 100644
--- a/car-lib/src/android/car/watchdog/CarWatchdogManager.java
+++ b/car-lib/src/android/car/watchdog/CarWatchdogManager.java
@@ -410,8 +410,8 @@
     /**
      * Returns resource overuse stats for a specific user package.
      *
-     * @param packageName Name of the package whose stats should to be returned.
-     * @param userId ID of the user whose stats should be returned.
+     * @param packageName Name of the package whose stats should be returned.
+     * @param userHandle Handle of the user whose stats should be returned.
      * @param resourceOveruseFlag Flag to indicate the types of resource overuse stats to return.
      * @param maxStatsPeriod Maximum period to aggregate the resource overuse stats.
      *
@@ -635,7 +635,10 @@
      * exception. This API may be used by CarSettings application or UI notification.
      *
      * @param packageName Name of the package whose setting should to be updated.
-     * @param userHandle  User whose setting should to be updated.
+     *                    Note: All packages under shared UID share the killable state as well. Thus
+     *                    setting the killable state for one package will set the killable state for
+     *                    all other packages that share a UID.
+     * @param userHandle  User whose setting should be updated.
      * @param isKillable  Whether or not the package for the specified user is killable on resource
      *                    overuse.
      *
@@ -657,7 +660,7 @@
      *
      * <p>This API may be used by CarSettings application or UI notification.
      *
-     * @param userHandle User whose killable states for all packages should to be returned.
+     * @param userHandle User whose killable states for all packages should be returned.
      *
      * @hide
      */
diff --git a/car-lib/src/android/car/watchdog/IoOveruseConfiguration.java b/car-lib/src/android/car/watchdog/IoOveruseConfiguration.java
index 608c3c3..2e23d46 100644
--- a/car-lib/src/android/car/watchdog/IoOveruseConfiguration.java
+++ b/car-lib/src/android/car/watchdog/IoOveruseConfiguration.java
@@ -48,6 +48,10 @@
     /**
      * Package specific thresholds only for system and vendor packages.
      *
+     * NOTE: For packages that share a UID, the package name should be the shared package name
+     * because the I/O usage is aggregated for all packages under the shared UID. The shared
+     * package names should have the prefix 'shared:'.
+     *
      * <p>System component must provide package specific thresholds only for system packages.
      * <p>Vendor component must provide package specific thresholds only for vendor packages.
      */
diff --git a/car-lib/src/android/car/watchdog/ResourceOveruseConfiguration.java b/car-lib/src/android/car/watchdog/ResourceOveruseConfiguration.java
index 03a972a..a411a0d 100644
--- a/car-lib/src/android/car/watchdog/ResourceOveruseConfiguration.java
+++ b/car-lib/src/android/car/watchdog/ResourceOveruseConfiguration.java
@@ -76,8 +76,10 @@
     /**
      * List of system or vendor packages that are safe to be killed on resource overuse.
      *
-     * <p>System component must provide only safe-to-kill system packages in this list.
-     * <p>Vendor component must provide only safe-to-kill vendor packages in this list.
+     * <p>When specifying shared package names, the package names should contain the prefix
+     * 'shared:'.
+     * <p>System components must provide only safe-to-kill system packages in this list.
+     * <p>Vendor components must provide only safe-to-kill vendor packages in this list.
      */
     private @NonNull List<String> mSafeToKillPackages;
 
@@ -87,6 +89,9 @@
      * <p>Any pre-installed package name starting with one of the prefixes or any package from the
      * vendor partition is identified as a vendor package and vendor provided thresholds are applied
      * to these packages. This list must be provided only by the vendor component.
+     *
+     * <p>When specifying shared package name prefixes, the prefix should contain 'shared:' at
+     * the beginning.
      */
     private @NonNull List<String> mVendorPackagePrefixes;
 
@@ -97,6 +102,10 @@
      * <p>This mapping must contain only packages that can be mapped to one of the
      * {@link ApplicationCategoryType} types. This mapping must be defined only by the system and
      * vendor components.
+     *
+     * <p>For packages under a shared UID, the application category type must be specified
+     * for the shared package name and not for individual packages under the shared UID. When
+     * specifying shared package names, the package names should contain the prefix 'shared:'.
      */
     private @NonNull Map<String, String> mPackagesToAppCategoryTypes;
 
diff --git a/car-lib/src/android/car/watchdog/ResourceOveruseStats.java b/car-lib/src/android/car/watchdog/ResourceOveruseStats.java
index 992cff2..d9b639f 100644
--- a/car-lib/src/android/car/watchdog/ResourceOveruseStats.java
+++ b/car-lib/src/android/car/watchdog/ResourceOveruseStats.java
@@ -33,6 +33,9 @@
 public final class ResourceOveruseStats implements Parcelable {
     /**
      * Name of the package, whose stats are recorded in the below fields.
+     *
+     * NOTE: For packages that share a UID, the package name will be the shared package name because
+     *       the stats are aggregated for all packages under the shared UID.
      */
     private @NonNull String mPackageName;
 
diff --git a/car-lib/src/com/android/car/internal/common/EventLogTags.logtags b/car-lib/src/com/android/car/internal/common/EventLogTags.logtags
index 65a3fb1..45fdfe9 100644
--- a/car-lib/src/com/android/car/internal/common/EventLogTags.logtags
+++ b/car-lib/src/com/android/car/internal/common/EventLogTags.logtags
@@ -69,8 +69,8 @@
 150100 car_user_svc_initial_user_info_req (request_type|1),(timeout|1)
 150101 car_user_svc_initial_user_info_resp (status|1),(action|1),(user_id|1),(flags|1),(safe_name|3),(user_locales|3)
 150103 car_user_svc_set_initial_user (user_id|1)
-150104 car_user_svc_set_lifecycle_listener (uid|1)
-150105 car_user_svc_reset_lifecycle_listener (uid|1)
+150104 car_user_svc_set_lifecycle_listener (uid|1),(package_name|3)
+150105 car_user_svc_reset_lifecycle_listener (uid|1),(package_name|3)
 150106 car_user_svc_switch_user_req (user_id|1),(timeout|1)
 150107 car_user_svc_switch_user_resp (hal_callback_status|1),(user_switch_status|1),(error_message|3)
 150108 car_user_svc_post_switch_user_req (target_user_id|1),(current_user_id|1)
@@ -86,7 +86,7 @@
 150118 car_user_svc_create_user_user_removed (user_id|1),(reason|3)
 150119 car_user_svc_remove_user_req (user_id|1),(hasCallerRestrictions|1)
 150120 car_user_svc_remove_user_resp (user_id|1),(result|1)
-150121 car_user_svc_notify_app_lifecycle_listener (uid|1),(event_type|1),(from_user_id|1),(to_user_id|1)
+150121 car_user_svc_notify_app_lifecycle_listener (uid|1),(package_name|3),(event_type|1),(from_user_id|1),(to_user_id|1)
 150122 car_user_svc_notify_internal_lifecycle_listener (listener_name|3),(event_type|1),(from_user_id|1),(to_user_id|1)
 150123 car_user_svc_pre_creation_requested (number_users|1),(number_guests|1)
 150124 car_user_svc_pre_creation_status (number_existing_users|1),(number_users_to_add|1),(number_users_to_remove|1),(number_existing_guests|1),(number_guests_to_add|1),(number_guests_to_remove|1),(number_invalid_users_to_remove|1)
@@ -111,8 +111,8 @@
 150152 car_user_hal_create_user_resp (request_id|1),(status|1),(result|1),(error_message|3)
 150153 car_user_hal_remove_user_req (target_user_id|1),(current_user_id|1)
 
-150171 car_user_mgr_add_listener (uid|1)
-150172 car_user_mgr_remove_listener (uid|1)
+150171 car_user_mgr_add_listener (uid|1),(package_name|3)
+150172 car_user_mgr_remove_listener (uid|1),(package_name|3)
 150173 car_user_mgr_disconnected (uid|1)
 150174 car_user_mgr_switch_user_req (uid|1),(user_id|1)
 150175 car_user_mgr_switch_user_resp (uid|1),(status|1),(error_message|3)
diff --git a/car-test-lib/src/android/car/testapi/BlockingUserLifecycleListener.java b/car-test-lib/src/android/car/testapi/BlockingUserLifecycleListener.java
index 627aa86..3693ebc 100644
--- a/car-test-lib/src/android/car/testapi/BlockingUserLifecycleListener.java
+++ b/car-test-lib/src/android/car/testapi/BlockingUserLifecycleListener.java
@@ -54,6 +54,8 @@
 
     private static final long DEFAULT_TIMEOUT_MS = 2_000;
 
+    private static int sNextId;
+
     private final Object mLock = new Object();
 
     private final CountDownLatch mLatch = new CountDownLatch(1);
@@ -79,6 +81,8 @@
 
     private final long mTimeoutMs;
 
+    private final int mId = ++sNextId;
+
     private BlockingUserLifecycleListener(Builder builder) {
         mExpectedEventTypes = Collections
                 .unmodifiableList(new ArrayList<>(builder.mExpectedEventTypes));
@@ -276,7 +280,7 @@
     @NonNull
     private String stateToString() {
         synchronized (mLock) {
-            return "timeout=" + mTimeoutMs + "ms"
+            return "id=" + mId + ",timeout=" + mTimeoutMs + "ms"
                     + ",expectedEventTypes=" + toString(mExpectedEventTypes)
                     + ",expectedEventTypesLeft=" + toString(mExpectedEventTypesLeft)
                     + (expectingSpecificUser() ? ",forUser=" + mForUserId : "")
diff --git a/car-test-lib/src/android/car/testapi/CarTelemetryController.java b/car-test-lib/src/android/car/testapi/CarTelemetryController.java
deleted file mode 100644
index e4df3f3..0000000
--- a/car-test-lib/src/android/car/testapi/CarTelemetryController.java
+++ /dev/null
@@ -1,48 +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 android.car.testapi;
-
-import android.car.telemetry.ManifestKey;
-
-/**
- * Controller to manipulate and verify {@link android.car.telemetry.CarTelemetryManager} in
- * unit tests.
- */
-public interface CarTelemetryController {
-    /**
-     * Returns {@code true} if a {@link
-     * android.car.telemetry.CarTelemetryManager.CarTelemetryResultsListener} is
-     * registered with the manager, otherwise returns {@code false}.
-     */
-    boolean isListenerSet();
-
-    /**
-     * Returns the number of valid manifests registered with the manager.
-     */
-    int getValidManifestsCount();
-
-    /**
-     * Associate a blob of data with the given key, used for testing the flush reports APIs.
-     */
-    void addDataForKey(ManifestKey key, byte[] data);
-
-    /**
-     * Configure the blob of data to be flushed with the
-     * {@code FakeCarTelemetryService#flushScriptExecutionErrors()} API.
-     */
-    void setErrorData(byte[] error);
-}
diff --git a/car-test-lib/src/android/car/testapi/FakeCar.java b/car-test-lib/src/android/car/testapi/FakeCar.java
index 0508ed0..0356774 100644
--- a/car-test-lib/src/android/car/testapi/FakeCar.java
+++ b/car-test-lib/src/android/car/testapi/FakeCar.java
@@ -127,14 +127,6 @@
         return mService.mCarUxRestrictionService;
     }
 
-    /**
-     * Returns a test controller that can modify and query the underlying service for the {@link
-     * android.car.telemetry.CarTelemetryManager}.
-     */
-    public CarTelemetryController getCarTelemetryController() {
-        return mService.mCarTelemetry;
-    }
-
     private static class FakeCarService extends ICar.Stub {
         @Mock ICarPackageManager.Stub mCarPackageManager;
         @Mock ICarDiagnostic.Stub mCarDiagnostic;
@@ -150,7 +142,6 @@
         private final FakeCarProjectionService mCarProjection;
         private final FakeInstrumentClusterNavigation mInstrumentClusterNavigation;
         private final FakeCarUxRestrictionsService mCarUxRestrictionService;
-        private final FakeCarTelemetryService mCarTelemetry;
 
         FakeCarService(Context context) {
             MockitoAnnotations.initMocks(this);
@@ -160,7 +151,6 @@
             mCarProjection = new FakeCarProjectionService(context);
             mInstrumentClusterNavigation = new FakeInstrumentClusterNavigation();
             mCarUxRestrictionService = new FakeCarUxRestrictionsService();
-            mCarTelemetry = new FakeCarTelemetryService();
         }
 
         @Override
@@ -203,8 +193,6 @@
                     return mCarDrivingState;
                 case Car.CAR_UX_RESTRICTION_SERVICE:
                     return mCarUxRestrictionService;
-                case Car.CAR_TELEMETRY_SERVICE:
-                    return mCarTelemetry;
                 default:
                     Log.w(TAG, "getCarService for unknown service:" + serviceName);
                     return null;
diff --git a/car-test-lib/src/android/car/testapi/FakeCarTelemetryService.java b/car-test-lib/src/android/car/testapi/FakeCarTelemetryService.java
deleted file mode 100644
index 4c6c912..0000000
--- a/car-test-lib/src/android/car/testapi/FakeCarTelemetryService.java
+++ /dev/null
@@ -1,128 +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 android.car.testapi;
-
-import static android.car.telemetry.CarTelemetryManager.ERROR_NEWER_MANIFEST_EXISTS;
-import static android.car.telemetry.CarTelemetryManager.ERROR_NONE;
-import static android.car.telemetry.CarTelemetryManager.ERROR_SAME_MANIFEST_EXISTS;
-
-import android.car.telemetry.CarTelemetryManager.AddManifestError;
-import android.car.telemetry.ICarTelemetryService;
-import android.car.telemetry.ICarTelemetryServiceListener;
-import android.car.telemetry.ManifestKey;
-import android.os.RemoteException;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * A fake implementation of {@link ICarTelemetryService.Stub} to facilitate the use of
- * {@link android.car.telemetry.CarTelemetryManager} in external unit tests.
- *
- * @hide
- */
-public class FakeCarTelemetryService extends ICarTelemetryService.Stub implements
-        CarTelemetryController {
-
-    private byte[] mErrorBytes;
-    private ICarTelemetryServiceListener mListener;
-
-    private final Map<String, Integer> mNameVersionMap = new HashMap<>();
-    private final Map<ManifestKey, byte[]> mManifestMap = new HashMap<>();
-    private final Map<ManifestKey, byte[]> mScriptResultMap = new HashMap<>();
-
-    @Override
-    public void setListener(ICarTelemetryServiceListener listener) {
-        mListener = listener;
-    }
-
-    @Override
-    public void clearListener() {
-        mListener = null;
-    }
-
-    @Override
-    public @AddManifestError int addManifest(ManifestKey key, byte[] manifest) {
-        if (mNameVersionMap.getOrDefault(key.getName(), 0) > key.getVersion()) {
-            return ERROR_NEWER_MANIFEST_EXISTS;
-        } else if (mNameVersionMap.getOrDefault(key.getName(), 0) == key.getVersion()) {
-            return ERROR_SAME_MANIFEST_EXISTS;
-        }
-        mNameVersionMap.put(key.getName(), key.getVersion());
-        mManifestMap.put(key, manifest);
-        return ERROR_NONE;
-    }
-
-    @Override
-    public boolean removeManifest(ManifestKey key) {
-        if (!mManifestMap.containsKey(key)) {
-            return false;
-        }
-        mNameVersionMap.remove(key.getName());
-        mManifestMap.remove(key);
-        return true;
-    }
-
-    @Override
-    public void removeAllManifests() {
-        mNameVersionMap.clear();
-        mManifestMap.clear();
-    }
-
-    @Override
-    public void sendFinishedReports(ManifestKey key) throws RemoteException {
-        if (!mScriptResultMap.containsKey(key)) {
-            return;
-        }
-        mListener.onResult(key, mScriptResultMap.get(key));
-        mScriptResultMap.remove(key);
-    }
-
-    @Override
-    public void sendAllFinishedReports() throws RemoteException {
-        for (Map.Entry<ManifestKey, byte[]> entry : mScriptResultMap.entrySet()) {
-            mListener.onResult(entry.getKey(), entry.getValue());
-        }
-        mScriptResultMap.clear();
-    }
-
-    @Override
-    public void sendScriptExecutionErrors() throws RemoteException {
-        mListener.onError(mErrorBytes);
-    }
-
-    /**************************** CarTelemetryController impl ********************************/
-    @Override
-    public boolean isListenerSet() {
-        return mListener != null;
-    }
-
-    @Override
-    public int getValidManifestsCount() {
-        return mManifestMap.size();
-    }
-
-    @Override
-    public void addDataForKey(ManifestKey key, byte[] data) {
-        mScriptResultMap.put(key, data);
-    }
-
-    @Override
-    public void setErrorData(byte[] error) {
-        mErrorBytes = error;
-    }
-}
diff --git a/car_product/build/car.mk b/car_product/build/car.mk
index df09aae..67202f2 100644
--- a/car_product/build/car.mk
+++ b/car_product/build/car.mk
@@ -45,6 +45,7 @@
     BugReportApp \
     NetworkPreferenceApp \
     SampleCustomInputService \
+    AdasLocationTestApp \
 
 # SEPolicy for test apps / services
 BOARD_SEPOLICY_DIRS += packages/services/Car/car_product/sepolicy/test
@@ -128,6 +129,7 @@
     car-frameworks-service \
     com.android.car.procfsinspector \
     libcar-framework-service-jni \
+    ScriptExecutor \
 
 # RROs
 PRODUCT_PACKAGES += \
diff --git a/car_product/build/car_base.mk b/car_product/build/car_base.mk
index ecd503b..76967a8 100644
--- a/car_product/build/car_base.mk
+++ b/car_product/build/car_base.mk
@@ -61,6 +61,7 @@
     A2dpSinkService \
     PackageInstaller \
     carbugreportd \
+    vehicle_binding_util \
 
 # ENABLE_CAMERA_SERVICE must be set as true from the product's makefile if it wants to support
 # Android Camera service.
@@ -109,6 +110,10 @@
     packages/services/Car/car_product/init/init.bootstat.rc:system/etc/init/init.bootstat.car.rc \
     packages/services/Car/car_product/init/init.car.rc:system/etc/init/init.car.rc
 
+# Device policy management support
+PRODUCT_COPY_FILES += \
+    frameworks/native/data/etc/android.software.device_admin.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.device_admin.xml
+
 # Enable car watchdog
 include packages/services/Car/cpp/watchdog/product/carwatchdog.mk
 
diff --git a/car_product/build/preinstalled-packages-product-car-base.xml b/car_product/build/preinstalled-packages-product-car-base.xml
index f8ffbc1..b241c89 100644
--- a/car_product/build/preinstalled-packages-product-car-base.xml
+++ b/car_product/build/preinstalled-packages-product-car-base.xml
@@ -271,6 +271,11 @@
     <install-in-user-type package="com.android.bluetoothmidiservice">
         <install-in user-type="FULL" />
     </install-in-user-type>
+    <!-- ManagedProvisioning app is used for provisioning the device. It
+         requires UX for the provisioning flow. -->
+    <install-in-user-type package="com.android.managedprovisioning">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
     <install-in-user-type package="com.android.statementservice">
         <install-in user-type="FULL" />
     </install-in-user-type>
diff --git a/car_product/car_ui_portrait/Android.mk b/car_product/car_ui_portrait/Android.mk
new file mode 100644
index 0000000..53ad94b
--- /dev/null
+++ b/car_product/car_ui_portrait/Android.mk
@@ -0,0 +1,21 @@
+# 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.
+#
+
+car_ui_portrait_modules := \
+    rro/car-ui-customizations \
+    rro/car-ui-toolbar-customizations \
+    apps/HideApps
+
+include $(call all-named-subdir-makefiles,$(car_ui_portrait_modules))
diff --git a/car_product/car_ui_portrait/OWNERS b/car_product/car_ui_portrait/OWNERS
new file mode 100644
index 0000000..f539bfb
--- /dev/null
+++ b/car_product/car_ui_portrait/OWNERS
@@ -0,0 +1,6 @@
+# Car UI Portrait Reference OWNERS
+hseog@google.com
+priyanksingh@google.com
+juliakawano@google.com
+stenning@google.com
+igorr@google.com
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSettings/Android.bp b/car_product/car_ui_portrait/apps/CarUiPortraitSettings/Android.bp
new file mode 100644
index 0000000..3c5fb85
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSettings/Android.bp
@@ -0,0 +1,53 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+    name: "CarUiPortraitSettings",
+    overrides: ["CarSettings"],
+    platform_apis: true,
+
+    manifest: "AndroidManifest.xml",
+
+    resource_dirs: ["res"],
+
+    static_libs: [
+        "CarSettings-core",
+    ],
+
+    certificate: "platform",
+
+    optimize: {
+        enabled: false,
+    },
+
+    privileged: true,
+
+    dex_preopt: {
+        enabled: false,
+    },
+
+    required: ["allowed_privapp_com.android.car.settings"],
+
+    dxflags: ["--multi-dex"],
+
+    product_variables: {
+        pdk: {
+            enabled: false,
+        },
+    },
+}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSettings/AndroidManifest.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSettings/AndroidManifest.xml
new file mode 100644
index 0000000..82fcdf5
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSettings/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.car.settings"
+          android:sharedUserId="android.uid.system"
+          coreApp="true">
+</manifest>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSettings/res/layout/settings_recyclerview_default.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSettings/res/layout/settings_recyclerview_default.xml
new file mode 100644
index 0000000..4616fdf
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSettings/res/layout/settings_recyclerview_default.xml
@@ -0,0 +1,32 @@
+<?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.
+  -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.android.car.ui.FocusArea
+        xmlns:app="http://schemas.android.com/apk/res-auto"
+        android:id="@+id/settings_car_ui_focus_area"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        <com.android.car.ui.recyclerview.CarUiRecyclerView
+            android:id="@+id/settings_recycler_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:tag="carUiPreferenceRecyclerView"
+            app:carUiSize="small"
+            app:enableDivider="true" />
+    </com.android.car.ui.FocusArea>
+</merge>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSettings/res/layout/top_level_recyclerview.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSettings/res/layout/top_level_recyclerview.xml
new file mode 100644
index 0000000..4dbe9be
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSettings/res/layout/top_level_recyclerview.xml
@@ -0,0 +1,32 @@
+<?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.
+  -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.android.car.ui.FocusArea
+        xmlns:app="http://schemas.android.com/apk/res-auto"
+        android:id="@+id/settings_car_ui_focus_area"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        <com.android.car.ui.recyclerview.CarUiRecyclerView
+            android:id="@+id/top_level_recycler_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:tag="carUiPreferenceRecyclerView"
+            app:carUiSize="small"
+            app:enableDivider="true" />
+    </com.android.car.ui.FocusArea>
+</merge>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSettings/res/values/config.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSettings/res/values/config.xml
new file mode 100644
index 0000000..eaf603f
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSettings/res/values/config.xml
@@ -0,0 +1,22 @@
+<?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>
+    <bool name="config_global_force_single_pane">false</bool>
+    <string name="config_homepage_fragment_class" translatable="false">com.android.car.settings.bluetooth.BluetoothSettingsFragment</string>
+    <bool name="config_top_level_enable_chevrons">false</bool>
+</resources>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSettings/res/values/dimens.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSettings/res/values/dimens.xml
new file mode 100644
index 0000000..f2a58a4
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSettings/res/values/dimens.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<resources>
+    <!-- Top-level menu -->
+    <dimen name="top_level_menu_width">400dp</dimen>
+    <dimen name="top_level_recyclerview_margin_right">@*android:dimen/car_padding_2</dimen>
+    <dimen name="top_level_foreground_icon_inset">8dp</dimen>
+</resources>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/Android.bp b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/Android.bp
new file mode 100644
index 0000000..01717c7
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/Android.bp
@@ -0,0 +1,77 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+    name: "CarUiPortraitSystemUI",
+
+    srcs: ["src/**/*.java"],
+
+    resource_dirs: ["res"],
+
+    static_libs: [
+        "CarSystemUI-core",
+    ],
+
+    libs: [
+        "android.car",
+    ],
+
+    manifest: "AndroidManifest.xml",
+
+    overrides: [
+        "CarSystemUI",
+    ],
+
+    platform_apis: true,
+    system_ext_specific: true,
+    certificate: "platform",
+    privileged: true,
+
+    optimize: {
+        proguard_flags_files: [
+            "proguard.flags",
+        ],
+    },
+    dxflags: ["--multi-dex"],
+
+    plugins: ["dagger2-compiler"],
+
+    required: ["privapp_whitelist_com.android.systemui", "allowed_privapp_com.android.carsystemui"],
+}
+
+//####################################################################################
+// Build a static library to help mocking in testing. This is meant to be used
+// for internal unit tests.
+//####################################################################################
+android_library {
+    name: "CarUiPortraitSystemUI-tests",
+
+    srcs: ["src/**/*.java"],
+
+    resource_dirs: ["res"],
+
+    libs: [
+        "android.car",
+    ],
+
+    static_libs: [
+        "CarSystemUI-tests",
+    ],
+
+    plugins: ["dagger2-compiler"],
+}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/AndroidManifest.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/AndroidManifest.xml
new file mode 100644
index 0000000..fc2e241
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+          package="com.android.systemui"
+          android:sharedUserId="android.uid.systemui"
+          coreApp="true">
+</manifest>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/proguard.flags b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/proguard.flags
new file mode 100644
index 0000000..3de0064
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/proguard.flags
@@ -0,0 +1,6 @@
+-keep class com.android.systemui.CarUiPortraitSystemUIFactory
+
+-keep class com.android.systemui.DaggerCarUiPortraitGlobalRootComponent { *; }
+-keep class com.android.systemui.DaggerCarUiPortraitGlobalRootComponent$CarUiPortraitSysUIComponentImpl { *; }
+
+-include ../../../../../../../packages/apps/Car/SystemUI/proguard.flags
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/car_ic_apps.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/car_ic_apps.xml
new file mode 100644
index 0000000..a98b3a7
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/car_ic_apps.xml
@@ -0,0 +1,28 @@
+<?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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <vector android:width="@dimen/system_bar_icon_drawing_size"
+                android:height="@dimen/system_bar_icon_drawing_size"
+                android:viewportWidth="24.0"
+                android:viewportHeight="24.0">
+            <path
+                android:fillColor="@color/car_nav_icon_fill_color"
+                android:pathData="M6,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,20c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM6,20c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM6,14c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,14c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM16,6c0,1.1 0.9,2 2,2s2,-0.9 2,-2 -0.9,-2 -2,-2 -2,0.9 -2,2zM12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,14c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,20c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2z"/>
+        </vector>
+    </item>
+</selector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/car_ic_hvac.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/car_ic_hvac.xml
new file mode 100644
index 0000000..b42c86c
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/car_ic_hvac.xml
@@ -0,0 +1,28 @@
+<?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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <vector android:width="@dimen/system_bar_icon_drawing_size"
+                android:height="@dimen/system_bar_icon_drawing_size"
+                android:viewportWidth="24"
+                android:viewportHeight="24">
+            <path
+                android:fillColor="@color/car_nav_icon_fill_color"
+                android:pathData="M16.34,8.36l-2.29,0.82c-0.18,-0.13 -0.38,-0.25 -0.58,-0.34c0.17,-0.83 0.63,-1.58 1.36,-2.06C16.85,5.44 16.18,2 13.39,2C9,2 7.16,5.01 8.36,7.66l0.82,2.29c-0.13,0.18 -0.25,0.38 -0.34,0.58c-0.83,-0.17 -1.58,-0.63 -2.06,-1.36C5.44,7.15 2,7.82 2,10.61c0,4.4 3.01,6.24 5.66,5.03l2.29,-0.82c0.18,0.13 0.38,0.25 0.58,0.34c-0.17,0.83 -0.63,1.58 -1.36,2.06C7.15,18.56 7.82,22 10.61,22c4.4,0 6.24,-3.01 5.03,-5.66l-0.82,-2.29c0.13,-0.18 0.25,-0.38 0.34,-0.58c0.83,0.17 1.58,0.63 2.06,1.36c1.34,2.01 4.77,1.34 4.77,-1.45C22,9 18.99,7.16 16.34,8.36zM12,13.5c-0.83,0 -1.5,-0.67 -1.5,-1.5c0,-0.83 0.67,-1.5 1.5,-1.5c0.83,0 1.5,0.67 1.5,1.5C13.5,12.83 12.83,13.5 12,13.5zM10.24,5.22C10.74,4.44 11.89,4 13.39,4c0.79,0 0.71,0.86 0.34,1.11c-1.22,0.81 -2,2.06 -2.25,3.44c-0.21,0.03 -0.42,0.08 -0.62,0.15l-0.68,-1.88C10,6.42 9.86,5.81 10.24,5.22zM6.83,13.82c-0.4,0.18 -1.01,0.32 -1.61,-0.06C4.44,13.26 4,12.11 4,10.61c0,-0.79 0.86,-0.71 1.11,-0.34c0.81,1.22 2.06,2 3.44,2.25c0.03,0.21 0.08,0.42 0.15,0.62L6.83,13.82zM13.76,18.78c-0.5,0.77 -1.65,1.22 -3.15,1.22c-0.79,0 -0.71,-0.86 -0.34,-1.11c1.22,-0.81 2,-2.06 2.25,-3.44c0.21,-0.03 0.42,-0.08 0.62,-0.15l0.68,1.88C14,17.58 14.14,18.18 13.76,18.78zM18.89,13.73c-0.81,-1.22 -2.06,-2 -3.44,-2.25c-0.03,-0.21 -0.08,-0.42 -0.15,-0.62l1.88,-0.68c0.4,-0.18 1.01,-0.32 1.61,0.06c0.77,0.5 1.22,1.65 1.22,3.15C20,14.19 19.14,14.11 18.89,13.73z"/>
+        </vector>
+    </item>
+</selector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/car_ic_mic.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/car_ic_mic.xml
new file mode 100644
index 0000000..f282b65
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/car_ic_mic.xml
@@ -0,0 +1,28 @@
+<?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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <vector android:width="@dimen/system_bar_icon_drawing_size"
+                android:height="@dimen/system_bar_icon_drawing_size"
+                android:viewportWidth="24.0"
+                android:viewportHeight="24.0">
+            <path
+                android:fillColor="@color/car_nav_icon_fill_color"
+                android:pathData="M12,14c1.66,0 3,-1.34 3,-3L15,5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6c0,1.66 1.34,3 3,3zM11,5c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v6c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1L11,5zM17,11c0,2.76 -2.24,5 -5,5s-5,-2.24 -5,-5L5,11c0,3.53 2.61,6.43 6,6.92L11,21h2v-3.08c3.39,-0.49 6,-3.39 6,-6.92h-2z"/>
+        </vector>
+    </item>
+</selector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/car_ic_notification.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/car_ic_notification.xml
new file mode 100644
index 0000000..27b69a8
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/car_ic_notification.xml
@@ -0,0 +1,28 @@
+<?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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <vector android:width="@dimen/system_bar_icon_drawing_size"
+                android:height="@dimen/system_bar_icon_drawing_size"
+                android:viewportWidth="24.0"
+                android:viewportHeight="24.0">
+            <path
+                android:fillColor="@color/car_nav_icon_fill_color"
+                android:pathData="M18,17v-6c0,-3.07 -1.63,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.64,5.36 6,7.92 6,11v6L4,17v2h16v-2h-2zM16,17L8,17v-6c0,-2.48 1.51,-4.5 4,-4.5s4,2.02 4,4.5v6zM12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2z"/>
+        </vector>
+    </item>
+</selector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/car_ic_user_icon.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/car_ic_user_icon.xml
new file mode 100644
index 0000000..45887dc
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/car_ic_user_icon.xml
@@ -0,0 +1,26 @@
+<?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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/system_bar_user_icon_drawing_size"
+    android:height="@dimen/system_bar_user_icon_drawing_size"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="@color/system_bar_icon_color"
+      android:pathData="M12,5.9c1.16,0 2.1,0.94 2.1,2.1s-0.94,2.1 -2.1,2.1S9.9,9.16 9.9,8s0.94,-2.1 2.1,-2.1m0,9c2.97,0 6.1,1.46 6.1,2.1v1.1L5.9,18.1L5.9,17c0,-0.64 3.13,-2.1 6.1,-2.1M12,4C9.79,4 8,5.79 8,8s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,13c-2.67,0 -8,1.34 -8,4v3h16v-3c0,-2.66 -5.33,-4 -8,-4z"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar.xml
new file mode 100644
index 0000000..7e72373
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar.xml
@@ -0,0 +1,30 @@
+<!--
+  ~ 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item android:id="@android:id/background">
+        <shape android:shape="rectangle">
+            <solid android:color="@color/hvac_off_background_color" />
+        </shape>
+    </item>
+    <item android:id="@android:id/progress">
+        <clip>
+            <shape android:shape="rectangle">
+                <solid android:color="@color/hvac_on_background_color" />
+            </shape>
+        </clip>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_background.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_background.xml
new file mode 100644
index 0000000..7104440
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_background.xml
@@ -0,0 +1,42 @@
+<!--
+  ~ 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item>
+        <shape android:shape="rectangle">
+            <corners android:radius="@dimen/hvac_panel_seek_bar_radius"/>
+            <solid android:color="@color/hvac_off_background_color" />
+        </shape>
+    </item>
+    <item
+        android:gravity="left"
+        android:width="@dimen/hvac_panel_button_dimen">
+        <selector>
+            <item android:state_selected="true">
+                <shape android:shape="rectangle">
+                    <corners android:radius="@dimen/hvac_panel_seek_bar_radius"/>
+                    <solid android:color="@color/hvac_on_background_color" />
+                </shape>
+            </item>
+            <item>
+                <shape android:shape="rectangle">
+                    <corners android:radius="@dimen/hvac_panel_seek_bar_radius"/>
+                    <solid android:color="@color/hvac_off_background_color" />
+                </shape>
+            </item>
+        </selector>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb.xml
new file mode 100644
index 0000000..63b731f
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape android:shape="oval">
+            <solid android:color="@color/hvac_on_background_color"/>
+            <size android:height="@dimen/hvac_panel_button_dimen"
+                  android:width="@dimen/hvac_panel_button_dimen"/>
+        </shape>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_1.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_1.xml
new file mode 100644
index 0000000..a0befd8
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_1.xml
@@ -0,0 +1,30 @@
+<!--
+  ~ 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item>
+        <shape android:shape="oval">
+            <solid android:color="@color/hvac_on_background_color"/>
+            <size android:height="@dimen/hvac_panel_button_dimen"
+                  android:width="@dimen/hvac_panel_button_dimen"/>
+        </shape>
+    </item>
+    <item
+        android:drawable="@drawable/ic_mode_fan_1"
+        android:gravity="center"
+        android:width="@dimen/hvac_panel_icon_dimen"
+        android:height="@dimen/hvac_panel_icon_dimen"/>
+</layer-list>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_2.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_2.xml
new file mode 100644
index 0000000..c0725c3
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_2.xml
@@ -0,0 +1,30 @@
+<!--
+  ~ 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item>
+        <shape android:shape="oval">
+            <solid android:color="@color/hvac_on_background_color"/>
+            <size android:height="@dimen/hvac_panel_button_dimen"
+                  android:width="@dimen/hvac_panel_button_dimen"/>
+        </shape>
+    </item>
+    <item
+        android:drawable="@drawable/ic_mode_fan_2"
+        android:gravity="center"
+        android:width="@dimen/hvac_panel_icon_dimen"
+        android:height="@dimen/hvac_panel_icon_dimen"/>
+</layer-list>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_3.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_3.xml
new file mode 100644
index 0000000..d11d90b
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_3.xml
@@ -0,0 +1,30 @@
+<!--
+  ~ 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item>
+        <shape android:shape="oval">
+            <solid android:color="@color/hvac_on_background_color"/>
+            <size android:height="@dimen/hvac_panel_button_dimen"
+                  android:width="@dimen/hvac_panel_button_dimen"/>
+        </shape>
+    </item>
+    <item
+        android:drawable="@drawable/ic_mode_fan_3"
+        android:gravity="center"
+        android:width="@dimen/hvac_panel_icon_dimen"
+        android:height="@dimen/hvac_panel_icon_dimen"/>
+</layer-list>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_4.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_4.xml
new file mode 100644
index 0000000..177d9a4
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_4.xml
@@ -0,0 +1,30 @@
+<!--
+  ~ 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item>
+        <shape android:shape="oval">
+            <solid android:color="@color/hvac_on_background_color"/>
+            <size android:height="@dimen/hvac_panel_button_dimen"
+                  android:width="@dimen/hvac_panel_button_dimen"/>
+        </shape>
+    </item>
+    <item
+        android:drawable="@drawable/ic_mode_fan_4"
+        android:gravity="center"
+        android:width="@dimen/hvac_panel_icon_dimen"
+        android:height="@dimen/hvac_panel_icon_dimen"/>
+</layer-list>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_5.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_5.xml
new file mode 100644
index 0000000..c87f92a
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_5.xml
@@ -0,0 +1,30 @@
+<!--
+  ~ 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item>
+        <shape android:shape="oval">
+            <solid android:color="@color/hvac_on_background_color"/>
+            <size android:height="@dimen/hvac_panel_button_dimen"
+                  android:width="@dimen/hvac_panel_button_dimen"/>
+        </shape>
+    </item>
+    <item
+        android:drawable="@drawable/ic_mode_fan_5"
+        android:gravity="center"
+        android:width="@dimen/hvac_panel_icon_dimen"
+        android:height="@dimen/hvac_panel_icon_dimen"/>
+</layer-list>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_6.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_6.xml
new file mode 100644
index 0000000..fc8452d
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_6.xml
@@ -0,0 +1,30 @@
+<!--
+  ~ 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item>
+        <shape android:shape="oval">
+            <solid android:color="@color/hvac_on_background_color"/>
+            <size android:height="@dimen/hvac_panel_button_dimen"
+                  android:width="@dimen/hvac_panel_button_dimen"/>
+        </shape>
+    </item>
+    <item
+        android:drawable="@drawable/ic_mode_fan_6"
+        android:gravity="center"
+        android:width="@dimen/hvac_panel_icon_dimen"
+        android:height="@dimen/hvac_panel_icon_dimen"/>
+</layer-list>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_7.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_7.xml
new file mode 100644
index 0000000..4531e65
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_7.xml
@@ -0,0 +1,30 @@
+<!--
+  ~ 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item>
+        <shape android:shape="oval">
+            <solid android:color="@color/hvac_on_background_color"/>
+            <size android:height="@dimen/hvac_panel_button_dimen"
+                  android:width="@dimen/hvac_panel_button_dimen"/>
+        </shape>
+    </item>
+    <item
+        android:drawable="@drawable/ic_mode_fan_7"
+        android:gravity="center"
+        android:width="@dimen/hvac_panel_icon_dimen"
+        android:height="@dimen/hvac_panel_icon_dimen"/>
+</layer-list>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_8.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_8.xml
new file mode 100644
index 0000000..9905a24
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_8.xml
@@ -0,0 +1,30 @@
+<!--
+  ~ 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item>
+        <shape android:shape="oval">
+            <solid android:color="@color/hvac_on_background_color"/>
+            <size android:height="@dimen/hvac_panel_button_dimen"
+                  android:width="@dimen/hvac_panel_button_dimen"/>
+        </shape>
+    </item>
+    <item
+        android:drawable="@drawable/ic_mode_fan_8"
+        android:gravity="center"
+        android:width="@dimen/hvac_panel_icon_dimen"
+        android:height="@dimen/hvac_panel_icon_dimen"/>
+</layer-list>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_button_cool_on_bg.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_button_cool_on_bg.xml
new file mode 100644
index 0000000..711158c
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_button_cool_on_bg.xml
@@ -0,0 +1,35 @@
+<?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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape>
+            <solid android:color="@color/hvac_on_cooling_background_color"/>
+            <corners android:radius="@dimen/hvac_panel_on_button_radius"/>
+        </shape>
+    </item>
+    <item>
+        <ripple android:color="@color/car_ui_ripple_color">
+            <item android:id="@android:id/mask">
+                <shape>
+                    <solid android:color="?android:colorAccent"/>
+                    <corners android:radius="@dimen/hvac_panel_on_button_radius"/>
+                </shape>
+            </item>
+        </ripple>
+    </item>
+</layer-list>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_button_heat_on_bg.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_button_heat_on_bg.xml
new file mode 100644
index 0000000..d069bd9
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_button_heat_on_bg.xml
@@ -0,0 +1,35 @@
+<?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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape>
+            <solid android:color="@color/hvac_on_heating_background_color"/>
+            <corners android:radius="@dimen/hvac_panel_on_button_radius"/>
+        </shape>
+    </item>
+    <item>
+        <ripple android:color="@color/car_ui_ripple_color">
+            <item android:id="@android:id/mask">
+                <shape>
+                    <solid android:color="?android:colorAccent"/>
+                    <corners android:radius="@dimen/hvac_panel_on_button_radius"/>
+                </shape>
+            </item>
+        </ripple>
+    </item>
+</layer-list>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_button_off_bg.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_button_off_bg.xml
new file mode 100644
index 0000000..d40ad01
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_button_off_bg.xml
@@ -0,0 +1,34 @@
+<?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.
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape>
+            <solid android:color="@color/hvac_off_background_color"/>
+            <corners android:radius="@dimen/hvac_panel_off_button_radius"/>
+        </shape>
+    </item>
+    <item>
+        <ripple android:color="@color/car_ui_ripple_color">
+            <item android:id="@android:id/mask">
+                <shape>
+                    <solid android:color="?android:colorAccent"/>
+                    <corners android:radius="@dimen/hvac_panel_off_button_radius"/>
+                </shape>
+            </item>
+        </ripple>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_button_on_bg.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_button_on_bg.xml
new file mode 100644
index 0000000..a5d66bc
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_button_on_bg.xml
@@ -0,0 +1,35 @@
+<?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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape>
+            <solid android:color="@color/hvac_on_background_color"/>
+            <corners android:radius="@dimen/hvac_panel_on_button_radius"/>
+        </shape>
+    </item>
+    <item>
+        <ripple android:color="@color/car_ui_ripple_color">
+            <item android:id="@android:id/mask">
+                <shape>
+                    <solid android:color="?android:colorAccent"/>
+                    <corners android:radius="@dimen/hvac_panel_on_button_radius"/>
+                </shape>
+            </item>
+        </ripple>
+    </item>
+</layer-list>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_cool_background.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_cool_background.xml
new file mode 100644
index 0000000..b1f9e79
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_cool_background.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true"
+          android:drawable="@drawable/hvac_button_cool_on_bg"/>
+    <item android:drawable="@drawable/hvac_button_off_bg"/>
+</selector>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_decrease_button.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_decrease_button.xml
new file mode 100644
index 0000000..ea3a853
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_decrease_button.xml
@@ -0,0 +1,50 @@
+<?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.
+  -->
+
+<layer-list
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <item>
+        <aapt:attr name="android:drawable">
+            <vector android:width="@dimen/hvac_temperature_button_size"
+                    android:height="@dimen/hvac_temperature_button_size"
+                    android:viewportWidth="64"
+                    android:viewportHeight="64">
+                <path
+                    android:pathData="M32,0L32,0A32,32 0,0 1,64 32L64,32A32,32 0,0 1,32 64L32,64A32,32 0,0 1,0 32L0,32A32,32 0,0 1,32 0z"
+                    android:fillColor="@color/hvac_temperature_adjust_button_color"/>
+            </vector>
+        </aapt:attr>
+    </item>
+    <item android:gravity="center">
+        <aapt:attr name="android:drawable">
+            <vector android:width="24dp"
+                    android:height="3dp"
+                    android:viewportWidth="24"
+                    android:viewportHeight="3">
+                <path
+                    android:fillColor="@color/hvac_temperature_decrease_arrow_color"
+                    android:pathData="M24,3.5H0V0.5H24V3.5Z"/>
+            </vector>
+        </aapt:attr>
+    </item>
+    <item>
+        <aapt:attr name="android:drawable">
+            <ripple android:color="?android:attr/colorControlHighlight"/>
+        </aapt:attr>
+    </item>
+</layer-list>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_default_background.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_default_background.xml
new file mode 100644
index 0000000..84f502b
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_default_background.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true"
+          android:drawable="@drawable/hvac_button_on_bg"/>
+    <item android:drawable="@drawable/hvac_button_off_bg"/>
+</selector>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_heat_background.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_heat_background.xml
new file mode 100644
index 0000000..09d091e
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_heat_background.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true"
+          android:drawable="@drawable/hvac_button_heat_on_bg"/>
+    <item android:drawable="@drawable/hvac_button_off_bg"/>
+</selector>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_increase_button.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_increase_button.xml
new file mode 100644
index 0000000..630727d
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_increase_button.xml
@@ -0,0 +1,53 @@
+<?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.
+  -->
+
+<layer-list
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <item>
+        <aapt:attr name="android:drawable">
+            <vector android:width="@dimen/hvac_temperature_button_size"
+                    android:height="@dimen/hvac_temperature_button_size"
+                    android:viewportWidth="64"
+                    android:viewportHeight="64">
+                <path
+                    android:pathData="M32,0L32,0A32,32 0,0 1,64 32L64,32A32,32 0,0 1,32 64L32,64A32,32 0,0 1,0 32L0,32A32,32 0,0 1,32 0z"
+                    android:fillColor="@color/hvac_temperature_adjust_button_color"/>
+            </vector>
+        </aapt:attr>
+    </item>
+    <item
+        android:gravity="center"
+        android:width="24dp"
+        android:height="24dp">
+        <aapt:attr name="android:drawable">
+            <vector android:width="24dp"
+                    android:height="24dp"
+                    android:viewportWidth="24"
+                    android:viewportHeight="24">
+                <path
+                    android:fillColor="@color/hvac_temperature_increase_arrow_color"
+                    android:pathData="M24,13.5H13.5V24H10.5V13.5H0V10.5H10.5V0H13.5V10.5H24V13.5Z"/>
+            </vector>
+        </aapt:attr>
+    </item>
+    <item>
+        <aapt:attr name="android:drawable">
+            <ripple android:color="?android:attr/colorControlHighlight"/>
+        </aapt:attr>
+    </item>
+</layer-list>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_panel_bg.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_panel_bg.xml
new file mode 100644
index 0000000..f0cd3bd
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_panel_bg.xml
@@ -0,0 +1,28 @@
+<?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.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="@color/hvac_background_color"/>
+
+    <!-- android:radius must be defined even with overrides. -->
+    <corners
+        android:radius="1dp"
+        android:topLeftRadius="@dimen/hvac_panel_bg_radius"
+        android:topRightRadius="@dimen/hvac_panel_bg_radius"
+        android:bottomLeftRadius="0dp"
+        android:bottomRightRadius="0dp"/>
+</shape>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_ac_off.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_ac_off.xml
new file mode 100644
index 0000000..5458c73
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_ac_off.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M42.0001,22H35.6601L40.7401,16.92C41.5201,16.14 41.5201,14.88 40.7401,14.1C39.9601,13.32 38.6801,13.32 37.9001,14.1L30.0001,22H26.0001V18L33.9001,10.1C34.6801,9.32 34.6801,8.04 33.9001,7.26C33.1201,6.48 31.8601,6.48 31.0801,7.26L26.0001,12.34V6C26.0001,4.9 25.1001,4 24.0001,4C22.9001,4 22.0001,4.9 22.0001,6V12.34L16.9201,7.26C16.1401,6.48 14.8801,6.48 14.1001,7.26C13.3201,8.04 13.3201,9.32 14.1001,10.1L22.0001,18V20.34L27.6601,26H30.0001L37.9001,33.9C38.6801,34.68 39.9601,34.68 40.7401,33.9C41.5201,33.12 41.5201,31.86 40.7401,31.08L35.6601,26H42.0001C43.1001,26 44.0001,25.1 44.0001,24C44.0001,22.9 43.1001,22 42.0001,22Z"
+      android:fillColor="@color/hvac_off_icon_fill_color"/>
+  <path
+      android:pathData="M1.5801,11.24L12.3401,22H6.0001C4.9001,22 4.0001,22.9 4.0001,24C4.0001,25.1 4.9001,26 6.0001,26H12.3401L7.2601,31.08C6.4801,31.86 6.4801,33.12 7.2601,33.9C8.0401,34.68 9.3201,34.68 10.1001,33.9L17.1801,26.82L21.1801,30.82L14.1001,37.9C13.3201,38.68 13.3201,39.96 14.1001,40.74C14.8801,41.52 16.1401,41.52 16.9201,40.74L22.0001,35.66V42C22.0001,43.1 22.9001,44 24.0001,44C25.1001,44 26.0001,43.1 26.0001,42V35.66L38.3401,48L41.1601,45.18L4.4001,8.4L1.5801,11.24Z"
+      android:fillColor="@color/hvac_off_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_ac_on.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_ac_on.xml
new file mode 100644
index 0000000..f86563e
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_ac_on.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M42,22H35.66L40.74,16.92C41.52,16.14 41.52,14.88 40.74,14.1C39.96,13.32 38.68,13.32 37.9,14.1L30,22H26V18L33.9,10.1C34.68,9.32 34.68,8.04 33.9,7.26C33.12,6.48 31.86,6.48 31.08,7.26L26,12.34V6C26,4.9 25.1,4 24,4C22.9,4 22,4.9 22,6V12.34L16.92,7.26C16.14,6.48 14.88,6.48 14.1,7.26C13.32,8.04 13.32,9.32 14.1,10.1L22,18V22H18L10.1,14.1C9.32,13.32 8.04,13.32 7.26,14.1C6.48,14.88 6.48,16.14 7.26,16.92L12.34,22H6C4.9,22 4,22.9 4,24C4,25.1 4.9,26 6,26H12.34L7.26,31.08C6.48,31.86 6.48,33.12 7.26,33.9C8.04,34.68 9.32,34.68 10.1,33.9L18,26H22V30L14.1,37.9C13.32,38.68 13.32,39.96 14.1,40.74C14.88,41.52 16.14,41.52 16.92,40.74L22,35.66V42C22,43.1 22.9,44 24,44C25.1,44 26,43.1 26,42V35.66L31.08,40.74C31.86,41.52 33.12,41.52 33.9,40.74C34.68,39.96 34.68,38.68 33.9,37.9L26,30V26H30L37.9,33.9C38.68,34.68 39.96,34.68 40.74,33.9C41.52,33.12 41.52,31.86 40.74,31.08L35.66,26H42C43.1,26 44,25.1 44,24C44,22.9 43.1,22 42,22Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+  <path
+      android:pathData="M42,22H35.66L40.74,16.92C41.52,16.14 41.52,14.88 40.74,14.1C39.96,13.32 38.68,13.32 37.9,14.1L30,22H26V18L33.9,10.1C34.68,9.32 34.68,8.04 33.9,7.26C33.12,6.48 31.86,6.48 31.08,7.26L26,12.34V6C26,4.9 25.1,4 24,4C22.9,4 22,4.9 22,6V12.34L16.92,7.26C16.14,6.48 14.88,6.48 14.1,7.26C13.32,8.04 13.32,9.32 14.1,10.1L22,18V22H18L10.1,14.1C9.32,13.32 8.04,13.32 7.26,14.1C6.48,14.88 6.48,16.14 7.26,16.92L12.34,22H6C4.9,22 4,22.9 4,24C4,25.1 4.9,26 6,26H12.34L7.26,31.08C6.48,31.86 6.48,33.12 7.26,33.9C8.04,34.68 9.32,34.68 10.1,33.9L18,26H22V30L14.1,37.9C13.32,38.68 13.32,39.96 14.1,40.74C14.88,41.52 16.14,41.52 16.92,40.74L22,35.66V42C22,43.1 22.9,44 24,44C25.1,44 26,43.1 26,42V35.66L31.08,40.74C31.86,41.52 33.12,41.52 33.9,40.74C34.68,39.96 34.68,38.68 33.9,37.9L26,30V26H30L37.9,33.9C38.68,34.68 39.96,34.68 40.74,33.9C41.52,33.12 41.52,31.86 40.74,31.08L35.66,26H42C43.1,26 44,25.1 44,24C44,22.9 43.1,22 42,22Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_airflow_feet_off.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_airflow_feet_off.xml
new file mode 100644
index 0000000..3cf394e
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_airflow_feet_off.xml
@@ -0,0 +1,31 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_wide_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="96"
+    android:viewportHeight="49">
+  <path
+      android:pathData="M40.209,16.7912L38.789,15.3812L36.999,17.1712L36.999,3.0012L34.999,3.0012L34.999,17.1712L33.209,15.3812L31.789,16.7912L35.999,21.0012L40.209,16.7912Z"
+      android:fillColor="@color/hvac_off_icon_fill_color"/>
+  <path
+      android:pathData="M34.0338,32.7879L24.8823,46.6596C24.5032,47.2343 24.9153,48 25.6038,48C27.1419,48 28.6223,47.4142 29.744,46.3617L39.1124,37.5709C40.9662,35.8313 43.413,34.8632 45.9551,34.8632H54.2319C56.5346,34.8632 58.6342,33.5453 59.6353,31.4715L66.7164,16.8024C67.3096,15.5736 66.4143,14.1474 65.0499,14.1474C64.0736,14.1474 63.155,14.6096 62.5732,15.3935L55.9967,24.2545C54.1102,26.7962 51.1319,28.2947 47.9666,28.2947H42.3809C39.0203,28.2947 35.8844,29.9828 34.0338,32.7879Z"
+      android:fillColor="@color/hvac_off_icon_fill_color"/>
+  <path
+      android:pathData="M61.9985,5.0526a5,5.0526 0,1 0,10 0a5,5.0526 0,1 0,-10 0z"
+      android:fillColor="@color/hvac_off_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_airflow_feet_on.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_airflow_feet_on.xml
new file mode 100644
index 0000000..a4c1eb2
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_airflow_feet_on.xml
@@ -0,0 +1,31 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_wide_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="96"
+    android:viewportHeight="49">
+  <path
+      android:pathData="M40.209,16.7912L38.789,15.3812L36.999,17.1712L36.999,3.0012L34.999,3.0012L34.999,17.1712L33.209,15.3812L31.789,16.7912L35.999,21.0012L40.209,16.7912Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+  <path
+      android:pathData="M34.0338,32.7879L24.8823,46.6596C24.5032,47.2343 24.9153,48 25.6038,48C27.1419,48 28.6223,47.4142 29.744,46.3617L39.1124,37.5709C40.9662,35.8313 43.413,34.8632 45.9551,34.8632H54.2319C56.5346,34.8632 58.6342,33.5453 59.6353,31.4715L66.7164,16.8024C67.3096,15.5736 66.4143,14.1474 65.0499,14.1474C64.0736,14.1474 63.155,14.6096 62.5732,15.3935L55.9967,24.2545C54.1102,26.7962 51.1319,28.2947 47.9666,28.2947H42.3809C39.0203,28.2947 35.8844,29.9828 34.0338,32.7879Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+  <path
+      android:pathData="M61.9985,5.0526a5,5.0526 0,1 0,10 0a5,5.0526 0,1 0,-10 0z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_airflow_head_off.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_airflow_head_off.xml
new file mode 100644
index 0000000..981958a
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_airflow_head_off.xml
@@ -0,0 +1,31 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_wide_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="96"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M40.79,7.79L39.38,9.21L41.17,11H27V13H41.17L39.38,14.79L40.79,16.21L45,12L40.79,7.79Z"
+      android:fillColor="@color/hvac_off_icon_fill_color"/>
+  <path
+      android:pathData="M34.0357,32.7879L24.8843,46.6596C24.5051,47.2343 24.9173,48 25.6058,48C27.1439,48 28.6243,47.4142 29.746,46.3617L39.1144,37.5709C40.9682,35.8313 43.4149,34.8632 45.9571,34.8632H54.2339C56.5366,34.8632 58.6362,33.5453 59.6372,31.4715L66.7184,16.8024C67.3115,15.5736 66.4162,14.1474 65.0518,14.1474C64.0756,14.1474 63.157,14.6096 62.5752,15.3935L55.9986,24.2545C54.1122,26.7962 51.1338,28.2947 47.9686,28.2947H42.3829C39.0223,28.2947 35.8864,29.9828 34.0357,32.7879Z"
+      android:fillColor="@color/hvac_off_icon_fill_color"/>
+  <path
+      android:pathData="M62.0005,5.0526a5,5.0526 0,1 0,10 0a5,5.0526 0,1 0,-10 0z"
+      android:fillColor="@color/hvac_off_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_airflow_head_on.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_airflow_head_on.xml
new file mode 100644
index 0000000..4dc3272
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_airflow_head_on.xml
@@ -0,0 +1,31 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_wide_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="96"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M40.79,7.79L39.38,9.21L41.17,11H27V13H41.17L39.38,14.79L40.79,16.21L45,12L40.79,7.79Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+  <path
+      android:pathData="M34.0357,32.7879L24.8843,46.6596C24.5051,47.2343 24.9173,48 25.6058,48C27.1439,48 28.6243,47.4142 29.746,46.3617L39.1144,37.5709C40.9682,35.8313 43.4149,34.8632 45.9571,34.8632H54.2339C56.5366,34.8632 58.6362,33.5453 59.6372,31.4715L66.7184,16.8024C67.3115,15.5736 66.4162,14.1474 65.0518,14.1474C64.0756,14.1474 63.157,14.6096 62.5752,15.3935L55.9986,24.2545C54.1122,26.7962 51.1338,28.2947 47.9686,28.2947H42.3829C39.0223,28.2947 35.8864,29.9828 34.0357,32.7879Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+  <path
+      android:pathData="M62.0005,5.0526a5,5.0526 0,1 0,10 0a5,5.0526 0,1 0,-10 0z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_airflow_windshield_off.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_airflow_windshield_off.xml
new file mode 100644
index 0000000..2cdfa90
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_airflow_windshield_off.xml
@@ -0,0 +1,53 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_wide_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="96"
+    android:viewportHeight="49">
+  <path
+      android:pathData="M34.0338,32.7879L24.8823,46.6596C24.5032,47.2343 24.9153,48 25.6038,48C27.1419,48 28.6223,47.4142 29.744,46.3617L39.1124,37.5709C40.9662,35.8313 43.413,34.8632 45.9551,34.8632H54.2319C56.5346,34.8632 58.6342,33.5453 59.6353,31.4715L66.7164,16.8024C67.3096,15.5736 66.4143,14.1474 65.0499,14.1474C64.0736,14.1474 63.155,14.6096 62.5732,15.3935L55.9967,24.2545C54.1102,26.7962 51.1319,28.2947 47.9666,28.2947H42.3809C39.0203,28.2947 35.8844,29.9828 34.0338,32.7879Z"
+      android:fillColor="@color/hvac_off_icon_fill_color"/>
+  <path
+      android:pathData="M61.9985,5.0526a5,5.0526 0,1 0,10 0a5,5.0526 0,1 0,-10 0z"
+      android:fillColor="@color/hvac_off_icon_fill_color"/>
+  <path
+      android:pathData="M41.5603,10.0488C37.5686,12.8863 45.4033,16.6289 41.5603,19.0972"
+      android:strokeWidth="2"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_off_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M37.0374,10.0488C33.0456,12.8863 40.8804,16.6289 37.0374,19.0972"
+      android:strokeWidth="2"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_off_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M32.5135,10.0488C28.5217,12.8863 36.3565,16.629 32.5135,19.0973"
+      android:strokeWidth="2"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_off_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M28.8859,15.7037H27.804L25.0881,6.2391C24.7768,5.1544 25.3126,4.0062 26.3439,3.5479C30.1667,1.8493 33.7188,1 37,1C40.2812,1 43.8333,1.8493 47.6561,3.5479C48.6874,4.0061 49.2232,5.1544 48.9119,6.2391L46.196,15.7037H45.1141"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_off_icon_fill_color"
+      android:strokeLineCap="round"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_airflow_windshield_on.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_airflow_windshield_on.xml
new file mode 100644
index 0000000..a3510b1
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_airflow_windshield_on.xml
@@ -0,0 +1,53 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_wide_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="96"
+    android:viewportHeight="49">
+  <path
+      android:pathData="M34.0338,32.7879L24.8823,46.6596C24.5032,47.2343 24.9153,48 25.6038,48C27.1419,48 28.6223,47.4142 29.744,46.3617L39.1124,37.5709C40.9662,35.8313 43.413,34.8632 45.9551,34.8632H54.2319C56.5346,34.8632 58.6342,33.5453 59.6353,31.4715L66.7164,16.8024C67.3096,15.5736 66.4143,14.1474 65.0499,14.1474C64.0736,14.1474 63.155,14.6096 62.5732,15.3935L55.9967,24.2545C54.1102,26.7962 51.1319,28.2947 47.9666,28.2947H42.3809C39.0203,28.2947 35.8844,29.9828 34.0338,32.7879Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+  <path
+      android:pathData="M61.9985,5.0526a5,5.0526 0,1 0,10 0a5,5.0526 0,1 0,-10 0z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+  <path
+      android:pathData="M41.5603,10.0488C37.5686,12.8863 45.4033,16.6289 41.5603,19.0972"
+      android:strokeWidth="2"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_on_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M37.0374,10.0488C33.0456,12.8863 40.8804,16.6289 37.0374,19.0972"
+      android:strokeWidth="2"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_on_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M32.5135,10.0488C28.5217,12.8863 36.3565,16.629 32.5135,19.0973"
+      android:strokeWidth="2"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_on_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M28.8859,15.7037H27.804L25.0881,6.2391C24.7768,5.1544 25.3126,4.0062 26.3439,3.5479C30.1667,1.8493 33.7188,1 37,1C40.2812,1 43.8333,1.8493 47.6561,3.5479C48.6874,4.0061 49.2232,5.1544 48.9119,6.2391L46.196,15.7037H45.1141"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_on_icon_fill_color"
+      android:strokeLineCap="round"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_auto_off.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_auto_off.xml
new file mode 100644
index 0000000..65a4b12
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_auto_off.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M0.2653,30L4.6853,17.828H7.3203L11.7573,30H9.2243L8.2383,27.093H3.7843L2.7983,30H0.2653ZM5.5183,21.942L4.4983,24.985H7.5243L6.5043,21.942L6.0793,20.48H5.9433L5.5183,21.942ZM17.9681,30.272C16.4721,30.272 15.2934,29.8243 14.4321,28.929C13.5821,28.0337 13.1571,26.7927 13.1571,25.206V17.828H15.4181V25.359C15.4181,26.2203 15.6334,26.8947 16.0641,27.382C16.4947,27.858 17.1294,28.096 17.9681,28.096C18.8067,28.096 19.4357,27.858 19.8551,27.382C20.2857,26.8947 20.5011,26.2203 20.5011,25.359V17.828H22.7621V25.206C22.7621,26.226 22.5694,27.1157 22.1841,27.875C21.7987,28.6343 21.2491,29.2237 20.5351,29.643C19.8211,30.0623 18.9654,30.272 17.9681,30.272ZM28.299,30V20.004H24.559V17.828H34.317V20.004H30.577V30H28.299ZM41.1274,30.272C39.926,30.272 38.8834,30 37.9994,29.456C37.1154,28.9007 36.4297,28.147 35.9424,27.195C35.455,26.2317 35.2114,25.138 35.2114,23.914C35.2114,22.6787 35.455,21.585 35.9424,20.633C36.4297,19.681 37.1154,18.933 37.9994,18.389C38.8834,17.8337 39.926,17.556 41.1274,17.556C42.3287,17.556 43.3714,17.8337 44.2554,18.389C45.1394,18.933 45.825,19.681 46.3124,20.633C46.7997,21.585 47.0434,22.6787 47.0434,23.914C47.0434,25.138 46.7997,26.2317 46.3124,27.195C45.825,28.147 45.1394,28.9007 44.2554,29.456C43.3714,30 42.3287,30.272 41.1274,30.272ZM41.1274,28.096C42.204,28.096 43.071,27.7333 43.7284,27.008C44.397,26.2827 44.7314,25.2513 44.7314,23.914C44.7314,22.5767 44.397,21.5453 43.7284,20.82C43.071,20.0947 42.204,19.732 41.1274,19.732C40.0507,19.732 39.178,20.0947 38.5094,20.82C37.852,21.5453 37.5234,22.5767 37.5234,23.914C37.5234,25.2513 37.852,26.2827 38.5094,27.008C39.178,27.7333 40.0507,28.096 41.1274,28.096Z"
+      android:fillColor="@color/hvac_off_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_auto_on.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_auto_on.xml
new file mode 100644
index 0000000..c847a95
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_auto_on.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M0.2653,30L4.6853,17.828H7.3203L11.7573,30H9.2243L8.2383,27.093H3.7843L2.7983,30H0.2653ZM5.5183,21.942L4.4983,24.985H7.5243L6.5043,21.942L6.0793,20.48H5.9433L5.5183,21.942ZM17.9681,30.272C16.4721,30.272 15.2934,29.8243 14.4321,28.929C13.5821,28.0337 13.1571,26.7927 13.1571,25.206V17.828H15.4181V25.359C15.4181,26.2203 15.6334,26.8947 16.0641,27.382C16.4947,27.858 17.1294,28.096 17.9681,28.096C18.8067,28.096 19.4357,27.858 19.8551,27.382C20.2857,26.8947 20.5011,26.2203 20.5011,25.359V17.828H22.7621V25.206C22.7621,26.226 22.5694,27.1157 22.1841,27.875C21.7987,28.6343 21.2491,29.2237 20.5351,29.643C19.8211,30.0623 18.9654,30.272 17.9681,30.272ZM28.299,30V20.004H24.559V17.828H34.317V20.004H30.577V30H28.299ZM41.1274,30.272C39.926,30.272 38.8834,30 37.9994,29.456C37.1154,28.9007 36.4297,28.147 35.9424,27.195C35.455,26.2317 35.2114,25.138 35.2114,23.914C35.2114,22.6787 35.455,21.585 35.9424,20.633C36.4297,19.681 37.1154,18.933 37.9994,18.389C38.8834,17.8337 39.926,17.556 41.1274,17.556C42.3287,17.556 43.3714,17.8337 44.2554,18.389C45.1394,18.933 45.825,19.681 46.3124,20.633C46.7997,21.585 47.0434,22.6787 47.0434,23.914C47.0434,25.138 46.7997,26.2317 46.3124,27.195C45.825,28.147 45.1394,28.9007 44.2554,29.456C43.3714,30 42.3287,30.272 41.1274,30.272ZM41.1274,28.096C42.204,28.096 43.071,27.7333 43.7284,27.008C44.397,26.2827 44.7314,25.2513 44.7314,23.914C44.7314,22.5767 44.397,21.5453 43.7284,20.82C43.071,20.0947 42.204,19.732 41.1274,19.732C40.0507,19.732 39.178,20.0947 38.5094,20.82C37.852,21.5453 37.5234,22.5767 37.5234,23.914C37.5234,25.2513 37.852,26.2827 38.5094,27.008C39.178,27.7333 40.0507,28.096 41.1274,28.096Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_defroster_rear_off.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_defroster_rear_off.xml
new file mode 100644
index 0000000..29bae07
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_defroster_rear_off.xml
@@ -0,0 +1,47 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_wide_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="96"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M55.4836,23.5C49.4334,27.8006 61.3082,33.4732 55.4836,37.2143"
+      android:strokeWidth="3"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_off_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M48.6281,23.5C42.5779,27.8006 54.4527,33.4732 48.6281,37.2143"
+      android:strokeWidth="3"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_off_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M41.7707,23.5C35.7205,27.8006 47.5953,33.4732 41.7707,37.2143"
+      android:strokeWidth="3"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_off_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M36,32.5H32C30.8954,32.5 30,31.6046 30,30.5V13C30,11.8954 30.8954,11 32,11H64.5C65.6046,11 66.5,11.8954 66.5,13V30.5C66.5,31.6046 65.6046,32.5 64.5,32.5H62"
+      android:strokeLineJoin="round"
+      android:strokeWidth="3"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_off_icon_fill_color"
+      android:strokeLineCap="round"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_defroster_rear_on.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_defroster_rear_on.xml
new file mode 100644
index 0000000..ca35897
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_defroster_rear_on.xml
@@ -0,0 +1,47 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_wide_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="96"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M55.4836,23.5C49.4334,27.8006 61.3082,33.4732 55.4836,37.2143"
+      android:strokeWidth="3"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_on_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M48.6281,23.5C42.5779,27.8006 54.4527,33.4732 48.6281,37.2143"
+      android:strokeWidth="3"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_on_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M41.7707,23.5C35.7205,27.8006 47.5953,33.4732 41.7707,37.2143"
+      android:strokeWidth="3"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_on_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M36,32.5H32C30.8954,32.5 30,31.6046 30,30.5V13C30,11.8954 30.8954,11 32,11H64.5C65.6046,11 66.5,11.8954 66.5,13V30.5C66.5,31.6046 65.6046,32.5 64.5,32.5H62"
+      android:strokeLineJoin="round"
+      android:strokeWidth="3"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_on_icon_fill_color"
+      android:strokeLineCap="round"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_defroster_windshield_off.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_defroster_windshield_off.xml
new file mode 100644
index 0000000..99a55de
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_defroster_windshield_off.xml
@@ -0,0 +1,47 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_wide_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="96"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M54.1007,23.7148C48.0506,28.0154 59.9254,33.6881 54.1007,37.4291"
+      android:strokeWidth="3"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_off_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M47.2453,23.7148C41.1951,28.0154 53.0699,33.6881 47.2453,37.4291"
+      android:strokeWidth="3"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_off_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M40.3878,23.7148C34.3377,28.0154 46.2125,33.6881 40.3878,37.4291"
+      android:strokeWidth="3"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_off_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M34.8897,32.2857H33.2499L29.1335,17.9407C28.6618,16.2966 29.4739,14.5563 31.0369,13.8618C36.831,11.2873 42.2146,10 47.1878,10C52.161,10 57.5447,11.2873 63.3387,13.8618C64.9018,14.5563 65.7139,16.2966 65.2421,17.9406L61.1257,32.2857H59.486"
+      android:strokeLineJoin="round"
+      android:strokeWidth="3"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_off_icon_fill_color"
+      android:strokeLineCap="round"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_defroster_windshield_on.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_defroster_windshield_on.xml
new file mode 100644
index 0000000..9728826
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_defroster_windshield_on.xml
@@ -0,0 +1,47 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_wide_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="96"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M54.1007,23.7148C48.0506,28.0154 59.9254,33.6881 54.1007,37.4291"
+      android:strokeWidth="3"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_on_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M47.2453,23.7148C41.1951,28.0154 53.0699,33.6881 47.2453,37.4291"
+      android:strokeWidth="3"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_on_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M40.3878,23.7148C34.3377,28.0154 46.2125,33.6881 40.3878,37.4291"
+      android:strokeWidth="3"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_on_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M34.8897,32.2857H33.2499L29.1335,17.9407C28.6618,16.2966 29.4739,14.5563 31.0369,13.8618C36.831,11.2873 42.2146,10 47.1878,10C52.161,10 57.5447,11.2873 63.3387,13.8618C64.9018,14.5563 65.7139,16.2966 65.2421,17.9406L61.1257,32.2857H59.486"
+      android:strokeLineJoin="round"
+      android:strokeWidth="3"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_on_icon_fill_color"
+      android:strokeLineCap="round"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_driver_seat_heat_high.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_driver_seat_heat_high.xml
new file mode 100644
index 0000000..650fd9e
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_driver_seat_heat_high.xml
@@ -0,0 +1,32 @@
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/hvac_panel_icon_dimen"
+        android:height="@dimen/hvac_panel_tall_icon_dimen"
+        android:viewportWidth="48"
+        android:viewportHeight="100">
+  <path
+      android:pathData="M11.522,0H9.708C8.311,0 7.033,0.705 6.408,1.821C4.369,5.463 3.834,9.63 4.898,13.59L11.268,37.284C11.404,37.79 12.136,37.932 12.495,37.52L16.362,33.081C19.399,29.596 19.855,24.842 17.528,20.946L12.459,12.461C11.081,10.155 11.271,7.354 12.949,5.213L13.276,4.797C14.324,3.461 14.146,1.646 12.854,0.492C12.5,0.177 12.022,0 11.522,0ZM38.065,36.811H25.871C23.839,36.811 21.858,37.378 20.205,38.432L14.336,42.174C13.636,42.62 13.552,43.521 14.161,44.065L15.714,45.451C17.542,47.083 20.021,48 22.607,48H38.065C41.142,48 43.636,45.773 43.636,43.027V41.784C43.636,39.037 41.142,36.811 38.065,36.811ZM39.06,13.841C38.405,14.32 38.089,14.748 37.933,15.092C37.782,15.428 37.739,15.787 37.799,16.216C37.933,17.181 38.537,18.271 39.372,19.674L39.506,19.899C40.228,21.107 41.121,22.603 41.377,24.089C41.525,24.942 41.481,25.861 41.069,26.767C40.66,27.663 39.952,28.411 39.001,29.04C38.31,29.497 37.38,29.307 36.923,28.616C36.466,27.925 36.656,26.994 37.347,26.537C37.96,26.132 38.221,25.78 38.339,25.523C38.451,25.276 38.488,24.99 38.421,24.599C38.265,23.692 37.648,22.643 36.794,21.209L36.775,21.176C36.019,19.906 35.059,18.293 34.827,16.63C34.703,15.738 34.778,14.795 35.197,13.862C35.613,12.938 36.319,12.129 37.289,11.42C37.957,10.931 38.896,11.076 39.385,11.745C39.874,12.414 39.729,13.352 39.06,13.841ZM31.797,15.092C31.952,14.748 32.269,14.32 32.924,13.841C33.592,13.352 33.738,12.414 33.248,11.745C32.759,11.076 31.821,10.931 31.152,11.42C30.183,12.129 29.476,12.938 29.061,13.862C28.641,14.795 28.566,15.738 28.691,16.63C28.923,18.293 29.883,19.906 30.639,21.176L30.658,21.209C31.511,22.643 32.128,23.692 32.285,24.599C32.352,24.99 32.315,25.276 32.202,25.523C32.085,25.78 31.823,26.132 31.211,26.537C30.52,26.994 30.33,27.925 30.787,28.616C31.243,29.307 32.174,29.497 32.865,29.04C33.816,28.411 34.524,27.663 34.932,26.767C35.345,25.861 35.388,24.942 35.241,24.089C34.985,22.603 34.092,21.107 33.37,19.899L33.236,19.674C32.401,18.271 31.797,17.181 31.662,16.216C31.602,15.787 31.646,15.428 31.797,15.092ZM26.787,13.841C26.132,14.32 25.816,14.748 25.661,15.092C25.51,15.428 25.466,15.787 25.526,16.216C25.66,17.181 26.264,18.271 27.1,19.674L27.234,19.899C27.955,21.107 28.848,22.603 29.105,24.089C29.252,24.942 29.208,25.861 28.796,26.767C28.388,27.663 27.68,28.411 26.729,29.04C26.038,29.497 25.107,29.307 24.65,28.616C24.193,27.925 24.383,26.994 25.074,26.537C25.687,26.132 25.949,25.78 26.066,25.523C26.178,25.276 26.216,24.99 26.148,24.599C25.992,23.692 25.375,22.643 24.522,21.209L24.502,21.176C23.747,19.906 22.786,18.293 22.555,16.63C22.43,15.738 22.505,14.795 22.925,13.862C23.34,12.938 24.046,12.129 25.016,11.42C25.684,10.931 26.623,11.076 27.112,11.745C27.601,12.414 27.456,13.352 26.787,13.841Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M24,90L24,90A5,5 0,0 1,29 95L29,95A5,5 0,0 1,24 100L24,100A5,5 0,0 1,19 95L19,95A5,5 0,0 1,24 90z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+  <path
+      android:pathData="M24,68L24,68A5,5 0,0 1,29 73L29,73A5,5 0,0 1,24 78L24,78A5,5 0,0 1,19 73L19,73A5,5 0,0 1,24 68z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_driver_seat_heat_low.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_driver_seat_heat_low.xml
new file mode 100644
index 0000000..a89ba0a
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_driver_seat_heat_low.xml
@@ -0,0 +1,33 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/hvac_panel_icon_dimen"
+        android:height="@dimen/hvac_panel_tall_icon_dimen"
+        android:viewportWidth="48"
+        android:viewportHeight="100">
+  <path
+      android:pathData="M11.522,0H9.708C8.311,0 7.033,0.705 6.408,1.821C4.369,5.463 3.834,9.63 4.898,13.59L11.268,37.284C11.404,37.79 12.136,37.932 12.495,37.52L16.362,33.081C19.399,29.596 19.855,24.842 17.528,20.946L12.459,12.461C11.081,10.155 11.271,7.354 12.949,5.213L13.276,4.797C14.324,3.461 14.146,1.646 12.854,0.492C12.5,0.177 12.022,0 11.522,0ZM38.065,36.811H25.871C23.839,36.811 21.858,37.378 20.205,38.432L14.336,42.174C13.636,42.62 13.552,43.521 14.161,44.065L15.714,45.451C17.542,47.083 20.021,48 22.607,48H38.065C41.142,48 43.636,45.773 43.636,43.027V41.784C43.636,39.037 41.142,36.811 38.065,36.811ZM39.06,13.841C38.405,14.32 38.089,14.748 37.933,15.092C37.782,15.428 37.739,15.787 37.799,16.216C37.933,17.181 38.537,18.271 39.372,19.674L39.506,19.899C40.228,21.107 41.121,22.603 41.377,24.089C41.525,24.942 41.481,25.861 41.069,26.767C40.66,27.663 39.952,28.411 39.001,29.04C38.31,29.497 37.38,29.307 36.923,28.616C36.466,27.925 36.656,26.994 37.347,26.537C37.96,26.132 38.221,25.78 38.339,25.523C38.451,25.276 38.488,24.99 38.421,24.599C38.265,23.692 37.648,22.643 36.794,21.209L36.775,21.176C36.019,19.906 35.059,18.293 34.827,16.63C34.703,15.738 34.778,14.795 35.197,13.862C35.613,12.938 36.319,12.129 37.289,11.42C37.957,10.931 38.896,11.076 39.385,11.745C39.874,12.414 39.729,13.352 39.06,13.841ZM31.797,15.092C31.952,14.748 32.269,14.32 32.924,13.841C33.592,13.352 33.738,12.414 33.248,11.745C32.759,11.076 31.821,10.931 31.152,11.42C30.183,12.129 29.476,12.938 29.061,13.862C28.641,14.795 28.566,15.738 28.691,16.63C28.923,18.293 29.883,19.906 30.639,21.176L30.658,21.209C31.511,22.643 32.128,23.692 32.285,24.599C32.352,24.99 32.315,25.276 32.202,25.523C32.085,25.78 31.823,26.132 31.211,26.537C30.52,26.994 30.33,27.925 30.787,28.616C31.243,29.307 32.174,29.497 32.865,29.04C33.816,28.411 34.524,27.663 34.932,26.767C35.345,25.861 35.388,24.942 35.241,24.089C34.985,22.603 34.092,21.107 33.37,19.899L33.236,19.674C32.401,18.271 31.797,17.181 31.662,16.216C31.602,15.787 31.646,15.428 31.797,15.092ZM26.787,13.841C26.132,14.32 25.816,14.748 25.661,15.092C25.51,15.428 25.466,15.787 25.526,16.216C25.66,17.181 26.264,18.271 27.1,19.674L27.234,19.899C27.955,21.107 28.848,22.603 29.105,24.089C29.252,24.942 29.208,25.861 28.796,26.767C28.388,27.663 27.68,28.411 26.729,29.04C26.038,29.497 25.107,29.307 24.65,28.616C24.193,27.925 24.383,26.994 25.074,26.537C25.687,26.132 25.949,25.78 26.066,25.523C26.178,25.276 26.216,24.99 26.148,24.599C25.992,23.692 25.375,22.643 24.522,21.209L24.502,21.176C23.747,19.906 22.786,18.293 22.555,16.63C22.43,15.738 22.505,14.795 22.925,13.862C23.34,12.938 24.046,12.129 25.016,11.42C25.684,10.931 26.623,11.076 27.112,11.745C27.601,12.414 27.456,13.352 26.787,13.841Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M24,90L24,90A5,5 0,0 1,29 95L29,95A5,5 0,0 1,24 100L24,100A5,5 0,0 1,19 95L19,95A5,5 0,0 1,24 90z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+  <path
+      android:pathData="M24,68L24,68A5,5 0,0 1,29 73L29,73A5,5 0,0 1,24 78L24,78A5,5 0,0 1,19 73L19,73A5,5 0,0 1,24 68z"
+      android:fillColor="@color/hvac_on_icon_fill_color"
+      android:fillAlpha="@dimen/hvac_heat_or_cool_off_alpha"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_driver_seat_heat_off.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_driver_seat_heat_off.xml
new file mode 100644
index 0000000..9117cac
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_driver_seat_heat_off.xml
@@ -0,0 +1,34 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/hvac_panel_icon_dimen"
+        android:height="@dimen/hvac_panel_tall_icon_dimen"
+        android:viewportWidth="48"
+        android:viewportHeight="100">
+  <path
+      android:pathData="M11.522,0H9.708C8.311,0 7.033,0.705 6.408,1.821C4.369,5.463 3.834,9.63 4.898,13.59L11.268,37.284C11.404,37.79 12.136,37.932 12.495,37.52L16.362,33.081C19.399,29.596 19.855,24.842 17.528,20.946L12.459,12.461C11.081,10.155 11.271,7.354 12.949,5.213L13.276,4.797C14.324,3.461 14.146,1.646 12.854,0.492C12.5,0.177 12.022,0 11.522,0ZM38.065,36.811H25.871C23.839,36.811 21.858,37.378 20.205,38.432L14.336,42.174C13.636,42.62 13.552,43.521 14.161,44.065L15.714,45.451C17.542,47.083 20.021,48 22.607,48H38.065C41.142,48 43.636,45.773 43.636,43.027V41.784C43.636,39.037 41.142,36.811 38.065,36.811ZM39.06,13.841C38.405,14.32 38.089,14.748 37.933,15.092C37.782,15.428 37.739,15.787 37.799,16.216C37.933,17.181 38.537,18.271 39.372,19.674L39.506,19.899C40.228,21.107 41.121,22.603 41.377,24.089C41.525,24.942 41.481,25.861 41.069,26.767C40.66,27.663 39.952,28.411 39.001,29.04C38.31,29.497 37.38,29.307 36.923,28.616C36.466,27.925 36.656,26.994 37.347,26.537C37.96,26.132 38.221,25.78 38.339,25.523C38.451,25.276 38.488,24.99 38.421,24.599C38.265,23.692 37.648,22.643 36.794,21.209L36.775,21.176C36.019,19.906 35.059,18.293 34.827,16.63C34.703,15.738 34.778,14.795 35.197,13.862C35.613,12.938 36.319,12.129 37.289,11.42C37.957,10.931 38.896,11.076 39.385,11.745C39.874,12.414 39.729,13.352 39.06,13.841ZM31.797,15.092C31.952,14.748 32.269,14.32 32.924,13.841C33.592,13.352 33.738,12.414 33.248,11.745C32.759,11.076 31.821,10.931 31.152,11.42C30.183,12.129 29.476,12.938 29.061,13.862C28.641,14.795 28.566,15.738 28.691,16.63C28.923,18.293 29.883,19.906 30.639,21.176L30.658,21.209C31.511,22.643 32.128,23.692 32.285,24.599C32.352,24.99 32.315,25.276 32.202,25.523C32.085,25.78 31.823,26.132 31.211,26.537C30.52,26.994 30.33,27.925 30.787,28.616C31.243,29.307 32.174,29.497 32.865,29.04C33.816,28.411 34.524,27.663 34.932,26.767C35.345,25.861 35.388,24.942 35.241,24.089C34.985,22.603 34.092,21.107 33.37,19.899L33.236,19.674C32.401,18.271 31.797,17.181 31.662,16.216C31.602,15.787 31.646,15.428 31.797,15.092ZM26.787,13.841C26.132,14.32 25.816,14.748 25.661,15.092C25.51,15.428 25.466,15.787 25.526,16.216C25.66,17.181 26.264,18.271 27.1,19.674L27.234,19.899C27.955,21.107 28.848,22.603 29.105,24.089C29.252,24.942 29.208,25.861 28.796,26.767C28.388,27.663 27.68,28.411 26.729,29.04C26.038,29.497 25.107,29.307 24.65,28.616C24.193,27.925 24.383,26.994 25.074,26.537C25.687,26.132 25.949,25.78 26.066,25.523C26.178,25.276 26.216,24.99 26.148,24.599C25.992,23.692 25.375,22.643 24.522,21.209L24.502,21.176C23.747,19.906 22.786,18.293 22.555,16.63C22.43,15.738 22.505,14.795 22.925,13.862C23.34,12.938 24.046,12.129 25.016,11.42C25.684,10.931 26.623,11.076 27.112,11.745C27.601,12.414 27.456,13.352 26.787,13.841Z"
+      android:fillColor="@color/hvac_off_icon_fill_color"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M24,90L24,90A5,5 0,0 1,29 95L29,95A5,5 0,0 1,24 100L24,100A5,5 0,0 1,19 95L19,95A5,5 0,0 1,24 90z"
+      android:fillColor="@color/hvac_off_icon_fill_color"
+      android:fillAlpha="@dimen/hvac_heat_or_cool_off_alpha"/>
+  <path
+      android:pathData="M24,68L24,68A5,5 0,0 1,29 73L29,73A5,5 0,0 1,24 78L24,78A5,5 0,0 1,19 73L19,73A5,5 0,0 1,24 68z"
+      android:fillColor="@color/hvac_off_icon_fill_color"
+      android:fillAlpha="@dimen/hvac_heat_or_cool_off_alpha"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_fan_off.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_fan_off.xml
new file mode 100644
index 0000000..649195b
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_fan_off.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M32.68,16.72L28.1,18.36C27.74,18.1 27.34,17.86 26.94,17.68C27.28,16.02 28.2,14.52 29.66,13.56C33.7,10.88 32.36,4 26.78,4C18,4 14.32,10.02 16.72,15.32L18.36,19.9C18.1,20.26 17.86,20.66 17.68,21.06C16.02,20.72 14.52,19.8 13.56,18.34C10.88,14.3 4,15.64 4,21.22C4,30.02 10.02,33.7 15.32,31.28L19.9,29.64C20.26,29.9 20.66,30.14 21.06,30.32C20.72,31.98 19.8,33.48 18.34,34.44C14.3,37.12 15.64,44 21.22,44C30.02,44 33.7,37.98 31.28,32.68L29.64,28.1C29.9,27.74 30.14,27.34 30.32,26.94C31.98,27.28 33.48,28.2 34.44,29.66C37.12,33.68 43.98,32.34 43.98,26.76C44,18 37.98,14.32 32.68,16.72ZM24,27C22.34,27 21,25.66 21,24C21,22.34 22.34,21 24,21C25.66,21 27,22.34 27,24C27,25.66 25.66,27 24,27Z"
+      android:fillColor="@color/hvac_off_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_fan_on.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_fan_on.xml
new file mode 100644
index 0000000..62bb353
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_fan_on.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M32.68,16.72L28.1,18.36C27.74,18.1 27.34,17.86 26.94,17.68C27.28,16.02 28.2,14.52 29.66,13.56C33.7,10.88 32.36,4 26.78,4C18,4 14.32,10.02 16.72,15.32L18.36,19.9C18.1,20.26 17.86,20.66 17.68,21.06C16.02,20.72 14.52,19.8 13.56,18.34C10.88,14.3 4,15.64 4,21.22C4,30.02 10.02,33.7 15.32,31.28L19.9,29.64C20.26,29.9 20.66,30.14 21.06,30.32C20.72,31.98 19.8,33.48 18.34,34.44C14.3,37.12 15.64,44 21.22,44C30.02,44 33.7,37.98 31.28,32.68L29.64,28.1C29.9,27.74 30.14,27.34 30.32,26.94C31.98,27.28 33.48,28.2 34.44,29.66C37.12,33.68 43.98,32.34 43.98,26.76C44,18 37.98,14.32 32.68,16.72ZM24,27C22.34,27 21,25.66 21,24C21,22.34 22.34,21 24,21C25.66,21 27,22.34 27,24C27,25.66 25.66,27 24,27Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_heated_steering_off.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_heated_steering_off.xml
new file mode 100644
index 0000000..48baaf5
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_heated_steering_off.xml
@@ -0,0 +1,49 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M41.1457,5.5455C35.3706,9.6506 46.7056,15.0654 41.1457,18.6364"
+      android:strokeWidth="3"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_off_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M35.6907,5.5455C29.9155,9.6506 41.2505,15.0654 35.6907,18.6364"
+      android:strokeWidth="3"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_off_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M30.2356,5.5455C24.4604,9.6506 35.7955,15.0654 30.2356,18.6364"
+      android:strokeWidth="3"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_off_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M24,6.5455C12,6.5455 6.5454,16.9091 6.5454,24C6.5454,31.0909 12,41.4546 24,41.4546C36,41.4546 41.4545,32.5005 41.4545,25.0005"
+      android:strokeLineJoin="round"
+      android:strokeWidth="4"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_off_icon_fill_color"/>
+  <path
+      android:pathData="M14.0005,24.0005L8.0005,24.5005L8.5005,28.0005L18.296,28.7261C20.3848,28.8809 22.0005,30.6207 22.0005,32.7152V41.0005H26.0005V32.7722C26.0005,30.6543 27.6514,28.9034 29.7656,28.7791L43.0005,28.0005L43.5005,24.5005L34.0005,24.0005L30.0005,22.0005H18.0005L14.0005,24.0005Z"
+      android:fillColor="@color/hvac_off_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_heated_steering_on.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_heated_steering_on.xml
new file mode 100644
index 0000000..425f2f6
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_heated_steering_on.xml
@@ -0,0 +1,49 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M41.1457,5.5455C35.3706,9.6506 46.7056,15.0654 41.1457,18.6364"
+      android:strokeWidth="3"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_on_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M35.6907,5.5455C29.9155,9.6506 41.2505,15.0654 35.6907,18.6364"
+      android:strokeWidth="3"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_on_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M30.2356,5.5455C24.4604,9.6506 35.7955,15.0654 30.2356,18.6364"
+      android:strokeWidth="3"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_on_icon_fill_color"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M24,6.5455C12,6.5455 6.5454,16.9091 6.5454,24C6.5454,31.0909 12,41.4546 24,41.4546C36,41.4546 41.4545,32.5005 41.4545,25.0005"
+      android:strokeLineJoin="round"
+      android:strokeWidth="4"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="@color/hvac_on_icon_fill_color"/>
+  <path
+      android:pathData="M14.0005,24.0005L8.0005,24.5005L8.5005,28.0005L18.296,28.7261C20.3848,28.8809 22.0005,30.6207 22.0005,32.7152V41.0005H26.0005V32.7722C26.0005,30.6543 27.6514,28.9034 29.7656,28.7791L43.0005,28.0005L43.5005,24.5005L34.0005,24.0005L30.0005,22.0005H18.0005L14.0005,24.0005Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_1.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_1.xml
new file mode 100644
index 0000000..00a1ed8
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_1.xml
@@ -0,0 +1,29 @@
+<?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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M32.68,16.72L28.1,18.36C27.74,18.1 27.34,17.86 26.94,17.68C27.28,16.02 28.2,14.52 29.66,13.56C33.7,10.88 32.36,4 26.78,4C18,4 14.32,10.02 16.72,15.32L18.36,19.9C18.1,20.26 17.86,20.66 17.68,21.06C16.02,20.72 14.52,19.8 13.56,18.34C10.88,14.3 4,15.64 4,21.22C4,30.02 10.02,33.7 15.32,31.28L19.9,29.64C20.26,29.9 20.66,30.14 21.06,30.32C20.72,31.98 19.8,33.48 18.34,34.44C14.3,37.12 15.64,44 21.22,44C30.02,44 33.7,37.98 31.28,32.68L29.64,28.1C29.9,27.74 30.14,27.34 30.32,26.94C31.98,27.28 33.48,28.2 34.44,29.66C37.12,33.68 43.98,32.34 43.98,26.76C44,18 37.98,14.32 32.68,16.72ZM24,27C22.34,27 21,25.66 21,24C21,22.34 22.34,21 24,21C25.66,21 27,22.34 27,24C27,25.66 25.66,27 24,27Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+  <path
+      android:pathData="M42.9713,47V38.792L41.3233,39.992L40.2673,38.376L43.4833,36.056H45.0673V47H42.9713Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_2.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_2.xml
new file mode 100644
index 0000000..3421517
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_2.xml
@@ -0,0 +1,29 @@
+<?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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M32.68,16.72L28.1,18.36C27.74,18.1 27.34,17.86 26.94,17.68C27.28,16.02 28.2,14.52 29.66,13.56C33.7,10.88 32.36,4 26.78,4C18,4 14.32,10.02 16.72,15.32L18.36,19.9C18.1,20.26 17.86,20.66 17.68,21.06C16.02,20.72 14.52,19.8 13.56,18.34C10.88,14.3 4,15.64 4,21.22C4,30.02 10.02,33.7 15.32,31.28L19.9,29.64C20.26,29.9 20.66,30.14 21.06,30.32C20.72,31.98 19.8,33.48 18.34,34.44C14.3,37.12 15.64,44 21.22,44C30.02,44 33.7,37.98 31.28,32.68L29.64,28.1C29.9,27.74 30.14,27.34 30.32,26.94C31.98,27.28 33.48,28.2 34.44,29.66C37.12,33.68 43.98,32.34 43.98,26.76C44,18 37.98,14.32 32.68,16.72ZM24,27C22.34,27 21,25.66 21,24C21,22.34 22.34,21 24,21C25.66,21 27,22.34 27,24C27,25.66 25.66,27 24,27Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+  <path
+      android:pathData="M39.3867,47V45.096C39.3867,45.096 39.4987,44.984 39.7227,44.76C39.9467,44.536 40.2294,44.2533 40.5707,43.912C40.9227,43.5707 41.2854,43.2133 41.6587,42.84C42.032,42.4667 42.3734,42.12 42.6827,41.8C42.992,41.48 43.2214,41.24 43.3707,41.08C43.744,40.6747 44.0054,40.3333 44.1547,40.056C44.304,39.7787 44.3787,39.4587 44.3787,39.096C44.3787,38.744 44.2454,38.4347 43.9787,38.168C43.712,37.9013 43.3387,37.768 42.8587,37.768C42.3787,37.768 42.0054,37.9067 41.7387,38.184C41.472,38.4613 41.2854,38.7707 41.1787,39.112L39.2907,38.328C39.408,37.9333 39.616,37.544 39.9147,37.16C40.224,36.776 40.624,36.456 41.1147,36.2C41.616,35.9333 42.208,35.8 42.8907,35.8C43.6374,35.8 44.2774,35.944 44.8107,36.232C45.3547,36.52 45.7707,36.904 46.0587,37.384C46.3574,37.864 46.5067,38.4027 46.5067,39C46.5067,39.6827 46.3414,40.312 46.0107,40.888C45.68,41.464 45.2694,41.992 44.7787,42.472C44.6187,42.6213 44.5014,42.7333 44.4267,42.808C44.3627,42.872 44.2934,42.936 44.2187,43C44.1547,43.064 44.0534,43.1653 43.9147,43.304C43.7867,43.432 43.584,43.6347 43.3067,43.912C43.04,44.1893 42.6614,44.5733 42.1707,45.064L42.2187,45.16H46.6507V47H39.3867Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_3.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_3.xml
new file mode 100644
index 0000000..58f47fe
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_3.xml
@@ -0,0 +1,29 @@
+<?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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M32.68,16.72L28.1,18.36C27.74,18.1 27.34,17.86 26.94,17.68C27.28,16.02 28.2,14.52 29.66,13.56C33.7,10.88 32.36,4 26.78,4C18,4 14.32,10.02 16.72,15.32L18.36,19.9C18.1,20.26 17.86,20.66 17.68,21.06C16.02,20.72 14.52,19.8 13.56,18.34C10.88,14.3 4,15.64 4,21.22C4,30.02 10.02,33.7 15.32,31.28L19.9,29.64C20.26,29.9 20.66,30.14 21.06,30.32C20.72,31.98 19.8,33.48 18.34,34.44C14.3,37.12 15.64,44 21.22,44C30.02,44 33.7,37.98 31.28,32.68L29.64,28.1C29.9,27.74 30.14,27.34 30.32,26.94C31.98,27.28 33.48,28.2 34.44,29.66C37.12,33.68 43.98,32.34 43.98,26.76C44,18 37.98,14.32 32.68,16.72ZM24,27C22.34,27 21,25.66 21,24C21,22.34 22.34,21 24,21C25.66,21 27,22.34 27,24C27,25.66 25.66,27 24,27Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+  <path
+      android:pathData="M42.9975,47.256C42.1122,47.256 41.3068,47.0213 40.5815,46.552C39.8562,46.072 39.3548,45.3467 39.0775,44.376L41.0615,43.592C41.3282,44.68 41.9735,45.224 42.9975,45.224C43.4455,45.224 43.8402,45.0907 44.1815,44.824C44.5228,44.5467 44.6935,44.1893 44.6935,43.752C44.6935,43.2827 44.5122,42.9147 44.1495,42.648C43.7868,42.3813 43.3068,42.248 42.7095,42.248H41.7655V40.344H42.6295C43.0668,40.344 43.4562,40.232 43.7975,40.008C44.1388,39.784 44.3095,39.4373 44.3095,38.968C44.3095,38.6053 44.1762,38.3067 43.9095,38.072C43.6535,37.8373 43.3122,37.72 42.8855,37.72C42.4162,37.72 42.0535,37.848 41.7975,38.104C41.5415,38.36 41.3655,38.6427 41.2695,38.952L39.3655,38.168C39.4935,37.8053 39.7068,37.4427 40.0055,37.08C40.3042,36.7173 40.6935,36.4133 41.1735,36.168C41.6535,35.9227 42.2295,35.8 42.9015,35.8C43.6055,35.8 44.2188,35.928 44.7415,36.184C45.2748,36.44 45.6908,36.792 45.9895,37.24C46.2882,37.6773 46.4375,38.1733 46.4375,38.728C46.4375,39.3573 46.2882,39.8747 45.9895,40.28C45.7015,40.6853 45.3868,40.9733 45.0455,41.144V41.272C45.5468,41.4747 45.9682,41.8 46.3095,42.248C46.6615,42.696 46.8375,43.2613 46.8375,43.944C46.8375,44.5733 46.6775,45.1387 46.3575,45.64C46.0375,46.1413 45.5895,46.536 45.0135,46.824C44.4375,47.112 43.7655,47.256 42.9975,47.256Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_4.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_4.xml
new file mode 100644
index 0000000..077d134
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_4.xml
@@ -0,0 +1,29 @@
+<?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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M32.68,16.72L28.1,18.36C27.74,18.1 27.34,17.86 26.94,17.68C27.28,16.02 28.2,14.52 29.66,13.56C33.7,10.88 32.36,4 26.78,4C18,4 14.32,10.02 16.72,15.32L18.36,19.9C18.1,20.26 17.86,20.66 17.68,21.06C16.02,20.72 14.52,19.8 13.56,18.34C10.88,14.3 4,15.64 4,21.22C4,30.02 10.02,33.7 15.32,31.28L19.9,29.64C20.26,29.9 20.66,30.14 21.06,30.32C20.72,31.98 19.8,33.48 18.34,34.44C14.3,37.12 15.64,44 21.22,44C30.02,44 33.7,37.98 31.28,32.68L29.64,28.1C29.9,27.74 30.14,27.34 30.32,26.94C31.98,27.28 33.48,28.2 34.44,29.66C37.12,33.68 43.98,32.34 43.98,26.76C44,18 37.98,14.32 32.68,16.72ZM24,27C22.34,27 21,25.66 21,24C21,22.34 22.34,21 24,21C25.66,21 27,22.34 27,24C27,25.66 25.66,27 24,27Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+  <path
+      android:pathData="M38.7282,44.984V43.288L43.5922,36.056H45.8642V43.032H47.2242V44.984H45.8642V47H43.7682V44.984H38.7282ZM41.0482,43.032H43.7682V39.176H43.6402L41.0482,43.032Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_5.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_5.xml
new file mode 100644
index 0000000..74b7fc2
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_5.xml
@@ -0,0 +1,29 @@
+<?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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M32.68,16.72L28.1,18.36C27.74,18.1 27.34,17.86 26.94,17.68C27.28,16.02 28.2,14.52 29.66,13.56C33.7,10.88 32.36,4 26.78,4C18,4 14.32,10.02 16.72,15.32L18.36,19.9C18.1,20.26 17.86,20.66 17.68,21.06C16.02,20.72 14.52,19.8 13.56,18.34C10.88,14.3 4,15.64 4,21.22C4,30.02 10.02,33.7 15.32,31.28L19.9,29.64C20.26,29.9 20.66,30.14 21.06,30.32C20.72,31.98 19.8,33.48 18.34,34.44C14.3,37.12 15.64,44 21.22,44C30.02,44 33.7,37.98 31.28,32.68L29.64,28.1C29.9,27.74 30.14,27.34 30.32,26.94C31.98,27.28 33.48,28.2 34.44,29.66C37.12,33.68 43.98,32.34 43.98,26.76C44,18 37.98,14.32 32.68,16.72ZM24,27C22.34,27 21,25.66 21,24C21,22.34 22.34,21 24,21C25.66,21 27,22.34 27,24C27,25.66 25.66,27 24,27Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+  <path
+      android:pathData="M42.9365,47.256C42.3925,47.256 41.8485,47.1493 41.3045,46.936C40.7712,46.7227 40.3018,46.3973 39.8965,45.96C39.4912,45.5227 39.2085,44.968 39.0485,44.296L40.9365,43.56C41.0538,44.0827 41.2832,44.5093 41.6245,44.84C41.9658,45.16 42.3978,45.32 42.9205,45.32C43.4218,45.32 43.8432,45.1493 44.1845,44.808C44.5365,44.4667 44.7125,44.0347 44.7125,43.512C44.7125,43 44.5472,42.5733 44.2165,42.232C43.8858,41.88 43.4538,41.704 42.9205,41.704C42.5898,41.704 42.2965,41.7733 42.0405,41.912C41.7845,42.0507 41.5658,42.2267 41.3845,42.44L39.3525,41.528L39.9765,36.056H46.1525V37.896H41.7205L41.3205,40.456L41.4485,40.488C41.6618,40.3067 41.9232,40.152 42.2325,40.024C42.5525,39.896 42.9258,39.832 43.3525,39.832C43.9605,39.832 44.5258,39.9867 45.0485,40.296C45.5712,40.6053 45.9925,41.0373 46.3125,41.592C46.6432,42.136 46.8085,42.776 46.8085,43.512C46.8085,44.2373 46.6432,44.8827 46.3125,45.448C45.9818,46.0133 45.5232,46.456 44.9365,46.776C44.3605,47.096 43.6938,47.256 42.9365,47.256Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_6.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_6.xml
new file mode 100644
index 0000000..503c42a
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_6.xml
@@ -0,0 +1,29 @@
+<?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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M32.68,16.72L28.1,18.36C27.74,18.1 27.34,17.86 26.94,17.68C27.28,16.02 28.2,14.52 29.66,13.56C33.7,10.88 32.36,4 26.78,4C18,4 14.32,10.02 16.72,15.32L18.36,19.9C18.1,20.26 17.86,20.66 17.68,21.06C16.02,20.72 14.52,19.8 13.56,18.34C10.88,14.3 4,15.64 4,21.22C4,30.02 10.02,33.7 15.32,31.28L19.9,29.64C20.26,29.9 20.66,30.14 21.06,30.32C20.72,31.98 19.8,33.48 18.34,34.44C14.3,37.12 15.64,44 21.22,44C30.02,44 33.7,37.98 31.28,32.68L29.64,28.1C29.9,27.74 30.14,27.34 30.32,26.94C31.98,27.28 33.48,28.2 34.44,29.66C37.12,33.68 43.98,32.34 43.98,26.76C44,18 37.98,14.32 32.68,16.72ZM24,27C22.34,27 21,25.66 21,24C21,22.34 22.34,21 24,21C25.66,21 27,22.34 27,24C27,25.66 25.66,27 24,27Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+  <path
+      android:pathData="M43.0392,47.256C42.4312,47.256 41.8765,47.1493 41.3752,46.936C40.8845,46.712 40.4685,46.4187 40.1272,46.056C39.4445,45.3413 39.1032,44.4667 39.1032,43.432C39.1032,42.696 39.2472,42.024 39.5352,41.416C39.8339,40.808 40.2072,40.1787 40.6552,39.528C41.0925,38.8987 41.5299,38.2693 41.9672,37.64C42.4152,37 42.8579,36.3653 43.2952,35.736L44.9272,36.856C44.5539,37.3787 44.1752,37.9067 43.7912,38.44C43.4179,38.9627 43.0445,39.4907 42.6712,40.024L42.7672,40.12C43.0125,40.0133 43.2899,39.96 43.5992,39.96C44.1539,39.96 44.6819,40.1093 45.1832,40.408C45.6845,40.7067 46.0952,41.1227 46.4152,41.656C46.7352,42.1893 46.8952,42.808 46.8952,43.512C46.8952,44.2373 46.7139,44.8827 46.3512,45.448C45.9885,46.0133 45.5139,46.456 44.9272,46.776C44.3405,47.096 43.7112,47.256 43.0392,47.256ZM42.9912,45.336C43.3219,45.336 43.6259,45.2613 43.9032,45.112C44.1805,44.952 44.4045,44.7387 44.5752,44.472C44.7565,44.1947 44.8472,43.88 44.8472,43.528C44.8472,43.1653 44.7565,42.8507 44.5752,42.584C44.4045,42.3067 44.1752,42.0933 43.8872,41.944C43.6099,41.784 43.3112,41.704 42.9912,41.704C42.6819,41.704 42.3832,41.784 42.0952,41.944C41.8179,42.0933 41.5885,42.3067 41.4072,42.584C41.2365,42.8507 41.1512,43.1653 41.1512,43.528C41.1512,43.88 41.2365,44.1947 41.4072,44.472C41.5885,44.7387 41.8179,44.952 42.0952,45.112C42.3725,45.2613 42.6712,45.336 42.9912,45.336Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_7.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_7.xml
new file mode 100644
index 0000000..bcf9158
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_7.xml
@@ -0,0 +1,29 @@
+<?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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M32.68,16.72L28.1,18.36C27.74,18.1 27.34,17.86 26.94,17.68C27.28,16.02 28.2,14.52 29.66,13.56C33.7,10.88 32.36,4 26.78,4C18,4 14.32,10.02 16.72,15.32L18.36,19.9C18.1,20.26 17.86,20.66 17.68,21.06C16.02,20.72 14.52,19.8 13.56,18.34C10.88,14.3 4,15.64 4,21.22C4,30.02 10.02,33.7 15.32,31.28L19.9,29.64C20.26,29.9 20.66,30.14 21.06,30.32C20.72,31.98 19.8,33.48 18.34,34.44C14.3,37.12 15.64,44 21.22,44C30.02,44 33.7,37.98 31.28,32.68L29.64,28.1C29.9,27.74 30.14,27.34 30.32,26.94C31.98,27.28 33.48,28.2 34.44,29.66C37.12,33.68 43.98,32.34 43.98,26.76C44,18 37.98,14.32 32.68,16.72ZM24,27C22.34,27 21,25.66 21,24C21,22.34 22.34,21 24,21C25.66,21 27,22.34 27,24C27,25.66 25.66,27 24,27Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+  <path
+      android:pathData="M41.5709,47.256L39.8109,46.28L44.3709,38.12L44.3069,38.024H39.2829V36.056H46.7229V38.12C45.8695,39.6347 45.0109,41.1547 44.1469,42.68C43.2829,44.2053 42.4242,45.7307 41.5709,47.256Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_8.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_8.xml
new file mode 100644
index 0000000..7d70370
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_8.xml
@@ -0,0 +1,29 @@
+<?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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M32.68,16.72L28.1,18.36C27.74,18.1 27.34,17.86 26.94,17.68C27.28,16.02 28.2,14.52 29.66,13.56C33.7,10.88 32.36,4 26.78,4C18,4 14.32,10.02 16.72,15.32L18.36,19.9C18.1,20.26 17.86,20.66 17.68,21.06C16.02,20.72 14.52,19.8 13.56,18.34C10.88,14.3 4,15.64 4,21.22C4,30.02 10.02,33.7 15.32,31.28L19.9,29.64C20.26,29.9 20.66,30.14 21.06,30.32C20.72,31.98 19.8,33.48 18.34,34.44C14.3,37.12 15.64,44 21.22,44C30.02,44 33.7,37.98 31.28,32.68L29.64,28.1C29.9,27.74 30.14,27.34 30.32,26.94C31.98,27.28 33.48,28.2 34.44,29.66C37.12,33.68 43.98,32.34 43.98,26.76C44,18 37.98,14.32 32.68,16.72ZM24,27C22.34,27 21,25.66 21,24C21,22.34 22.34,21 24,21C25.66,21 27,22.34 27,24C27,25.66 25.66,27 24,27Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+  <path
+      android:pathData="M43.0087,47.256C42.23,47.256 41.542,47.112 40.9447,46.824C40.3474,46.5253 39.878,46.1307 39.5367,45.64C39.206,45.1387 39.0407,44.5787 39.0407,43.96C39.0407,43.32 39.2114,42.776 39.5527,42.328C39.894,41.8693 40.278,41.5227 40.7047,41.288V41.16C40.3634,40.9253 40.0594,40.616 39.7927,40.232C39.5367,39.8373 39.4087,39.3893 39.4087,38.888C39.4087,38.3013 39.5634,37.7787 39.8727,37.32C40.182,36.8507 40.6087,36.4827 41.1527,36.216C41.6967,35.9387 42.3154,35.8 43.0087,35.8C43.702,35.8 44.3154,35.9387 44.8487,36.216C45.3927,36.4827 45.8194,36.8507 46.1287,37.32C46.4487,37.7787 46.6087,38.3013 46.6087,38.888C46.6087,39.3893 46.4754,39.8373 46.2087,40.232C45.9527,40.616 45.6487,40.9253 45.2967,41.16V41.288C45.734,41.5227 46.1234,41.8693 46.4647,42.328C46.806,42.776 46.9767,43.32 46.9767,43.96C46.9767,44.5787 46.806,45.1387 46.4647,45.64C46.134,46.1307 45.67,46.5253 45.0727,46.824C44.486,47.112 43.798,47.256 43.0087,47.256ZM43.0087,40.376C43.446,40.376 43.814,40.2533 44.1127,40.008C44.422,39.752 44.5767,39.4213 44.5767,39.016C44.5767,38.6 44.422,38.2747 44.1127,38.04C43.814,37.7947 43.446,37.672 43.0087,37.672C42.5607,37.672 42.182,37.7947 41.8727,38.04C41.574,38.2747 41.4247,38.6 41.4247,39.016C41.4247,39.4213 41.574,39.752 41.8727,40.008C42.182,40.2533 42.5607,40.376 43.0087,40.376ZM43.0087,45.32C43.5527,45.32 43.9954,45.176 44.3367,44.888C44.6887,44.6 44.8647,44.2213 44.8647,43.752C44.8647,43.304 44.6887,42.936 44.3367,42.648C43.9847,42.36 43.542,42.216 43.0087,42.216C42.4754,42.216 42.0274,42.36 41.6647,42.648C41.3127,42.936 41.1367,43.304 41.1367,43.752C41.1367,44.2213 41.3127,44.6 41.6647,44.888C42.0167,45.176 42.4647,45.32 43.0087,45.32Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_passenger_seat_heat_high.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_passenger_seat_heat_high.xml
new file mode 100644
index 0000000..0d02e85
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_passenger_seat_heat_high.xml
@@ -0,0 +1,32 @@
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/hvac_panel_icon_dimen"
+        android:height="@dimen/hvac_panel_tall_icon_dimen"
+        android:viewportWidth="48"
+        android:viewportHeight="100">
+  <path
+      android:pathData="M36.48,0H38.294C39.691,0 40.969,0.705 41.594,1.821C43.633,5.463 44.168,9.63 43.104,13.59L36.734,37.284C36.598,37.79 35.866,37.932 35.507,37.52L31.64,33.081C28.603,29.596 28.147,24.842 30.474,20.946L35.543,12.461C36.921,10.155 36.731,7.354 35.053,5.213L34.726,4.797C33.678,3.461 33.856,1.646 35.148,0.492C35.501,0.177 35.98,0 36.48,0ZM9.936,36.811H22.131C24.163,36.811 26.144,37.378 27.797,38.432L33.666,42.174C34.366,42.62 34.45,43.521 33.841,44.065L32.288,45.451C30.46,47.083 27.981,48 25.395,48H9.936C6.86,48 4.366,45.773 4.366,43.027V41.784C4.366,39.037 6.86,36.811 9.936,36.811ZM23.104,13.84C22.449,14.319 22.133,14.746 21.978,15.091C21.827,15.427 21.783,15.786 21.843,16.215C21.977,17.179 22.581,18.27 23.417,19.673L23.551,19.898C24.272,21.106 25.166,22.602 25.422,24.088C25.569,24.941 25.525,25.86 25.113,26.766C24.705,27.662 23.997,28.41 23.046,29.039C22.354,29.496 21.424,29.306 20.967,28.615C20.51,27.924 20.7,26.993 21.391,26.536C22.004,26.131 22.266,25.779 22.383,25.522C22.495,25.275 22.533,24.989 22.465,24.598C22.309,23.691 21.692,22.641 20.839,21.207L20.819,21.175C20.063,19.905 19.103,18.292 18.871,16.629C18.747,15.737 18.822,14.794 19.242,13.861C19.657,12.937 20.363,12.128 21.333,11.419C22.001,10.93 22.94,11.075 23.429,11.744C23.918,12.412 23.773,13.351 23.104,13.84ZM15.844,15.091C15.999,14.746 16.316,14.319 16.97,13.84C17.639,13.351 17.784,12.412 17.295,11.744C16.806,11.075 15.867,10.93 15.199,11.419C14.229,12.128 13.523,12.937 13.108,13.861C12.688,14.794 12.613,15.737 12.738,16.629C12.969,18.292 13.929,19.905 14.685,21.175L14.705,21.207C15.558,22.641 16.175,23.691 16.331,24.598C16.399,24.989 16.361,25.275 16.249,25.522C16.132,25.779 15.87,26.131 15.257,26.536C14.566,26.993 14.376,27.924 14.833,28.615C15.29,29.306 16.221,29.496 16.912,29.039C17.862,28.41 18.571,27.662 18.979,26.766C19.392,25.86 19.435,24.941 19.288,24.088C19.031,22.602 18.138,21.106 17.417,19.898L17.283,19.673C16.447,18.27 15.843,17.179 15.709,16.215C15.649,15.786 15.693,15.427 15.844,15.091ZM10.832,13.84C10.178,14.319 9.861,14.746 9.706,15.091C9.555,15.427 9.511,15.786 9.571,16.215C9.705,17.179 10.31,18.27 11.145,19.673L11.279,19.898C12,21.106 12.894,22.602 13.15,24.088C13.297,24.941 13.254,25.86 12.841,26.766C12.433,27.662 11.725,28.41 10.774,29.039C10.083,29.496 9.152,29.306 8.695,28.615C8.239,27.924 8.428,26.993 9.12,26.536C9.732,26.131 9.994,25.779 10.111,25.522C10.224,25.275 10.261,24.989 10.194,24.598C10.037,23.691 9.42,22.641 8.567,21.207L8.548,21.175C7.792,19.905 6.832,18.292 6.6,16.629C6.475,15.737 6.55,14.794 6.97,13.861C7.385,12.937 8.091,12.128 9.061,11.419C9.73,10.93 10.668,11.075 11.157,11.744C11.646,12.412 11.501,13.351 10.832,13.84Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M24,90L24,90A5,5 0,0 1,29 95L29,95A5,5 0,0 1,24 100L24,100A5,5 0,0 1,19 95L19,95A5,5 0,0 1,24 90z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+  <path
+      android:pathData="M24,68L24,68A5,5 0,0 1,29 73L29,73A5,5 0,0 1,24 78L24,78A5,5 0,0 1,19 73L19,73A5,5 0,0 1,24 68z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_passenger_seat_heat_low.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_passenger_seat_heat_low.xml
new file mode 100644
index 0000000..b95b31a
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_passenger_seat_heat_low.xml
@@ -0,0 +1,33 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/hvac_panel_icon_dimen"
+        android:height="@dimen/hvac_panel_tall_icon_dimen"
+        android:viewportWidth="48"
+        android:viewportHeight="100">
+  <path
+      android:pathData="M36.48,0H38.294C39.691,0 40.969,0.705 41.594,1.821C43.633,5.463 44.168,9.63 43.104,13.59L36.734,37.284C36.598,37.79 35.866,37.932 35.507,37.52L31.64,33.081C28.603,29.596 28.147,24.842 30.474,20.946L35.543,12.461C36.921,10.155 36.731,7.354 35.053,5.213L34.726,4.797C33.678,3.461 33.856,1.646 35.148,0.492C35.501,0.177 35.98,0 36.48,0ZM9.936,36.811H22.131C24.163,36.811 26.144,37.378 27.797,38.432L33.666,42.174C34.366,42.62 34.45,43.521 33.841,44.065L32.288,45.451C30.46,47.083 27.981,48 25.395,48H9.936C6.86,48 4.366,45.773 4.366,43.027V41.784C4.366,39.037 6.86,36.811 9.936,36.811ZM23.104,13.84C22.449,14.319 22.133,14.746 21.978,15.091C21.827,15.427 21.783,15.786 21.843,16.215C21.977,17.179 22.581,18.27 23.417,19.673L23.551,19.898C24.272,21.106 25.166,22.602 25.422,24.088C25.569,24.941 25.525,25.86 25.113,26.766C24.705,27.662 23.997,28.41 23.046,29.039C22.354,29.496 21.424,29.306 20.967,28.615C20.51,27.924 20.7,26.993 21.391,26.536C22.004,26.131 22.266,25.779 22.383,25.522C22.495,25.275 22.533,24.989 22.465,24.598C22.309,23.691 21.692,22.641 20.839,21.207L20.819,21.175C20.063,19.905 19.103,18.292 18.871,16.629C18.747,15.737 18.822,14.794 19.242,13.861C19.657,12.937 20.363,12.128 21.333,11.419C22.001,10.93 22.94,11.075 23.429,11.744C23.918,12.412 23.773,13.351 23.104,13.84ZM15.844,15.091C15.999,14.746 16.316,14.319 16.97,13.84C17.639,13.351 17.784,12.412 17.295,11.744C16.806,11.075 15.867,10.93 15.199,11.419C14.229,12.128 13.523,12.937 13.108,13.861C12.688,14.794 12.613,15.737 12.738,16.629C12.969,18.292 13.929,19.905 14.685,21.175L14.705,21.207C15.558,22.641 16.175,23.691 16.331,24.598C16.399,24.989 16.361,25.275 16.249,25.522C16.132,25.779 15.87,26.131 15.257,26.536C14.566,26.993 14.376,27.924 14.833,28.615C15.29,29.306 16.221,29.496 16.912,29.039C17.862,28.41 18.571,27.662 18.979,26.766C19.392,25.86 19.435,24.941 19.288,24.088C19.031,22.602 18.138,21.106 17.417,19.898L17.283,19.673C16.447,18.27 15.843,17.179 15.709,16.215C15.649,15.786 15.693,15.427 15.844,15.091ZM10.832,13.84C10.178,14.319 9.861,14.746 9.706,15.091C9.555,15.427 9.511,15.786 9.571,16.215C9.705,17.179 10.31,18.27 11.145,19.673L11.279,19.898C12,21.106 12.894,22.602 13.15,24.088C13.297,24.941 13.254,25.86 12.841,26.766C12.433,27.662 11.725,28.41 10.774,29.039C10.083,29.496 9.152,29.306 8.695,28.615C8.239,27.924 8.428,26.993 9.12,26.536C9.732,26.131 9.994,25.779 10.111,25.522C10.224,25.275 10.261,24.989 10.194,24.598C10.037,23.691 9.42,22.641 8.567,21.207L8.548,21.175C7.792,19.905 6.832,18.292 6.6,16.629C6.475,15.737 6.55,14.794 6.97,13.861C7.385,12.937 8.091,12.128 9.061,11.419C9.73,10.93 10.668,11.075 11.157,11.744C11.646,12.412 11.501,13.351 10.832,13.84Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M24,90L24,90A5,5 0,0 1,29 95L29,95A5,5 0,0 1,24 100L24,100A5,5 0,0 1,19 95L19,95A5,5 0,0 1,24 90z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+  <path
+      android:pathData="M24,68L24,68A5,5 0,0 1,29 73L29,73A5,5 0,0 1,24 78L24,78A5,5 0,0 1,19 73L19,73A5,5 0,0 1,24 68z"
+      android:fillColor="@color/hvac_on_icon_fill_color"
+      android:fillAlpha="@dimen/hvac_heat_or_cool_off_alpha"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_passenger_seat_heat_off.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_passenger_seat_heat_off.xml
new file mode 100644
index 0000000..cbf3fd1
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_passenger_seat_heat_off.xml
@@ -0,0 +1,34 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/hvac_panel_icon_dimen"
+        android:height="@dimen/hvac_panel_tall_icon_dimen"
+        android:viewportWidth="48"
+        android:viewportHeight="100">
+  <path
+      android:pathData="M36.48,0H38.294C39.691,0 40.969,0.705 41.594,1.821C43.633,5.463 44.168,9.63 43.104,13.59L36.734,37.284C36.598,37.79 35.866,37.932 35.507,37.52L31.64,33.081C28.603,29.596 28.147,24.842 30.474,20.946L35.543,12.461C36.921,10.155 36.731,7.354 35.053,5.213L34.726,4.797C33.678,3.461 33.856,1.646 35.148,0.492C35.501,0.177 35.98,0 36.48,0ZM9.936,36.811H22.131C24.163,36.811 26.144,37.378 27.797,38.432L33.666,42.174C34.366,42.62 34.45,43.521 33.841,44.065L32.288,45.451C30.46,47.083 27.981,48 25.395,48H9.936C6.86,48 4.366,45.773 4.366,43.027V41.784C4.366,39.037 6.86,36.811 9.936,36.811ZM23.104,13.84C22.449,14.319 22.133,14.746 21.978,15.091C21.827,15.427 21.783,15.786 21.843,16.215C21.977,17.179 22.581,18.27 23.417,19.673L23.551,19.898C24.272,21.106 25.166,22.602 25.422,24.088C25.569,24.941 25.525,25.86 25.113,26.766C24.705,27.662 23.997,28.41 23.046,29.039C22.354,29.496 21.424,29.306 20.967,28.615C20.51,27.924 20.7,26.993 21.391,26.536C22.004,26.131 22.266,25.779 22.383,25.522C22.495,25.275 22.533,24.989 22.465,24.598C22.309,23.691 21.692,22.641 20.839,21.207L20.819,21.175C20.063,19.905 19.103,18.292 18.871,16.629C18.747,15.737 18.822,14.794 19.242,13.861C19.657,12.937 20.363,12.128 21.333,11.419C22.001,10.93 22.94,11.075 23.429,11.744C23.918,12.412 23.773,13.351 23.104,13.84ZM15.844,15.091C15.999,14.746 16.316,14.319 16.97,13.84C17.639,13.351 17.784,12.412 17.295,11.744C16.806,11.075 15.867,10.93 15.199,11.419C14.229,12.128 13.523,12.937 13.108,13.861C12.688,14.794 12.613,15.737 12.738,16.629C12.969,18.292 13.929,19.905 14.685,21.175L14.705,21.207C15.558,22.641 16.175,23.691 16.331,24.598C16.399,24.989 16.361,25.275 16.249,25.522C16.132,25.779 15.87,26.131 15.257,26.536C14.566,26.993 14.376,27.924 14.833,28.615C15.29,29.306 16.221,29.496 16.912,29.039C17.862,28.41 18.571,27.662 18.979,26.766C19.392,25.86 19.435,24.941 19.288,24.088C19.031,22.602 18.138,21.106 17.417,19.898L17.283,19.673C16.447,18.27 15.843,17.179 15.709,16.215C15.649,15.786 15.693,15.427 15.844,15.091ZM10.832,13.84C10.178,14.319 9.861,14.746 9.706,15.091C9.555,15.427 9.511,15.786 9.571,16.215C9.705,17.179 10.31,18.27 11.145,19.673L11.279,19.898C12,21.106 12.894,22.602 13.15,24.088C13.297,24.941 13.254,25.86 12.841,26.766C12.433,27.662 11.725,28.41 10.774,29.039C10.083,29.496 9.152,29.306 8.695,28.615C8.239,27.924 8.428,26.993 9.12,26.536C9.732,26.131 9.994,25.779 10.111,25.522C10.224,25.275 10.261,24.989 10.194,24.598C10.037,23.691 9.42,22.641 8.567,21.207L8.548,21.175C7.792,19.905 6.832,18.292 6.6,16.629C6.475,15.737 6.55,14.794 6.97,13.861C7.385,12.937 8.091,12.128 9.061,11.419C9.73,10.93 10.668,11.075 11.157,11.744C11.646,12.412 11.501,13.351 10.832,13.84Z"
+      android:fillColor="@color/hvac_off_icon_fill_color"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M24,90L24,90A5,5 0,0 1,29 95L29,95A5,5 0,0 1,24 100L24,100A5,5 0,0 1,19 95L19,95A5,5 0,0 1,24 90z"
+      android:fillColor="@color/hvac_off_icon_fill_color"
+      android:fillAlpha="@dimen/hvac_heat_or_cool_off_alpha"/>
+  <path
+      android:pathData="M24,68L24,68A5,5 0,0 1,29 73L29,73A5,5 0,0 1,24 78L24,78A5,5 0,0 1,19 73L19,73A5,5 0,0 1,24 68z"
+      android:fillColor="@color/hvac_off_icon_fill_color"
+      android:fillAlpha="@dimen/hvac_heat_or_cool_off_alpha"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_power_off.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_power_off.xml
new file mode 100644
index 0000000..aef5c8a
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_power_off.xml
@@ -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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M26.0001,4H22.0001V24H26.0001V4ZM33.9201,14.1L36.7401,11.28C43.7601,18.32 43.7601,29.7 36.7401,36.72C29.7201,43.76 18.3201,43.76 11.3001,36.74C4.2601,29.7 4.2601,18.3 11.2801,11.28L14.1001,14.08C8.6401,19.54 8.6601,28.44 14.1201,33.9C19.5601,39.34 28.4401,39.34 33.9001,33.88C39.3601,28.42 39.3801,19.56 33.9201,14.1Z"
+      android:fillColor="@color/hvac_off_icon_fill_color"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_power_on.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_power_on.xml
new file mode 100644
index 0000000..359ab84
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_power_on.xml
@@ -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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M26.0001,4H22.0001V24H26.0001V4ZM33.9201,14.1L36.7401,11.28C43.7601,18.32 43.7601,29.7 36.7401,36.72C29.7201,43.76 18.3201,43.76 11.3001,36.74C4.2601,29.7 4.2601,18.3 11.2801,11.28L14.1001,14.08C8.6401,19.54 8.6601,28.44 14.1201,33.9C19.5601,39.34 28.4401,39.34 33.9001,33.88C39.3601,28.42 39.3801,19.56 33.9201,14.1Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_recirculate_off.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_recirculate_off.xml
new file mode 100644
index 0000000..dbe02ed
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_recirculate_off.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M12,27C12,23.14 15.5,20 19.8,20H32.34L27.16,25.18L30,28L40,18L30,8L27.18,10.82L32.34,16H19.8C13.3,16 8,20.94 8,27C8,33.06 13.3,38 19.8,38H34V34H19.8C15.5,34 12,30.86 12,27Z"
+      android:fillColor="@color/hvac_off_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_recirculate_on.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_recirculate_on.xml
new file mode 100644
index 0000000..55aa087
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_recirculate_on.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M12,27C12,23.14 15.5,20 19.8,20H32.34L27.16,25.18L30,28L40,18L30,8L27.18,10.82L32.34,16H19.8C13.3,16 8,20.94 8,27C8,33.06 13.3,38 19.8,38H34V34H19.8C15.5,34 12,30.86 12,27Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_sync_off.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_sync_off.xml
new file mode 100644
index 0000000..97698d7
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_sync_off.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M33.6001,24.0001L40.8001,16.8001L33.6001,9.6001V14.4001H9.6001V19.2001H33.6001V24.0001Z"
+      android:fillColor="@color/hvac_off_icon_fill_color"/>
+  <path
+      android:pathData="M14.4002,24L7.2002,31.2L14.4002,38.4V33.6H38.4002V28.8H14.4002V24Z"
+      android:fillColor="@color/hvac_off_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_sync_on.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_sync_on.xml
new file mode 100644
index 0000000..ab3b3ab
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_sync_on.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/hvac_panel_icon_dimen"
+    android:height="@dimen/hvac_panel_icon_dimen"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M33.6001,24.0001L40.8001,16.8001L33.6001,9.6001V14.4001H9.6001V19.2001H33.6001V24.0001Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+  <path
+      android:pathData="M14.4002,24L7.2002,31.2L14.4002,38.4V33.6H38.4002V28.8H14.4002V24Z"
+      android:fillColor="@color/hvac_on_icon_fill_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/nav_bar_button_background.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/nav_bar_button_background.xml
new file mode 100644
index 0000000..de76f55
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/nav_bar_button_background.xml
@@ -0,0 +1,74 @@
+<!--
+  ~ 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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:aapt="http://schemas.android.com/aapt">
+    <item android:state_activated="true">
+        <layer-list>
+            <item>
+                <shape android:shape="rectangle">
+                    <size android:width="@dimen/system_bar_button_size"
+                          android:height="@dimen/system_bar_button_size"/>
+                    <corners
+                        android:radius="@dimen/system_bar_button_corner_radius"/>
+                    <solid
+                        android:color="@color/car_nav_icon_fill_color"/>
+                </shape>
+            </item>
+            <item>
+                <ripple android:color="@color/car_ui_ripple_color">
+                    <item>
+                        <shape android:shape="rectangle">
+                            <size android:width="@dimen/system_bar_button_size"
+                                  android:height="@dimen/system_bar_button_size"/>
+                            <corners
+                                android:radius="@dimen/system_bar_button_corner_radius"/>
+                            <solid
+                                android:color="@color/car_nav_icon_fill_color"/>
+                        </shape>
+                    </item>
+                </ripple>
+            </item>
+        </layer-list>
+    </item>
+    <item>
+        <layer-list>
+            <item>
+                <shape android:shape="rectangle">
+                    <size android:width="@dimen/system_bar_button_size"
+                          android:height="@dimen/system_bar_button_size"/>
+                    <corners
+                        android:radius="@dimen/system_bar_button_corner_radius"/>
+                    <solid
+                        android:color="@color/car_nav_icon_background_color"/>
+                </shape>
+            </item>
+            <item>
+                <ripple android:color="@color/car_ui_ripple_color">
+                    <item>
+                        <shape android:shape="rectangle">
+                            <size android:width="@dimen/system_bar_button_size"
+                                  android:height="@dimen/system_bar_button_size"/>
+                            <corners
+                                android:radius="@dimen/system_bar_button_corner_radius"/>
+                            <solid
+                                android:color="@color/car_nav_icon_background_color"/>
+                        </shape>
+                    </item>
+                </ripple>
+            </item>
+        </layer-list>
+    </item>
+</selector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_bottom_system_bar.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_bottom_system_bar.xml
new file mode 100644
index 0000000..557547a
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_bottom_system_bar.xml
@@ -0,0 +1,127 @@
+<?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.
+  -->
+
+<com.android.systemui.car.systembar.CarSystemBarView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@drawable/system_bar_background"
+    android:gravity="center"
+    android:orientation="horizontal">
+
+    <LinearLayout
+        android:id="@+id/nav_buttons"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <com.android.systemui.car.hvac.TemperatureControlView
+            android:id="@+id/driver_hvac"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:layout_gravity="start"
+            android:gravity="start|center_vertical"
+            systemui:hvacAreaId="49">
+            <include layout="@layout/adjustable_temperature_view"/>
+        </com.android.systemui.car.hvac.TemperatureControlView>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="center"
+            android:gravity="center"
+            android:layoutDirection="ltr">
+
+            <com.android.systemui.car.systembar.CarSystemBarButton
+                android:id="@+id/grid_nav"
+                style="@style/SystemBarButton"
+                systemui:componentNames="com.android.car.carlauncher/.AppGridActivity"
+                systemui:highlightWhenSelected="true"
+                systemui:icon="@drawable/car_ic_apps"
+                systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end"
+                systemui:clearBackStack="true"/>
+
+            <com.android.systemui.car.systembar.CarSystemBarButton
+                android:id="@+id/standalone_notifications"
+                style="@style/SystemBarButton"
+                systemui:componentNames="com.android.car.notification/.CarNotificationCenterActivity"
+                systemui:highlightWhenSelected="true"
+                systemui:icon="@drawable/car_ic_notification"
+                systemui:intent="intent:#Intent;component=com.android.car.notification/.CarNotificationCenterActivity;launchFlags=0x24000000;end"
+                systemui:longIntent="intent:#Intent;component=com.android.car.bugreport/.BugReportActivity;end"/>
+
+            <com.android.systemui.car.systembar.CarSystemBarButton
+                android:id="@+id/hvac"
+                style="@style/SystemBarButton"
+                systemui:highlightWhenSelected="true"
+                systemui:icon="@drawable/car_ic_hvac"
+                systemui:broadcast="true"/>
+
+            <com.android.systemui.car.systembar.AssitantButton
+                android:id="@+id/assist"
+                style="@style/SystemBarButton"
+                systemui:highlightWhenSelected="true"
+                systemui:icon="@drawable/car_ic_mic"
+                systemui:useDefaultAppIconForRole="true"/>
+        </LinearLayout>
+
+        <com.android.systemui.car.hvac.TemperatureControlView
+            android:id="@+id/passenger_hvac"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_gravity="end"
+            android:layout_weight="1"
+            android:gravity="end|center_vertical"
+            systemui:hvacAreaId="68">
+            <include layout="@layout/adjustable_temperature_view"/>
+        </com.android.systemui.car.hvac.TemperatureControlView>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/occlusion_buttons"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:gravity="center"
+        android:layoutDirection="ltr"
+        android:visibility="gone">
+        <com.android.systemui.car.hvac.TemperatureControlView
+            android:id="@+id/driver_hvac"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:layout_gravity="start"
+            android:gravity="start|center_vertical"
+            systemui:hvacAreaId="49">
+            <include layout="@layout/adjustable_temperature_view"/>
+        </com.android.systemui.car.hvac.TemperatureControlView>
+
+        <com.android.systemui.car.hvac.TemperatureControlView
+            android:id="@+id/passenger_hvac"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_gravity="end"
+            android:layout_weight="1"
+            android:gravity="end|center_vertical"
+            systemui:hvacAreaId="68">
+            <include layout="@layout/adjustable_temperature_view"/>
+        </com.android.systemui.car.hvac.TemperatureControlView>
+    </LinearLayout>
+</com.android.systemui.car.systembar.CarSystemBarView>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_top_system_bar.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_top_system_bar.xml
new file mode 100644
index 0000000..e30ec45
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_top_system_bar.xml
@@ -0,0 +1,85 @@
+<?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.
+  -->
+
+<com.android.systemui.car.systembar.CarSystemBarView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/car_top_bar"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@drawable/status_bar_background"
+    android:gravity="center"
+    android:orientation="horizontal">
+
+    <com.android.systemui.car.systembar.CarSystemBarButton
+        android:id="@+id/user_name"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_marginStart="@dimen/car_padding_3"
+        android:layout_gravity="start"
+        android:gravity="start"
+        android:orientation="horizontal"
+        systemui:intent="intent:#Intent;component=com.android.car.settings/.profiles.ProfileSwitcherActivity;launchFlags=0x24000000;end">
+            <ImageView
+                android:id="@+id/user_avatar"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:src="@drawable/car_ic_user_icon"
+                android:layout_marginEnd="@dimen/system_bar_user_icon_padding"/>
+            <TextView
+                android:id="@+id/user_name_text"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:gravity="center_vertical"
+                android:textAppearance="@style/TextAppearance.SystemBar.Username"
+                android:maxLines="1"
+                android:maxLength="10"/>
+    </com.android.systemui.car.systembar.CarSystemBarButton>
+
+    <com.android.systemui.statusbar.policy.Clock
+        android:id="@+id/clock"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:paddingStart="@dimen/car_padding_2"
+        android:paddingEnd="@dimen/car_padding_2"
+        android:elevation="5dp"
+        android:singleLine="true"
+        android:textAppearance="@style/TextAppearance.SystemBar.Clock"
+        systemui:amPmStyle="normal"/>
+
+    <com.android.systemui.car.systembar.CarSystemBarButton
+        android:id="@+id/system_icon_area"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_marginEnd="@dimen/car_padding_3"
+        android:layout_gravity="end"
+        android:gravity="end"
+        systemui:intent="intent:#Intent;component=com.android.car.settings/.common.CarSettingActivities$HomepageActivity;launchFlags=0x24000000;end">
+
+        <com.android.systemui.statusbar.phone.StatusIconContainer
+            android:id="@+id/statusIcons"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scaleType="fitCenter"
+            android:gravity="center"
+            android:orientation="horizontal"
+        />
+    </com.android.systemui.car.systembar.CarSystemBarButton>
+</com.android.systemui.car.systembar.CarSystemBarView>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/fan_direction.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/fan_direction.xml
new file mode 100644
index 0000000..1770796
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/fan_direction.xml
@@ -0,0 +1,110 @@
+<!--
+  ~ 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.
+  -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+       xmlns:systemui="http://schemas.android.com/apk/res-auto"
+       android:layout_width="match_parent"
+       android:layout_height="match_parent">
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <com.android.systemui.car.hvac.toggle.HvacIntegerToggleButton
+            android:id="@+id/direction_face"
+            android:layout_width="@dimen/hvac_panel_airflow_button_width_1"
+            android:layout_height="@dimen/hvac_panel_button_dimen"
+            android:layout_weight="1"
+            android:background="@drawable/hvac_default_background"
+            systemui:hvacAreaId="117"
+            systemui:hvacPropertyId="356517121"
+            systemui:hvacToggleOffButtonDrawable="@drawable/ic_airflow_head_off"
+            systemui:hvacToggleOnButtonDrawable="@drawable/ic_airflow_head_on"
+            systemui:hvacTurnOffIfAutoOn="true"
+            systemui:onValue="1"
+            systemui:offValue="0"
+            systemui:preventToggleOff="true"/>
+        <View
+            android:layout_width="32dp"
+            android:layout_height="match_parent"/>
+        <com.android.systemui.car.hvac.toggle.HvacIntegerToggleButton
+            android:id="@+id/direction_floor"
+            android:layout_width="@dimen/hvac_panel_airflow_button_width_1"
+            android:layout_height="@dimen/hvac_panel_button_dimen"
+            android:layout_weight="1"
+            android:background="@drawable/hvac_default_background"
+            systemui:hvacAreaId="117"
+            systemui:hvacPropertyId="356517121"
+            systemui:hvacToggleOffButtonDrawable="@drawable/ic_airflow_feet_off"
+            systemui:hvacToggleOnButtonDrawable="@drawable/ic_airflow_feet_on"
+            systemui:hvacTurnOffIfAutoOn="true"
+            systemui:onValue="2"
+            systemui:offValue="0"
+            systemui:preventToggleOff="true"/>
+        <View
+            android:layout_width="32dp"
+            android:layout_height="match_parent"/>
+        <com.android.systemui.car.hvac.toggle.HvacIntegerToggleButton
+            android:id="@+id/direction_defrost_front_and_floor"
+            android:layout_width="@dimen/hvac_panel_airflow_button_width_1"
+            android:layout_height="@dimen/hvac_panel_button_dimen"
+            android:layout_weight="1"
+            android:background="@drawable/hvac_default_background"
+            systemui:hvacAreaId="117"
+            systemui:hvacPropertyId="356517121"
+            systemui:hvacToggleOffButtonDrawable="@drawable/ic_airflow_windshield_off"
+            systemui:hvacToggleOnButtonDrawable="@drawable/ic_airflow_windshield_on"
+            systemui:hvacTurnOffIfAutoOn="true"
+            systemui:onValue="6"
+            systemui:offValue="0"
+            systemui:preventToggleOff="true"/>
+    </LinearLayout>
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="32dp"/>
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <com.android.systemui.car.hvac.toggle.HvacBooleanToggleButton
+            android:id="@+id/direction_defrost_front"
+            android:layout_width="@dimen/hvac_panel_airflow_button_width_2"
+            android:layout_height="@dimen/hvac_panel_button_dimen"
+            android:layout_weight="1"
+            android:background="@drawable/hvac_default_background"
+            systemui:hvacAreaId="1"
+            systemui:hvacPropertyId="320865540"
+            systemui:hvacToggleOffButtonDrawable="@drawable/ic_defroster_windshield_off"
+            systemui:hvacToggleOnButtonDrawable="@drawable/ic_defroster_windshield_on"
+            systemui:hvacTurnOffIfAutoOn="true"
+            systemui:onValue="1"
+            systemui:offValue="0"/>
+        <View
+            android:layout_width="32dp"
+            android:layout_height="match_parent"/>
+        <com.android.systemui.car.hvac.toggle.HvacBooleanToggleButton
+            android:id="@+id/direction_defrost_rear"
+            android:layout_width="@dimen/hvac_panel_airflow_button_width_2"
+            android:layout_height="@dimen/hvac_panel_button_dimen"
+            android:layout_weight="1"
+            android:background="@drawable/hvac_default_background"
+            systemui:hvacAreaId="2"
+            systemui:hvacPropertyId="320865540"
+            systemui:hvacToggleOffButtonDrawable="@drawable/ic_defroster_rear_off"
+            systemui:hvacToggleOnButtonDrawable="@drawable/ic_defroster_rear_on"
+            systemui:hvacTurnOffIfAutoOn="true"
+            systemui:onValue="1"
+            systemui:offValue="0"/>
+    </LinearLayout>
+</merge>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/hvac_panel_container.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/hvac_panel_container.xml
new file mode 100644
index 0000000..86df240
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/hvac_panel_container.xml
@@ -0,0 +1,192 @@
+<?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.
+  -->
+
+<com.android.car.ui.FocusArea
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/hvac_panel_container"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/hvac_panel_full_expanded_height"
+    android:layout_gravity="bottom">
+    <com.android.systemui.car.hvac.HvacPanelView
+        android:id="@+id/hvac_panel"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@drawable/hvac_panel_bg">
+
+        <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/top_guideline"
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:orientation="horizontal"
+            app:layout_constraintGuide_begin="@dimen/hvac_panel_buttons_guideline"/>
+
+        <!-- ************************ -->
+        <!-- First group of buttons. -->
+        <!-- ************************ -->
+
+        <com.android.systemui.car.hvac.toggle.HvacBooleanToggleButton
+            android:id="@+id/cooling_on_off"
+            android:layout_width="@dimen/hvac_panel_button_dimen"
+            android:layout_height="@dimen/hvac_panel_long_button_dimen"
+            android:layout_marginLeft="@dimen/hvac_panel_button_external_margin"
+            android:layout_marginTop="@dimen/hvac_panel_button_external_top_margin"
+            android:background="@drawable/hvac_default_background"
+            app:layout_constraintLeft_toLeftOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/top_guideline"
+            systemui:hvacAreaId="117"
+            systemui:hvacPropertyId="354419973"
+            systemui:hvacToggleOffButtonDrawable="@drawable/ic_ac_off"
+            systemui:hvacToggleOnButtonDrawable="@drawable/ic_ac_on"
+            systemui:hvacTurnOffIfAutoOn="true"/>
+
+        <com.android.systemui.car.hvac.SeatTemperatureLevelButton
+            android:id="@+id/seat_heater_driver_on_off"
+            android:layout_width="@dimen/hvac_panel_button_dimen"
+            android:layout_height="@dimen/hvac_panel_long_button_dimen"
+            android:layout_marginLeft="@dimen/hvac_panel_button_internal_margin"
+            android:layout_marginTop="@dimen/hvac_panel_button_external_top_margin"
+            android:background="@drawable/hvac_heat_background"
+            app:layout_constraintRight_toRightOf="@+id/hvac_on_off"
+            app:layout_constraintTop_toBottomOf="@+id/top_guideline"
+            systemui:hvacAreaId="1"
+            systemui:seatTemperatureType="heating"
+            systemui:seatTemperatureIconDrawableList="@array/hvac_driver_seat_heat_icons"/>
+
+        <com.android.systemui.car.hvac.toggle.HvacBooleanToggleButton
+            android:id="@+id/hvac_on_off"
+            android:layout_width="@dimen/hvac_panel_long_button_dimen"
+            android:layout_height="@dimen/hvac_panel_button_dimen"
+            android:layout_marginBottom="@dimen/hvac_panel_button_external_bottom_margin"
+            android:layout_marginLeft="@dimen/hvac_panel_button_external_margin"
+            android:layout_marginTop="@dimen/hvac_panel_button_internal_margin"
+            android:background="@drawable/hvac_default_background"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintLeft_toLeftOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/cooling_on_off"
+            systemui:hvacAreaId="117"
+            systemui:hvacPropertyId="354419984"
+            systemui:hvacToggleOffButtonDrawable="@drawable/ic_power_off"
+            systemui:hvacToggleOnButtonDrawable="@drawable/ic_power_on"
+            systemui:hvacTurnOffIfPowerOff="false"/>
+
+        <!-- ************************ -->
+        <!-- Second group of buttons. -->
+        <!-- ************************ -->
+
+        <LinearLayout
+            android:id="@+id/airflow_group"
+            android:layout_width="@dimen/hvac_panel_slider_width"
+            android:layout_height="0dp"
+            android:layout_marginLeft="@dimen/hvac_panel_button_internal_margin"
+            android:layout_marginRight="@dimen/hvac_panel_button_internal_margin"
+            android:layout_marginTop="@dimen/hvac_panel_button_external_top_margin"
+            android:orientation="vertical"
+            app:layout_constraintLeft_toRightOf="@+id/seat_heater_driver_on_off"
+            app:layout_constraintRight_toLeftOf="@+id/seat_heater_passenger_on_off"
+            app:layout_constraintTop_toBottomOf="@+id/top_guideline">
+           <include layout="@layout/fan_direction"/>
+        </LinearLayout>
+
+        <com.android.systemui.car.hvac.custom.FanSpeedSeekBar
+            android:id="@+id/fan_speed_control"
+            android:layout_width="@dimen/hvac_panel_slider_width"
+            android:layout_height="@dimen/hvac_panel_button_dimen"
+            android:layout_marginBottom="@dimen/hvac_panel_button_external_bottom_margin"
+            android:layout_marginLeft="@dimen/hvac_panel_button_internal_margin"
+            android:layout_marginRight="@dimen/hvac_panel_button_internal_margin"
+            android:layout_marginTop="@dimen/hvac_panel_button_internal_margin"
+            android:progressDrawable="@drawable/fan_speed_seek_bar"
+            android:thumb="@drawable/fan_speed_seek_bar_thumb"
+            android:maxHeight="@dimen/hvac_panel_button_dimen"
+            android:minHeight="@dimen/hvac_panel_button_dimen"
+            android:background="@drawable/fan_speed_seek_bar_background"
+            android:splitTrack="false"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintLeft_toLeftOf="@+id/airflow_group"
+            app:layout_constraintRight_toRightOf="@+id/airflow_group"
+            app:layout_constraintTop_toBottomOf="@+id/airflow_group"/>
+
+        <!-- ************************* -->
+        <!-- Third group of buttons. -->
+        <!-- ************************* -->
+
+        <com.android.systemui.car.hvac.SeatTemperatureLevelButton
+            android:id="@+id/seat_heater_passenger_on_off"
+            android:layout_width="@dimen/hvac_panel_button_dimen"
+            android:layout_height="@dimen/hvac_panel_long_button_dimen"
+            android:layout_marginRight="@dimen/hvac_panel_button_internal_margin"
+            android:layout_marginTop="@dimen/hvac_panel_button_external_top_margin"
+            android:background="@drawable/hvac_heat_background"
+            app:layout_constraintLeft_toLeftOf="@+id/hvac_driver_passenger_sync"
+            app:layout_constraintTop_toBottomOf="@+id/top_guideline"
+            systemui:hvacAreaId="4"
+            systemui:seatTemperatureType="heating"
+            systemui:seatTemperatureIconDrawableList="@array/hvac_passenger_seat_heat_icons"/>
+
+        <com.android.systemui.car.hvac.toggle.HvacBooleanToggleButton
+            android:id="@+id/recycle_air_on_off"
+            android:layout_width="@dimen/hvac_panel_button_dimen"
+            android:layout_height="@dimen/hvac_panel_button_dimen"
+            android:layout_marginRight="@dimen/hvac_panel_button_external_margin"
+            android:layout_marginTop="@dimen/hvac_panel_button_external_top_margin"
+            android:background="@drawable/hvac_default_background"
+            app:layout_constraintRight_toRightOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/top_guideline"
+            systemui:hvacAreaId="117"
+            systemui:hvacPropertyId="354419976"
+            systemui:hvacTurnOffIfAutoOn="true"
+            systemui:hvacToggleOnButtonDrawable="@drawable/ic_recirculate_on"
+            systemui:hvacToggleOffButtonDrawable="@drawable/ic_recirculate_off"/>
+
+        <com.android.systemui.car.hvac.toggle.HvacBooleanToggleButton
+            android:id="@+id/auto_temperature_on_off"
+            android:layout_width="@dimen/hvac_panel_button_dimen"
+            android:layout_height="@dimen/hvac_panel_button_dimen"
+            android:layout_marginRight="@dimen/hvac_panel_button_external_margin"
+            android:layout_marginTop="@dimen/hvac_panel_button_internal_margin"
+            android:background="@drawable/hvac_default_background"
+            app:layout_constraintRight_toRightOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/recycle_air_on_off"
+            systemui:hvacAreaId="117"
+            systemui:hvacPropertyId="354419978"
+            systemui:hvacToggleOnButtonDrawable="@drawable/ic_auto_on"
+            systemui:hvacToggleOffButtonDrawable="@drawable/ic_auto_off"/>
+
+        <com.android.systemui.car.hvac.toggle.HvacBooleanToggleButton
+            android:id="@+id/hvac_driver_passenger_sync"
+            android:layout_width="@dimen/hvac_panel_long_button_dimen"
+            android:layout_height="@dimen/hvac_panel_button_dimen"
+            android:layout_marginBottom="@dimen/hvac_panel_button_external_bottom_margin"
+            android:layout_marginRight="@dimen/hvac_panel_button_external_margin"
+            android:layout_marginTop="@dimen/hvac_panel_button_internal_margin"
+            android:background="@drawable/hvac_default_background"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintRight_toRightOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/auto_temperature_on_off"
+            systemui:hvacAreaId="117"
+            systemui:hvacPropertyId="354419977"
+            systemui:hvacToggleOffButtonDrawable="@drawable/ic_sync_off"
+            systemui:hvacToggleOnButtonDrawable="@drawable/ic_sync_on"
+            systemui:hvacTurnOffIfAutoOn="true"/>
+
+        <include
+            layout="@layout/hvac_panel_handle_bar"/>
+    </com.android.systemui.car.hvac.HvacPanelView>
+</com.android.car.ui.FocusArea>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/hvac_panel_handle_bar.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/hvac_panel_handle_bar.xml
new file mode 100644
index 0000000..62a1e81
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/hvac_panel_handle_bar.xml
@@ -0,0 +1,37 @@
+<?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.
+  -->
+
+<merge
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <FrameLayout
+        android:id="@+id/handle_bar"
+        android:layout_width="@dimen/hvac_panel_handle_bar_container_width"
+        android:layout_height="@dimen/hvac_panel_handle_bar_container_height"
+        android:layout_gravity="center"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintHorizontal_chainStyle="packed">
+        <View
+            android:layout_width="@dimen/hvac_panel_handle_bar_width"
+            android:layout_height="@dimen/hvac_panel_handle_bar_height"
+            android:layout_marginTop="@dimen/hvac_panel_handle_bar_margin_top"
+            android:layout_gravity="top|center_horizontal"
+            android:background="@drawable/hvac_panel_handle_bar"/>
+    </FrameLayout>
+</merge>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/text_toast.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/text_toast.xml
new file mode 100644
index 0000000..87eb12c
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/text_toast.xml
@@ -0,0 +1,45 @@
+<?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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:gravity="center_vertical"
+    android:maxWidth="@*android:dimen/toast_width"
+    android:background="@android:drawable/toast_frame"
+    android:elevation="@*android:dimen/toast_elevation"
+    android:paddingEnd="@dimen/toast_margin"
+    android:paddingTop="@dimen/toast_margin"
+    android:paddingBottom="@dimen/toast_margin"
+    android:paddingStart="@dimen/toast_margin"
+    android:layout_marginBottom="@dimen/toast_bottom_margin">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="@dimen/toast_icon_dimen"
+        android:layout_height="@dimen/toast_icon_dimen"
+        android:layout_marginEnd="@dimen/toast_margin"/>
+    <TextView
+        android:id="@+id/text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:textAppearance="@*android:style/TextAppearance.Toast"/>
+</LinearLayout>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/arrays.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/arrays.xml
new file mode 100644
index 0000000..828003f
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/arrays.xml
@@ -0,0 +1,39 @@
+<?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>
+    <array name="hvac_driver_seat_heat_icons">
+        <item>@drawable/ic_driver_seat_heat_off</item>
+        <item>@drawable/ic_driver_seat_heat_low</item>
+        <item>@drawable/ic_driver_seat_heat_high</item>
+    </array>
+    <array name="hvac_passenger_seat_heat_icons">
+        <item>@drawable/ic_passenger_seat_heat_off</item>
+        <item>@drawable/ic_passenger_seat_heat_low</item>
+        <item>@drawable/ic_passenger_seat_heat_high</item>
+    </array>
+    <array name="hvac_fan_speed_icons">
+        <item>@drawable/fan_speed_seek_bar_thumb_1</item>
+        <item>@drawable/fan_speed_seek_bar_thumb_2</item>
+        <item>@drawable/fan_speed_seek_bar_thumb_3</item>
+        <item>@drawable/fan_speed_seek_bar_thumb_4</item>
+        <item>@drawable/fan_speed_seek_bar_thumb_5</item>
+        <item>@drawable/fan_speed_seek_bar_thumb_6</item>
+        <item>@drawable/fan_speed_seek_bar_thumb_7</item>
+        <item>@drawable/fan_speed_seek_bar_thumb_8</item>
+    </array>
+</resources>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/attrs.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/attrs.xml
new file mode 100644
index 0000000..120905f
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/attrs.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ 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>
+    <declare-styleable name="FanSpeedSeekBar">
+        <!-- List of drawables that will be shown when the seat heat level button is clicked.
+             This list should have exactly R.integer.hvac_seat_heat_level_count items.
+             The first item should have the "off" drawable. -->
+        <attr name="fanSpeedThumbIcons" format="reference"/>
+    </declare-styleable>
+</resources>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/colors.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/colors.xml
new file mode 100644
index 0000000..8792680
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/colors.xml
@@ -0,0 +1,45 @@
+<?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="rear_view_camera_button_background">#CCFFFFFF</color>
+    <color name="rear_view_camera_exit_icon_color">@android:color/black</color>
+
+    <color name="car_nav_icon_fill_color">#FFFFFF</color>
+    <color name="car_nav_icon_background_color">#282A2D</color>
+
+    <drawable name="system_bar_background">#000000</drawable>
+    <color name="system_bar_text_color">#E8EAED</color>
+    <color name="hvac_temperature_adjust_button_color">#282A2D</color>
+    <color name="hvac_temperature_decrease_arrow_color">#E8EAED</color>
+    <color name="hvac_temperature_increase_arrow_color">#E8EAED</color>
+
+    <color name="status_bar_background_color">#00000000</color>
+    <drawable name="status_bar_background">@color/status_bar_background_color</drawable>
+
+    <color name="hvac_background_color">#202124</color>
+    <color name="hvac_master_switch_color">@color/car_nav_icon_fill_color</color>
+    <color name="hvac_on_icon_fill_color">@android:color/black</color>
+    <color name="hvac_off_icon_fill_color">@android:color/white</color>
+    <color name="hvac_on_cooling_background_color">#6BF0FF</color>
+    <color name="hvac_on_heating_background_color">#EE675C</color>
+    <color name="hvac_on_background_color">#6BF0FF</color>
+    <color name="hvac_off_background_color">#3C4043</color>
+    <color name="hvac_panel_handle_bar_color">#3C4043</color>
+
+    <color name="dark_mode_icon_color_single_tone">@color/car_nav_icon_fill_color</color>
+    <color name="light_mode_icon_color_single_tone">@color/car_nav_icon_fill_color</color>
+</resources>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/config.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/config.xml
new file mode 100644
index 0000000..fa95f56
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/config.xml
@@ -0,0 +1,33 @@
+<?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>
+    <string name="config_systemUIFactoryComponent" translatable="false">
+        com.android.systemui.CarUiPortraitSystemUIFactory
+    </string>
+
+    <!-- Car System UI's OverlayViewsMediator.
+         Whenever a new class is added, make sure to also add that class to OverlayWindowModule. -->
+    <string-array name="config_carSystemUIOverlayViewsMediators" translatable="false">
+        <item>com.android.systemui.car.hvac.HvacPanelOverlayViewMediator</item>
+        <item>com.android.systemui.car.keyguard.CarKeyguardViewMediator</item>
+        <item>com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator</item>
+        <item>com.android.systemui.car.userswitcher.UserSwitchTransitionViewMediator</item>
+    </string-array>
+
+    <integer name="hvac_num_fan_speeds">8</integer>
+</resources>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/dimens.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/dimens.xml
new file mode 100644
index 0000000..8ed622b
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/dimens.xml
@@ -0,0 +1,79 @@
+<?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>
+    <!-- dimensions for rear view camera -->
+    <dimen name="rear_view_camera_width">1020dp</dimen>
+    <dimen name="rear_view_camera_height">720dp</dimen>
+
+    <dimen name="rear_view_camera_exit_button_width">48dp</dimen>
+    <dimen name="rear_view_camera_exit_button_height">48dp</dimen>
+    <dimen name="rear_view_camera_exit_button_margin">24dp</dimen>
+    <dimen name="rear_view_camera_exit_icon_width">26dp</dimen>
+    <dimen name="rear_view_camera_exit_icon_height">26dp</dimen>
+
+    <dimen name="hvac_container_padding">24dp</dimen>
+    <dimen name="hvac_temperature_text_size">56sp</dimen>
+    <dimen name="hvac_temperature_text_padding">12dp</dimen>
+    <dimen name="hvac_temperature_button_size">64dp</dimen>
+
+    <dimen name="system_bar_icon_drawing_size">56dp</dimen>
+    <dimen name="system_bar_button_size">88dp</dimen>
+    <!-- Margin between the system bar buttons -->
+    <dimen name="system_bar_button_margin">40dp</dimen>
+    <!-- Padding between the system bar button and the icon within it -->
+    <dimen name="system_bar_button_padding">16dp</dimen>
+    <dimen name="system_bar_button_corner_radius">24dp</dimen>
+
+    <dimen name="system_bar_user_icon_drawing_size">44dp</dimen>
+    <dimen name="status_bar_system_icon_spacing">32dp</dimen>
+
+    <dimen name="hvac_panel_handle_bar_container_height">64dp</dimen>
+    <dimen name="hvac_panel_handle_bar_container_width">728dp</dimen>
+    <dimen name="hvac_panel_handle_bar_height">6dp</dimen>
+    <dimen name="hvac_panel_handle_bar_margin_top">17dp</dimen>
+    <dimen name="hvac_panel_handle_bar_width">120dp</dimen>
+    <dimen name="hvac_panel_bg_radius">24dp</dimen>
+    <dimen name="hvac_panel_off_button_radius">24dp</dimen>
+    <dimen name="hvac_panel_on_button_radius">24dp</dimen>
+    <dimen name="hvac_panel_seek_bar_radius">44dp</dimen>
+    <dimen name="hvac_panel_full_expanded_height">456dp</dimen>
+    <dimen name="hvac_panel_title_margin">36dp</dimen>
+    <dimen name="hvac_panel_buttons_guideline">@dimen/hvac_panel_handle_bar_container_height</dimen>
+
+    <dimen name="hvac_panel_icon_dimen">48dp</dimen>
+    <dimen name="hvac_panel_tall_icon_dimen">108dp</dimen>
+    <dimen name="hvac_panel_wide_icon_dimen">96dp</dimen>
+
+    <dimen name="hvac_panel_button_dimen">88dp</dimen>
+    <dimen name="hvac_panel_long_button_dimen">208dp</dimen>
+    <dimen name="hvac_panel_airflow_button_width_1">210dp</dimen>
+    <dimen name="hvac_panel_airflow_button_width_2">332dp</dimen>
+    <dimen name="hvac_panel_slider_width">696dp</dimen>
+
+    <dimen name="hvac_panel_button_external_margin">24dp</dimen>
+    <dimen name="hvac_panel_button_external_top_margin">16dp</dimen>
+    <dimen name="hvac_panel_button_external_bottom_margin">48dp</dimen>
+    <dimen name="hvac_panel_button_internal_margin">32dp</dimen>
+
+    <item name="hvac_heat_or_cool_off_alpha" format="float" type="dimen">0.3</item>
+
+    <dimen name="toast_margin">24dp</dimen>
+    <dimen name="toast_icon_dimen">48dp</dimen>
+    <dimen name="toast_bottom_margin">32dp</dimen>
+
+</resources>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/integers.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/integers.xml
new file mode 100644
index 0000000..282373c
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/integers.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ 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>
+    <integer name="hvac_seat_heat_level_count">3</integer>
+</resources>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/strings.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/strings.xml
new file mode 100644
index 0000000..c9cff07
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/strings.xml
@@ -0,0 +1,24 @@
+<?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:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Format for temperature in the temperature control view (No decimal) -->
+    <string name="hvac_temperature_format_fahrenheit" translatable="false">%.0f</string>
+
+    <!-- HVAC panel header [CHAR LIMIT=40]-->
+    <string name="hvac_panel_header">Comfort controls</string>
+</resources>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/styles.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/styles.xml
new file mode 100644
index 0000000..6e400e5
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/styles.xml
@@ -0,0 +1,33 @@
+<!--
+  ~ 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">
+
+    <style name="SystemBarButton">
+        <item name="android:layout_width">@dimen/system_bar_button_size</item>
+        <item name="android:layout_height">@dimen/system_bar_button_size</item>
+        <item name="android:background">@drawable/nav_bar_button_background</item>
+        <item name="android:gravity">center</item>
+        <item name="unselectedAlpha">1.0</item>
+    </style>
+
+    <style name="TextAppearance.SystemBar.Username"
+           parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:textSize">@dimen/car_body1_size</item>
+        <item name="android:textColor">@color/system_bar_text_color</item>
+    </style>
+</resources>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/CarUiPortraitGlobalRootComponent.java b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/CarUiPortraitGlobalRootComponent.java
new file mode 100644
index 0000000..af85885
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/CarUiPortraitGlobalRootComponent.java
@@ -0,0 +1,44 @@
+/*
+ * 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.systemui;
+
+import com.android.systemui.dagger.GlobalModule;
+import com.android.systemui.dagger.WMModule;
+
+import javax.inject.Singleton;
+
+import dagger.Component;
+
+/**
+ * Root Component for Dagger injection for CarUiPortraitSystemUI
+ */
+@Singleton
+@Component(
+        modules = {
+                GlobalModule.class,
+                CarUiPortraitSysUIComponentModule.class,
+                WMModule.class
+        })
+interface CarUiPortraitGlobalRootComponent extends CarGlobalRootComponent {
+    @Component.Builder
+    interface Builder extends CarGlobalRootComponent.Builder {
+        CarUiPortraitGlobalRootComponent build();
+    }
+
+    @Override
+    CarUiPortraitSysUIComponent.Builder getSysUIComponent();
+}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/CarUiPortraitSysUIComponent.java b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/CarUiPortraitSysUIComponent.java
new file mode 100644
index 0000000..25e488f
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/CarUiPortraitSysUIComponent.java
@@ -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.
+ */
+
+package com.android.systemui;
+
+import com.android.systemui.dagger.DependencyProvider;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.SystemUIModule;
+
+import dagger.Subcomponent;
+
+/**
+ * Dagger Subcomponent for Core SysUI.
+ */
+@SysUISingleton
+@Subcomponent(modules = {
+        CarComponentBinder.class,
+        DependencyProvider.class,
+        SystemUIModule.class,
+        CarSystemUIModule.class,
+        CarUiPortraitSystemUIBinder.class})
+public interface CarUiPortraitSysUIComponent extends CarSysUIComponent {
+    /**
+     * Builder for a CarSysUIComponent.
+     */
+    @Subcomponent.Builder
+    interface Builder extends CarSysUIComponent.Builder {
+        CarUiPortraitSysUIComponent build();
+    }
+}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/CarUiPortraitSysUIComponentModule.java b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/CarUiPortraitSysUIComponentModule.java
new file mode 100644
index 0000000..196c917
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/CarUiPortraitSysUIComponentModule.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui;
+
+import dagger.Binds;
+import dagger.Module;
+
+/**
+ * Dagger module for including the CarUiPortraitSysUIComponent.
+ *
+ * TODO(b/162923491): Remove or otherwise refactor this module. This is a stop gap.
+ */
+@Module(subcomponents = {CarUiPortraitSysUIComponent.class})
+public abstract class CarUiPortraitSysUIComponentModule {
+    @Binds
+    abstract CarGlobalRootComponent bindSystemUIRootComponent(
+            CarUiPortraitGlobalRootComponent systemUIRootComponent);
+}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/CarUiPortraitSystemUIBinder.java b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/CarUiPortraitSystemUIBinder.java
new file mode 100644
index 0000000..a7cddb8
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/CarUiPortraitSystemUIBinder.java
@@ -0,0 +1,24 @@
+/*
+ * 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.systemui;
+
+import dagger.Module;
+
+/** Binder for AAECarSystemUI specific {@link SystemUI} modules and components. */
+@Module
+abstract class CarUiPortraitSystemUIBinder extends CarSystemUIBinder {
+}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/CarUiPortraitSystemUIFactory.java b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/CarUiPortraitSystemUIFactory.java
new file mode 100644
index 0000000..ef75f48
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/CarUiPortraitSystemUIFactory.java
@@ -0,0 +1,31 @@
+/*
+ * 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.systemui;
+
+import android.content.Context;
+
+import com.android.systemui.dagger.GlobalRootComponent;
+
+/**
+ * Class factory to provide AAECarSystemUI specific SystemUI components.
+ */
+public class CarUiPortraitSystemUIFactory extends CarSystemUIFactory {
+    @Override
+    protected GlobalRootComponent buildGlobalRootComponent(Context context) {
+        return DaggerCarUiPortraitGlobalRootComponent.builder().context(context).build();
+    }
+}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/hvac/custom/FanSpeedSeekBar.java b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/hvac/custom/FanSpeedSeekBar.java
new file mode 100644
index 0000000..bc7f4f2
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/hvac/custom/FanSpeedSeekBar.java
@@ -0,0 +1,217 @@
+/*
+ * 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.systemui.car.hvac.custom;
+
+import static android.car.VehiclePropertyIds.HVAC_AUTO_ON;
+import static android.car.VehiclePropertyIds.HVAC_FAN_SPEED;
+import static android.car.VehiclePropertyIds.HVAC_POWER_ON;
+
+import android.car.hardware.CarPropertyValue;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.widget.SeekBar;
+
+import androidx.annotation.ArrayRes;
+
+import com.android.systemui.R;
+import com.android.systemui.car.hvac.HvacController;
+import com.android.systemui.car.hvac.HvacPropertySetter;
+import com.android.systemui.car.hvac.HvacView;
+
+/** Custom seek bar to control fan speed. */
+public class FanSpeedSeekBar extends SeekBar implements HvacView {
+
+    private static final boolean DEBUG = Build.IS_ENG || Build.IS_USERDEBUG;
+    private static final String TAG = "FanSpeedSeekBar";
+
+    private final SparseArray<Drawable> mIcons = new SparseArray<>();
+
+    private HvacPropertySetter mHvacPropertySetter;
+    private int mHvacGlobalAreaId;
+
+    private boolean mPowerOn;
+    private boolean mAutoOn;
+
+    private float mOnAlpha;
+    private float mOffAlpha;
+
+    private final OnSeekBarChangeListener mSeekBarChangeListener = new OnSeekBarChangeListener() {
+        @Override
+        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+            int prevProgress = getProgress();
+            // Limit updates to the hvac property to be only those that come from the user in order
+            // to avoid an infinite loop.
+            if (shouldAllowControl() && fromUser && progress == prevProgress) {
+                mHvacPropertySetter.setHvacProperty(HVAC_FAN_SPEED, getAreaId(), progress);
+            } else if (progress != prevProgress) {
+                // There is an edge case with seek bar touch handling that can lead to an
+                // inconsistent state of the progress state and UI. We need to set the progress to
+                // a different value before setting it to the value we expect in order to ensure
+                // that the update doesn't get dropped.
+                setProgress(progress);
+                setProgress(prevProgress);
+                updateUI();
+            }
+        }
+
+        @Override
+        public void onStartTrackingTouch(SeekBar seekBar) {
+            // no-op.
+        }
+
+        @Override
+        public void onStopTrackingTouch(SeekBar seekBar) {
+            // no-op.
+        }
+    };
+
+    public FanSpeedSeekBar(Context context) {
+        super(context);
+        init(null);
+    }
+
+    public FanSpeedSeekBar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(attrs);
+    }
+
+    public FanSpeedSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init(attrs);
+    }
+
+    public FanSpeedSeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init(attrs);
+    }
+
+    private void init(AttributeSet attrs) {
+        int speeds = mContext.getResources().getInteger(R.integer.hvac_num_fan_speeds);
+        if (speeds < 1) {
+            throw new IllegalArgumentException("The nuer of fan speeds should be > 1");
+        }
+
+        setMin(1);
+        incrementProgressBy(1);
+        setMax(speeds);
+        int thumbRadius = mContext.getResources().getDimensionPixelSize(
+                R.dimen.hvac_panel_seek_bar_radius);
+        setPadding(thumbRadius, 0, thumbRadius, 0);
+        mHvacGlobalAreaId = mContext.getResources().getInteger(R.integer.hvac_global_area_id);
+
+        mOnAlpha = mContext.getResources().getFloat(R.dimen.hvac_turned_on_alpha);
+        mOffAlpha = mContext.getResources().getFloat(R.dimen.hvac_turned_off_alpha);
+
+        if (attrs == null) {
+            return;
+        }
+
+        // Get fan speed thumb drawables.
+        TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.FanSpeedSeekBar);
+        @ArrayRes int drawableListRes = typedArray.getResourceId(
+                R.styleable.FanSpeedSeekBar_fanSpeedThumbIcons,
+                R.array.hvac_fan_speed_icons);
+
+        TypedArray fanSpeedThumbIcons = mContext.getResources().obtainTypedArray(drawableListRes);
+        if (fanSpeedThumbIcons.length() != speeds) {
+            throw new IllegalArgumentException(
+                    "R.styeable.SeatHeatLevelButton_seatHeaterIconDrawableList should have the "
+                            + "same length as R.integer.hvac_seat_heat_level_count");
+        }
+
+        for (int i = 0; i < speeds; i++) {
+            mIcons.set(i + 1, fanSpeedThumbIcons.getDrawable(i));
+        }
+        fanSpeedThumbIcons.recycle();
+        typedArray.recycle();
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        setOnSeekBarChangeListener(mSeekBarChangeListener);
+    }
+
+    @Override
+    public void setHvacPropertySetter(HvacPropertySetter hvacPropertySetter) {
+        mHvacPropertySetter = hvacPropertySetter;
+    }
+
+    @Override
+    public void onHvacTemperatureUnitChanged(boolean usesFahrenheit) {
+        // no-op.
+    }
+
+    @Override
+    public void onPropertyChanged(CarPropertyValue value) {
+        if (value == null) {
+            if (DEBUG) {
+                Log.w(TAG, "onPropertyChanged: received null value");
+            }
+            return;
+        }
+
+        if (DEBUG) {
+            Log.w(TAG, "onPropertyChanged: property id: " + value.getPropertyId());
+            Log.w(TAG, "onPropertyChanged: area id: " + value.getAreaId());
+            Log.w(TAG, "onPropertyChanged: value: " + value.getValue());
+        }
+
+        if (value.getPropertyId() == HVAC_FAN_SPEED) {
+            int level = (int) value.getValue();
+            setProgress(level, /* animate= */ true);
+        }
+
+        if (value.getPropertyId() == HVAC_POWER_ON) {
+            mPowerOn = (boolean) value.getValue();
+        }
+
+        if (value.getPropertyId() == HVAC_AUTO_ON) {
+            mAutoOn = (boolean) value.getValue();
+        }
+
+        updateUI();
+    }
+
+    @Override
+    public @HvacController.HvacProperty Integer getHvacPropertyToView() {
+        return HVAC_FAN_SPEED;
+    }
+
+    @Override
+    public @HvacController.AreaId Integer getAreaId() {
+        return mHvacGlobalAreaId;
+    }
+
+    private void updateUI() {
+        int progress = getProgress();
+        setThumb(mIcons.get(progress));
+        setSelected(progress > 0);
+        setAlpha(shouldAllowControl() ? mOnAlpha : mOffAlpha);
+        // Steal touch events if shouldn't allow control.
+        setOnTouchListener(shouldAllowControl() ? null : (v, event) -> true);
+    }
+
+    private boolean shouldAllowControl() {
+        return mPowerOn && !mAutoOn;
+    }
+}
diff --git a/car_product/car_ui_portrait/apps/HideApps/Android.mk b/car_product/car_ui_portrait/apps/HideApps/Android.mk
new file mode 100644
index 0000000..74cdcaa
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/HideApps/Android.mk
@@ -0,0 +1,34 @@
+#
+# 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_PACKAGE_NAME := CarUiPortraitHideApps
+LOCAL_SDK_VERSION := current
+
+# Add packages here to remove them from the build
+LOCAL_OVERRIDES_PACKAGES := \
+    CarRotaryController \
+    RotaryPlayground \
+    RotaryIME \
+    CarRotaryImeRRO \
+
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+include $(BUILD_PACKAGE)
diff --git a/car_product/car_ui_portrait/apps/HideApps/AndroidManifest.xml b/car_product/car_ui_portrait/apps/HideApps/AndroidManifest.xml
new file mode 100644
index 0000000..a234af5
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/HideApps/AndroidManifest.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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.car.caruiportrait.hideapps">
+    <application
+        android:allowBackup="false"
+        android:debuggable="false"
+        android:label="CarUiPortraitHideApps">
+    </application>
+</manifest>
diff --git a/car_product/car_ui_portrait/apps/car_ui_portrait_apps.mk b/car_product/car_ui_portrait/apps/car_ui_portrait_apps.mk
new file mode 100644
index 0000000..8e01ccc
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/car_ui_portrait_apps.mk
@@ -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.
+#
+
+# All apps that should be included in CarUiPortrait builds
+PRODUCT_PACKAGES += \
+    CarUiPortraitSettings \
+    CarUiPortraitSystemUI \
+    CarNotification \
+    PaintBooth \
+
+# All apps to be excluded in car_ui_portrait builds should be specified as part of CarUiPortraitHideApps.
+PRODUCT_PACKAGES += \
+    CarUiPortraitHideApps
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/bootanimation/README b/car_product/car_ui_portrait/bootanimation/README
new file mode 100644
index 0000000..2ce687f
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/README
@@ -0,0 +1,68 @@
+# Boot Animation
+
+The boot animation format is described in full by [FORMAT.md](https://android.googlesource.com/platform/frameworks/base/+/master/cmds/bootanimation/FORMAT.md)
+
+## Command to create the zip:
+zip -0qry -i \*.txt \*.png \*.wav @ ../bootanimation.zip *.txt part*
+
+## zipfile layout
+
+The `bootanimation.zip` archive file includes:
+
+    desc.txt - a text file
+    part0  \
+    part1   \  directories full of PNG frames
+    ...     /
+    partN  /
+
+## desc.txt format
+
+The first line defines the general parameters of the animation:
+
+    WIDTH HEIGHT FPS [PROGRESS]
+
+  * **WIDTH:** animation width (pixels)
+  * **HEIGHT:** animation height (pixels)
+  * **FPS:** frames per second, e.g. 60
+  * **PROGRESS:** whether to show a progress percentage on the last part
+      + The percentage will be displayed with an x-coordinate of 'c', and a
+        y-coordinate set to 1/3 of the animation height.
+
+It is followed by a number of rows of the form:
+
+    TYPE COUNT PAUSE PATH [FADE [#RGBHEX [CLOCK1 [CLOCK2]]]]
+
+  * **TYPE:** a single char indicating what type of animation segment this is:
+      + `p` -- this part will play unless interrupted by the end of the boot
+      + `c` -- this part will play to completion, no matter what
+      + `f` -- same as `p` but in addition the specified number of frames is being faded out while
+        continue playing. Only the first interrupted `f` part is faded out, other subsequent `f`
+        parts are skipped
+  * **COUNT:** how many times to play the animation, or 0 to loop forever until boot is complete
+  * **PAUSE:** number of FRAMES to delay after this part ends
+  * **PATH:** directory in which to find the frames for this part (e.g. `part0`)
+  * **FADE:** _(ONLY FOR `f` TYPE)_ number of frames to fade out when interrupted where `0` means
+              _immediately_ which makes `f ... 0` behave like `p` and doesn't count it as a fading
+              part
+  * **RGBHEX:** _(OPTIONAL)_ a background color, specified as `#RRGGBB`
+  * **CLOCK1, CLOCK2:** _(OPTIONAL)_ the coordinates at which to draw the current time (for watches):
+      + If only `CLOCK1` is provided it is the y-coordinate of the clock and the x-coordinate
+        defaults to `c`
+      + If both `CLOCK1` and `CLOCK2` are provided then `CLOCK1` is the x-coordinate and `CLOCK2` is
+        the y-coodinate
+      + Values can be either a positive integer, a negative integer, or `c`
+          - `c` -- will centre the text
+          - `n` -- will position the text n pixels from the start; left edge for x-axis, bottom edge
+            for y-axis
+          - `-n` -- will position the text n pixels from the end; right edge for x-axis, top edge
+            for y-axis
+          - Examples:
+              * `-24` or `c -24` will position the text 24 pixels from the top of the screen,
+                centred horizontally
+              * `16 c` will position the text 16 pixels from the left of the screen, centred
+                vertically
+              * `-32 32` will position the text such that the bottom right corner is 32 pixels above
+                and 32 pixels left of the edges of the screen
+
+There is also a special TYPE, `$SYSTEM`, that loads `/system/media/bootanimation.zip`
+and plays that.
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/bootanimation/bootanimation.zip b/car_product/car_ui_portrait/bootanimation/bootanimation.zip
new file mode 100644
index 0000000..d7b90b3
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/bootanimation.zip
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/desc.txt b/car_product/car_ui_portrait/bootanimation/parts/desc.txt
new file mode 100644
index 0000000..a4ebbac
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/desc.txt
@@ -0,0 +1,2 @@
+986 1000 60
+c 1 0 part0
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00060.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00060.png
new file mode 100644
index 0000000..3a12b7e
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00060.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00061.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00061.png
new file mode 100644
index 0000000..3a12b7e
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00061.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00062.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00062.png
new file mode 100644
index 0000000..455560c
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00062.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00063.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00063.png
new file mode 100644
index 0000000..7450c5f
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00063.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00064.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00064.png
new file mode 100644
index 0000000..7795a77
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00064.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00065.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00065.png
new file mode 100644
index 0000000..fae1212
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00065.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00066.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00066.png
new file mode 100644
index 0000000..df8f1cd
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00066.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00067.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00067.png
new file mode 100644
index 0000000..f936d27
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00067.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00068.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00068.png
new file mode 100644
index 0000000..2a6bb73
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00068.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00069.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00069.png
new file mode 100644
index 0000000..e565ea5
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00069.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00070.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00070.png
new file mode 100644
index 0000000..41cbf3c
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00070.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00071.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00071.png
new file mode 100644
index 0000000..956259a
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00071.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00072.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00072.png
new file mode 100644
index 0000000..e86e314
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00072.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00073.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00073.png
new file mode 100644
index 0000000..4cbd91c
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00073.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00074.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00074.png
new file mode 100644
index 0000000..a00c625
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00074.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00075.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00075.png
new file mode 100644
index 0000000..9deae48
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00075.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00076.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00076.png
new file mode 100644
index 0000000..3fad600
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00076.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00077.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00077.png
new file mode 100644
index 0000000..17f8728
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00077.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00078.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00078.png
new file mode 100644
index 0000000..caaad59
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00078.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00079.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00079.png
new file mode 100644
index 0000000..0b106e5
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00079.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00080.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00080.png
new file mode 100644
index 0000000..fb9e49a
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00080.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00081.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00081.png
new file mode 100644
index 0000000..ac81644
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00081.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00082.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00082.png
new file mode 100644
index 0000000..f25cec5
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00082.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00083.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00083.png
new file mode 100644
index 0000000..3a4e680
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00083.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00084.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00084.png
new file mode 100644
index 0000000..7a9c203
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00084.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00085.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00085.png
new file mode 100644
index 0000000..765afd9
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00085.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00086.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00086.png
new file mode 100644
index 0000000..a3a233c
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00086.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00087.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00087.png
new file mode 100644
index 0000000..f2aa138
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00087.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00088.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00088.png
new file mode 100644
index 0000000..a1e35ec
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00088.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00089.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00089.png
new file mode 100644
index 0000000..e49f6ee
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00089.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00090.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00090.png
new file mode 100644
index 0000000..6934ac3
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00090.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00091.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00091.png
new file mode 100644
index 0000000..0dc6b52
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00091.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00092.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00092.png
new file mode 100644
index 0000000..d0cc426
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00092.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00093.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00093.png
new file mode 100644
index 0000000..5e5a8da
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00093.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00094.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00094.png
new file mode 100644
index 0000000..608e17f
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00094.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00095.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00095.png
new file mode 100644
index 0000000..a802840
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00095.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00096.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00096.png
new file mode 100644
index 0000000..5f600f1
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00096.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00097.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00097.png
new file mode 100644
index 0000000..76448c2
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00097.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00098.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00098.png
new file mode 100644
index 0000000..fbbb879
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00098.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00099.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00099.png
new file mode 100644
index 0000000..35f6feb
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00099.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00100.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00100.png
new file mode 100644
index 0000000..b980f13
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00100.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00101.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00101.png
new file mode 100644
index 0000000..11617e7
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00101.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00102.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00102.png
new file mode 100644
index 0000000..77f660c
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00102.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00103.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00103.png
new file mode 100644
index 0000000..7653dd8
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00103.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00104.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00104.png
new file mode 100644
index 0000000..5afc5f3
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00104.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00105.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00105.png
new file mode 100644
index 0000000..7331f7b
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00105.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00106.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00106.png
new file mode 100644
index 0000000..45b3473
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00106.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00107.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00107.png
new file mode 100644
index 0000000..8f48b1d
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00107.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00108.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00108.png
new file mode 100644
index 0000000..7c1dfbb
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00108.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00109.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00109.png
new file mode 100644
index 0000000..60240eb
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00109.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00110.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00110.png
new file mode 100644
index 0000000..7f7821f
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00110.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00111.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00111.png
new file mode 100644
index 0000000..1d4fc02
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00111.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00112.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00112.png
new file mode 100644
index 0000000..4272e3b
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00112.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00113.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00113.png
new file mode 100644
index 0000000..74aa68b
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00113.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00114.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00114.png
new file mode 100644
index 0000000..9f9962a
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00114.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00115.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00115.png
new file mode 100644
index 0000000..1afd28a
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00115.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00116.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00116.png
new file mode 100644
index 0000000..5f6eab0
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00116.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00117.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00117.png
new file mode 100644
index 0000000..7640188
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00117.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00118.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00118.png
new file mode 100644
index 0000000..b8691e5
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00118.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00119.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00119.png
new file mode 100644
index 0000000..8c75267
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00119.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00120.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00120.png
new file mode 100644
index 0000000..38a22cc
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00120.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00121.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00121.png
new file mode 100644
index 0000000..52b1a3d
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00121.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00122.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00122.png
new file mode 100644
index 0000000..1e56cea
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00122.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00123.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00123.png
new file mode 100644
index 0000000..a5c7458
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00123.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00124.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00124.png
new file mode 100644
index 0000000..89f4952
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00124.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00125.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00125.png
new file mode 100644
index 0000000..3044353
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00125.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00126.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00126.png
new file mode 100644
index 0000000..d9e42d7
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00126.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00127.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00127.png
new file mode 100644
index 0000000..ea03d6a
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00127.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00128.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00128.png
new file mode 100644
index 0000000..39b7176
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00128.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00129.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00129.png
new file mode 100644
index 0000000..39cd88f
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00129.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00130.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00130.png
new file mode 100644
index 0000000..1fb3509
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00130.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00131.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00131.png
new file mode 100644
index 0000000..3f35990
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00131.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00132.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00132.png
new file mode 100644
index 0000000..aa6ad9a
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00132.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00133.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00133.png
new file mode 100644
index 0000000..8075ac8
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00133.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00134.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00134.png
new file mode 100644
index 0000000..7b9c3b0
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00134.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00135.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00135.png
new file mode 100644
index 0000000..ec203ec
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00135.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00136.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00136.png
new file mode 100644
index 0000000..cbd7182
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00136.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00137.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00137.png
new file mode 100644
index 0000000..8319819
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00137.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00138.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00138.png
new file mode 100644
index 0000000..3729ade
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00138.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00139.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00139.png
new file mode 100644
index 0000000..b41c045
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00139.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00140.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00140.png
new file mode 100644
index 0000000..f2a840a
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00140.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00141.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00141.png
new file mode 100644
index 0000000..9bdd6db
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00141.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00142.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00142.png
new file mode 100644
index 0000000..63dd4d0
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00142.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00143.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00143.png
new file mode 100644
index 0000000..de44f52
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00143.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00144.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00144.png
new file mode 100644
index 0000000..20e5242
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00144.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00145.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00145.png
new file mode 100644
index 0000000..9db1d26
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00145.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00146.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00146.png
new file mode 100644
index 0000000..aa55e1e
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00146.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00147.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00147.png
new file mode 100644
index 0000000..077c3b0
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00147.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00148.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00148.png
new file mode 100644
index 0000000..46cf822
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00148.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00149.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00149.png
new file mode 100644
index 0000000..179870f
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00149.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00150.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00150.png
new file mode 100644
index 0000000..55cc405
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00150.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00151.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00151.png
new file mode 100644
index 0000000..6adcea3
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00151.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00152.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00152.png
new file mode 100644
index 0000000..b11dc75
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00152.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00153.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00153.png
new file mode 100644
index 0000000..cb87e84
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00153.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00154.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00154.png
new file mode 100644
index 0000000..af1acad
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00154.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00155.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00155.png
new file mode 100644
index 0000000..0abfa3d
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00155.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00156.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00156.png
new file mode 100644
index 0000000..b2cf2ae
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00156.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00157.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00157.png
new file mode 100644
index 0000000..2e4648d
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00157.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00158.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00158.png
new file mode 100644
index 0000000..bc05bf9
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00158.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00159.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00159.png
new file mode 100644
index 0000000..ff331f7
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00159.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00160.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00160.png
new file mode 100644
index 0000000..fe111e6
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00160.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00161.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00161.png
new file mode 100644
index 0000000..9e81d38
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00161.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00162.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00162.png
new file mode 100644
index 0000000..1cf960c
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00162.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00163.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00163.png
new file mode 100644
index 0000000..f5e4543
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00163.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00164.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00164.png
new file mode 100644
index 0000000..cac88e2
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00164.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00165.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00165.png
new file mode 100644
index 0000000..c843493
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00165.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00166.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00166.png
new file mode 100644
index 0000000..761eeba
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00166.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00167.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00167.png
new file mode 100644
index 0000000..0f3402e
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00167.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00168.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00168.png
new file mode 100644
index 0000000..bb74e3c
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00168.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00169.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00169.png
new file mode 100644
index 0000000..23c31e0
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00169.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00170.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00170.png
new file mode 100644
index 0000000..989f4a8
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00170.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00171.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00171.png
new file mode 100644
index 0000000..91f2130
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00171.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00172.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00172.png
new file mode 100644
index 0000000..90bfe15
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00172.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00173.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00173.png
new file mode 100644
index 0000000..cdf1e30
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00173.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00174.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00174.png
new file mode 100644
index 0000000..e00a7ce
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00174.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00175.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00175.png
new file mode 100644
index 0000000..5f7cf40
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00175.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00176.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00176.png
new file mode 100644
index 0000000..a4d9bdf
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00176.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00177.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00177.png
new file mode 100644
index 0000000..e044dbc
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00177.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00178.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00178.png
new file mode 100644
index 0000000..52b0244
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00178.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00179.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00179.png
new file mode 100644
index 0000000..8873cde
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00179.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00180.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00180.png
new file mode 100644
index 0000000..81385e5
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00180.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00181.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00181.png
new file mode 100644
index 0000000..104cd1e
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00181.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00182.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00182.png
new file mode 100644
index 0000000..0ea0bd1
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00182.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00183.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00183.png
new file mode 100644
index 0000000..c70211e
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00183.png
Binary files differ
diff --git a/car_product/car_ui_portrait/car_ui_portrait.ini b/car_product/car_ui_portrait/car_ui_portrait.ini
new file mode 100644
index 0000000..7804eca
--- /dev/null
+++ b/car_product/car_ui_portrait/car_ui_portrait.ini
@@ -0,0 +1,11 @@
+hw.audioInput=yes
+hw.lcd.density=160
+hw.gpu.enabled=yes
+hw.camera.back=none
+hw.camera.front=none
+hw.mainKeys=no
+skin.dynamic=yes
+skin.name=1224x2175
+skin.path=1224x2175
+hw.lcd.width=1224
+hw.lcd.height=2175
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/overlay/README b/car_product/car_ui_portrait/overlay/README
new file mode 100644
index 0000000..e49b1b0
--- /dev/null
+++ b/car_product/car_ui_portrait/overlay/README
@@ -0,0 +1,2 @@
+This project currently using the old approach for static RROs targeting the android package due to
+b/186753067. Please use the current approach for RROs for any other application/package.
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/Android.bp b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/Android.bp
new file mode 100644
index 0000000..2a4357c
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+android_app {
+    name: "CarEvsCameraPreviewAppRRO",
+    resource_dirs: ["res"],
+    platform_apis: true,
+    certificate: "platform",
+    aaptflags: [
+        "--no-resource-deduping",
+        "--no-resource-removal"
+    ],
+    static_libs: [
+        "androidx-constraintlayout_constraintlayout",
+        "androidx-constraintlayout_constraintlayout-solver",
+    ],
+}
diff --git a/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/AndroidManifest.xml b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/AndroidManifest.xml
new file mode 100644
index 0000000..c878ee1
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/AndroidManifest.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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.google.android.car.evs.caruiportrait.rro">
+    <application android:hasCode="false"/>
+    <overlay android:priority="20"
+             android:targetName="CarEvsCameraPreviewApp"
+             android:targetPackage="com.google.android.car.evs"
+             android:resourcesMap="@xml/overlays"
+             android:isStatic="true"/>
+</manifest>
diff --git a/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/drawable/close_bg.xml b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/drawable/close_bg.xml
new file mode 100644
index 0000000..9a4596b
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/drawable/close_bg.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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="?android:attr/colorBackground"/>
+    <corners android:radius="@dimen/close_button_radius"/>
+</shape>
diff --git a/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/drawable/ic_close.xml b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/drawable/ic_close.xml
new file mode 100644
index 0000000..5874541
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/drawable/ic_close.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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/close_icon_dimen"
+        android:height="@dimen/close_icon_dimen"
+        android:viewportWidth="26"
+        android:viewportHeight="26">
+    <path
+        android:pathData="M25.8327 2.75199L23.2477 0.166992L12.9994 10.4153L2.75102 0.166992L0.166016 2.75199L10.4144 13.0003L0.166016 23.2487L2.75102 25.8337L12.9994 15.5853L23.2477 25.8337L25.8327 23.2487L15.5844 13.0003L25.8327 2.75199Z"
+        android:fillColor="?android:attr/textColorPrimary"/>
+</vector>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/layout/evs_preview_activity.xml b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/layout/evs_preview_activity.xml
new file mode 100644
index 0000000..bfd6370
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/layout/evs_preview_activity.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@android:color/transparent">
+    <LinearLayout
+        android:id="@+id/evs_preview_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@android:color/transparent"
+        android:orientation="vertical"/>
+
+    <ImageButton
+        android:id="@+id/close_button"
+        android:layout_width="@dimen/close_button_dimen"
+        android:layout_height="@dimen/close_button_dimen"
+        android:layout_marginLeft="@dimen/close_button_margin"
+        android:layout_marginTop="@dimen/close_button_margin"
+        android:background="@drawable/close_bg"
+        android:scaleType="center"
+        android:alpha="0.5"
+        android:src="@drawable/ic_close"/>
+</FrameLayout>
diff --git a/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/config.xml b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/config.xml
new file mode 100644
index 0000000..935c096
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/config.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ 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>
+    <!-- Shade of the background behind the camera window. 1.0 for fully opaque, 0.0 for fully
+         transparent. -->
+    <item name="config_cameraBackgroundScrim" format="float" type="dimen">0.7</item>
+</resources>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/dimens.xml b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/dimens.xml
new file mode 100644
index 0000000..ea3ce97
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/dimens.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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">1224dp</dimen>
+    <dimen name="camera_preview_height">720dp</dimen>
+
+    <dimen name="close_icon_dimen">28dp</dimen>
+    <dimen name="close_button_dimen">80dp</dimen>
+    <dimen name="close_button_margin">24dp</dimen>
+    <dimen name="close_button_radius">20dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/strings.xml b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/strings.xml
new file mode 100644
index 0000000..77cc4d1
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/strings.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>
+    <string name="app_name">Rearview Camera</string>
+</resources>
diff --git a/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/xml/overlays.xml
new file mode 100644
index 0000000..681ef74
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/xml/overlays.xml
@@ -0,0 +1,29 @@
+<?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.
+  -->
+<overlay>
+    <item target="layout/evs_preview_activity" value="@layout/evs_preview_activity"/>
+
+    <item target="string/app_name" value="@string/app_name"/>
+
+    <item target="dimen/camera_preview_width" value="@dimen/camera_preview_width"/>
+    <item target="dimen/camera_preview_height" value="@dimen/camera_preview_height"/>
+
+    <item target="id/evs_preview_container" value="@id/evs_preview_container"/>
+    <item target="id/close_button" value="@id/close_button"/>
+
+    <item target="dimen/config_cameraBackgroundScrim" value="@dimen/config_cameraBackgroundScrim"/>
+</overlay>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/Android.bp b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/Android.bp
new file mode 100644
index 0000000..fecf167
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/Android.bp
@@ -0,0 +1,29 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+android_app {
+    name: "CarUiPortraitDialerRRO",
+    resource_dirs: ["res"],
+    platform_apis: true,
+    aaptflags: [
+        "--no-resource-deduping",
+        "--no-resource-removal"
+    ],
+    static_libs: [
+        "androidx-constraintlayout_constraintlayout",
+        "androidx-constraintlayout_constraintlayout-solver",
+    ],
+}
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/AndroidManifest.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/AndroidManifest.xml
new file mode 100644
index 0000000..fea65f9
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/AndroidManifest.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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.car.dialer.caruiportrait.rro">
+    <application android:hasCode="false"/>
+    <overlay android:priority="20"
+             android:targetName="CarDialerApp"
+             android:targetPackage="com.android.car.dialer"
+             android:resourcesMap="@xml/overlays"
+             android:isStatic="true" />
+</manifest>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/ic_phone.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/ic_phone.xml
new file mode 100644
index 0000000..b659da1
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/ic_phone.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="40dp"
+    android:height="40dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:pathData="M0 0h24v24H0z" />
+    <path
+        android:fillColor="#fffafafa"
+        android:pathData="M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27 .67 -.36 1.02-.24 1.12
+.37 2.33 .57 3.57 .57 .55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17
+0-.55 .45 -1 1-1h3.5c.55 0 1 .45 1 1 0 1.25 .2 2.45 .57 3.57 .11 .35 .03 .74-.25
+1.02l-2.2 2.2z" />
+</vector>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/icon_call_button.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/icon_call_button.xml
new file mode 100644
index 0000000..748a1d0
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/icon_call_button.xml
@@ -0,0 +1,30 @@
+<?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.
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape
+            android:shape="rectangle">
+            <solid
+                android:color="#52CCB0" />
+            <corners android:radius="100dp"/>
+            <size
+                android:width="424dp"
+                android:height="120dp"/>
+        </shape>
+    </item>
+    <item android:drawable="@drawable/ic_phone" android:gravity="center"/>
+</layer-list>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/keypad_default_background.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/keypad_default_background.xml
new file mode 100644
index 0000000..bf49159
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/keypad_default_background.xml
@@ -0,0 +1,28 @@
+<?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.
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape
+            android:shape="oval">
+            <solid
+                android:color="#282A2D" />
+            <size
+                android:width="120dp"
+                android:height="120dp"/>
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/values/styles.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/values/styles.xml
new file mode 100644
index 0000000..678cf0c
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/values/styles.xml
@@ -0,0 +1,37 @@
+<?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">
+    <!-- Phone -->
+    <style name="KeypadButtonStyle">
+        <item name="android:clickable">true</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_marginTop">16dp</item>
+        <item name="android:layout_marginRight">16dp</item>
+        <item name="android:layout_marginBottom">16dp</item>
+        <item name="android:layout_marginLeft">16dp</item>
+        <item name="android:background">@drawable/keypad_default_background</item>
+        <item name="android:focusable">true</item>
+    </style>
+
+    <style name="DialpadPrimaryButton">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">150dp</item>
+        <item name="android:layout_marginBottom">180dp</item>
+        <item name="android:scaleType">center</item>
+    </style>
+</resources>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/xml/overlays.xml
new file mode 100644
index 0000000..a404157
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/xml/overlays.xml
@@ -0,0 +1,23 @@
+<?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.
+  -->
+<overlay>
+    <item target="style/KeypadButtonStyle" value="@style/KeypadButtonStyle"/>
+    <item target="style/DialpadPrimaryButton" value="@style/DialpadPrimaryButton"/>
+
+    <item target="drawable/ic_phone" value="@drawable/ic_phone"/>
+    <item target="drawable/icon_call_button" value="@drawable/icon_call_button"/>
+</overlay>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/Android.bp b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/Android.bp
new file mode 100644
index 0000000..7e3df0a
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/Android.bp
@@ -0,0 +1,31 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+android_app {
+    name: "CarUiPortraitLauncherRRO",
+    resource_dirs: ["res"],
+    platform_apis: true,
+    aaptflags: [
+        "--no-resource-deduping",
+        "--no-resource-removal"
+    ],
+    static_libs: [
+        "androidx.cardview_cardview",
+        "androidx-constraintlayout_constraintlayout",
+        "car-media-common",
+        "car-apps-common",
+    ],
+}
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/AndroidManifest.xml b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/AndroidManifest.xml
new file mode 100644
index 0000000..1969da8
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/AndroidManifest.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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.car.carlauncher.caruiportrait.rro">
+    <application android:hasCode="false"/>
+    <overlay android:priority="20"
+             android:targetName="CarLauncher"
+             android:targetPackage="com.android.car.carlauncher"
+             android:resourcesMap="@xml/overlays"
+             android:isStatic="true" />
+</manifest>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/drawable/default_audio_background.xml b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/drawable/default_audio_background.xml
new file mode 100644
index 0000000..f00f03b
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/drawable/default_audio_background.xml
@@ -0,0 +1,34 @@
+<?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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:bottom="@dimen/default_audio_icon_padding"
+          android:top="@dimen/default_audio_icon_padding"
+          android:right="@dimen/default_audio_icon_padding"
+          android:left="@dimen/default_audio_icon_padding">
+        <shape android:shape="oval">
+            <stroke
+                android:width="@dimen/default_audio_icon_outer_ring_thickness"
+                android:color="@color/default_audio_background_image_color"/>
+            <size
+                android:width="@dimen/default_audio_icon_outer_ring_size"
+                android:height="@dimen/default_audio_icon_outer_ring_size"/>
+        </shape>
+    </item>
+    <item android:gravity="center"
+          android:drawable="@drawable/ic_play_music"/>
+</layer-list>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/drawable/ic_play_music.xml b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/drawable/ic_play_music.xml
new file mode 100644
index 0000000..91190e7
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/drawable/ic_play_music.xml
@@ -0,0 +1,27 @@
+<?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.
+  -->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/default_audio_icon_inner_icon_size"
+    android:height="@dimen/default_audio_icon_inner_icon_size"
+    android:viewportWidth="40"
+    android:viewportHeight="40">
+    <path
+        android:pathData="M40,20C40,8.96 31.04,0 20,0C8.96,0 0,8.96 0,20C0,31.04 8.96,40 20,40C31.04,40 40,31.04 40,20ZM28,14H22V25C22,27.76 19.76,30 17,30C14.24,30 12,27.76 12,25C12,22.24 14.24,20 17,20C18.14,20 19.16,20.38 20,21.02V10H28V14Z"
+        android:fillColor="@color/default_audio_background_image_color"/>
+</vector>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout-land/car_launcher.xml b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout-land/car_launcher.xml
new file mode 100644
index 0000000..7f79ef1
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout-land/car_launcher.xml
@@ -0,0 +1,32 @@
+<?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.
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="horizontal"
+    android:layoutDirection="ltr">
+
+    <com.android.car.ui.FocusArea
+        android:id="@+id/bottom_card"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:padding="10dp"
+        android:layoutDirection="locale"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout/card_content_descriptive_text_only.xml b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout/card_content_descriptive_text_only.xml
new file mode 100644
index 0000000..fbd91ba
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout/card_content_descriptive_text_only.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!-- Layout for a DescriptiveTextView. Required by HomeCardFragment, but currently not used by the CarUiPortrait launcher. -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:orientation="horizontal">
+
+    <include layout="@layout/descriptive_text"
+             android:layout_height="match_parent"
+             android:layout_width="wrap_content"/>
+
+    <TextView
+        android:id="@+id/tap_for_more_text"
+        android:layout_height="match_parent"
+        android:layout_width="wrap_content"
+        android:singleLine="true"
+        android:text="Tap for more"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:visibility="gone"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout/card_content_descriptive_text_with_controls.xml b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout/card_content_descriptive_text_with_controls.xml
new file mode 100644
index 0000000..90a9042
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout/card_content_descriptive_text_with_controls.xml
@@ -0,0 +1,70 @@
+<?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.
+  -->
+
+<!-- Layout for a DescriptiveTextWithControlsView. Required by HomeCardFragment, but currently not used by the CarUiPortrait launcher. -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent">
+
+    <include layout="@layout/descriptive_text"
+             android:layout_height="match_parent"
+             android:layout_width="0dp"
+             android:layout_weight="1"
+             android:layout_gravity="start|center_vertical"/>
+
+    <LinearLayout
+        android:id="@+id/button_trio"
+        android:gravity="center"
+        android:layout_height="match_parent"
+        android:layout_width="0dp"
+        android:layout_weight="1"
+        android:orientation="horizontal"
+        android:layout_gravity="end|center_vertical">
+
+        <ImageButton
+            android:id="@+id/button_left"
+            android:layout_height="@dimen/control_bar_action_icon_size"
+            android:layout_width="@dimen/control_bar_action_icon_size"
+            android:background="@android:color/transparent"
+            android:scaleType="centerInside"/>
+
+        <Space
+            android:layout_height="match_parent"
+            android:layout_width="0dp"
+            android:layout_weight="1"/>
+
+        <ImageButton
+            android:id="@+id/button_center"
+            android:layout_height="@dimen/control_bar_action_icon_size"
+            android:layout_width="@dimen/control_bar_action_icon_size"
+            android:background="@android:color/transparent"
+            android:scaleType="centerInside"/>
+
+        <Space
+            android:layout_height="match_parent"
+            android:layout_width="0dp"
+            android:layout_weight="1"/>
+
+        <ImageButton
+            android:id="@+id/button_right"
+            android:layout_height="@dimen/control_bar_action_icon_size"
+            android:layout_width="@dimen/control_bar_action_icon_size"
+            android:background="@android:color/transparent"
+            android:scaleType="centerInside"/>
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout/card_content_media.xml b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout/card_content_media.xml
new file mode 100644
index 0000000..932a22f
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout/card_content_media.xml
@@ -0,0 +1,46 @@
+<?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.
+  -->
+
+<!-- Layout specifically for the media card, which uses media-specific playback_controls.xml -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:orientation="horizontal">
+
+    <include layout="@layout/descriptive_text"
+             android:id="@+id/media_descriptive_text"
+             android:layout_height="match_parent"
+             android:layout_width="0dp"
+             android:layout_weight="1"
+             android:layout_gravity="start"/>
+
+    <FrameLayout
+        android:layout_height="match_parent"
+        android:layout_width="0dp"
+        android:layout_weight="1"
+        android:layout_gravity="end">
+
+    <com.android.car.media.common.PlaybackControlsActionBar
+        android:id="@+id/media_playback_controls_bar"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        app:enableOverflow="true"
+        app:columns="@integer/playback_controls_bar_columns"/>
+    </FrameLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout/card_content_text_block.xml b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout/card_content_text_block.xml
new file mode 100644
index 0000000..f08886b
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout/card_content_text_block.xml
@@ -0,0 +1,41 @@
+<?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.
+  -->
+
+<!-- Layout for a TextBlockView. Required by HomeCardFragment, but currently not used by the CarUiPortrait launcher. -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:orientation="horizontal">
+
+    <TextView
+        android:id="@+id/text_block"
+        android:gravity="start"
+        android:layout_height="match_parent"
+        android:layout_width="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+    <TextView
+        android:id="@+id/tap_for_more_text"
+        android:layout_height="match_parent"
+        android:layout_width="wrap_content"
+        android:singleLine="true"
+        android:text="Tap for more"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:visibility="gone"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout/card_fragment.xml b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout/card_fragment.xml
new file mode 100644
index 0000000..48efb6f
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout/card_fragment.xml
@@ -0,0 +1,105 @@
+<?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.
+  -->
+
+<androidx.cardview.widget.CardView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/card_view"
+    android:foreground="?android:attr/selectableItemBackground"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:visibility="gone">
+
+    <FrameLayout
+        android:id="@+id/card_background"
+        android:visibility="gone"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+
+    <RelativeLayout
+        android:layout_height="match_parent"
+        android:layout_width="match_parent">
+
+        <FrameLayout
+            android:id="@+id/control_bar_image_container"
+            android:layout_height="@dimen/control_bar_image_size"
+            android:layout_width="@dimen/control_bar_image_size"
+            android:layout_marginStart="@dimen/card_icon_margin_start">
+
+            <com.android.car.apps.common.CrossfadeImageView
+                android:id="@+id/card_background_image"
+                android:layout_height="match_parent"
+                android:layout_width="match_parent"/>
+
+            <ImageView
+                android:id="@+id/card_icon"
+                android:layout_height="@dimen/control_bar_app_icon_size"
+                android:layout_width="@dimen/control_bar_app_icon_size"
+                android:layout_gravity="bottom|end"
+                android:layout_marginEnd="@dimen/control_bar_app_icon_margin"
+                android:layout_marginBottom="@dimen/control_bar_app_icon_margin"
+                android:scaleType="centerInside"/>
+        </FrameLayout>
+
+        <!-- Do not show app name -->
+        <TextView
+            android:id="@+id/card_name"
+            android:layout_height="match_parent"
+            android:layout_width="0dp"
+            android:visibility="gone"
+            android:layout_centerVertical="true"
+            android:layout_toEndOf="@id/card_icon"/>
+
+        <FrameLayout
+            android:layout_height="match_parent"
+            android:layout_width="0dp"
+            android:layout_toEndOf="@id/control_bar_image_container"
+            android:layout_alignParentEnd="true"
+            android:layout_marginStart="@dimen/card_content_margin_start">
+
+            <ViewStub android:id="@+id/media_layout"
+                      android:inflatedId="@+id/media_layout"
+                      android:layout_height="match_parent"
+                      android:layout_width="match_parent"
+                      android:visibility="gone"
+                      android:layout="@layout/card_content_media"/>
+
+            <!-- Following ViewStubs are required by the HomeCardFragment, but are currently unused
+            as the portrait launcher only shows an audio card and the respective media layout. -->
+            <ViewStub android:id="@+id/descriptive_text_layout"
+                      android:inflatedId="@+id/descriptive_text_layout"
+                      android:layout_height="match_parent"
+                      android:layout_width="match_parent"
+                      android:visibility="gone"
+                      android:layout="@layout/card_content_descriptive_text_only"/>
+
+            <ViewStub android:id="@+id/text_block_layout"
+                      android:inflatedId="@+id/text_block_layout"
+                      android:layout_height="match_parent"
+                      android:layout_width="match_parent"
+                      android:visibility="gone"
+                      android:layout="@layout/card_content_text_block"/>
+
+            <ViewStub android:id="@+id/descriptive_text_with_controls_layout"
+                      android:inflatedId="@+id/descriptive_text_with_controls_layout"
+                      android:layout_height="match_parent"
+                      android:layout_width="match_parent"
+                      android:visibility="gone"
+                      android:layout="@layout/card_content_descriptive_text_with_controls"/>
+
+        </FrameLayout>
+    </RelativeLayout>
+</androidx.cardview.widget.CardView>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout/descriptive_text.xml b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout/descriptive_text.xml
new file mode 100644
index 0000000..1c55cca
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout/descriptive_text.xml
@@ -0,0 +1,73 @@
+<?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.
+  -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent">
+
+    <!-- optional_image is required by the HomeCardFragment. Intentionally not shown by setting
+    0 height and width. -->
+    <ImageView
+        android:id="@+id/optional_image"
+        android:layout_height="0dp"
+        android:layout_width="0dp"
+        android:visibility="gone"
+        android:layout_alignParentStart="true"/>
+
+    <TextView
+        android:id="@+id/primary_text"
+        android:layout_height="wrap_content"
+        android:layout_width="0dp"
+        android:singleLine="true"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:layout_alignParentStart="true"
+        android:layout_alignParentEnd="true"
+        android:layout_alignParentTop="true"
+        android:layout_marginTop="@dimen/descriptive_text_vertical_margin"/>
+
+    <Chronometer
+        android:id="@+id/optional_timer"
+        android:visibility="gone"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:layout_alignParentStart="true"
+        android:layout_alignParentBottom="true"
+        android:layout_marginBottom="@dimen/descriptive_text_vertical_margin"/>
+
+    <TextView
+        android:id="@+id/optional_timer_separator"
+        android:visibility="gone"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:text="@string/ongoing_call_duration_text_separator"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:layout_toEndOf="@id/optional_timer"
+        android:layout_alignParentBottom="true"
+        android:layout_marginBottom="@dimen/descriptive_text_vertical_margin"/>
+
+    <TextView
+        android:id="@+id/secondary_text"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:singleLine="true"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:layout_toEndOf="@id/optional_timer_separator"
+        android:layout_alignParentBottom="true"
+        android:layout_marginBottom="@dimen/descriptive_text_vertical_margin"/>
+</RelativeLayout>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/colors.xml b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/colors.xml
new file mode 100644
index 0000000..a3a7f24
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/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>
+    <color name="default_audio_background_image_color">#515355</color>
+</resources>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/config.xml b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/config.xml
new file mode 100644
index 0000000..c912ab9
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/config.xml
@@ -0,0 +1,28 @@
+<?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>
+    <!-- A list of package names that provide the cards to display on the home screen -->
+    <string-array name="config_homeCardModuleClasses" translatable="false">
+        <item>com.android.car.carlauncher.homescreen.audio.AudioCard</item>
+    </string-array>
+
+    <string-array name="config_foregroundDAComponents" translatable="false">
+        <item>com.android.car.carlauncher/.AppGridActivity</item>
+        <item>com.android.car.notification/.CarNotificationCenterActivity</item>
+    </string-array>
+</resources>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/dimens.xml b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/dimens.xml
new file mode 100644
index 0000000..d90de2d
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/dimens.xml
@@ -0,0 +1,50 @@
+<?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>
+    <dimen name="card_icon_margin_start">8dp</dimen>
+    <dimen name="card_content_margin_start">16dp</dimen>
+
+    <dimen name="descriptive_text_vertical_margin">16dp</dimen>
+
+    <dimen name="control_bar_image_size">120dp</dimen>
+    <dimen name="control_bar_app_icon_size">36dp</dimen>
+    <dimen name="control_bar_app_icon_margin">6dp</dimen>
+
+    <dimen name="control_bar_action_icon_size">88dp</dimen>
+
+    <!--Percent by which to blur the image used for the card's background as a float between 0 and 1, where 0 is not blurred-->
+    <dimen name="card_background_image_blur_radius" format="float">0</dimen>
+
+    <!-- screen height of the device. This is used when custom policy is provided using
+    config_deviceSpecificDisplayAreaPolicyProvider -->
+    <dimen name="total_screen_height">2175dp</dimen>
+    <!-- screen width of the device. This is used when custom policy is provided using
+    config_deviceSpecificDisplayAreaPolicyProvider -->
+    <dimen name="total_screen_width">1224dp</dimen>
+
+    <dimen name="control_bar_height">136dp</dimen>
+    <!-- This height is from the top of navbar not accounting for the control bar height. -->
+    <dimen name="default_app_display_area_height">1055dp</dimen>
+    <dimen name="title_bar_display_area_height">40dp</dimen>
+    <!-- This value is 500dp + (top of title bar)  -->
+    <dimen name="title_bar_display_area_touch_drag_threshold">1204dp</dimen>
+
+    <dimen name="default_audio_icon_padding">26dp</dimen>
+    <dimen name="default_audio_icon_outer_ring_size">66dp</dimen>
+    <dimen name="default_audio_icon_outer_ring_thickness">8dp</dimen>
+    <dimen name="default_audio_icon_inner_icon_size">40dp</dimen>
+</resources>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/integers.xml b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/integers.xml
new file mode 100644
index 0000000..7d54116
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/integers.xml
@@ -0,0 +1,23 @@
+<?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>
+    <!-- Number of buttons shown for the media playback controls bar -->
+    <integer name="playback_controls_bar_columns">3</integer>
+    <!--  Entry/exit animation transition speed in milliseconds.  This is the animation of foreground DA.-->
+    <integer name="enter_exit_animation_foreground_display_area_duration_ms">500</integer>
+</resources>
+
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/strings.xml b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/strings.xml
new file mode 100644
index 0000000..2c0a471
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/strings.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>
+    <string name="ongoing_call_duration_text_separator">&#160;&#8226;&#160;</string>
+</resources>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/xml/overlays.xml
new file mode 100644
index 0000000..9cc1245
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/xml/overlays.xml
@@ -0,0 +1,58 @@
+<!--
+  ~ 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.
+  -->
+<overlay>
+    <item target="id/bottom_card" value="@id/bottom_card" />
+    <item target="id/card_background" value="@id/card_background" />
+    <item target="id/card_background_image" value="@id/card_background_image" />
+    <item target="id/card_icon" value="@id/card_icon" />
+    <item target="id/card_name" value="@id/card_name" />
+    <item target="id/card_view" value="@id/card_view" />
+    <item target="id/descriptive_text_layout" value="@id/descriptive_text_layout" />
+    <item target="id/text_block_layout" value="@id/text_block_layout" />
+    <item target="id/descriptive_text_with_controls_layout" value="@id/descriptive_text_with_controls_layout" />
+    <item target="id/media_descriptive_text" value="@id/media_descriptive_text" />
+    <item target="id/media_layout" value="@id/media_layout"/>
+    <item target="id/media_playback_controls_bar" value="@id/media_playback_controls_bar" />
+    <item target="id/optional_image" value="@id/optional_image" />
+    <item target="id/optional_timer" value="@id/optional_timer"/>
+    <item target="id/optional_timer_separator" value="@id/optional_timer_separator"/>
+    <item target="id/primary_text" value="@id/primary_text" />
+    <item target="id/secondary_text" value="@id/secondary_text"/>
+
+    <item target="id/button_trio" value="@id/button_trio"/>
+    <item target="id/button_center" value="@id/button_center"/>
+    <item target="id/button_left" value="@id/button_left"/>
+    <item target="id/button_right" value="@id/button_right"/>
+
+    <item target="layout/card_content_media" value="@layout/card_content_media" />
+    <item target="layout/card_fragment" value="@layout/card_fragment" />
+    <item target="layout/car_launcher" value="@layout/car_launcher"/>
+    <item target="layout/descriptive_text" value="@layout/descriptive_text" />
+
+    <item target="dimen/card_background_image_blur_radius" value="@dimen/card_background_image_blur_radius" />
+    <item target="dimen/total_screen_height" value="@dimen/total_screen_height"/>
+    <item target="dimen/total_screen_width" value="@dimen/total_screen_width"/>
+    <item target="dimen/control_bar_height" value="@dimen/control_bar_height"/>
+    <item target="dimen/default_app_display_area_height" value="@dimen/default_app_display_area_height"/>
+    <item target="dimen/title_bar_display_area_height" value="@dimen/title_bar_display_area_height"/>
+    <item target="dimen/title_bar_display_area_touch_drag_threshold" value="@dimen/title_bar_display_area_touch_drag_threshold"/>
+
+    <item target="drawable/default_audio_background" value="@drawable/default_audio_background"/>
+    <item target="integer/enter_exit_animation_foreground_display_area_duration_ms" value="@integer/enter_exit_animation_foreground_display_area_duration_ms"/>
+
+    <item target="array/config_homeCardModuleClasses" value="@array/config_homeCardModuleClasses"/>
+    <item target="array/config_foregroundDAComponents" value="@array/config_foregroundDAComponents"/>
+</overlay>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/Android.bp b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/Android.bp
new file mode 100644
index 0000000..a36ec21
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/Android.bp
@@ -0,0 +1,25 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+android_app {
+    name: "CarUiPortraitMediaRRO",
+    resource_dirs: ["res"],
+    platform_apis: true,
+    aaptflags: [
+        "--no-resource-deduping",
+        "--no-resource-removal"
+    ],
+}
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/AndroidManifest.xml b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/AndroidManifest.xml
new file mode 100644
index 0000000..fa12e4f
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/AndroidManifest.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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.car.media.caruiportrait.rro">
+    <application android:hasCode="false"/>
+    <overlay android:priority="20"
+             android:targetName="CarMediaApp"
+             android:targetPackage="com.android.car.media"
+             android:resourcesMap="@xml/overlays"
+             android:isStatic="true" />
+</manifest>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/drawable/image_background.xml b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/drawable/image_background.xml
new file mode 100644
index 0000000..e5f9fa5
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/drawable/image_background.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners android:radius="@dimen/image_radius"/>
+</shape>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/values/bools.xml b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/values/bools.xml
new file mode 100644
index 0000000..8f979df
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/values/bools.xml
@@ -0,0 +1,22 @@
+<?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:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Now playing and mini playback controls will be shown in sysui instead of media center. -->
+    <bool name="show_mini_playback_controls">false</bool>
+    <bool name="switch_to_playback_view_when_playable_item_is_clicked">false</bool>
+</resources>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/values/dimens.xml b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/values/dimens.xml
new file mode 100644
index 0000000..9726095
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/values/dimens.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:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <dimen name="image_radius">24dp</dimen>
+</resources>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/values/styles.xml b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/values/styles.xml
new file mode 100644
index 0000000..dbc8eec
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/values/styles.xml
@@ -0,0 +1,23 @@
+<?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">
+    <style name="MediaIconContainerStyle">
+        <item name="android:background">@drawable/image_background</item>
+        <item name="android:clipToOutline">true</item>
+    </style>
+</resources>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/xml/overlays.xml
new file mode 100644
index 0000000..5846fa3
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/xml/overlays.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.
+  -->
+
+<overlay>
+    <item target="bool/show_mini_playback_controls" value="@bool/show_mini_playback_controls" />
+    <item target="bool/switch_to_playback_view_when_playable_item_is_clicked" value="@bool/switch_to_playback_view_when_playable_item_is_clicked" />
+
+    <item target="style/MediaIconContainerStyle" value="@style/MediaIconContainerStyle" />
+</overlay>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/Android.bp b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/Android.bp
new file mode 100644
index 0000000..960301a
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+android_app {
+    name: "CarUiPortraitNotificationRRO",
+    resource_dirs: ["res"],
+    platform_apis: true,
+    aaptflags: [
+        "--no-resource-deduping",
+        "--no-resource-removal"
+    ],
+    static_libs: [
+        "CarNotificationLib",
+    ],
+}
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/AndroidManifest.xml b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/AndroidManifest.xml
new file mode 100644
index 0000000..47e6a4a
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.car.notification.caruiportrait.rro">
+    <application android:hasCode="false"/>
+    <overlay android:priority="20"
+             android:targetName="CarNotification"
+             android:targetPackage="com.android.car.notification"
+             android:resourcesMap="@xml/overlays"
+             android:isStatic="true" />
+</manifest>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/car_notification_actions_view.xml b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/car_notification_actions_view.xml
new file mode 100644
index 0000000..a3c2ce3
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/car_notification_actions_view.xml
@@ -0,0 +1,61 @@
+<?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.
+  -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <com.android.car.notification.template.CarNotificationActionButton
+            android:id="@+id/action_1"
+            style="@style/NotificationActionButton1"
+            android:layout_width="0dp"
+            android:layout_weight="1"
+            android:layout_height="@dimen/action_button_height"
+            android:minWidth="@dimen/action_button_min_width"
+            android:paddingBottom="@dimen/action_button_padding_bottom"
+            android:paddingTop="@dimen/action_button_padding_top"
+            android:visibility="gone"/>
+
+        <com.android.car.notification.template.CarNotificationActionButton
+            android:id="@+id/action_2"
+            style="@style/NotificationActionButton2"
+            android:layout_weight="1"
+            android:layout_width="0dp"
+            android:layout_height="@dimen/action_button_height"
+            android:layout_marginStart="@dimen/action_button_spacing_start"
+            android:minWidth="@dimen/action_button_min_width"
+            android:paddingBottom="@dimen/action_button_padding_bottom"
+            android:paddingTop="@dimen/action_button_padding_top"
+            android:visibility="gone"/>
+
+        <com.android.car.notification.template.CarNotificationActionButton
+            android:id="@+id/action_3"
+            style="@style/NotificationActionButton3"
+            android:layout_width="0dp"
+            android:layout_weight="1"
+            android:layout_height="@dimen/action_button_height"
+            android:layout_marginStart="@dimen/action_button_spacing_start"
+            android:minWidth="@dimen/action_button_min_width"
+            android:paddingBottom="@dimen/action_button_padding_bottom"
+            android:paddingTop="@dimen/action_button_padding_top"
+            android:visibility="gone"/>
+
+    </LinearLayout>
+</merge>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/headsup_container_bottom.xml b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/headsup_container_bottom.xml
new file mode 100644
index 0000000..3cff34a
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/headsup_container_bottom.xml
@@ -0,0 +1,31 @@
+<?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.
+  -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/notification_headsup"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <com.android.car.notification.headsup.HeadsUpContainerView
+        android:id="@+id/headsup_content"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_marginBottom="@dimen/headsup_notification_bottom_margin"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/message_headsup_notification_template.xml b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/message_headsup_notification_template.xml
new file mode 100644
index 0000000..18f3304
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/message_headsup_notification_template.xml
@@ -0,0 +1,73 @@
+<?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.
+  -->
+
+<com.android.car.ui.FocusArea
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <androidx.cardview.widget.CardView
+        android:id="@+id/card_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        app:cardCornerRadius="@dimen/notification_card_radius">
+
+        <RelativeLayout
+            android:id="@+id/inner_template_view"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/car_notification_card_inner_top_margin">
+
+            <com.android.car.notification.template.CarNotificationHeaderView
+                android:id="@+id/notification_header"
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                android:layout_alignParentTop="true"
+                android:layout_alignParentStart="true"
+                app:isHeadsUp="true"/>
+
+            <com.android.car.notification.template.CarNotificationBodyView
+                android:id="@+id/notification_body"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:minHeight="@dimen/notification_touch_target_size"
+                android:gravity="center_vertical"
+                android:layout_alignParentTop="true"
+                android:layout_alignParentStart="true"
+                android:layout_toStartOf="@id/message_count"
+                android:layout_marginStart="@dimen/card_body_margin_start"
+                app:maxLines="@integer/config_headsUpNotificationMaxBodyLines"
+                app:showBigIcon="true"/>
+
+            <FrameLayout
+                android:id="@+id/notification_actions_wrapper"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/notification_body">
+
+                <com.android.car.notification.template.CarNotificationActionsView
+                    android:id="@+id/notification_actions"
+                    style="@style/NotificationActionViewLayout"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"/>
+
+            </FrameLayout>
+        </RelativeLayout>
+    </androidx.cardview.widget.CardView>
+</com.android.car.ui.FocusArea>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/notification_center_activity.xml b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/notification_center_activity.xml
new file mode 100644
index 0000000..83a6525
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/notification_center_activity.xml
@@ -0,0 +1,81 @@
+<?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.
+  -->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <com.android.car.ui.FocusParkingView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+    <com.android.car.ui.FocusArea
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <com.android.car.notification.CarNotificationView
+            android:id="@+id/notification_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+            <FrameLayout
+                android:id="@+id/exit_button_container"
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                android:visibility="gone"/>
+
+            <TextView
+                android:id="@+id/empty_notification_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                app:layout_constraintBottom_toTopOf="@id/manage_button"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintVertical_chainStyle="packed"
+                android:text="@string/empty_notification_header"
+                android:textAppearance="?android:attr/textAppearanceLarge"
+                android:visibility="gone"/>
+
+            <Button
+                android:id="@+id/manage_button"
+                style="@style/ManageButton"
+                android:layout_width="wrap_content"
+                android:layout_height="@dimen/manage_button_height"
+                android:layout_marginTop="@dimen/manage_button_top_margin"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/empty_notification_text"
+                app:layout_constraintVertical_chainStyle="packed"
+                android:text="@string/manage_text"
+                android:visibility="gone"/>
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/notifications"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:orientation="vertical"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/notification_center_title"/>
+        </com.android.car.notification.CarNotificationView>
+    </com.android.car.ui.FocusArea>
+</FrameLayout>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/config.xml b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/config.xml
new file mode 100644
index 0000000..4862022
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/config.xml
@@ -0,0 +1,36 @@
+<?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>
+    <bool name="config_showHeadsUpNotificationOnBottom">true</bool>
+
+    <string name="config_headsUpNotificationAnimationHelper" translatable="false">
+        com.android.car.notification.headsup.animationhelper.CarHeadsUpNotificationBottomAnimationHelper</string>
+
+    <!-- If false, small icon will be used to distinguish the app, large icon will be used
+         in notification body and notification header will be shown.-->
+    <bool name="config_useLauncherIcon">false</bool>
+
+    <!-- Whether to show header for the notifications center -->
+    <bool name="config_showHeaderForNotifications">true</bool>
+
+    <!-- Whether to show footer for the notifications center -->
+    <bool name="config_showFooterForNotifications">false</bool>
+
+    <!-- Whether to show Recents/Older header for notifications list -->
+    <bool name="config_showRecentAndOldHeaders">false</bool>
+</resources>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/dimens.xml b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/dimens.xml
new file mode 100644
index 0000000..cc5397d
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/dimens.xml
@@ -0,0 +1,43 @@
+<?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>
+    <!-- Horizontal margin for HUNs -->
+    <dimen name="notification_headsup_card_margin_horizontal">0dp</dimen>
+    <dimen name="car_notification_card_inner_top_margin">8dp</dimen>
+
+    <!-- Card View -->
+    <dimen name="card_start_margin">16dp</dimen>
+    <dimen name="card_end_margin">8dp</dimen>
+    <dimen name="card_body_margin_bottom">8dp</dimen>
+    <dimen name="card_body_margin_start">16dp</dimen>
+    <dimen name="card_header_margin_bottom">8dp</dimen>
+    <dimen name="notification_card_radius">24dp</dimen>
+    <dimen name="headsup_notification_bottom_margin">296dp</dimen>
+
+    <!-- Icons -->
+    <dimen name="notification_touch_target_size">120dp</dimen>
+
+    <!-- Action View -->
+    <dimen name="action_button_height">88dp</dimen>
+    <dimen name="action_button_radius">24dp</dimen>
+    <dimen name="action_view_left_margin">8dp</dimen>
+    <dimen name="action_view_right_margin">8dp</dimen>
+
+    <dimen name="card_min_bottom_padding">8dp</dimen>
+    <dimen name="card_min_top_padding">8dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/strings.xml b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/strings.xml
new file mode 100644
index 0000000..981ffdc
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/strings.xml
@@ -0,0 +1,24 @@
+<?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>
+    <!-- The assistant action label to read aloud a message notification and optionally prompt user to respond [CHAR_LIMIT=20]-->
+    <string name="assist_action_play_label">Play message</string>
+
+    <!-- Notification header text displayed on top of the notification center shade [CHAR_LIMIT=25] -->
+    <string name="notification_header">Notification Center</string>
+</resources>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/styles.xml b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/styles.xml
new file mode 100644
index 0000000..d588c34
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/styles.xml
@@ -0,0 +1,51 @@
+<?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="NotificationBodyTitleText">
+        <item name="android:textSize">32sp</item>
+        <item name="android:maxLines">1</item>
+        <item name="android:ellipsize">end</item>
+        <item name="android:textAlignment">viewStart</item>
+    </style>
+
+    <style name="NotificationBodyContentText">
+        <item name="android:textSize">28sp</item>
+        <item name="android:ellipsize">end</item>
+        <item name="android:textColor">@color/secondary_text_color</item>
+        <item name="android:textAlignment">viewStart</item>
+    </style>
+
+    <style name="NotificationActionViewLayout">
+        <item name="android:layout_gravity">center</item>
+        <item name="android:layout_marginLeft">8dp</item>
+        <item name="android:layout_marginRight">8dp</item>
+        <item name="android:layout_marginTop">8dp</item>
+        <item name="android:layout_marginBottom">8dp</item>
+    </style>
+
+    <style name="NotificationActionButtonBase" parent="@android:Widget.DeviceDefault.Button.Borderless.Colored">
+        <item name="android:minWidth">@dimen/action_button_min_width</item>
+        <item name="android:gravity">center</item>
+        <item name="android:textSize">32sp</item>
+        <item name="android:textAllCaps">false</item>
+        <item name="android:textColor">@color/notification_accent_color</item>
+        <item name="android:maxLines">1</item>
+        <item name="android:ellipsize">end</item>
+        <item name="android:background">@drawable/action_button_background</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/xml/overlays.xml
new file mode 100644
index 0000000..b768ecb
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/xml/overlays.xml
@@ -0,0 +1,76 @@
+<?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.
+  -->
+
+<overlay>
+    <item target="attr/cardCornerRadius" value="@attr/cardCornerRadius"/>
+    <item target="attr/isHeadsUp" value="@attr/isHeadsUp"/>
+    <item target="attr/maxLines" value="@attr/maxLines"/>
+    <item target="attr/showBigIcon" value="@attr/showBigIcon"/>
+
+    <item target="bool/config_showHeadsUpNotificationOnBottom" value="@bool/config_showHeadsUpNotificationOnBottom" />
+    <item target="bool/config_useLauncherIcon" value="@bool/config_useLauncherIcon"/>
+    <item target="bool/config_showHeaderForNotifications" value="@bool/config_showHeaderForNotifications"/>
+    <item target="bool/config_showFooterForNotifications" value="@bool/config_showFooterForNotifications"/>
+    <item target="bool/config_showRecentAndOldHeaders" value="@bool/config_showRecentAndOldHeaders"/>
+
+    <item target="dimen/action_button_height" value="@dimen/action_button_height" />
+    <item target="dimen/action_button_radius" value="@dimen/action_button_radius" />
+    <item target="dimen/action_view_left_margin" value="@dimen/action_view_left_margin" />
+    <item target="dimen/action_view_right_margin" value="@dimen/action_view_right_margin" />
+    <item target="dimen/car_notification_card_inner_top_margin" value="@dimen/car_notification_card_inner_top_margin"/>
+    <item target="dimen/card_start_margin" value="@dimen/card_start_margin" />
+    <item target="dimen/card_body_margin_bottom" value="@dimen/card_body_margin_bottom" />
+    <item target="dimen/card_end_margin" value="@dimen/card_start_margin" />
+    <item target="dimen/card_header_margin_bottom" value="@dimen/card_header_margin_bottom" />
+    <item target="dimen/notification_card_radius" value="@dimen/notification_card_radius"/>
+    <item target="dimen/notification_headsup_card_margin_horizontal" value="@dimen/notification_headsup_card_margin_horizontal" />
+    <item target="dimen/notification_touch_target_size" value="@dimen/notification_touch_target_size"/>
+
+    <item target="id/action_1" value="@id/action_1" />
+    <item target="id/action_2" value="@id/action_2" />
+    <item target="id/action_3" value="@id/action_3" />
+    <item target="id/card_view" value="@id/card_view" />
+    <item target="id/headsup_content" value="@id/headsup_content"/>
+    <item target="id/inner_template_view" value="@id/inner_template_view" />
+    <item target="id/notification_actions" value="@id/notification_actions" />
+    <item target="id/notification_actions_wrapper" value="@id/notification_actions_wrapper" />
+    <item target="id/notification_body" value="@id/notification_body"/>
+    <item target="id/notification_header" value="@id/notification_header"/>
+    <item target="id/notification_headsup" value="@id/notification_headsup"/>
+    <item target="id/notification_view" value="@id/notification_view"/>
+    <item target="id/notifications" value="@id/notifications"/>
+    <item target="id/manage_button" value="@id/manage_button"/>
+    <item target="id/empty_notification_text" value="@id/empty_notification_text"/>
+    <item target="id/exit_button_container" value="@id/exit_button_container"/>
+
+    <item target="layout/car_notification_actions_view" value="@layout/car_notification_actions_view"/>
+    <item target="layout/headsup_container_bottom" value="@layout/headsup_container_bottom"/>
+    <item target="layout/message_headsup_notification_template" value="@layout/message_headsup_notification_template" />
+    <item target="layout/notification_center_activity" value="@layout/notification_center_activity"/>
+
+    <item target="string/assist_action_play_label" value="@string/assist_action_play_label"/>
+    <item target="string/config_headsUpNotificationAnimationHelper" value="@string/config_headsUpNotificationAnimationHelper" />
+    <item target="string/notification_header" value="@string/notification_header"/>
+
+    <item target="style/NotificationActionButtonBase" value="@style/NotificationActionButtonBase"/>
+    <item target="style/NotificationActionViewLayout" value="@style/NotificationActionViewLayout"/>
+    <item target="style/NotificationBodContentText" value="@style/NotificationBodyContentText" />
+    <item target="style/NotificationBodyTitleText" value="@style/NotificationBodyTitleText" />
+
+    <item target="dimen/card_min_bottom_padding" value="@dimen/card_min_bottom_padding"/>
+    <item target="dimen/card_min_top_padding" value="@dimen/card_min_top_padding"/>
+</overlay>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitSettingsProviderRRO/Android.bp b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsProviderRRO/Android.bp
new file mode 100644
index 0000000..b63e2fe
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsProviderRRO/Android.bp
@@ -0,0 +1,25 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+android_app {
+    name: "CarUiPortraitSettingsProviderRRO",
+    resource_dirs: ["res"],
+    platform_apis: true,
+    aaptflags: [
+        "--no-resource-deduping",
+        "--no-resource-removal"
+    ],
+}
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitSettingsProviderRRO/AndroidManifest.xml b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsProviderRRO/AndroidManifest.xml
new file mode 100644
index 0000000..807a91a
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsProviderRRO/AndroidManifest.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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.providers.settings.caruiportrait.rro">
+    <application android:hasCode="false" />
+    <overlay android:priority="20"
+             android:targetName="SettingsProvider"
+             android:targetPackage="com.android.providers.settings"
+             android:resourcesMap="@xml/overlays"
+             android:isStatic="true" />
+</manifest>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitSettingsProviderRRO/res/values/defaults.xml b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsProviderRRO/res/values/defaults.xml
new file mode 100644
index 0000000..4182cf1
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsProviderRRO/res/values/defaults.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>
+    <!-- Set default screen orientation. -->
+    <integer name="def_user_rotation">1</integer>
+</resources>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitSettingsProviderRRO/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsProviderRRO/res/xml/overlays.xml
new file mode 100644
index 0000000..23a2903
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsProviderRRO/res/xml/overlays.xml
@@ -0,0 +1,18 @@
+<!--
+  ~ 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.
+  -->
+<overlay>
+    <item target="integer/def_user_rotation" value="@integer/def_user_rotation"/>
+</overlay>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/android/Android.bp b/car_product/car_ui_portrait/rro/android/Android.bp
new file mode 100644
index 0000000..73878ea
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/Android.bp
@@ -0,0 +1,36 @@
+// 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"],
+}
+
+runtime_resource_overlay {
+    name: "CarUiPortraitFrameworkResRRO",
+    resource_dirs: ["res"],
+    certificate: "platform",
+    manifest: "AndroidManifest.xml",
+    system_ext_specific: true,
+}
+
+android_app {
+    name: "CarUiPortraitFrameworkResRROTest",
+    resource_dirs: ["res"],
+    platform_apis: true,
+    manifest: "AndroidManifest-test.xml",
+    aaptflags: [
+        "--no-resource-deduping",
+        "--no-resource-removal"
+    ],
+}
diff --git a/car_product/car_ui_portrait/rro/android/AndroidManifest-test.xml b/car_product/car_ui_portrait/rro/android/AndroidManifest-test.xml
new file mode 100644
index 0000000..0895ea8
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/AndroidManifest-test.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.caruiportrait.rro.test">
+    <application android:hasCode="false" />
+    <overlay
+        android:targetPackage="android"
+        android:priority="21"
+        android:category="caruiportrait.rro"/>
+</manifest>
diff --git a/car_product/car_ui_portrait/rro/android/AndroidManifest.xml b/car_product/car_ui_portrait/rro/android/AndroidManifest.xml
new file mode 100644
index 0000000..41a3491
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/AndroidManifest.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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.caruiportrait.rro">
+    <application android:hasCode="false" />
+    <overlay
+        android:targetPackage="android"
+        android:isStatic="true"
+        android:priority="20"
+        android:category="caruiportrait.rro"/>
+</manifest>
diff --git a/car_product/car_ui_portrait/rro/android/res/anim/fade_in.xml b/car_product/car_ui_portrait/rro/android/res/anim/fade_in.xml
new file mode 100644
index 0000000..bff68d0
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/anim/fade_in.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:interpolator="@android:interpolator/decelerate_quad">
+
+    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+        android:duration="@android:integer/config_longAnimTime"/>
+</set>
diff --git a/car_product/car_ui_portrait/rro/android/res/anim/fade_out.xml b/car_product/car_ui_portrait/rro/android/res/anim/fade_out.xml
new file mode 100644
index 0000000..b1b60db
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/anim/fade_out.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:interpolator="@android:interpolator/decelerate_quad">
+
+    <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+        android:duration="@android:integer/config_longAnimTime"/>
+</set>
diff --git a/car_product/car_ui_portrait/rro/android/res/color-night/text_color_on_accent_device_default.xml b/car_product/car_ui_portrait/rro/android/res/color-night/text_color_on_accent_device_default.xml
new file mode 100644
index 0000000..24f27f2
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/color-night/text_color_on_accent_device_default.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+<!-- Please see primary_text_material_light.xml -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:color="@*android:color/system_neutral1_900"/>
+    <item android:color="@*android:color/system_neutral1_400"/>
+</selector>
diff --git a/car_product/car_ui_portrait/rro/android/res/color-night/text_color_primary_device_default_dark.xml b/car_product/car_ui_portrait/rro/android/res/color-night/text_color_primary_device_default_dark.xml
new file mode 100644
index 0000000..c82f99c
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/color-night/text_color_primary_device_default_dark.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+<!-- Please see primary_text_material_dark.xml -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:color="@*android:color/system_neutral1_500"/>
+    <item android:color="@*android:color/system_neutral1_50"/>
+</selector>
diff --git a/car_product/car_ui_portrait/rro/android/res/color-night/text_color_primary_device_default_light.xml b/car_product/car_ui_portrait/rro/android/res/color-night/text_color_primary_device_default_light.xml
new file mode 100644
index 0000000..c9b5a6b
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/color-night/text_color_primary_device_default_light.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+<!-- Please see primary_text_material_light.xml -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:color="@*android:color/system_neutral1_400"/>
+    <item android:color="@*android:color/system_neutral1_900"/>
+</selector>
diff --git a/car_product/car_ui_portrait/rro/android/res/color-night/text_color_secondary_device_default_dark.xml b/car_product/car_ui_portrait/rro/android/res/color-night/text_color_secondary_device_default_dark.xml
new file mode 100644
index 0000000..470ed75
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/color-night/text_color_secondary_device_default_dark.xml
@@ -0,0 +1,23 @@
+<?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.
+  -->
+<!-- Please see secondary_text_material_dark.xml -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="@*android:color/system_neutral2_200"/>
+    <item android:color="@*android:color/system_neutral2_200"/>
+</selector>
diff --git a/car_product/car_ui_portrait/rro/android/res/color-night/text_color_secondary_device_default_light.xml b/car_product/car_ui_portrait/rro/android/res/color-night/text_color_secondary_device_default_light.xml
new file mode 100644
index 0000000..30759cc
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/color-night/text_color_secondary_device_default_light.xml
@@ -0,0 +1,23 @@
+<?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.
+  -->
+<!-- Please see secondary_text_material_light.xml -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="@*android:color/system_neutral2_700"/>
+    <item android:color="@*android:color/system_neutral2_700"/>
+</selector>
diff --git a/car_product/car_ui_portrait/rro/android/res/color-night/text_color_tertiary_device_default_dark.xml b/car_product/car_ui_portrait/rro/android/res/color-night/text_color_tertiary_device_default_dark.xml
new file mode 100644
index 0000000..f3d50db
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/color-night/text_color_tertiary_device_default_dark.xml
@@ -0,0 +1,23 @@
+<?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.
+  -->
+<!-- Please see tertiary_text_material_dark.xml -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="@*android:color/system_neutral2_400"/>
+    <item android:color="@*android:color/system_neutral2_400"/>
+</selector>
diff --git a/car_product/car_ui_portrait/rro/android/res/color-night/text_color_tertiary_device_default_light.xml b/car_product/car_ui_portrait/rro/android/res/color-night/text_color_tertiary_device_default_light.xml
new file mode 100644
index 0000000..638084e
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/color-night/text_color_tertiary_device_default_light.xml
@@ -0,0 +1,23 @@
+<?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.
+  -->
+<!-- Please see tertiary_text_material_light.xml -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="@*android:color/system_neutral2_500"/>
+    <item android:color="@*android:color/system_neutral2_500"/>
+</selector>
diff --git a/car_product/car_ui_portrait/rro/android/res/color/btn_device_default_dark.xml b/car_product/car_ui_portrait/rro/android/res/color/btn_device_default_dark.xml
new file mode 100644
index 0000000..037fe3e
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/color/btn_device_default_dark.xml
@@ -0,0 +1,23 @@
+<?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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="@*android:color/system_accent1_200"/>
+    <item android:color="@*android:color/system_accent1_200"/>
+</selector>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/android/res/color/text_color_on_accent_device_default.xml b/car_product/car_ui_portrait/rro/android/res/color/text_color_on_accent_device_default.xml
new file mode 100644
index 0000000..c9b5a6b
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/color/text_color_on_accent_device_default.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+<!-- Please see primary_text_material_light.xml -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:color="@*android:color/system_neutral1_400"/>
+    <item android:color="@*android:color/system_neutral1_900"/>
+</selector>
diff --git a/car_product/car_ui_portrait/rro/android/res/color/text_color_primary_device_default_dark.xml b/car_product/car_ui_portrait/rro/android/res/color/text_color_primary_device_default_dark.xml
new file mode 100644
index 0000000..0bb281f
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/color/text_color_primary_device_default_dark.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+<!-- Please see primary_text_material_dark.xml -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:color="@*android:color/system_neutral1_400"/>
+    <item android:color="@*android:color/system_neutral1_900"/>
+</selector>
diff --git a/car_product/car_ui_portrait/rro/android/res/color/text_color_primary_device_default_light.xml b/car_product/car_ui_portrait/rro/android/res/color/text_color_primary_device_default_light.xml
new file mode 100644
index 0000000..9729379
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/color/text_color_primary_device_default_light.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+<!-- Please see primary_text_material_light.xml -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:color="@*android:color/system_neutral1_500"/>
+    <item android:color="@*android:color/system_neutral1_50"/>
+</selector>
diff --git a/car_product/car_ui_portrait/rro/android/res/color/text_color_secondary_device_default_dark.xml b/car_product/car_ui_portrait/rro/android/res/color/text_color_secondary_device_default_dark.xml
new file mode 100644
index 0000000..4b6483f
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/color/text_color_secondary_device_default_dark.xml
@@ -0,0 +1,23 @@
+<?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.
+  -->
+<!-- Please see secondary_text_material_dark.xml -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="@*android:color/system_neutral2_700"/>
+    <item android:color="@*android:color/system_neutral2_700"/>
+</selector>
diff --git a/car_product/car_ui_portrait/rro/android/res/color/text_color_secondary_device_default_light.xml b/car_product/car_ui_portrait/rro/android/res/color/text_color_secondary_device_default_light.xml
new file mode 100644
index 0000000..0d9f871
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/color/text_color_secondary_device_default_light.xml
@@ -0,0 +1,23 @@
+<?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.
+  -->
+<!-- Please see secondary_text_material_light.xml -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="@*android:color/system_neutral2_200"/>
+    <item android:color="@*android:color/system_neutral2_200"/>
+</selector>
diff --git a/car_product/car_ui_portrait/rro/android/res/color/text_color_tertiary_device_default_dark.xml b/car_product/car_ui_portrait/rro/android/res/color/text_color_tertiary_device_default_dark.xml
new file mode 100644
index 0000000..1cf8447
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/color/text_color_tertiary_device_default_dark.xml
@@ -0,0 +1,23 @@
+<?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.
+  -->
+<!-- Please see tertiary_text_material_dark.xml -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="@*android:color/system_neutral2_500"/>
+    <item android:color="@*android:color/system_neutral2_500"/>
+</selector>
diff --git a/car_product/car_ui_portrait/rro/android/res/color/text_color_tertiary_device_default_light.xml b/car_product/car_ui_portrait/rro/android/res/color/text_color_tertiary_device_default_light.xml
new file mode 100644
index 0000000..45160e3
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/color/text_color_tertiary_device_default_light.xml
@@ -0,0 +1,23 @@
+<?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.
+  -->
+<!-- Please see tertiary_text_material_light.xml -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="@*android:color/system_neutral2_400"/>
+    <item android:color="@*android:color/system_neutral2_400"/>
+</selector>
diff --git a/car_product/car_ui_portrait/rro/android/res/drawable/alert_dialog_bg.xml b/car_product/car_ui_portrait/rro/android/res/drawable/alert_dialog_bg.xml
new file mode 100644
index 0000000..b01931d
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/drawable/alert_dialog_bg.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.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <corners android:radius="@dimen/alert_dialog_corner_radius"/>
+</shape>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/android/res/drawable/btn_borderless_car.xml b/car_product/car_ui_portrait/rro/android/res/drawable/btn_borderless_car.xml
new file mode 100644
index 0000000..0693426
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/drawable/btn_borderless_car.xml
@@ -0,0 +1,56 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true">
+        <inset android:insetLeft="@*android:dimen/button_inset_horizontal_material"
+               android:insetTop="@*android:dimen/button_inset_vertical_material"
+               android:insetRight="@*android:dimen/button_inset_horizontal_material"
+               android:insetBottom="@*android:dimen/button_inset_vertical_material">
+            <selector>
+                <item android:state_focused="true" android:state_pressed="true">
+                    <shape android:shape="rectangle">
+                        <corners android:radius="?android:attr/buttonCornerRadius" />
+                        <solid android:color="#8A94CBFF"/>
+                        <stroke android:width="4dp"
+                                android:color="#94CBFF"/>
+                        <padding android:left="@*android:dimen/button_padding_horizontal_material"
+                                 android:top="@*android:dimen/button_padding_vertical_material"
+                                 android:right="@*android:dimen/button_padding_horizontal_material"
+                                 android:bottom="@*android:dimen/button_padding_vertical_material" />
+                    </shape>
+                </item>
+                <item android:state_focused="true">
+                    <shape android:shape="rectangle">
+                        <corners android:radius="?android:attr/buttonCornerRadius" />
+                        <solid android:color="#3D94CBFF"/>
+                        <stroke android:width="8dp" android:color="#94CBFF"/>
+                        <padding android:left="@*android:dimen/button_padding_horizontal_material"
+                                 android:top="@*android:dimen/button_padding_vertical_material"
+                                 android:right="@*android:dimen/button_padding_horizontal_material"
+                                 android:bottom="@*android:dimen/button_padding_vertical_material" />
+                    </shape>
+                </item>
+            </selector>
+        </inset>
+    </item>
+    <item>
+        <ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="?android:attr/colorControlHighlight">
+            <item android:id="@android:id/mask"
+                  android:drawable="@*android:drawable/btn_default_mtrl_shape" />
+        </ripple>
+    </item>
+</selector>
diff --git a/car_product/car_ui_portrait/rro/android/res/drawable/car_dialog_button_background.xml b/car_product/car_ui_portrait/rro/android/res/drawable/car_dialog_button_background.xml
new file mode 100644
index 0000000..bb8aeb1
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/drawable/car_dialog_button_background.xml
@@ -0,0 +1,32 @@
+<?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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape>
+            <color android:color="@color/car_alert_dialog_action_button_color"/>
+            <corners android:radius="@dimen/alert_dialog_button_corner_radius"/>
+        </shape>
+    </item>
+    <item>
+        <ripple android:color="?android:attr/colorControlHighlight">
+            <item android:id="@android:id/mask">
+                <color android:color="@*android:color/car_white_1000"/>
+            </item>
+        </ripple>
+    </item>
+</layer-list>
diff --git a/car_product/car_ui_portrait/rro/android/res/drawable/toast_frame.xml b/car_product/car_ui_portrait/rro/android/res/drawable/toast_frame.xml
new file mode 100644
index 0000000..8b829db
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/drawable/toast_frame.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <solid android:color="#2E3134" />
+    <corners android:radius="@dimen/toast_corner_radius" />
+</shape>
diff --git a/car_product/car_ui_portrait/rro/android/res/layout/transient_notification.xml b/car_product/car_ui_portrait/rro/android/res/layout/transient_notification.xml
new file mode 100644
index 0000000..2eeaa7a
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/layout/transient_notification.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:gravity="center_vertical"
+    android:maxWidth="@dimen/toast_width"
+    android:background="@drawable/toast_frame"
+    android:elevation="@dimen/toast_elevation"
+    android:paddingEnd="@dimen/toast_margin"
+    android:paddingTop="@dimen/toast_margin"
+    android:paddingBottom="@dimen/toast_margin"
+    android:paddingStart="@dimen/toast_margin"
+    android:layout_marginBottom="@dimen/toast_bottom_margin">
+
+    <TextView
+        android:id="@android:id/message"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:textAppearance="@style/TextAppearance_Toast"/>
+</LinearLayout>
diff --git a/car_product/car_ui_portrait/rro/android/res/values-night/colors_device_default.xml b/car_product/car_ui_portrait/rro/android/res/values-night/colors_device_default.xml
new file mode 100644
index 0000000..c26d6df
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/values-night/colors_device_default.xml
@@ -0,0 +1,83 @@
+<?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="primary_device_default_dark">@*android:color/system_neutral1_900</color>
+    <color name="primary_device_default_light">@*android:color/system_neutral1_50</color>
+    <color name="primary_device_default_settings">@*android:color/system_neutral1_900</color>
+    <color name="primary_device_default_settings_light">@*android:color/primary_device_default_light</color>
+    <color name="primary_dark_device_default_dark">@*android:color/primary_device_default_dark</color>
+    <color name="primary_dark_device_default_light">@*android:color/primary_device_default_light</color>
+    <color name="primary_dark_device_default_settings">@*android:color/primary_device_default_dark</color>
+    <color name="primary_dark_device_default_settings_light">@*android:color/primary_device_default_light</color>
+    <color name="secondary_device_default_settings">@*android:color/secondary_material_settings</color>
+    <color name="secondary_device_default_settings_light">@*android:color/secondary_material_settings_light</color>
+    <color name="tertiary_device_default_settings">@*android:color/tertiary_material_settings</color>
+    <color name="quaternary_device_default_settings">@*android:color/quaternary_material_settings</color>
+    <color name="navigation_bar_divider_device_default_settings">#1f000000</color>
+
+    <!--  Accent colors  -->
+    <color name="accent_device_default_light">@*android:color/system_accent1_600</color>
+    <color name="accent_device_default_dark">@*android:color/system_accent1_100</color>
+    <color name="accent_device_default">@*android:color/accent_device_default_light</color>
+    <color name="accent_primary_device_default">@*android:color/system_accent1_100</color>
+    <color name="accent_secondary_device_default">@*android:color/system_accent2_100</color>
+    <color name="accent_tertiary_device_default">@*android:color/system_accent3_100</color>
+
+    <!-- Accent variants -->
+    <color name="accent_primary_variant_light_device_default">@*android:color/system_accent1_600</color>
+    <color name="accent_secondary_variant_light_device_default">@*android:color/system_accent2_600</color>
+    <color name="accent_tertiary_variant_light_device_default">@*android:color/system_accent3_600</color>
+    <color name="accent_primary_variant_dark_device_default">@*android:color/system_accent1_300</color>
+    <color name="accent_secondary_variant_dark_device_default">@*android:color/system_accent2_300</color>
+    <color name="accent_tertiary_variant_dark_device_default">@*android:color/system_accent3_300</color>
+
+    <!-- Background colors -->
+    <color name="background_device_default_dark">@*android:color/system_neutral1_900</color>
+    <color name="background_device_default_light">@*android:color/system_neutral1_50</color>
+    <color name="background_floating_device_default_dark">@*android:color/background_device_default_dark</color>
+    <color name="background_floating_device_default_light">@*android:color/background_device_default_light</color>
+
+    <!-- Surface colors -->
+    <color name="surface_header_dark">@*android:color/system_neutral1_700</color>
+    <color name="surface_header_light">@*android:color/system_neutral1_100</color>
+    <color name="surface_variant_dark">@*android:color/system_neutral1_700</color>
+    <color name="surface_variant_light">@*android:color/system_neutral2_100</color>
+    <color name="surface_dark">@*android:color/system_neutral1_800</color>
+    <color name="surface_highlight_light">@*android:color/system_neutral1_0</color>
+
+    <!-- Please refer to text_color_[primary]_device_default_[light].xml for text colors-->
+    <color name="foreground_device_default_light">@*android:color/text_color_primary_device_default_light</color>
+    <color name="foreground_device_default_dark">@*android:color/text_color_primary_device_default_dark</color>
+
+    <!-- Error color -->
+    <color name="error_color_device_default_dark">@*android:color/error_color_material_dark</color>
+    <color name="error_color_device_default_light">@*android:color/error_color_material_light</color>
+
+    <color name="list_divider_color_light">@*android:color/system_neutral1_200</color>
+    <color name="list_divider_color_dark">@*android:color/system_neutral1_700</color>
+    <color name="list_divider_opacity_device_default_light">@android:color/white</color>
+    <color name="list_divider_opacity_device_default_dark">@android:color/white</color>
+
+    <color name="loading_gradient_background_color_dark">#44484C</color>
+    <color name="loading_gradient_background_color_light">#F8F9FA</color>
+    <color name="loading_gradient_highlight_color_dark">#4D5155</color>
+    <color name="loading_gradient_highlight_color_light">#F1F3F4</color>
+
+    <color name="edge_effect_device_default_light">@android:color/black</color>
+    <color name="edge_effect_device_default_dark">@android:color/white</color>
+</resources>
diff --git a/car_product/car_ui_portrait/rro/android/res/values-sw900dp/dimens.xml b/car_product/car_ui_portrait/rro/android/res/values-sw900dp/dimens.xml
new file mode 100644
index 0000000..177ff6d
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/values-sw900dp/dimens.xml
@@ -0,0 +1,22 @@
+<?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>
+    <!-- Height of the bottom navigation / climate bar. -->
+    <!--    TODO: remove-->
+    <dimen name="navigation_bar_height">160dp</dimen>
+    <dimen name="navigation_bar_height_landscape">160dp</dimen>
+</resources>
diff --git a/car_product/car_ui_portrait/rro/android/res/values/colors.xml b/car_product/car_ui_portrait/rro/android/res/values/colors.xml
new file mode 100644
index 0000000..16f1e2b
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/values/colors.xml
@@ -0,0 +1,69 @@
+<?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="car_alert_dialog_action_button_color">#2E3134</color>
+  <color name="car_card_ripple_background_dark">?android:attr/colorControlHighlight</color>
+  <color name="car_card_ripple_background_light">?android:attr/colorControlHighlight</color>
+
+  <color name="system_accent1_0">#ffffff</color>
+  <!--  duped-->
+  <color name="system_accent1_10">#defbff</color>
+  <color name="system_accent1_50">#defbff</color>
+  <color name="system_accent1_100">#acf6fe</color>
+  <color name="system_accent1_200">#6bf0ff</color>
+  <color name="system_accent1_300">#00e8fe</color>
+  <color name="system_accent1_400">#00e1fa</color>
+  <color name="system_accent1_500">#00daf8</color>
+  <color name="system_accent1_600">#00c9e3</color>
+  <color name="system_accent1_700">#00b2c7</color>
+  <color name="system_accent1_800">#009eae</color>
+  <color name="system_accent1_900">#00797f</color>
+  <color name="system_accent1_1000">#000000</color>
+
+  <color name="system_accent2_0">#ffffff</color>
+  <color name="system_accent2_10">#e5f2ff</color>
+  <color name="system_accent2_50">#e5f2ff</color>
+  <color name="system_accent2_100">#c8deed</color>
+  <color name="system_accent2_200">#aec7da</color>
+  <color name="system_accent2_300">#91afc6</color>
+  <color name="system_accent2_400">#7b9cb5</color>
+  <color name="system_accent2_500">#648aa6</color>
+  <color name="system_accent2_600">#567b94</color>
+  <color name="system_accent2_700">#46667c</color>
+  <color name="system_accent2_800">#385366</color>
+  <color name="system_accent2_900">#263d4e</color>
+  <color name="system_accent2_1000">#000000</color>
+
+  <color name="system_accent3_0">#ffffff</color>
+  <color name="system_accent3_10">#e2f8ed</color>
+  <color name="system_accent3_50">#e2f8ed</color>
+  <color name="system_accent3_100">#b9eed2</color>
+  <color name="system_accent3_200">#8de2b7</color>
+  <color name="system_accent3_300">#5cd69b</color>
+  <color name="system_accent3_400">#29cb86</color>
+  <color name="system_accent3_500">#00c171</color>
+  <color name="system_accent3_600">#00b166</color>
+  <color name="system_accent3_700">#009e59</color>
+  <color name="system_accent3_800">#008c4d</color>
+  <color name="system_accent3_900">#006c37</color>
+  <color name="system_accent3_1000">#000000</color>
+
+  <color name="error_color_device_default_dark">#ec928e</color> <!-- Material Red 300 -->
+  <color name="error_color_device_default_light">#b3261e</color> <!-- Material Red 600 -->
+
+  <color name="list_divider_color">#2E3134</color>
+</resources>
diff --git a/car_product/car_ui_portrait/rro/android/res/values/colors_device_default.xml b/car_product/car_ui_portrait/rro/android/res/values/colors_device_default.xml
new file mode 100644
index 0000000..e9c768b
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/values/colors_device_default.xml
@@ -0,0 +1,82 @@
+<?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">
+
+    <!--title bars-->
+    <color name="primary_device_default_dark">@*android:color/system_neutral1_200</color>
+    <color name="primary_device_default_light">@*android:color/system_neutral1_300</color>
+
+    <color name="primary_device_default_settings">@*android:color/system_neutral1_200</color>
+    <color name="primary_device_default_settings_light">@*android:color/primary_device_default_light</color>
+    <color name="primary_dark_device_default_dark">@*android:color/primary_device_default_dark</color>
+    <color name="primary_dark_device_default_light">@*android:color/primary_device_default_light</color>
+    <color name="primary_dark_device_default_settings">@*android:color/primary_device_default_dark</color>
+    <color name="primary_dark_device_default_settings_light">@*android:color/primary_device_default_light</color>
+    <color name="secondary_device_default_settings">@*android:color/secondary_material_settings</color>
+    <color name="secondary_device_default_settings_light">@*android:color/secondary_material_settings_light</color>
+    <color name="tertiary_device_default_settings">@*android:color/tertiary_material_settings</color>
+    <color name="quaternary_device_default_settings">@*android:color/quaternary_material_settings</color>
+    <color name="navigation_bar_divider_device_default_settings">#1f000000</color>
+
+    <!--  Accent colors edit  -->
+    <color name="accent_device_default_light">@*android:color/system_accent1_200</color>
+    <color name="accent_device_default_dark">@*android:color/system_accent1_200</color>
+    <color name="accent_device_default">@*android:color/accent_device_default_light</color>
+    <color name="accent_primary_device_default">@*android:color/system_accent1_200</color>
+    <color name="accent_secondary_device_default">@*android:color/system_accent2_300</color>
+    <color name="accent_tertiary_device_default">@*android:color/system_accent3_300</color>
+
+    <!-- Accent variants edit -->
+    <color name="accent_primary_variant_light_device_default">@*android:color/system_accent1_200</color>
+    <color name="accent_secondary_variant_light_device_default">@*android:color/system_accent2_300</color>
+    <color name="accent_tertiary_variant_light_device_default">@*android:color/system_accent3_300</color>
+    <color name="accent_primary_variant_dark_device_default">@*android:color/system_accent1_300</color>
+    <color name="accent_secondary_variant_dark_device_default">@*android:color/system_accent2_300</color>
+    <color name="accent_tertiary_variant_dark_device_default">@*android:color/system_accent3_300</color>
+
+    <!-- Background colors -->
+    <color name="background_device_default_dark">@*android:color/system_neutral1_0</color>
+    <color name="background_device_default_light">@*android:color/system_neutral1_900</color>
+    <color name="background_floating_device_default_dark">@*android:color/background_device_default_dark</color>
+    <color name="background_floating_device_default_light">@*android:color/background_device_default_light</color>
+
+    <!-- Surface colors -->
+    <color name="surface_header_dark">@*android:color/system_neutral1_100</color>
+    <color name="surface_header_light">@*android:color/system_neutral1_700</color>
+    <color name="surface_variant_dark">@*android:color/system_neutral1_100</color>
+    <color name="surface_variant_light">@*android:color/system_neutral2_700</color>
+    <color name="surface_dark">@*android:color/system_neutral1_200</color>
+    <color name="surface_highlight_light">@*android:color/system_neutral1_1000</color>
+
+    <!-- Please refer to text_color_[primary]_device_default_[light].xml for text colors-->
+    <color name="foreground_device_default_light">@*android:color/text_color_primary_device_default_dark</color>
+    <color name="foreground_device_default_dark">@*android:color/text_color_primary_device_default_light</color>
+
+    <color name="list_divider_color_light">@*android:color/system_neutral1_700</color>
+    <color name="list_divider_color_dark">@*android:color/system_neutral1_200</color>
+    <color name="list_divider_opacity_device_default_light">@android:color/black</color>
+    <color name="list_divider_opacity_device_default_dark">@android:color/black</color>
+
+    <color name="loading_gradient_background_color_dark">#F8F9FA</color>
+    <color name="loading_gradient_background_color_light">#44484C</color>
+    <color name="loading_gradient_highlight_color_dark">#F1F3F4</color>
+    <color name="loading_gradient_highlight_color_light">#4D5155</color>
+
+    <color name="edge_effect_device_default_light">@android:color/white</color>
+    <color name="edge_effect_device_default_dark">@android:color/black</color>
+
+</resources>
diff --git a/car_product/car_ui_portrait/rro/android/res/values/config.xml b/car_product/car_ui_portrait/rro/android/res/values/config.xml
new file mode 100644
index 0000000..8ab71a7
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/values/config.xml
@@ -0,0 +1,29 @@
+<?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:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- IME should not hide nav bar -->
+    <bool name="config_automotiveHideNavBarForKeyboard">false</bool>
+
+    <!-- Class name of the device specific implementation of DisplayAreaPolicy.Provider
+    or empty if the default should be used. -->
+    <string translatable="false" name="config_deviceSpecificDisplayAreaPolicyProvider">
+        com.android.server.wm.CarDisplayAreaPolicyProvider
+    </string>
+
+    <!-- Colon separated list of package names that should be granted Notification Listener access -->
+    <string name="config_defaultListenerAccessPackages" translatable="false">com.android.car.notification</string>
+</resources>
diff --git a/car_product/car_ui_portrait/rro/android/res/values/dimens.xml b/car_product/car_ui_portrait/rro/android/res/values/dimens.xml
new file mode 100644
index 0000000..de98f91
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/values/dimens.xml
@@ -0,0 +1,52 @@
+<?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>
+    <!-- Height of the status bar -->
+    <dimen name="status_bar_height">92dp</dimen>
+    <!-- Height of the bottom navigation / climate bar. -->
+    <dimen name="navigation_bar_height">160dp</dimen>
+    <dimen name="navigation_bar_height_landscape">160dp</dimen>
+
+    <!-- ****** Alert dialog dimens ***** -->
+
+    <!-- Dialog corner radius -->
+    <dimen name="alert_dialog_corner_radius">24dp</dimen>
+    <!-- Dialog button corner radius -->
+    <dimen name="alert_dialog_button_corner_radius">16dp</dimen>
+    <!-- Dialog header size -->
+    <dimen name="car_card_header_height">88dp</dimen>
+    <!-- Dialog image size -->
+    <dimen name="car_alert_dialog_title_image_size">@dimen/car_card_header_height</dimen>
+    <!-- Default dialog margin -->
+    <dimen name="car_alert_dialog_margin">24dp</dimen>
+    <!-- Dialog button layout height -->
+    <dimen name="button_layout_height">88dp</dimen>
+
+    <!-- ****** Toast dimens ***** -->
+
+    <!-- Toast corner radius -->
+    <dimen name="toast_corner_radius">24dp</dimen>
+    <!-- Toast margin -->
+    <dimen name="toast_margin">24dp</dimen>
+    <!-- Toast elevation -->
+    <dimen name="toast_elevation">2dp</dimen>
+    <!-- Toast max width -->
+    <dimen name="toast_width">760dp</dimen>
+    <!-- Toast y offset (should be the same as the height of the audio bar -->
+    <dimen name="toast_y_offset">136dp</dimen>
+    <dimen name="toast_bottom_margin">32dp</dimen>
+</resources>
diff --git a/car_product/car_ui_portrait/rro/android/res/values/styles.xml b/car_product/car_ui_portrait/rro/android/res/values/styles.xml
new file mode 100644
index 0000000..c32411b
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/values/styles.xml
@@ -0,0 +1,71 @@
+<?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="DialogActionButton">
+        <item name="android:textSize">32sp</item>
+        <item name="android:textColor">@android:color/white</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:background">@drawable/car_dialog_button_background</item>
+    </style>
+
+    <style name="TextAppearance_Toast">
+        <item name="android:textColorHighlight">?android:textColorHighlight</item>
+        <item name="android:textColorHint">?android:textColorHint</item>
+        <item name="android:textColorLink">?android:textColorLink</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:textSize">28sp</item>
+        <item name="android:lineHeight">36sp</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
+    <!-- Override the default activity transitions. We have to do a full copy and not just inherit
+         and override because we're replacing the default style across the system.
+    -->
+    <style name="Animation.Activity" parent="*android:Animation.Material.Activity">
+        <item name="android:activityOpenEnterAnimation">@*android:anim/fade_in</item>
+        <item name="android:activityOpenExitAnimation">@*android:anim/fade_out</item>
+        <item name="android:activityCloseEnterAnimation">@*android:anim/fade_in</item>
+        <item name="android:activityCloseExitAnimation">@*android:anim/fade_out</item>
+        <item name="android:taskOpenEnterAnimation">@*android:anim/fade_in</item>
+        <item name="android:taskOpenExitAnimation">@*android:anim/fade_out</item>
+        <item name="android:launchTaskBehindTargetAnimation">@*android:anim/launch_task_behind_target</item>
+        <item name="android:launchTaskBehindSourceAnimation">@*android:anim/launch_task_behind_source</item>
+        <item name="android:taskCloseEnterAnimation">@*android:anim/fade_in</item>
+        <item name="android:taskCloseExitAnimation">@*android:anim/fade_out</item>
+        <item name="android:taskToFrontEnterAnimation">@*android:anim/fade_in</item>
+        <item name="android:taskToFrontExitAnimation">@*android:anim/fade_out</item>
+        <item name="android:taskToBackEnterAnimation">@*android:anim/task_close_enter</item>
+        <item name="android:taskToBackExitAnimation">@*android:anim/task_close_exit</item>
+        <item name="android:wallpaperOpenEnterAnimation">@*android:anim/wallpaper_open_enter</item>
+        <item name="android:wallpaperOpenExitAnimation">@*android:anim/wallpaper_open_exit</item>
+        <item name="android:wallpaperCloseEnterAnimation">@*android:anim/wallpaper_close_enter</item>
+        <item name="android:wallpaperCloseExitAnimation">@*android:anim/wallpaper_close_exit</item>
+        <item name="android:wallpaperIntraOpenEnterAnimation">@*android:anim/wallpaper_intra_open_enter</item>
+        <item name="android:wallpaperIntraOpenExitAnimation">@*android:anim/wallpaper_intra_open_exit</item>
+        <item name="android:wallpaperIntraCloseEnterAnimation">@*android:anim/wallpaper_intra_close_enter</item>
+        <item name="android:wallpaperIntraCloseExitAnimation">@*android:anim/wallpaper_intra_close_exit</item>
+        <item name="android:fragmentOpenEnterAnimation">@*android:animator/fragment_open_enter</item>
+        <item name="android:fragmentOpenExitAnimation">@*android:animator/fragment_open_exit</item>
+        <item name="android:fragmentCloseEnterAnimation">@*android:animator/fragment_close_enter</item>
+        <item name="android:fragmentCloseExitAnimation">@*android:animator/fragment_close_exit</item>
+        <item name="android:fragmentFadeEnterAnimation">@*android:animator/fragment_fade_enter</item>
+        <item name="android:fragmentFadeExitAnimation">@*android:animator/fragment_fade_exit</item>
+    </style>
+</resources>
diff --git a/car_product/car_ui_portrait/rro/android/res/values/styles_device_default.xml b/car_product/car_ui_portrait/rro/android/res/values/styles_device_default.xml
new file mode 100644
index 0000000..fadcfd5
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/values/styles_device_default.xml
@@ -0,0 +1,164 @@
+<?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.
+-->
+
+<!--
+    This is an override of frameworks/base/core/res/res/values/styles_device_default.xml
+    It is how the device default is changed to match the desired look for a car theme.
+-->
+<resources>
+
+    <style name="TextAppearance.DeviceDefault" parent="android:TextAppearance.Material.Large">
+        <item name="android:textSize">@*android:dimen/car_body3_size</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+    </style>
+    <style name="TextAppearance.DeviceDefault.Inverse" parent="android:TextAppearance.Material.Inverse">
+        <item name="android:textSize">@*android:dimen/car_body3_size</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+    </style>
+
+    <style name="TextAppearance.DeviceDefault.Large" parent="android:TextAppearance.Material.Large">
+        <item name="android:textSize">@*android:dimen/car_body1_size</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+    </style>
+    <style name="TextAppearance.DeviceDefault.Large.Inverse" parent="android:TextAppearance.Material.Large.Inverse">
+        <item name="android:textSize">@*android:dimen/car_body1_size</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+    </style>
+
+    <style name="TextAppearance.DeviceDefault.Medium" parent="android:TextAppearance.Material.Medium">
+        <item name="android:textSize">@*android:dimen/car_body2_size</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
+    </style>
+    <style name="TextAppearance.DeviceDefault.Medium.Inverse" parent="android:TextAppearance.Material.Medium.Inverse">
+        <item name="android:textSize">@*android:dimen/car_body2_size</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
+    </style>
+
+    <style name="TextAppearance.DeviceDefault.Small" parent="android:TextAppearance.Material.Small">
+        <item name="android:textSize">@*android:dimen/car_body4_size</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+    </style>
+    <style name="TextAppearance.DeviceDefault.Small.Inverse" parent="android:TextAppearance.Material.Small.Inverse">
+        <item name="android:textSize">@*android:dimen/car_body4_size</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+    </style>
+
+    <style name="TextAppearance.DeviceDefault.Subhead" parent="android:TextAppearance.Material.Subhead">
+        <item name="android:textSize">@*android:dimen/car_body1_size</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+    </style>
+
+    <style name="TextAppearance.DeviceDefault.Widget.Button.Borderless.Colored"
+           parent="android:TextAppearance.DeviceDefault.Widget.Button">
+        <item name="android:textColor">@*android:color/car_borderless_button_text_color</item>
+    </style>
+
+    <style name="DialogWindowTitle.DeviceDefault" parent="*android:DialogWindowTitle.Material">
+        <item name="android:textAppearance">@*android:style/TextAppearance.DeviceDefault.DialogWindowTitle</item>
+    </style>
+    <style name="TextAppearance.DeviceDefault.DialogWindowTitle" parent="android:TextAppearance.Material.DialogWindowTitle">
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@*android:dimen/car_body2_size</item>
+        <item name="android:textColor">@*android:color/car_body2</item>
+    </style>
+    <style name="TextAppearance.Material.DialogWindowTitle" parent="android:TextAppearance.Material.Title" />
+    <style name="TextAppearance.Material.Title" parent="android:TextAppearance.Material">
+        <item name="android:textSize">@*android:dimen/text_size_title_material</item>
+        <item name="android:fontFamily">@*android:string/font_family_title_material</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
+    <style name="TextAppearance.DeviceDefault.Widget.Button" parent="android:TextAppearance.Material.Widget.Button">
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+        <item name="android:textAllCaps">@*android:bool/config_buttonTextAllCaps</item>
+        <item name="android:textSize">@*android:dimen/car_action1_size</item>
+        <item name="android:textColor">@*android:color/car_button_text_color</item>
+    </style>
+
+    <style name="Widget.DeviceDefault.TextView" parent="android:Widget.Material.TextView">
+        <item name="android:ellipsize">none</item>
+        <item name="android:textSize">@*android:dimen/car_body1_size</item>
+        <item name="android:requiresFadingEdge">horizontal</item>
+        <item name="android:fadingEdgeLength">@*android:dimen/car_textview_fading_edge_length</item>
+    </style>
+
+    <style name="Widget.DeviceDefault.Button" parent="android:Widget.Material.Button">
+        <item name="android:singleLine">true</item>
+        <item name="android:ellipsize">none</item>
+        <item name="android:requiresFadingEdge">horizontal</item>
+        <item name="android:fadingEdgeLength">@*android:dimen/car_textview_fading_edge_length</item>
+        <item name="android:background">@*android:drawable/car_button_background</item>
+        <item name="android:layout_height">@*android:dimen/car_button_height</item>
+        <item name="android:minWidth">@*android:dimen/car_button_min_width</item>
+        <item name="android:paddingStart">@*android:dimen/car_button_horizontal_padding</item>
+        <item name="android:paddingEnd">@*android:dimen/car_button_horizontal_padding</item>
+    </style>
+
+    <style name="Widget.DeviceDefault.Button.Borderless" parent="android:Widget.Material.Button.Borderless">
+        <item name="android:background">@drawable/btn_borderless_car</item>
+    </style>
+
+    <style name="Widget.DeviceDefault.CompoundButton.CheckBox" parent="android:Widget.Material.CompoundButton.CheckBox">
+        <item name="android:button">@*android:drawable/car_checkbox</item>
+    </style>
+
+    <style name="Widget.DeviceDefault.CompoundButton.Switch" parent="android:Widget.Material.CompoundButton.Switch">
+        <item name="android:thumb">@*android:drawable/car_switch_thumb</item>
+        <item name="android:track">@*android:drawable/car_switch_track</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
+    <style name="Widget.DeviceDefault.ProgressBar.Horizontal" parent="android:Widget.Material.ProgressBar.Horizontal">
+        <item name="android:minHeight">@*android:dimen/car_progress_bar_height</item>
+        <item name="android:maxHeight">@*android:dimen/car_progress_bar_height</item>
+    </style>
+
+    <style name="Widget.DeviceDefault.SeekBar" parent="android:Widget.Material.SeekBar">
+        <item name="android:progressDrawable">@*android:drawable/car_seekbar_track</item>
+        <item name="android:thumb">@*android:drawable/car_seekbar_thumb</item>
+    </style>
+
+    <style name="Widget.DeviceDefault.ActionBar.Solid" parent="android:Widget.Material.ActionBar.Solid">
+        <item name="android:textSize">@*android:dimen/car_body3_size</item>
+    </style>
+
+    <!-- Preference Styles -->
+    <style name="Preference.DeviceDefault" parent="*android:Preference.Material">
+        <item name="android:layout">@*android:layout/car_preference</item>
+    </style>
+    <style name="Preference.DeviceDefault.Category" parent="*android:Preference.Material.Category">
+        <item name="android:layout">@*android:layout/car_preference_category</item>
+    </style>
+    <style name="Preference.DeviceDefault.CheckBoxPreference" parent="*android:Preference.Material.CheckBoxPreference">
+        <item name="android:layout">@*android:layout/car_preference</item>
+    </style>
+    <style name="Preference.DeviceDefault.DialogPreference" parent="*android:Preference.Material.DialogPreference">
+        <item name="android:layout">@*android:layout/car_preference</item>
+    </style>
+    <style name="Preference.DeviceDefault.DialogPreference.EditTextPreference" parent="*android:Preference.Material.DialogPreference.EditTextPreference">
+        <item name="android:layout">@*android:layout/car_preference</item>
+    </style>
+    <style name="Preference.DeviceDefault.SwitchPreference" parent="*android:Preference.Material.SwitchPreference">
+        <item name="android:layout">@*android:layout/car_preference</item>
+    </style>
+
+    <!-- AlertDialog Style -->
+    <style name="AlertDialog.DeviceDefault" parent="*android:AlertDialog.Material">
+        <item name="android:layout">@*android:layout/car_alert_dialog</item>
+    </style>
+
+</resources>
diff --git a/car_product/car_ui_portrait/rro/android/res/values/themes_device_defaults.xml b/car_product/car_ui_portrait/rro/android/res/values/themes_device_defaults.xml
new file mode 100644
index 0000000..dc0198b
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/values/themes_device_defaults.xml
@@ -0,0 +1,263 @@
+<?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.
+-->
+
+<!--
+    This is an override of frameworks/base/core/res/res/values/themes_device_default.xml
+    It is how the device default is changed to match the desired look for a car theme.
+-->
+<resources>
+    <style name="Theme.DeviceDefault" parent="*android:Theme.DeviceDefaultBase">
+        <!-- Text styles -->
+        <!-- TODO clean up -->
+
+        <item name="android:textAppearanceListItem">@*android:style/TextAppearance.DeviceDefault.Large</item>
+        <item name="android:textAppearanceListItemSmall">@*android:style/TextAppearance.DeviceDefault.Large</item>
+        <item name="android:textAppearanceListItemSecondary">@*android:style/TextAppearance.DeviceDefault.Small</item>
+
+        <item name="android:borderlessButtonStyle">@*android:style/Widget.DeviceDefault.Button.Borderless.Colored</item>
+        <item name="android:buttonBarButtonStyle">@*android:style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
+        <item name="android:buttonStyle">@*android:style/Widget.DeviceDefault.Button</item>
+
+
+        <item name="android:listPreferredItemHeightSmall">@*android:dimen/car_single_line_list_item_height</item>
+
+
+        <item name="android:selectableItemBackground">@*android:drawable/item_background</item>
+
+        <item name="android:actionBarSize">@*android:dimen/car_app_bar_height</item>
+
+
+        <!-- Color palette -->
+        <item name="android:statusBarColor">@android:color/black</item>
+        <item name="android:colorButtonNormal">@color/btn_device_default_dark</item>
+        <item name="android:colorControlHighlight">@color/btn_device_default_dark</item>
+        <item name="android:colorControlNormal">@color/btn_device_default_dark</item>
+
+        <item name="android:listDivider">@color/list_divider_color</item>
+    </style>
+
+    <style name="Theme.DeviceDefault.Dialog" parent="android:Theme.Material.Dialog">
+        <item name="android:textAppearanceLarge">@*android:style/TextAppearance.DeviceDefault.Large</item>
+        <item name="android:textAppearanceMedium">@*android:style/TextAppearance.DeviceDefault.Medium</item>
+        <item name="android:textAppearanceSmall">@*android:style/TextAppearance.DeviceDefault.Small</item>
+        <item name="android:textAppearanceLargeInverse">@*android:style/TextAppearance.DeviceDefault.Large.Inverse</item>
+        <item name="android:textAppearanceMediumInverse">@*android:style/TextAppearance.DeviceDefault.Medium.Inverse</item>
+        <item name="android:textAppearanceSmallInverse">@*android:style/TextAppearance.DeviceDefault.Small.Inverse</item>
+        <item name="android:textAppearanceListItem">@*android:style/TextAppearance.DeviceDefault.Large</item>
+        <item name="android:textAppearanceListItemSmall">@*android:style/TextAppearance.DeviceDefault.Large</item>
+        <item name="android:textAppearanceListItemSecondary">@*android:style/TextAppearance.DeviceDefault.Small</item>
+        <item name="android:textAppearanceButton">@*android:style/Widget.DeviceDefault.Button</item>
+        <item name="android:borderlessButtonStyle">@*android:style/Widget.DeviceDefault.Button.Borderless.Colored</item>
+        <item name="android:buttonBarButtonStyle">@*android:style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
+        <item name="android:buttonStyle">@*android:style/Widget.DeviceDefault.Button</item>
+        <item name="android:selectableItemBackground">@*android:drawable/item_background</item>
+        <item name="android:windowTitleStyle">?android:attr/textAppearanceLarge</item>
+        <!-- Color palette -->
+        <item name="android:colorButtonNormal">@color/btn_device_default_dark</item>
+    </style>
+
+    <style name="Theme.DeviceDefault.Dialog.NoActionBar" parent="android:Theme.DeviceDefault.Dialog">
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
+    </style>
+
+    <style name="Theme.DeviceDefault.Dialog.Alert" parent="android:Theme.Material.Dialog.Alert">
+        <item name="android:textAppearanceLarge">@*android:style/TextAppearance.DeviceDefault.Large</item>
+        <item name="android:textAppearanceMedium">@*android:style/TextAppearance.DeviceDefault.Medium</item>
+        <item name="android:textAppearanceSmall">@*android:style/TextAppearance.DeviceDefault.Small</item>
+        <item name="android:textAppearanceButton">@*android:style/Widget.DeviceDefault.Button</item>
+        <item name="android:alertDialogStyle">@*android:style/AlertDialog.DeviceDefault</item>
+        <item name="android:borderlessButtonStyle">@*android:style/Widget.DeviceDefault.Button.Borderless.Colored</item>
+        <item name="android:buttonBarButtonStyle">@*android:style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
+        <item name="android:buttonStyle">@*android:style/Widget.DeviceDefault.Button</item>
+        <item name="android:selectableItemBackground">@*android:drawable/item_background</item>
+        <item name="android:textAppearanceListItem">@*android:style/TextAppearance.DeviceDefault.Large</item>
+        <item name="android:textAppearanceListItemSmall">@*android:style/TextAppearance.DeviceDefault.Large</item>
+        <item name="android:textAppearanceListItemSecondary">@*android:style/TextAppearance.DeviceDefault.Small</item>
+        <item name="android:windowTitleStyle">?android:attr/textAppearanceLarge</item>
+        <!-- Color palette -->
+        <item name="android:colorButtonNormal">@color/btn_device_default_dark</item>
+    </style>
+
+    <style name="Theme.DeviceDefault.Settings.Dialog" parent="android:Theme.DeviceDefault.Dialog.Alert">
+    </style>
+
+    <!-- The light theme is defined to be the same as the default since currently there is only one
+        defined theme palette -->
+    <style name="Theme.DeviceDefault.Light" parent="android:Theme.DeviceDefault"/>
+    <style name="Theme.DeviceDefault.Light.Dialog" parent="android:Theme.DeviceDefault.Dialog"/>
+    <style name="Theme.DeviceDefault.Light.Dialog.Alert" parent="android:Theme.DeviceDefault.Dialog.Alert"/>
+    <style name="Theme.DeviceDefault.Light.Dialog.NoActionBar" parent="android:Theme.DeviceDefault.Dialog.NoActionBar"/>
+
+    <style name="Theme.DeviceDefault.Light.NoActionBar" parent="android:Theme.DeviceDefault.Light">
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
+    </style>
+    <style name="Theme.DeviceDefault.NoActionBar" parent="android:Theme.DeviceDefault">
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
+    </style>
+
+    <style name="Theme.DeviceDefault.InputMethod" parent="android:Theme.Material.InputMethod">
+        <!-- Color palette -->
+        <item name="android:colorAccent">@*android:color/accent_device_default_light</item>
+        <item name="android:colorBackground">@*android:color/primary_device_default_light</item>
+        <item name="android:listDivider">@*android:color/car_keyboard_divider_line</item>
+        <item name="android:selectableItemBackground">@*android:drawable/item_background</item>
+        <item name="android:textColorPrimary">@*android:color/car_keyboard_text_primary_color</item>
+        <item name="android:textColorSecondary">@*android:color/car_keyboard_text_secondary_color</item>
+    </style>
+
+    <style name="Theme.DeviceDefault.Settings" parent="android:Theme.DeviceDefault"/>
+    <style name="Theme.DeviceDefault.Settings.NoActionBar" parent="android:Theme.DeviceDefault.NoActionBar"/>
+
+    <style name="Theme.DeviceDefault.Light.DarkActionBar"  parent="android:Theme.DeviceDefault"/>
+    <!-- DeviceDefault theme for the default system theme.  -->
+    <style name="Theme.DeviceDefault.System" parent="android:Theme.DeviceDefault.Light.DarkActionBar" />
+
+    <!-- Theme used for the intent picker activity. -->
+    <style name="Theme.DeviceDefault.Resolver" parent="android:Theme.DeviceDefault">
+        <item name="android:windowEnterTransition">@empty</item>
+        <item name="android:windowExitTransition">@empty</item>
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:backgroundDimEnabled">true</item>
+        <item name="android:statusBarColor">@android:color/transparent</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:colorControlActivated">?*android:attr/colorControlHighlight</item>
+        <item name="android:listPreferredItemPaddingStart">?*android:attr/dialogPreferredPadding</item>
+        <item name="android:listPreferredItemPaddingEnd">?*android:attr/dialogPreferredPadding</item>
+
+        <!-- Dialog attributes -->
+        <item name="android:dialogCornerRadius">@*android:dimen/config_dialogCornerRadius</item>
+        <item name="android:alertDialogTheme">@android:style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+        <!-- Button styles -->
+        <item name="android:buttonCornerRadius">@*android:dimen/config_buttonCornerRadius</item>
+        <item name="android:buttonBarButtonStyle">@*android:style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
+        <item name="android:borderlessButtonStyle">@*android:style/Widget.DeviceDefault.Button.Borderless.Colored</item>
+        <item name="android:buttonStyle">@*android:style/Widget.DeviceDefault.Button</item>
+
+        <!-- Color palette -->
+        <item name="android:colorButtonNormal">@color/btn_device_default_dark</item>
+
+        <!-- Progress bar attributes -->
+        <item name="*android:colorProgressBackgroundNormal">@*android:color/config_progress_background_tint</item>
+        <item name="*android:progressBarCornerRadius">@*android:dimen/config_progressBarCornerRadius</item>
+
+        <!-- Toolbar attributes -->
+        <item name="android:toolbarStyle">@*android:style/Widget.DeviceDefault.Toolbar</item>
+
+        <item name="*android:toastFrameBackground">@*android:drawable/toast_frame</item>
+        <item name="android:textAppearanceListItem">@android:style/TextAppearance.DeviceDefault.Large</item>
+        <item name="android:textAppearanceListItemSmall">@android:style/TextAppearance.DeviceDefault.Large</item>
+        <item name="android:textAppearanceListItemSecondary">@android:style/TextAppearance.DeviceDefault.Small</item>
+
+        <!-- Icon sizes -->
+        <item name="*android:iconfactoryIconSize">@*android:dimen/resolver_icon_size</item>
+        <item name="*android:iconfactoryBadgeSize">@*android:dimen/resolver_badge_size</item>
+    </style>
+
+
+    <!-- DeviceDefault theme for windows that want to have the user's selected wallpaper appear
+    behind them. -->
+    <style name="Theme.DeviceDefault.Wallpaper" parent="android:Theme.DeviceDefault">
+        <!-- Color palette -->
+
+        <!-- Dialog attributes -->
+        <item name="android:dialogCornerRadius">@*android:dimen/config_dialogCornerRadius</item>
+        <item name="android:alertDialogTheme">@*android:style/Theme.DeviceDefault.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="android:textAppearanceButton">@*android:style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="android:buttonCornerRadius">@*android:dimen/config_buttonCornerRadius</item>
+        <item name="android:buttonBarButtonStyle">@*android:style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
+
+        <!-- Progress bar attributes -->
+        <item name="*android:colorProgressBackgroundNormal">@*android:color/config_progress_background_tint</item>
+        <item name="*android:progressBarCornerRadius">@*android:dimen/config_progressBarCornerRadius</item>
+
+        <!-- Toolbar attributes -->
+        <item name="android:toolbarStyle">@*android:style/Widget.DeviceDefault.Toolbar</item>
+
+        <item name="android:windowBackground">@*android:color/transparent</item>
+        <item name="android:colorBackgroundCacheHint">@null</item>
+        <item name="android:windowShowWallpaper">true</item>
+    </style>
+
+    <!-- DeviceDefault theme for windows that want to have the user's selected wallpaper appear
+    behind them and without an action bar. -->
+    <style name="Theme.DeviceDefault.Wallpaper.NoTitleBar" parent="android:Theme.DeviceDefault.Wallpaper">
+        <!-- Color palette -->
+
+        <!-- Dialog attributes -->
+        <item name="android:dialogCornerRadius">@*android:dimen/config_dialogCornerRadius</item>
+        <item name="android:alertDialogTheme">@*android:style/Theme.DeviceDefault.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="android:textAppearanceButton">@*android:style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="android:buttonCornerRadius">@*android:dimen/config_buttonCornerRadius</item>
+        <item name="android:buttonBarButtonStyle">@*android:style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
+
+        <!-- Progress bar attributes -->
+        <item name="*android:colorProgressBackgroundNormal">@*android:color/config_progress_background_tint</item>
+        <item name="*android:progressBarCornerRadius">@*android:dimen/config_progressBarCornerRadius</item>
+
+        <!-- Toolbar attributes -->
+        <item name="android:toolbarStyle">@*android:style/Widget.DeviceDefault.Toolbar</item>
+
+        <item name="android:windowNoTitle">true</item>
+    </style>
+
+    <!-- DeviceDefault theme for panel windows. This removes all extraneous window decorations, so
+    you basically have an empty rectangle in which to place your content. It makes the window
+    floating, with a transparent background, and turns off dimming behind the window.
+    Used for Autofill screens.-->
+    <style name="Theme.DeviceDefault.Panel" parent="android:Theme.Material.Panel">
+        <!-- Color palette -->
+
+        <!-- Dialog attributes -->
+        <item name="android:dialogCornerRadius">@*android:dimen/config_dialogCornerRadius</item>
+        <item name="android:alertDialogTheme">@*android:style/Theme.DeviceDefault.Dialog.Alert</item>
+
+        <!-- Text styles -->
+        <item name="android:textAppearanceButton">@*android:style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+        <!-- Button styles -->
+        <item name="android:buttonCornerRadius">@*android:dimen/config_buttonCornerRadius</item>
+        <item name="android:buttonBarButtonStyle">@*android:style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
+
+        <!-- Progress bar attributes -->
+        <item name="*android:colorProgressBackgroundNormal">@*android:color/config_progress_background_tint</item>
+        <item name="*android:progressBarCornerRadius">@*android:dimen/config_progressBarCornerRadius</item>
+
+        <!-- Toolbar attributes -->
+        <item name="android:toolbarStyle">@*android:style/Widget.DeviceDefault.Toolbar</item>
+
+        <!-- Hide action bar -->
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
+
+        <item name="android:selectableItemBackground">@*android:drawable/item_background</item>
+    </style>
+
+    <style name="Theme.DeviceDefault.Light.Panel" parent="android:Theme.DeviceDefault.Panel"/>
+</resources>
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/Android.mk b/car_product/car_ui_portrait/rro/car-ui-customizations/Android.mk
new file mode 100644
index 0000000..96c6d30
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/Android.mk
@@ -0,0 +1,57 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+CAR_UI_RRO_SET_NAME := generated_caruiportrait_customization
+CAR_UI_RRO_MANIFEST_FILE := $(LOCAL_PATH)/AndroidManifest.xml
+CAR_UI_RESOURCE_DIR := $(LOCAL_PATH)/res
+CAR_UI_RRO_TARGETS := \
+    com.android.car.ui.paintbooth \
+    com.google.android.car.ui.paintbooth \
+    com.google.android.carui.ats \
+    com.android.car.rotaryplayground \
+    com.android.car.themeplayground \
+    com.android.car.carlauncher \
+    com.android.car.home \
+    com.android.car.media \
+    com.android.car.radio \
+    com.android.car.calendar \
+    com.android.car.companiondevicesupport \
+    com.android.car.systemupdater \
+    com.android.car.dialer \
+    com.android.car.linkviewer \
+    com.android.car.settings \
+    com.android.car.voicecontrol \
+    com.android.car.faceenroll \
+    com.android.car.developeroptions \
+    com.android.managedprovisioning \
+    com.android.settings.intelligence \
+    com.google.android.apps.automotive.inputmethod \
+    com.google.android.apps.automotive.inputmethod.dev \
+    com.google.android.apps.automotive.templates.host \
+    com.google.android.embedded.projection \
+    com.google.android.gms \
+    com.google.android.gsf \
+    com.google.android.packageinstaller \
+    com.google.android.permissioncontroller \
+    com.google.android.carassistant \
+    com.google.android.tts \
+    com.android.htmlviewer \
+    com.android.vending \
+
+include packages/apps/Car/libs/car-ui-lib/generate_rros.mk
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/AndroidManifest.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/AndroidManifest.xml
new file mode 100644
index 0000000..9d3a1a4
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="{{RRO_PACKAGE_NAME}}">
+    <application android:hasCode="false"/>
+    <overlay android:priority="10"
+             android:targetName="car-ui-lib"
+             android:targetPackage="{{TARGET_PACKAGE_NAME}}"
+             android:resourcesMap="@xml/overlays"
+             android:isStatic="true"
+             android:requiredSystemPropertyName="ro.build.characteristics"
+             android:requiredSystemPropertyValue="automotive"/>
+</manifest>
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/README b/car_product/car_ui_portrait/rro/car-ui-customizations/README
new file mode 100644
index 0000000..1e14b27
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/README
@@ -0,0 +1,2 @@
+The values in this RRO are for modifying the car-ui-lib values and should be applied to all
+applications using car-ui-lib
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/product.mk b/car_product/car_ui_portrait/rro/car-ui-customizations/product.mk
new file mode 100644
index 0000000..a7c3808
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/product.mk
@@ -0,0 +1,51 @@
+#
+# 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.
+#
+
+# Inherit from this product to include the "Car Ui Portrait" RROs for CarUi
+# Include generated RROs
+PRODUCT_PACKAGES += \
+    generated_caruiportrait_customization-com-android-car-ui-paintbooth \
+    generated_caruiportrait_customization-com-google-android-car-ui-paintbooth \
+    generated_caruiportrait_customization-com-google-android-carui-ats \
+    generated_caruiportrait_customization-com-android-car-rotaryplayground \
+    generated_caruiportrait_customization-com-android-car-themeplayground \
+    generated_caruiportrait_customization-com-android-car-carlauncher \
+    generated_caruiportrait_customization-com-android-car-home \
+    generated_caruiportrait_customization-com-android-car-media \
+    generated_caruiportrait_customization-com-android-car-radio \
+    generated_caruiportrait_customization-com-android-car-calendar \
+    generated_caruiportrait_customization-com-android-car-companiondevicesupport \
+    generated_caruiportrait_customization-com-android-car-systemupdater \
+    generated_caruiportrait_customization-com-android-car-dialer \
+    generated_caruiportrait_customization-com-android-car-linkviewer \
+    generated_caruiportrait_customization-com-android-car-settings \
+    generated_caruiportrait_customization-com-android-car-voicecontrol \
+    generated_caruiportrait_customization-com-android-car-faceenroll \
+    generated_caruiportrait_customization-com-android-car-developeroptions \
+    generated_caruiportrait_customization-com-android-managedprovisioning \
+    generated_caruiportrait_customization-com-android-settings-intelligence \
+    generated_caruiportrait_customization-com-google-android-apps-automotive-inputmethod \
+    generated_caruiportrait_customization-com-google-android-apps-automotive-inputmethod-dev \
+    generated_caruiportrait_customization-com-google-android-apps-automotive-templates-host \
+    generated_caruiportrait_customization-com-google-android-embedded-projection \
+    generated_caruiportrait_customization-com-google-android-gms \
+    generated_caruiportrait_customization-com-google-android-gsf \
+    generated_caruiportrait_customization-com-google-android-packageinstaller \
+    generated_caruiportrait_customization-com-google-android-permissioncontroller \
+    generated_caruiportrait_customization-com-google-android-carassistant \
+    generated_caruiportrait_customization-com-google-android-tts \
+    generated_caruiportrait_customization-com-android-htmlviewer \
+    generated_caruiportrait_customization-com-android-vending
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/color/car_ui_text_color_primary.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/color/car_ui_text_color_primary.xml
new file mode 100644
index 0000000..e87a692
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/color/car_ui_text_color_primary.xml
@@ -0,0 +1,28 @@
+<?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.
+  -->
+<!-- Copy of ?android:attr/textColorPrimary (frameworks/base/res/res/color/text_color_primary.xml)
+     but with a ux restricted state. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item android:state_enabled="false"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="?android:attr/textColorPrimary"/>
+    <item app:state_ux_restricted="true"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="?android:attr/textColorPrimary"/>
+    <item android:color="?android:attr/textColorPrimary"/>
+</selector>
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/color/car_ui_text_color_secondary.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/color/car_ui_text_color_secondary.xml
new file mode 100644
index 0000000..0f1fcb5
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/color/car_ui_text_color_secondary.xml
@@ -0,0 +1,28 @@
+<?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.
+  -->
+<!-- Copy of ?android:attr/textColorSecondary (frameworks/base/res/res/color/text_color_secondary.xml)
+     but with a ux restricted state. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item android:state_enabled="false"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="?android:attr/textColorSecondary"/>
+    <item app:state_ux_restricted="true"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="?android:attr/textColorSecondary"/>
+    <item android:color="?android:attr/textColorSecondary"/>
+</selector>
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/color/car_ui_toolbar_tab_item_selector.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/color/car_ui_toolbar_tab_item_selector.xml
new file mode 100644
index 0000000..02d4374
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/color/car_ui_toolbar_tab_item_selector.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@color/car_ui_text_color_primary" android:state_activated="true"/>
+    <item android:color="@color/car_ui_text_color_secondary"/>
+</selector>
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_activity_background.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_activity_background.xml
new file mode 100644
index 0000000..f70ad67
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_activity_background.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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item android:drawable="?android:attr/colorBackground"/>
+</layer-list>
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_recyclerview_ic_down.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_recyclerview_ic_down.xml
new file mode 100644
index 0000000..e2d1b93
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_recyclerview_ic_down.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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="48dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:pathData="M14.83,16.42L24,25.59l9.17,-9.17L36,19.25l-12,12 -12,-12z"
+        android:fillColor="#000000"/>
+</vector>
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_recyclerview_ic_up.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_recyclerview_ic_up.xml
new file mode 100644
index 0000000..c8cc84f
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_recyclerview_ic_up.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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="48dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:pathData="M14.83,30.83L24,21.66l9.17,9.17L36,28 24,16 12,28z"
+        android:fillColor="#000000"/>
+</vector>
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_recyclerview_scrollbar_thumb.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_recyclerview_scrollbar_thumb.xml
new file mode 100644
index 0000000..54922cf
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_recyclerview_scrollbar_thumb.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="#99000000" />
+    <corners android:radius="100dp"/>
+</shape>
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_toolbar_menu_item_divider.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_toolbar_menu_item_divider.xml
new file mode 100644
index 0000000..9b47736
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_toolbar_menu_item_divider.xml
@@ -0,0 +1,21 @@
+<?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.
+  ~
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <size android:width="16dp"/>
+</shape>
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_toolbar_menu_item_icon_background.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_toolbar_menu_item_icon_background.xml
new file mode 100644
index 0000000..57ac917
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_toolbar_menu_item_icon_background.xml
@@ -0,0 +1,23 @@
+<?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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <size
+        android:width="40dp"
+        android:height="40dp"/>
+    <solid android:color="@android:color/transparent"/>
+</shape>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_toolbar_menu_item_icon_ripple.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_toolbar_menu_item_icon_ripple.xml
new file mode 100644
index 0000000..9ac2a1f
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_toolbar_menu_item_icon_ripple.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.
+  ~
+ -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="#27ffffff"
+        android:radius="48dp"/>
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/layout/car_ui_alert_dialog_title_with_subtitle.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/layout/car_ui_alert_dialog_title_with_subtitle.xml
new file mode 100644
index 0000000..faf9eeb
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/layout/car_ui_alert_dialog_title_with_subtitle.xml
@@ -0,0 +1,60 @@
+<?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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/title_template"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <!-- Leave this view here so that we don't get any null pointer errors in the alert dialog
+         class. -->
+    <ImageView
+        android:id="@+id/car_ui_alert_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:scaleType="fitCenter"
+        android:visibility="gone"/>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/alert_dialog_margin"
+        android:layout_marginEnd="@dimen/alert_dialog_margin"
+        android:layout_marginTop="@dimen/alert_dialog_margin"
+        android:orientation="vertical">
+        <TextView
+            android:id="@+id/car_ui_alert_title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:textAppearance="@style/TextAppearance_CarUi_AlertDialog_Title" />
+
+        <TextView
+            android:id="@+id/car_ui_alert_subtitle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:textAppearance="@style/TextAppearance_CarUi_AlertDialog_Subtitle"/>
+    </LinearLayout>
+
+    <View
+        android:id="@+id/empty_space"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:visibility="gone"/>
+</LinearLayout>
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/values/attrs.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/values/attrs.xml
new file mode 100644
index 0000000..6b60f12
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/values/attrs.xml
@@ -0,0 +1,19 @@
+<?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>
+    <attr name="state_ux_restricted" format="boolean" />
+</resources>
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/values/dimens.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/values/dimens.xml
new file mode 100644
index 0000000..a3ce819
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/values/dimens.xml
@@ -0,0 +1,23 @@
+<?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>
+    <dimen name="car_ui_body1_size">32sp</dimen>
+    <dimen name="car_ui_body3_size">24sp</dimen>
+
+    <dimen name="alert_dialog_margin">36dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/values/drawables.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/values/drawables.xml
new file mode 100644
index 0000000..f44dbf0
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/values/drawables.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">
+    <!-- Toolbar background color -->
+    <drawable name="car_ui_toolbar_background">@*android:color/background_device_default_dark</drawable>
+</resources>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/values/styles.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/values/styles.xml
new file mode 100644
index 0000000..f154c0b
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/values/styles.xml
@@ -0,0 +1,52 @@
+<?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">
+
+    <style name="TextAppearance_CarUi_AlertDialog_Title" parent="android:TextAppearance.DeviceDefault">
+        <item name="android:textSize">32sp</item>
+    </style>
+    <style name="TextAppearance_CarUi_AlertDialog_Subtitle" parent="android:TextAppearance.DeviceDefault">
+        <item name="android:textSize">24sp</item>
+    </style>
+
+    <style name="TextAppearance.CarUi.PreferenceCategoryTitle" parent="android:TextAppearance.DeviceDefault">
+        <item name="android:fontFamily">sans-serif-medium</item>
+        <item name="android:textColor">?android:attr/colorAccent</item>
+        <item name="android:textSize">24sp</item>
+    </style>
+
+    <style name="TextAppearance.CarUi.PreferenceSummary" parent="android:TextAppearance.DeviceDefault">
+        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textSize">24sp</item>
+    </style>
+
+    <style name="TextAppearance.CarUi.PreferenceTitle" parent="android:TextAppearance.DeviceDefault">
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textSize">32sp</item>
+    </style>
+
+    <style name="TextAppearance.CarUi.Widget" parent="android:TextAppearance.DeviceDefault.Widget">
+        <item name="android:textAlignment">viewStart</item>
+    </style>
+
+    <style name="TextAppearance.CarUi.Widget.Toolbar"/>
+
+    <style name="TextAppearance.CarUi.Widget.Toolbar.Title">
+        <item name="android:singleLine">true</item>
+        <item name="android:textSize">32sp</item>
+    </style>
+</resources>
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/values/themes.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/values/themes.xml
new file mode 100644
index 0000000..85dfc95
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/values/themes.xml
@@ -0,0 +1,18 @@
+<!--
+  ~ 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>
+
+</resources>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/values/values.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/values/values.xml
new file mode 100644
index 0000000..a09f6e8
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/values/values.xml
@@ -0,0 +1,19 @@
+<?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>
+    <bool name="car_ui_scrollbar_enable">false</bool>
+</resources>
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/xml/overlays.xml
new file mode 100644
index 0000000..17c973e
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/xml/overlays.xml
@@ -0,0 +1,54 @@
+<?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.
+  -->
+<overlay>
+    <item target="layout/car_ui_alert_dialog_title_with_subtitle" value="@layout/car_ui_alert_dialog_title_with_subtitle"/>
+
+    <item target="id/car_ui_alert_icon" value="@id/car_ui_alert_icon"/>
+    <item target="id/car_ui_alert_title" value="@id/car_ui_alert_title"/>
+    <item target="id/car_ui_alert_subtitle" value="@id/car_ui_alert_subtitle"/>
+
+    <item target="dimen/car_ui_body1_size" value="@dimen/car_ui_body1_size"/>
+    <item target="dimen/car_ui_body3_size" value="@dimen/car_ui_body3_size"/>
+    <item target="dimen/alert_dialog_margin" value="@dimen/alert_dialog_margin"/>
+
+    <item target="drawable/car_ui_recyclerview_ic_up" value="@drawable/car_ui_recyclerview_ic_up" />
+    <item target="drawable/car_ui_recyclerview_ic_down" value="@drawable/car_ui_recyclerview_ic_down" />
+    <item target="drawable/car_ui_recyclerview_scrollbar_thumb" value="@drawable/car_ui_recyclerview_scrollbar_thumb" />
+    <item target="drawable/car_ui_activity_background" value="@drawable/car_ui_activity_background" />
+    <item target="drawable/car_ui_toolbar_menu_item_icon_background" value="@drawable/car_ui_toolbar_menu_item_icon_background" />
+
+    <item target="color/car_ui_text_color_primary" value="@color/car_ui_text_color_primary" />
+    <item target="color/car_ui_text_color_secondary" value="@color/car_ui_text_color_secondary" />
+    <item target="color/car_ui_toolbar_tab_item_selector" value="@color/car_ui_toolbar_tab_item_selector" />
+
+
+    <item target="drawable/car_ui_toolbar_background" value="@drawable/car_ui_toolbar_background" />
+    <item target="drawable/car_ui_toolbar_menu_item_divider" value="@drawable/car_ui_toolbar_menu_item_divider" />
+    <item target="drawable/car_ui_toolbar_menu_item_icon_ripple" value="@drawable/car_ui_toolbar_menu_item_icon_ripple" />
+    <item target="bool/car_ui_scrollbar_enable" value="@bool/car_ui_scrollbar_enable" />
+
+    <item target="style/TextAppearance_CarUi_AlertDialog_Title" value="@style/TextAppearance_CarUi_AlertDialog_Title" />
+    <item target="style/TextAppearance_CarUi_AlertDialog_Subtitle" value="@style/TextAppearance_CarUi_AlertDialog_Subtitle" />
+    <item target="style/TextAppearance.CarUi.PreferenceCategoryTitle" value="@style/TextAppearance.CarUi.PreferenceCategoryTitle" />
+    <item target="style/TextAppearance.CarUi.PreferenceSummary" value="@style/TextAppearance.CarUi.PreferenceSummary" />
+    <item target="style/TextAppearance.CarUi.PreferenceTitle" value="@style/TextAppearance.CarUi.PreferenceTitle" />
+    <item target="style/TextAppearance.CarUi.Widget" value="@style/TextAppearance.CarUi.Widget" />
+    <item target="style/TextAppearance.CarUi.Widget.Toolbar" value="@style/TextAppearance.CarUi.Widget.Toolbar" />
+    <item target="style/TextAppearance.CarUi.Widget.Toolbar.Title" value="@style/TextAppearance.CarUi.Widget.Toolbar.Title" />
+
+    <item target="attr/state_ux_restricted" value="@attr/state_ux_restricted"/>
+</overlay>
diff --git a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/Android.mk b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/Android.mk
new file mode 100644
index 0000000..1b06791
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/Android.mk
@@ -0,0 +1,28 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+CAR_UI_RRO_SET_NAME := generated_caruiportrait_toolbar
+CAR_UI_RRO_MANIFEST_FILE := $(LOCAL_PATH)/AndroidManifest.xml
+CAR_UI_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+CAR_UI_RRO_TARGETS := \
+    com.android.car.media \
+    com.android.car.dialer \
+
+include packages/apps/Car/libs/car-ui-lib/generate_rros.mk
diff --git a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/AndroidManifest.xml b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/AndroidManifest.xml
new file mode 100644
index 0000000..9d3a1a4
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="{{RRO_PACKAGE_NAME}}">
+    <application android:hasCode="false"/>
+    <overlay android:priority="10"
+             android:targetName="car-ui-lib"
+             android:targetPackage="{{TARGET_PACKAGE_NAME}}"
+             android:resourcesMap="@xml/overlays"
+             android:isStatic="true"
+             android:requiredSystemPropertyName="ro.build.characteristics"
+             android:requiredSystemPropertyValue="automotive"/>
+</manifest>
diff --git a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/README b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/README
new file mode 100644
index 0000000..e199f7b
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/README
@@ -0,0 +1,2 @@
+The values in this RRO are to change the placement of the car-ui toolbar as such it currently
+is only targeting a limited set of applications.
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/product.mk b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/product.mk
new file mode 100644
index 0000000..7b7cccd
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/product.mk
@@ -0,0 +1,21 @@
+#
+# 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.
+#
+
+# Inherit from this product to include the "Car Ui Portrait" RROs for CarUi
+# Include generated RROs
+PRODUCT_PACKAGES += \
+    generated_caruiportrait_toolbar-com-android-car-media \
+    generated_caruiportrait_toolbar-com-android-car-dialer \
diff --git a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/color/car_ui_text_color_primary.xml b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/color/car_ui_text_color_primary.xml
new file mode 100644
index 0000000..860f219
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/color/car_ui_text_color_primary.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+<!-- Copy of ?android:attr/textColorPrimary (frameworks/base/res/res/color/text_color_primary.xml)
+     but with a ux restricted state. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item android:state_enabled="false"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="?android:attr/colorForeground"/>
+    <item app:state_ux_restricted="true"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="?android:attr/colorForeground"/>
+    <item android:color="?android:attr/colorForeground"/>
+</selector>
diff --git a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/color/car_ui_text_color_secondary.xml b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/color/car_ui_text_color_secondary.xml
new file mode 100644
index 0000000..f99fc86
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/color/car_ui_text_color_secondary.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+<!-- Copy of ?android:attr/textColorSecondary (frameworks/base/res/res/color/text_color_secondary.xml)
+     but with a ux restricted state. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item android:state_enabled="false"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="?android:attr/colorForeground"/>
+    <item app:state_ux_restricted="true"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="?android:attr/colorForeground"/>
+    <item android:color="?android:attr/colorForeground"/>
+</selector>
diff --git a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/color/car_ui_toolbar_tab_item_selector.xml b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/color/car_ui_toolbar_tab_item_selector.xml
new file mode 100644
index 0000000..02d4374
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/color/car_ui_toolbar_tab_item_selector.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@color/car_ui_text_color_primary" android:state_activated="true"/>
+    <item android:color="@color/car_ui_text_color_secondary"/>
+</selector>
diff --git a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/color/tab_side_indicator_color.xml b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/color/tab_side_indicator_color.xml
new file mode 100644
index 0000000..0cf2a1a
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/color/tab_side_indicator_color.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="#52CCB0" android:state_activated="true"/>
+    <item android:color="@android:color/transparent"/>
+</selector>
diff --git a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/drawable/car_ui_toolbar_menu_item_divider.xml b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/drawable/car_ui_toolbar_menu_item_divider.xml
new file mode 100644
index 0000000..9b47736
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/drawable/car_ui_toolbar_menu_item_divider.xml
@@ -0,0 +1,21 @@
+<?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.
+  ~
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <size android:width="16dp"/>
+</shape>
diff --git a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/drawable/car_ui_toolbar_menu_item_icon_ripple.xml b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/drawable/car_ui_toolbar_menu_item_icon_ripple.xml
new file mode 100644
index 0000000..9ac2a1f
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/drawable/car_ui_toolbar_menu_item_icon_ripple.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.
+  ~
+ -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="#27ffffff"
+        android:radius="48dp"/>
diff --git a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/drawable/tab_background.xml b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/drawable/tab_background.xml
new file mode 100644
index 0000000..0162e5b
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/drawable/tab_background.xml
@@ -0,0 +1,28 @@
+<?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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_activated="true">
+        <shape android:shape="rectangle">
+            <solid android:color="#2E3134"/>
+        </shape>
+    </item>
+    <item android:state_activated="false">
+        <shape android:shape="rectangle">
+            <solid android:color="@android:color/transparent"/>
+        </shape>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/layout/car_ui_base_layout_toolbar.xml b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/layout/car_ui_base_layout_toolbar.xml
new file mode 100644
index 0000000..39d910c
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/layout/car_ui_base_layout_toolbar.xml
@@ -0,0 +1,200 @@
+<?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.
+  -->
+<!-- This is for the two-row version of the toolbar -->
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:tag="CarUiBaseLayoutToolbar">
+
+    <!-- When not in touch mode, if we clear focus in current window, Android will re-focus the
+         first focusable view in the window automatically. Adding a FocusParkingView to the window
+         can fix this issue, because it can take focus, and it is transparent and its default focus
+         highlight is disabled, so it's invisible to the user no matter whether it's focused or not.
+         -->
+    <com.android.car.ui.FocusParkingView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+    <FrameLayout
+        android:id="@+id/car_ui_base_layout_content_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:paddingStart="24dp"
+        android:paddingEnd="24dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintStart_toEndOf="@id/left_part_of_toolbar_focus_area"
+        app:layout_constraintEnd_toEndOf="parent"/>
+
+    <com.android.car.ui.FocusArea
+        android:id="@+id/top_part_of_toolbar_focus_area"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="90dp"
+            android:background="?android:attr/colorBackground"
+            android:tag="car_ui_top_inset"
+            app:layout_constraintTop_toTopOf="parent">
+            <com.android.car.ui.baselayout.ClickBlockingView
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintBottom_toBottomOf="parent"/>
+
+            <FrameLayout
+                android:id="@+id/car_ui_toolbar_nav_icon_container"
+                android:layout_width="90dp"
+                android:layout_height="0dp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent">
+
+                <ImageView
+                    android:id="@+id/car_ui_toolbar_nav_icon"
+                    android:layout_width="@dimen/car_ui_toolbar_nav_icon_size"
+                    android:layout_height="@dimen/car_ui_toolbar_nav_icon_size"
+                    android:layout_gravity="center"
+                    android:scaleType="fitXY"
+                    android:background="@drawable/car_ui_toolbar_menu_item_icon_ripple"
+                    android:tint="@color/car_ui_text_color_primary"/>
+
+                <ImageView
+                    android:id="@+id/car_ui_toolbar_logo"
+                    android:layout_width="@dimen/car_ui_toolbar_logo_size"
+                    android:layout_height="@dimen/car_ui_toolbar_logo_size"
+                    android:layout_gravity="center"
+                    android:scaleType="fitXY" />
+            </FrameLayout>
+
+            <FrameLayout
+                android:id="@+id/car_ui_toolbar_title_logo_container"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintEnd_toStartOf="@id/car_ui_toolbar_title_container"
+                app:layout_constraintHorizontal_chainStyle="packed">
+
+                <ImageView
+                    android:id="@+id/car_ui_toolbar_title_logo"
+                    android:layout_width="@dimen/car_ui_toolbar_logo_size"
+                    android:layout_height="@dimen/car_ui_toolbar_logo_size"
+                    android:layout_gravity="center"
+                    android:scaleType="fitXY" />
+            </FrameLayout>
+
+            <LinearLayout android:layout_height="wrap_content"
+                          android:layout_width="wrap_content"
+                          android:id="@+id/car_ui_toolbar_title_container"
+                          android:orientation="vertical"
+                          android:layout_marginStart="16dp"
+                          app:layout_goneMarginStart="0dp"
+                          app:layout_constraintBottom_toBottomOf="parent"
+                          app:layout_constraintTop_toTopOf="parent"
+                          app:layout_constraintEnd_toEndOf="parent"
+                          app:layout_constraintStart_toEndOf="@id/car_ui_toolbar_title_logo_container">
+                <TextView android:id="@+id/car_ui_toolbar_title"
+                          android:layout_width="wrap_content"
+                          android:layout_height="wrap_content"
+                          android:singleLine="true"
+                          android:textAlignment="viewStart"
+                          android:textAppearance="@style/TextAppearance.CarUi.Widget.Toolbar.Title"/>
+                <TextView android:id="@+id/car_ui_toolbar_subtitle"
+                          android:layout_width="wrap_content"
+                          android:layout_height="wrap_content"
+                          android:visibility="gone"
+                          android:textAlignment="viewStart"
+                          android:textAppearance="?android:attr/textAppearanceSmall"/>
+            </LinearLayout>
+
+            <FrameLayout
+                android:id="@+id/car_ui_toolbar_search_view_container"
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toStartOf="@+id/car_ui_toolbar_menu_items_container"
+                app:layout_constraintStart_toEndOf="@+id/car_ui_toolbar_nav_icon_container"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <LinearLayout
+                android:id="@+id/car_ui_toolbar_menu_items_container"
+                android:divider="@drawable/car_ui_toolbar_menu_item_divider"
+                android:showDividers="beginning|middle|end"
+                android:layout_width="wrap_content"
+                android:layout_height="0dp"
+                android:orientation="horizontal"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <ProgressBar
+                android:id="@+id/car_ui_toolbar_progress_bar"
+                style="@android:style/Widget.DeviceDefault.ProgressBar.Horizontal"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:indeterminate="true"
+                android:visibility="gone"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent" />
+
+            <!-- Hairline across bottom of toolbar -->
+            <View
+                android:layout_width="match_parent"
+                android:layout_height="2dp"
+                android:background="?android:attr/listDivider"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.android.car.ui.FocusArea>
+
+    <com.android.car.ui.FocusArea
+        android:id="@+id/left_part_of_toolbar_focus_area"
+        android:layout_width="wrap_content"
+        android:layout_height="0dp"
+        android:tag="car_ui_left_inset"
+        android:orientation="horizontal"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/top_part_of_toolbar_focus_area"
+        app:layout_constraintBottom_toBottomOf="parent">
+
+        <com.android.car.ui.toolbar.TabLayout
+            android:id="@+id/car_ui_toolbar_tabs"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:orientation="vertical"/>
+    </com.android.car.ui.FocusArea>
+
+    <!-- Hairline to the right of the tabs -->
+    <View
+        android:layout_width="2dp"
+        android:layout_height="match_parent"
+        android:background="?android:attr/listDivider"
+        android:focusable="false"
+        app:layout_constraintStart_toEndOf="@id/left_part_of_toolbar_focus_area"
+        app:layout_constraintTop_toBottomOf="@id/top_part_of_toolbar_focus_area"
+        app:layout_constraintBottom_toBottomOf="parent"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/layout/car_ui_toolbar_tab_item.xml b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/layout/car_ui_toolbar_tab_item.xml
new file mode 100644
index 0000000..63ac2b4
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/layout/car_ui_toolbar_tab_item.xml
@@ -0,0 +1,56 @@
+<?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.
+  -->
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="320dp"
+    android:layout_height="96dp"
+    android:background="@drawable/tab_background">
+
+    <View
+        android:layout_width="8dp"
+        android:layout_height="match_parent"
+        android:background="@color/tab_side_indicator_color"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <ImageView
+        android:id="@+id/car_ui_toolbar_tab_item_icon"
+        android:layout_width="40dp"
+        android:layout_height="40dp"
+        android:layout_marginStart="24dp"
+        android:layout_marginEnd="24dp"
+        android:scaleType="fitCenter"
+        android:tint="@color/car_ui_toolbar_tab_item_selector"
+        android:tintMode="src_in"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/car_ui_toolbar_tab_item_text"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"/>
+
+    <TextView
+        android:id="@+id/car_ui_toolbar_tab_item_text"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:textSize="28sp"
+        android:singleLine="true"
+        app:layout_constraintStart_toEndOf="@id/car_ui_toolbar_tab_item_icon"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"/>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/values-port/values.xml b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/values-port/values.xml
new file mode 100644
index 0000000..299d726
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/values-port/values.xml
@@ -0,0 +1,19 @@
+<?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>
+    <bool name="car_ui_toolbar_tab_flexible_layout">false</bool>
+</resources>
diff --git a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/values/attrs.xml b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/values/attrs.xml
new file mode 100644
index 0000000..e06d40a
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/values/attrs.xml
@@ -0,0 +1,78 @@
+<?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>
+    <attr name="layout_constraintGuide_begin" format="dimension"/>
+    <attr name="layout_constraintGuide_end" format="dimension"/>
+    <attr name="layout_constraintGuide_percent" format="float"/>
+
+    <attr name="layout_constraintLeft_toLeftOf" format="reference|enum">
+        <enum name="parent" value="0"/>
+    </attr>
+    <attr name="layout_constraintLeft_toRightOf" format="reference|enum">
+        <enum name="parent" value="0"/>
+    </attr>
+    <attr name="layout_constraintRight_toLeftOf" format="reference|enum">
+        <enum name="parent" value="0"/>
+    </attr>
+    <attr name="layout_constraintRight_toRightOf" format="reference|enum">
+        <enum name="parent" value="0"/>
+    </attr>
+    <attr name="layout_constraintTop_toTopOf" format="reference|enum">
+        <enum name="parent" value="0"/>
+    </attr>
+    <attr name="layout_constraintTop_toBottomOf" format="reference|enum">
+        <enum name="parent" value="0"/>
+    </attr>
+    <attr name="layout_constraintBottom_toTopOf" format="reference|enum">
+        <enum name="parent" value="0"/>
+    </attr>
+    <attr name="layout_constraintBottom_toBottomOf" format="reference|enum">
+        <enum name="parent" value="0"/>
+    </attr>
+    <attr name="layout_constraintBaseline_toBaselineOf" format="reference|enum">
+        <enum name="parent" value="0"/>
+    </attr>
+    <attr name="layout_constraintStart_toEndOf" format="reference|enum">
+        <enum name="parent" value="0"/>
+    </attr>
+    <attr name="layout_constraintStart_toStartOf" format="reference|enum">
+        <enum name="parent" value="0"/>
+    </attr>
+    <attr name="layout_constraintEnd_toStartOf" format="reference|enum">
+        <enum name="parent" value="0"/>
+    </attr>
+    <attr name="layout_constraintEnd_toEndOf" format="reference|enum">
+        <enum name="parent" value="0"/>
+    </attr>
+
+    <attr name="layout_constraintHorizontal_bias" format="float"/>
+    <attr name="layout_constraintVertical_bias" format="float"/>
+
+    <attr name="layout_goneMarginLeft" format="dimension"/>
+    <attr name="layout_goneMarginTop" format="dimension"/>
+    <attr name="layout_goneMarginRight" format="dimension"/>
+    <attr name="layout_goneMarginBottom" format="dimension"/>
+    <attr name="layout_goneMarginStart" format="dimension"/>
+    <attr name="layout_goneMarginEnd" format="dimension"/>
+
+    <attr name="layout_constraintHorizontal_chainStyle" format="enum">
+        <enum name="spread" value="0"/>
+        <enum name="spread_inside" value="1"/>
+        <enum name="packed" value="2"/>
+    </attr>
+    <attr name="state_ux_restricted" format="boolean" />
+</resources>
diff --git a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/values/themes.xml b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/values/themes.xml
new file mode 100644
index 0000000..62b5c1b
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/values/themes.xml
@@ -0,0 +1,27 @@
+<!--
+  ~ 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="TextAppearance.CarUi.Widget" parent="android:TextAppearance.Material.Widget">
+        <item name="android:textAlignment">viewStart</item>
+    </style>
+
+    <style name="TextAppearance.CarUi.Widget.Toolbar"/>
+
+    <style name="TextAppearance.CarUi.Widget.Toolbar.Title">
+    <item name="android:singleLine">true</item>
+    <item name="android:textSize">32sp</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/values/values.xml b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/values/values.xml
new file mode 100644
index 0000000..c8f85cf
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/values/values.xml
@@ -0,0 +1,24 @@
+<?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>
+    <bool name="car_ui_toolbar_logo_fills_nav_icon_space">false</bool>
+    <bool name="car_ui_toolbar_tab_flexible_layout">false</bool>
+    <bool name="car_ui_scrollbar_enable">false</bool>
+
+    <dimen name="car_ui_toolbar_logo_size">44dp</dimen>
+    <dimen name="car_ui_toolbar_nav_icon_size">44dp</dimen>
+</resources>
diff --git a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/xml/overlays.xml
new file mode 100644
index 0000000..4d4a97f
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/xml/overlays.xml
@@ -0,0 +1,64 @@
+<?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.
+  -->
+<overlay>
+    <item target="layout/car_ui_base_layout_toolbar" value="@layout/car_ui_base_layout_toolbar"/>
+    <item target="layout/car_ui_toolbar_tab_item" value="@layout/car_ui_toolbar_tab_item"/>
+
+    <item target="bool/car_ui_toolbar_logo_fills_nav_icon_space" value="@bool/car_ui_toolbar_logo_fills_nav_icon_space" />
+    <item target="bool/car_ui_toolbar_tab_flexible_layout" value="@bool/car_ui_toolbar_tab_flexible_layout" />
+    <item target="bool/car_ui_scrollbar_enable" value="@bool/car_ui_scrollbar_enable" />
+
+    <item target="id/car_ui_toolbar_nav_icon_container" value="@id/car_ui_toolbar_nav_icon_container" />
+    <item target="id/car_ui_toolbar_nav_icon" value="@id/car_ui_toolbar_nav_icon" />
+    <item target="id/car_ui_toolbar_logo" value="@id/car_ui_toolbar_logo" />
+    <item target="id/car_ui_toolbar_title_logo_container" value="@id/car_ui_toolbar_title_logo_container" />
+    <item target="id/car_ui_toolbar_title_logo" value="@id/car_ui_toolbar_title_logo" />
+    <item target="id/car_ui_toolbar_title" value="@id/car_ui_toolbar_title" />
+    <item target="id/car_ui_toolbar_title_container" value="@id/car_ui_toolbar_title_container" />
+    <item target="id/car_ui_toolbar_subtitle" value="@id/car_ui_toolbar_subtitle" />
+    <item target="id/car_ui_toolbar_tabs" value="@id/car_ui_toolbar_tabs" />
+    <item target="id/car_ui_toolbar_menu_items_container" value="@id/car_ui_toolbar_menu_items_container" />
+    <item target="id/car_ui_toolbar_search_view_container" value="@id/car_ui_toolbar_search_view_container" />
+    <item target="id/car_ui_toolbar_progress_bar" value="@id/car_ui_toolbar_progress_bar" />
+    <item target="id/car_ui_base_layout_content_container" value="@id/car_ui_base_layout_content_container" />
+    <item target="id/car_ui_toolbar_tab_item_icon" value="@id/car_ui_toolbar_tab_item_icon" />
+    <item target="id/car_ui_toolbar_tab_item_text" value="@id/car_ui_toolbar_tab_item_text" />
+
+    <item target="attr/layout_constraintGuide_begin" value="@attr/layout_constraintGuide_begin"/>
+    <item target="attr/layout_constraintGuide_end" value="@attr/layout_constraintGuide_end"/>
+    <item target="attr/layout_constraintStart_toStartOf" value="@attr/layout_constraintStart_toStartOf"/>
+    <item target="attr/layout_constraintStart_toEndOf" value="@attr/layout_constraintStart_toEndOf"/>
+    <item target="attr/layout_constraintEnd_toStartOf" value="@attr/layout_constraintEnd_toStartOf"/>
+    <item target="attr/layout_constraintEnd_toEndOf" value="@attr/layout_constraintEnd_toEndOf"/>
+    <item target="attr/layout_constraintLeft_toLeftOf" value="@attr/layout_constraintLeft_toLeftOf"/>
+    <item target="attr/layout_constraintLeft_toRightOf" value="@attr/layout_constraintLeft_toRightOf"/>
+    <item target="attr/layout_constraintRight_toLeftOf" value="@attr/layout_constraintRight_toLeftOf"/>
+    <item target="attr/layout_constraintRight_toRightOf" value="@attr/layout_constraintRight_toRightOf"/>
+    <item target="attr/layout_constraintTop_toTopOf" value="@attr/layout_constraintTop_toTopOf"/>
+    <item target="attr/layout_constraintTop_toBottomOf" value="@attr/layout_constraintTop_toBottomOf"/>
+    <item target="attr/layout_constraintBottom_toTopOf" value="@attr/layout_constraintBottom_toTopOf"/>
+    <item target="attr/layout_constraintBottom_toBottomOf" value="@attr/layout_constraintBottom_toBottomOf"/>
+    <item target="attr/layout_constraintHorizontal_bias" value="@attr/layout_constraintHorizontal_bias"/>
+    <item target="attr/layout_goneMarginLeft" value="@attr/layout_goneMarginLeft"/>
+    <item target="attr/layout_goneMarginRight" value="@attr/layout_goneMarginRight"/>
+    <item target="attr/layout_goneMarginTop" value="@attr/layout_goneMarginTop"/>
+    <item target="attr/layout_goneMarginBottom" value="@attr/layout_goneMarginBottom"/>
+    <item target="attr/layout_goneMarginStart" value="@attr/layout_goneMarginStart"/>
+    <item target="attr/layout_goneMarginEnd" value="@attr/layout_goneMarginEnd"/>
+    <item target="attr/layout_constraintHorizontal_chainStyle" value="@attr/layout_constraintHorizontal_chainStyle"/>
+    <item target="attr/state_ux_restricted" value="@attr/state_ux_restricted"/>
+</overlay>
diff --git a/car_product/car_ui_portrait/rro/car_ui_portrait_rro.mk b/car_product/car_ui_portrait/rro/car_ui_portrait_rro.mk
new file mode 100644
index 0000000..5a4082b
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car_ui_portrait_rro.mk
@@ -0,0 +1,33 @@
+#
+# 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.
+#
+
+$(call inherit-product, packages/services/Car/car_product/car_ui_portrait/rro/car-ui-customizations/product.mk)
+$(call inherit-product, packages/services/Car/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/product.mk)
+
+# All RROs to be included in car_ui_portrait builds.
+PRODUCT_PACKAGES += \
+    CarEvsCameraPreviewAppRRO \
+    CarUiPortraitDialerRRO \
+    CarUiPortraitMediaRRO \
+    CarUiPortraitLauncherRRO \
+    CarUiPortraitNotificationRRO \
+    CarUiPortraitFrameworkResRRO \
+    CarUiPortraitFrameworkResRROTest
+
+ifneq ($(INCLUDE_SEAHAWK_ONLY_RROS),)
+PRODUCT_PACKAGES += \
+    CarUiPortraitSettingsProviderRRO
+endif
diff --git a/car_product/car_ui_portrait/rro/common-res/res/color/car_ui_text_color_primary.xml b/car_product/car_ui_portrait/rro/common-res/res/color/car_ui_text_color_primary.xml
new file mode 100644
index 0000000..860f219
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/common-res/res/color/car_ui_text_color_primary.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+<!-- Copy of ?android:attr/textColorPrimary (frameworks/base/res/res/color/text_color_primary.xml)
+     but with a ux restricted state. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item android:state_enabled="false"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="?android:attr/colorForeground"/>
+    <item app:state_ux_restricted="true"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="?android:attr/colorForeground"/>
+    <item android:color="?android:attr/colorForeground"/>
+</selector>
diff --git a/car_product/car_ui_portrait/tools/export_emulator.py b/car_product/car_ui_portrait/tools/export_emulator.py
new file mode 100755
index 0000000..aa75f3a
--- /dev/null
+++ b/car_product/car_ui_portrait/tools/export_emulator.py
@@ -0,0 +1,180 @@
+#!/usr/bin/env python3
+
+import subprocess
+import os
+import sys
+from shutil import copy2, copytree, rmtree
+from argparse import ArgumentParser as AP
+
+# Mostly adapted from https://cs.android.com/android/platform/superproject/+/master:device/generic/car/tools/run_local_avd.sh
+
+def fromTop(path):
+    return os.path.join(os.environ['ANDROID_BUILD_TOP'], path)
+
+def fromProductOut(path):
+    return os.path.join(os.environ['ANDROID_PRODUCT_OUT'], path)
+
+def copyImages(outputDir, abi):
+    outputDir = os.path.join(outputDir, abi)
+    os.mkdir(outputDir)
+
+    try:
+        copy2(fromProductOut('system-qemu.img'), os.path.join(outputDir, 'system.img'))
+        copy2(fromProductOut('vendor-qemu.img'), os.path.join(outputDir, 'vendor.img'))
+        if os.path.isfile(fromProductOut('kernel-ranchu-64')):
+            copy2(fromProductOut('kernel-ranchu-64'), outputDir)
+        else:
+            copy2(fromProductOut('kernel-ranchu'), outputDir)
+        copy2(fromProductOut('ramdisk-qemu.img'), os.path.join(outputDir, 'ramdisk.img'))
+        copy2(fromProductOut('encryptionkey.img'), outputDir)
+        # take prebuilt userdata.img
+        # Ref: https://cs.android.com/android/platform/superproject/+/master:development/build/sdk.atree?q=userdata.img&ss=android%2Fplatform%2Fsuperproject:development%2Fbuild%2F
+        copy2(fromTop('device/generic/goldfish/data/etc/userdata.img'), outputDir)
+        copytree(fromProductOut('data'), os.path.join(outputDir, 'data'), dirs_exist_ok=True)
+        copy2(fromProductOut('system/build.prop'), os.path.join(outputDir, 'build.prop'))
+        copy2(fromProductOut('VerifiedBootParams.textproto'), outputDir)
+        copy2(fromProductOut('config.ini'), outputDir)
+        copy2(fromProductOut('advancedFeatures.ini'), outputDir)
+    except FileNotFoundError as f:
+        print("File not found: "+f.filename+", did you build android first?")
+        sys.exit(1)
+
+def readScreenDimens(configini):
+    width = 1080
+    height = 1920
+    density = 160
+    with open(configini, 'r') as f:
+        for line in f.readlines():
+            parts = line.split(' = ')
+            if len(parts) != 2:
+                continue
+            if parts[0] == 'hw.lcd.width':
+                width = parts[1]
+            if parts[0] == 'hw.lcd.height':
+                height = parts[1]
+    return (width, height, density)
+
+def buildAVD(outputDir, abi):
+    os.makedirs(os.path.join(outputDir, '.android/avd/my_car_avd.avd/'), exist_ok=True)
+    with open(os.path.join(outputDir, '.android/avd/my_car_avd.ini'), 'w') as f:
+        f.write('avd.ini.encoding=UTF-8\n')
+        f.write('path=required_but_we_want_to_use_path.rel_instead\n')
+        f.write('path.rel=avd/my_car_avd.avd\n')
+
+    width, height, density = readScreenDimens(fromProductOut('config.ini'))
+
+    with open(os.path.join(outputDir, '.android/avd/my_car_avd.avd/config.ini'), 'w') as f:
+        f.write(f'''
+image.sysdir.1 = unused_because_passing_-sysdir_to_emulator
+hw.lcd.density = {density}
+hw.lcd.width = {width}
+hw.lcd.height = {height}
+AvdId = my_car_avd
+avd.ini.displayname = my_car_avd
+hw.ramSize = 3584
+abi.type = {abi}
+
+tag.display = Automotive
+tag.id = android-automotive
+hw.device.manufacturer = google
+hw.device.name = hawk
+avd.ini.encoding = UTF-8
+disk.dataPartition.size = 6442450944
+fastboot.chosenSnapshotFile =
+fastboot.forceChosenSnapshotBoot = no
+fastboot.forceColdBoot = no
+fastboot.forceFastBoot = yes
+hw.accelerometer = no
+hw.arc = false
+hw.audioInput = yes
+hw.battery = no
+hw.camera.back = None
+hw.camera.front = None
+hw.cpu.arch = x86_64
+hw.cpu.ncore = 4
+hw.dPad = no
+hw.device.hash2 = MD5:1fdb01985c7b4d7c19ec309cc238b0f9
+hw.gps = yes
+hw.gpu.enabled = yes
+hw.gpu.mode = auto
+hw.initialOrientation = landscape
+hw.keyboard = yes
+hw.keyboard.charmap = qwerty2
+hw.keyboard.lid = false
+hw.mainKeys = no
+hw.sdCard = no
+hw.sensors.orientation = no
+hw.sensors.proximity = no
+hw.trackBall = no
+runtime.network.latency = none
+runtime.network.speed = full
+''')
+
+def genStartScript(outputDir):
+    filepath = os.path.join(outputDir, 'start_emu.sh')
+    with open(os.open(filepath, os.O_CREAT | os.O_WRONLY, 0o750), 'w') as f:
+        f.write(f'''
+# This file is auto-generated from export_emulator.py
+OS="$(uname -s)"
+if [[ $OS == "Linux" ]]; then
+    DEFAULT_ANDROID_SDK_ROOT="$HOME/Android/Sdk"
+elif [[ $OS == "Darwin" ]]; then
+    DEFAULT_ANDROID_SDK_ROOT="/Users/$USER/Library/Android/sdk"
+else
+    echo Sorry, this does not work on $OS
+    exit
+fi
+if [[ -z $ANDROID_SDK_ROOT ]]; then
+    ANDROID_SDK_ROOT="$DEFAULT_ANDROID_SDK_ROOT"
+fi
+if ! [[ -d $ANDROID_SDK_ROOT ]]; then
+    echo Could not find android SDK root. Did you install an SDK with android studio?
+    exit
+fi
+
+# TODO: this ANDROID_EMULATOR_HOME may need to not be changed.
+# we had to change it so we could find the avd by a relative path,
+# but changing it means makes it give an "emulator is out of date"
+# warning
+
+# TODO: You shouldn't need to pass -sysdir, it should be specified
+# in the avd ini file. But I couldn't figure out how to make that work
+# with a relative path.
+
+ANDROID_EMULATOR_HOME=$(dirname $0)/.android \
+ANDROID_AVD_HOME=.android/avd \
+ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT \
+$ANDROID_SDK_ROOT/emulator/emulator \
+-avd my_car_avd -sysdir x86_64 $@
+''')
+
+
+def main():
+    parser = AP(description="Export the current build as a sharable emulator")
+    parser.add_argument('-o', '--output', default="/tmp/exported_emulator",
+                        help='Output folder. Defaults to /tmp/exported_emulator. Will wipe any existing contents!')
+    args = parser.parse_args()
+
+    if 'ANDROID_BUILD_TOP' not in os.environ or 'ANDROID_PRODUCT_OUT' not in os.environ:
+        print("Please run lunch first")
+        sys.exit(1)
+
+    if os.path.isfile(args.output):
+        print("Something already exists at "+args.output)
+        sys.exit(1)
+
+    if not os.path.isdir(os.path.dirname(args.output)):
+        print("Parent directory of "+args.output+" must already exist")
+        sys.exit(1)
+
+    rmtree(args.output, ignore_errors=True)
+    os.mkdir(args.output)
+
+    copyImages(args.output, 'x86_64')
+    buildAVD(args.output, 'x86_64')
+    genStartScript(args.output)
+    print("Done. Exported to "+args.output)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/car_product/init/init.car.rc b/car_product/init/init.car.rc
index a4880ab..4780dd4 100644
--- a/car_product/init/init.car.rc
+++ b/car_product/init/init.car.rc
@@ -1,6 +1,7 @@
 # Insert car-specific startup services here
 on post-fs-data
     mkdir /data/system/car 0700 system system
+    mkdir /data/system/car/watchdog 0700 system system
 
 # A property to enable EVS services conditionally
 on property:persist.automotive.evs.mode=0
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-af/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-af/strings.xml
index abf8fd2..6bd87c4 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-af/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-af/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Bestuurder"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"kry benaderde ligging net op die voorgrond"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Aktiveer mikrofoon"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-am/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-am/strings.xml
index 0f190c4..f73000b 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-am/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-am/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"ነጂ"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"ከፊት ለፊት ብቻ ግምታዊ አካባቢን ድረስ"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"ማይክሮፎንን አንቃ"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-ar/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-ar/strings.xml
index aa51d5d..5efe621 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-ar/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-ar/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"السائق"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"الوصول إلى الموقع الجغرافي التقريبي في الواجهة الأمامية فقط"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"تشغيل الميكروفون"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-as/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-as/strings.xml
index ea877d4..7b0fa92 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-as/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-as/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"চালক"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"কেৱল অগ্ৰভূমিত আনুমানিক অৱস্থান এক্সেছ কৰক"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"মাইক্ৰ’ফ’ন সক্ষম কৰক।"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-az/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-az/strings.xml
index 3e2ccc1..65ec685 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-az/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-az/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Sürücü"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"yalnız ön planda təqribi məkana daxil olun"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Mikrofonu aktiv edin"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-b+sr+Latn/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-b+sr+Latn/strings.xml
index a20c58e..346f0e2 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-b+sr+Latn/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-b+sr+Latn/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Vozač"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"pristup približnoj lokaciji samo u prvom planu"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Omogući mikrofon"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-be/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-be/strings.xml
index d6e8de4..dc483db 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-be/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-be/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Вадзіцель"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"доступ да прыблізнага месцазнаходжання толькі ў асноўным рэжыме"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Уключыць мікрафон"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-bg/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-bg/strings.xml
index 7a2fd23..9842971 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-bg/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-bg/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Шофьор"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"достъп до приблизителното местоположение само на преден план"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Активиране на микрофона"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-bn/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-bn/strings.xml
index e9725c8..22ee318 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-bn/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-bn/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"ড্রাইভার"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"শুধুমাত্র অ্যাপটি খোলা থাকলে আপনার আনুমানিক লোকেশন অ্যাক্সেস করা"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"মাইক্রোফোন চালু করুন"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-bs/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-bs/strings.xml
index a20c58e..346f0e2 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-bs/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-bs/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Vozač"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"pristup približnoj lokaciji samo u prvom planu"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Omogući mikrofon"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-ca/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-ca/strings.xml
index 8d24e22..e1a2044 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-ca/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-ca/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Conductor"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"accedeix a la ubicació aproximada només en primer pla"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Activa el micròfon"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-cs/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-cs/strings.xml
index 2432df2..ce005f1 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-cs/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-cs/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Řidič"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"přístup k přibližné poloze jen na popředí"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Aktivovat mikrofon"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-da/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-da/strings.xml
index 731f251..6db644f 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-da/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-da/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Chauffør"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"få kun adgang til omtrentlig lokation i forgrunden"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Aktivér mikrofon"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-de/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-de/strings.xml
index 9febde6..ba314a7 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-de/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-de/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Fahrer"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"Nur bei Ausführung im Vordergrund auf den ungefähren Standort zugreifen"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Mikrofon aktivieren"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-el/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-el/strings.xml
index fcfe739..4eca60d 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-el/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-el/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Οδηγός"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"πρόσβαση στην κατά προσέγγιση τοποθεσία μόνο στο προσκήνιο"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Ενεργοποίηση μικροφώνου"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-en-rAU/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-en-rAU/strings.xml
index 85c4908..91594b0 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-en-rAU/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-en-rAU/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Driver"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"access approximate location only in the foreground"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Enable microphone"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-en-rCA/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-en-rCA/strings.xml
index 85c4908..91594b0 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-en-rCA/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-en-rCA/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Driver"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"access approximate location only in the foreground"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Enable microphone"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-en-rGB/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-en-rGB/strings.xml
index 85c4908..91594b0 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-en-rGB/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-en-rGB/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Driver"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"access approximate location only in the foreground"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Enable microphone"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-en-rIN/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-en-rIN/strings.xml
index 85c4908..91594b0 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-en-rIN/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-en-rIN/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Driver"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"access approximate location only in the foreground"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Enable microphone"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-en-rXC/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-en-rXC/strings.xml
index 7778984..3e208b3 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-en-rXC/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-en-rXC/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‏‎‏‎‎‎‎‏‏‏‏‎‎‏‎‎‎‎‏‎‎‎‏‏‎‎‏‎‎‎‏‏‎‎‏‎‏‏‏‎‎‎‏‏‎‏‏‎‏‏‎‏‎‎‎Driver‎‏‎‎‏‎"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‏‏‏‎‏‎‏‏‎‎‎‏‏‎‏‏‎‎‎‎‏‎‏‎‏‎access approximate location only in the foreground‎‏‎‎‏‎"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‎‏‏‎‏‏‎‎‎‏‏‏‏‎‎‏‎‏‎‏‏‏‏‏‎‎‎‏‏‏‏‎‏‎‏‏‎‏‎‎‏‎‏‏‏‏‎‎‏‏‏‏‏‎Enable Microphone‎‏‎‎‏‎"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-es-rUS/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-es-rUS/strings.xml
index 5666331..ced0bde 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-es-rUS/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-es-rUS/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Conductor"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"acceder a la ubicación aproximada solo en primer plano"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Habilitar micrófono"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-es/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-es/strings.xml
index 3e3ad48..af83a18 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-es/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-es/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Conductor"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"acceder a la ubicación aproximada solo al estar en primer plano"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Habilitar micrófono"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-et/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-et/strings.xml
index 27ae02b..1876c65 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-et/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-et/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Sõitja"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"juurdepääs ligikaudsele asukohale ainult esiplaanil"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Luba mikrofon"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-eu/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-eu/strings.xml
index f7e62cf..0e06b76 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-eu/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-eu/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Gidaria"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"atzitu gutxi gorabeherako kokapena aurreko planoan bakarrik"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Gaitu mikrofonoa"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-fa/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-fa/strings.xml
index 4d257d6..4c9f73a 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-fa/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-fa/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"راننده"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"دسترسی به مکان تقریبی فقط در پیش‌زمینه"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"فعال کردن میکروفن"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-fi/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-fi/strings.xml
index b2d5135..ce5daf3 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-fi/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-fi/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Kuljettaja"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"käyttää likimääräistä sijaintia vain etualalla"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Laita mikrofoni päälle"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-fr-rCA/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-fr-rCA/strings.xml
index 2ac4be7..eb83b85 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-fr-rCA/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-fr-rCA/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Conducteur"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"accéder à votre position approximative seulement en avant-plan"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Activer le microphone"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-fr/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-fr/strings.xml
index 81af986..02b7a24 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-fr/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-fr/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Conducteur"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"accéder à la position approximative au premier plan uniquement"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Activer le micro"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-gl/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-gl/strings.xml
index 3754442..30b25d4 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-gl/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-gl/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Condutor"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"acceder á localización aproximada só en primeiro plano"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Activar micrófono"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-gu/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-gu/strings.xml
index 5c6ff84..e157fd3 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-gu/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-gu/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"ડ્રાઇવર"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"ફૉરગ્રાઉન્ડમાં ફક્ત અંદાજિત સ્થાન ઍક્સેસ કરો"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"માઇક્રોફોન ચાલુ કરો"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-hi/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-hi/strings.xml
index 003478d..a5ac0f3 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-hi/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-hi/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"ड्राइवर"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"अनुमानित जगह की जानकारी सिर्फ़ तब ऐक्सेस करें, जब ऐप्लिकेशन स्क्रीन पर खुला हो"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"माइक्रोफ़ोन चालू करें"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-hr/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-hr/strings.xml
index 4cffd31..0ac52d4 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-hr/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-hr/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Vozač"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"pristupiti približnoj lokaciji samo u prednjem planu"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Omogući mikrofon"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-hu/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-hu/strings.xml
index 52b7760..778f57c 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-hu/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-hu/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Sofőr"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"megközelítőleges helyadatokhoz való hozzáférés csak előtérben"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Mikrofonhoz való hozzáférés engedélyezése"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-hy/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-hy/strings.xml
index 68df140..0dc5851 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-hy/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-hy/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Վարորդ"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"տեղադրության մոտավոր տվյալների հասանելիություն միայն ֆոնային ռեժիմում"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Միացնել խոսափողը"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-in/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-in/strings.xml
index 21c21a8..52cda98 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-in/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-in/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Pengemudi"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"akses perkiraan lokasi hanya saat di latar depan"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Aktifkan Mikrofon"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-is/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-is/strings.xml
index 1eb3776..b922593 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-is/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-is/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Ökumaður"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"aðgangur að áætlaðri staðsetningu aðeins í forgrunni"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Kveikja á hljóðnema"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-it/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-it/strings.xml
index c704544..10c4836 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-it/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-it/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Autista"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"Accesso alla posizione approssimativa solo in primo piano"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Attiva il microfono"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-iw/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-iw/strings.xml
index a8d3bee..657ebf2 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-iw/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-iw/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"נהג/ת"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"קבלת גישה למיקום משוער בחזית בלבד"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"הפעלת המיקרופון"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-ja/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-ja/strings.xml
index eb41c01..6da0c2c 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-ja/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-ja/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"ドライバー"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"フォアグラウンドでのみおおよその位置情報を取得"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"マイクを有効にする"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-ka/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-ka/strings.xml
index 4d26423..7fe766c 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-ka/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-ka/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"მძღოლი"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"მიახლოებით მდებარეობაზე წვდომა მხოლოდ წინა პლანზე"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"მიკროფონის ჩართვა"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-kk/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-kk/strings.xml
index b12a6d5..36195f0 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-kk/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-kk/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Көлік жүргізуші"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"болжалды орналасқан жер туралы ақпаратқа тек ашық экранда кіру"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Микрофонды қосу"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-km/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-km/strings.xml
index 4975540..1a74261 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-km/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-km/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"អ្នក​បើកបរ"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"ចូលប្រើ​ទីតាំង​ប្រហាក់ប្រហែល​តែនៅផ្ទៃ​ខាងមុខប៉ុណ្ណោះ"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"បើក​មីក្រូហ្វូន"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-kn/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-kn/strings.xml
index 27e9a9e..ecfa406 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-kn/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-kn/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"ಡ್ರೈವರ್"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"ಮುನ್ನೆಲೆಯಲ್ಲಿ ಮಾತ್ರ ಅಂದಾಜು ಸ್ಥಳವನ್ನು ಪ್ರವೇಶಿಸಿ"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"ಮೈಕ್ರೋಫೋನ್ ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-ko/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-ko/strings.xml
index 20facb1..52c715d 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-ko/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-ko/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"운전자"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"포그라운드에서만 대략적인 위치에 액세스"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"마이크 사용"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-ky/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-ky/strings.xml
index 1a43c49..e062223 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-ky/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-ky/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Айдоочу"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"болжолдуу аныкталган жайгашкан жерге активдүү режимде гана кирүүгө уруксат берүү"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Микрофонду иштетүү"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-lo/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-lo/strings.xml
index 877a259..fabaa3d 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-lo/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-lo/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"ຄົນຂັບລົດ"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"ເຂົ້າເຖິງສະຖານທີ່ໂດຍປະມານເມື່ອຢູ່ໃນພື້ນໜ້າເທົ່ານັ້ນ"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"ເປີດການນຳໃຊ້ໄມໂຄຣໂຟນ"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-lt/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-lt/strings.xml
index 134230f..a02befb 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-lt/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-lt/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Vairuotojas"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"pasiekti apytikslę vietovę, tik kai programa veikia priekiniame plane"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Įgalinti mikrofoną"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-lv/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-lv/strings.xml
index 3cc5c88..333add2 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-lv/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-lv/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Vadītājs"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"piekļuve aptuvenai atrašanās vietai, tikai darbojoties priekšplānā"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Iespējot mikrofonu"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-mk/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-mk/strings.xml
index 3b1eda7..ed49dfe 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-mk/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-mk/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Возач"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"пристап до приближната локација само во преден план"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Овозможи го микрофонот"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-ml/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-ml/strings.xml
index 7205182..a69ea5d 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-ml/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-ml/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"ഡ്രൈവർ"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"ഏകദേശ ലൊക്കേഷൻ ഫോർഗ്രൗണ്ടിൽ മാത്രം ആക്‌സസ് ചെയ്യുക"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"മൈക്രോഫോൺ പ്രവർത്തനക്ഷമമാക്കുക"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-mn/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-mn/strings.xml
index dd1d6a2..5b3d6fb 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-mn/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-mn/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Жолооч"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"ойролцоо байршилд зөвхөн дэлгэц дээр хандах"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Микрофоныг идэвхжүүлэх"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-mr/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-mr/strings.xml
index a0619ec..30efa52 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-mr/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-mr/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"ड्रायव्हर"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"फक्त फोअरग्राउंडमध्ये अंदाजे स्थान अ‍ॅक्सेस करा"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"मायक्रोफोन सुरू करा"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-ms/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-ms/strings.xml
index 7f22021..6e830f7 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-ms/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-ms/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Pemandu"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"akses lokasi anggaran hanya di latar depan"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Dayakan Mikrofon"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-my/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-my/strings.xml
index aea1568..43c3d37 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-my/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-my/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"ယာဉ်မောင်းသူ"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"မျက်နှာစာတွင်သာ ခန့်မှန်းခြေ တည်နေရာ အသုံးပြုခြင်း"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"မိုက်ခရိုဖုန်း ဖွင့်ရန်"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-nb/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-nb/strings.xml
index 6e0de84..cbc2918 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-nb/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-nb/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Sjåfør"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"bare tilgang til omtrentlig posisjon i forgrunnen"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Slå på mikrofonen"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-ne/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-ne/strings.xml
index 76ca044..eda9747 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-ne/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-ne/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"चालक"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"अग्रभूमिमा मात्र अनुमानित स्थानमाथि पहुँच राख्नुहोस्"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"माइक्रोफोन अन गर्नुहोस्"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-nl/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-nl/strings.xml
index 873fb01..d79cba2 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-nl/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-nl/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Chauffeur"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"alleen toegang tot geschatte locatie op de voorgrond"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Microfoon aanzetten"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-or/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-or/strings.xml
index 964683f..939b105 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-or/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-or/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"ଡ୍ରାଇଭର୍"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"କେବଳ ଫୋର୍‌ଗ୍ରାଉଣ୍ଡରେ ହାରାହାରି ଲୋକେସନ୍ ଆକ୍ସେସ୍ କରନ୍ତୁ"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"ମାଇକ୍ରୋଫୋନକୁ ସକ୍ଷମ କରନ୍ତୁ"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-pa/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-pa/strings.xml
index 16541d3..2a2e55e 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-pa/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-pa/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"ਡਰਾਈਵਰ"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"ਸਿਰਫ਼ ਫੋਰਗ੍ਰਾਊਂਡ ਵਿੱਚ ਅਨੁਮਾਨਿਤ ਟਿਕਾਣੇ ਤੱਕ ਪਹੁੰਚ ਕਰੋ"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਨੂੰ ਚਾਲੂ ਕਰੋ"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-pl/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-pl/strings.xml
index d0ff092..32fe8a4 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-pl/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-pl/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Kierowca"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"dostęp do przybliżonej lokalizacji tylko na pierwszym planie"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Włącz mikrofon"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-pt-rPT/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-pt-rPT/strings.xml
index 984000a..016617e 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-pt-rPT/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-pt-rPT/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Condutor"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"apenas aceder à localização aproximada em primeiro plano"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Ativar microfone"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-pt/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-pt/strings.xml
index 7ac9eef..efca2bf 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-pt/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-pt/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Motorista"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"acessar local aproximado apenas em primeiro plano"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Ativar microfone"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-ro/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-ro/strings.xml
index ec78db2..adf01f0 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-ro/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-ro/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Șofer"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"să acceseze locația aproximativă numai în prim-plan"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Activați microfonul"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-ru/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-ru/strings.xml
index ed49d76..12a3f43 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-ru/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-ru/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Водитель"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"Доступ к приблизительному местоположению только в активном режиме"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Включить"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-si/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-si/strings.xml
index fcba27e..9d75b4f 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-si/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-si/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"රියදුරු"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"පෙරබිම තුළ පමණක් ආසන්න ස්ථානයට ප්‍රවේශය"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"මයික්‍රෆෝනය සබල කරන්න"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-sk/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-sk/strings.xml
index c9a990d..125cb5a 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-sk/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-sk/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Vodič"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"prístup k približnej polohe iba v popredí"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Aktivovať mikrofón"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-sl/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-sl/strings.xml
index 918dda4..52841ab 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-sl/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-sl/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Voznik"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"dostop do približne lokacije samo, ko deluje v ospredju"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Omogoči mikrofon"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-sq/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-sq/strings.xml
index e4192e7..dc7bbe2 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-sq/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-sq/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Drejtuesi"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"qasu në vendndodhjen e përafërt vetëm në plan të parë"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Aktivizo mikrofonin"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-sr/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-sr/strings.xml
index 8cce889..2cfe36e 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-sr/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-sr/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Возач"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"приступ приближној локацији само у првом плану"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Омогући микрофон"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-sv/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-sv/strings.xml
index b5b4f63..1aa7c0c 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-sv/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-sv/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Förare"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"endast åtkomst till ungefärlig plats i förgrunden"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Aktivera mikrofon"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-sw/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-sw/strings.xml
index 765973c..60e5469 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-sw/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-sw/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Dereva"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"kufikia mahali palipokadiriwa ikiwa tu programu imefunguliwa kwenye skrini"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Washa Maikrofoni"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-ta/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-ta/strings.xml
index 264f0c1..38a2e03 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-ta/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-ta/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"கார் உரிமையாளர்"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"முன்புலத்தில் இயங்கும்போது மட்டும் தோராயமான இருப்பிடத்தைக் கண்டறிதல்"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"மைக்ரோஃபோனை இயக்கு"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-te/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-te/strings.xml
index bf45f96..c95285e 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-te/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-te/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"డ్రైవర్"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"స్క్రీన్‌పై ఉన్నప్పుడు మాత్రమే సమీప లొకేషన్‌ను యాక్సెస్ చేయండి"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"మైక్రోఫోన్‌ను ఎనేబుల్ చేయండి"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-th/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-th/strings.xml
index 8fae2ca..6f617e3 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-th/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-th/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"ผู้ขับรถ"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"เข้าถึงตำแหน่งโดยประมาณเมื่ออยู่เบื้องหน้าเท่านั้น"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"เปิดใช้ไมโครโฟน"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-tl/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-tl/strings.xml
index d425a87..c42b2c8 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-tl/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-tl/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Driver"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"i-access lang ang tinatantyang lokasyon sa foreground"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"I-enable ang Mikropono"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-tr/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-tr/strings.xml
index 244564e..0a13258 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-tr/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-tr/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Sürücü"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"yalnızca ön planda yaklaşık konuma erişme"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Mikrofonu Etkinleştir"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-uk/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-uk/strings.xml
index b46d679..c15df06 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-uk/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-uk/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Водій"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"отримувати доступ до даних про приблизне місцезнаходження лише в активному режимі"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Увімкнути мікрофон"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-ur/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-ur/strings.xml
index 89fd655..d3cea05 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-ur/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-ur/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"ڈرائیور"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"صرف پیش منظر میں تخمینی مقام تک رسائی"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"مائیکروفون فعال کریں"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-uz/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-uz/strings.xml
index 9914c10..d15ff7c 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-uz/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-uz/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Haydovchi"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"taxminiy joylashuv axborotini olishga faqat old fonda ruxsat"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Mikrofonni yoqish"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-vi/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-vi/strings.xml
index d478052..1e19523 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-vi/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-vi/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Tài xế"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"chỉ truy cập thông tin vị trí gần đúng khi ứng dụng mở trên màn hình"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Cấp quyền truy cập micrô"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-zh-rCN/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-zh-rCN/strings.xml
index 06fe50d..6556c7b 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-zh-rCN/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-zh-rCN/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"司机"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"只有在前台运行时才能获取大致位置信息"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"启用麦克风"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-zh-rHK/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-zh-rHK/strings.xml
index fd93b30..0d8543b 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-zh-rHK/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-zh-rHK/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"司機"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"只在前景存取概略位置"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"啟用麥克風"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-zh-rTW/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-zh-rTW/strings.xml
index 59f09e1..7340ecd 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-zh-rTW/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-zh-rTW/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"駕駛"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"僅可在前景中取得概略位置"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"啟用麥克風"</string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-zu/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-zu/strings.xml
index adb9402..c99c505 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-zu/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-zu/strings.xml
@@ -19,4 +19,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="owner_name" msgid="3416113395996003764">"Umshayeli"</string>
     <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"finyelela indawo enembile kuphela engaphambili"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Nika amandla Imakrofoni"</string>
 </resources>
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 d7c566c..a1b0cd2 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
@@ -151,4 +151,9 @@
     <!-- Whether this device is supporting the microphone toggle -->
     <bool name="config_supportsMicToggle">true</bool>
 
+    <!-- Whether the airplane mode should be reset when device boots in non-safemode after exiting
+     from safemode.
+     This flag should be enabled only when the product does not have any UI to toggle airplane
+     mode like automotive devices.-->
+    <bool name="config_autoResetAirplaneMode">true</bool>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values/policy_exempt_apps.xml b/car_product/overlay/frameworks/base/core/res/res/values/policy_exempt_apps.xml
index 9ab0ed3..27986a4 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values/policy_exempt_apps.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values/policy_exempt_apps.xml
@@ -20,7 +20,6 @@
     device policies or APIs.
     -->
     <string-array translatable="false" name="policy_exempt_apps">
-        <item>com.android.car.carlauncher</item>
         <item>com.android.car.cluster.home</item>
         <item>com.android.car.hvac</item>
         <item>com.android.car.media</item>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values/strings.xml
index 34fd92f..eff76d4 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values/strings.xml
@@ -19,4 +19,6 @@
     <!-- Default name of the owner user [CHAR LIMIT=20] -->
     <string name="owner_name">Driver</string>
     <string name="permlab_accessCoarseLocation">access approximate location only in the foreground</string>
+    <!--- Action button in the dialog triggered if microphone is disabled but an app tried to access it. [CHAR LIMIT=60] -->
+    <string name="sensor_privacy_start_use_dialog_turn_on_button">Enable Microphone</string>
 </resources>
diff --git a/car_product/sepolicy/private/carservice_app.te b/car_product/sepolicy/private/carservice_app.te
index 04a5808..87fd0b7 100644
--- a/car_product/sepolicy/private/carservice_app.te
+++ b/car_product/sepolicy/private/carservice_app.te
@@ -103,3 +103,6 @@
 allow carservice_app gpu_device:dir r_dir_perms;
 allow carservice_app gpu_service:service_manager find;
 binder_call(carservice_app, gpuservice)
+
+# Allow reading and writing /proc/loadavg/
+allow carservice_app proc_loadavg:file { open read getattr };
diff --git a/cpp/evs/manager/1.1/VirtualCamera.cpp b/cpp/evs/manager/1.1/VirtualCamera.cpp
index a7e6329..3b0ef95 100644
--- a/cpp/evs/manager/1.1/VirtualCamera.cpp
+++ b/cpp/evs/manager/1.1/VirtualCamera.cpp
@@ -399,6 +399,9 @@
                             if (pHwCamera == nullptr) {
                                 continue;
                             }
+                            if (mFramesHeld[key].size() == 0) {
+                                continue;
+                            }
 
                             const auto frame = mFramesHeld[key].back();
                             if (frame.timestamp > lastFrameTimestamp) {
diff --git a/cpp/security/vehicle_binding_util/Android.bp b/cpp/security/vehicle_binding_util/Android.bp
new file mode 100644
index 0000000..3f235b1
--- /dev/null
+++ b/cpp/security/vehicle_binding_util/Android.bp
@@ -0,0 +1,84 @@
+// 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"],
+}
+
+cc_defaults {
+    name: "vehicle_binding_util_defaults",
+    cflags: [
+        "-Wall",
+        "-Wno-missing-field-initializers",
+        "-Werror",
+        "-Wno-unused-variable",
+    ],
+    shared_libs: [
+        "android.hardware.automotive.vehicle@2.0",
+        "libbase",
+        "libbinder",
+        "libcutils",
+        "libhidlbase",
+        "liblog",
+        "liblogwrap",
+        "libutils",
+    ],
+    static_libs: [
+        "libbase",
+    ],
+}
+
+cc_library_static {
+    name: "libvehicle_binding_util",
+    srcs: [
+        "src/VehicleBindingUtil.cpp",
+    ],
+    defaults: [
+        "vehicle_binding_util_defaults",
+    ],
+    export_include_dirs: [
+        "src",
+    ],
+}
+
+cc_test {
+    name: "libvehicle_binding_util_test",
+    defaults: [
+        "vehicle_binding_util_defaults",
+    ],
+    test_suites: ["general-tests"],
+    srcs: [
+        "tests/VehicleBindingUtilTests.cpp",
+    ],
+    static_libs: [
+        "libbase",
+        "libgmock",
+        "libgtest",
+        "libvehicle_binding_util",
+    ],
+}
+
+cc_binary {
+    name: "vehicle_binding_util",
+    defaults: [
+        "vehicle_binding_util_defaults",
+    ],
+    srcs: [
+        "src/main.cpp",
+    ],
+    init_rc: ["vehicle_binding_util.rc"],
+    static_libs: [
+        "libvehicle_binding_util",
+    ],
+}
diff --git a/cpp/security/vehicle_binding_util/src/VehicleBindingUtil.cpp b/cpp/security/vehicle_binding_util/src/VehicleBindingUtil.cpp
new file mode 100644
index 0000000..c15bd3c
--- /dev/null
+++ b/cpp/security/vehicle_binding_util/src/VehicleBindingUtil.cpp
@@ -0,0 +1,197 @@
+/*
+ * 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 "VehicleBindingUtil.h"
+
+#include <android-base/logging.h>
+#include <android/hardware/automotive/vehicle/2.0/types.h>
+#include <cutils/properties.h>  // for property_get
+#include <logwrap/logwrap.h>
+#include <utils/SystemClock.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/random.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <vector>
+
+namespace android {
+namespace automotive {
+namespace security {
+namespace {
+
+using android::hardware::automotive::vehicle::V2_0::IVehicle;
+using android::hardware::automotive::vehicle::V2_0::StatusCode;
+using android::hardware::automotive::vehicle::V2_0::VehicleArea;
+using android::hardware::automotive::vehicle::V2_0::VehiclePropConfig;
+using android::hardware::automotive::vehicle::V2_0::VehicleProperty;
+using android::hardware::automotive::vehicle::V2_0::VehiclePropertyStatus;
+using android::hardware::automotive::vehicle::V2_0::VehiclePropValue;
+
+template <typename T>
+using hidl_vec = android::hardware::hidl_vec<T>;
+
+bool isSeedVhalPropertySupported(sp<IVehicle> vehicle) {
+    bool is_supported = false;
+
+    hidl_vec<int32_t> props = {
+            static_cast<int32_t>(VehicleProperty::STORAGE_ENCRYPTION_BINDING_SEED)};
+    vehicle->getPropConfigs(props,
+                            [&is_supported](StatusCode status,
+                                            hidl_vec<VehiclePropConfig> /*propConfigs*/) {
+                                is_supported = (status == StatusCode::OK);
+                            });
+    return is_supported;
+}
+
+std::string toHexString(const std::vector<uint8_t>& bytes) {
+    const char lookup[] = "0123456789abcdef";
+    std::string out;
+    out.reserve(bytes.size() * 2);
+    for (auto b : bytes) {
+        out += lookup[b >> 4];
+        out += lookup[b & 0xf];
+    }
+    return out;
+}
+
+BindingStatus setSeedVhalProperty(sp<IVehicle> vehicle, const std::vector<uint8_t>& seed) {
+    VehiclePropValue propValue;
+    propValue.timestamp = elapsedRealtimeNano();
+    propValue.areaId = toInt(VehicleArea::GLOBAL);
+    propValue.prop = toInt(VehicleProperty::STORAGE_ENCRYPTION_BINDING_SEED);
+    propValue.status = VehiclePropertyStatus::AVAILABLE;
+    propValue.value.bytes = seed;
+    StatusCode vhal_status = vehicle->set(propValue);
+    if (vhal_status == StatusCode::OK) {
+        return BindingStatus::OK;
+    }
+
+    LOG(ERROR) << "Unable to set the VHAL property: " << toString(vhal_status);
+    return BindingStatus::ERROR;
+}
+
+BindingStatus getSeedVhalProperty(sp<IVehicle> vehicle, std::vector<uint8_t>* seed) {
+    VehiclePropValue desired_prop;
+    desired_prop.prop = static_cast<int32_t>(VehicleProperty::STORAGE_ENCRYPTION_BINDING_SEED);
+    BindingStatus status = BindingStatus::ERROR;
+    vehicle->get(desired_prop,
+                 [&status, &seed](StatusCode prop_status, const VehiclePropValue& propValue) {
+                     if (prop_status != StatusCode::OK) {
+                         LOG(ERROR) << "Error reading vehicle property: " << toString(prop_status);
+                     } else {
+                         status = BindingStatus::OK;
+                         *seed = std::vector<uint8_t>{propValue.value.bytes.begin(),
+                                                      propValue.value.bytes.end()};
+                     }
+                 });
+
+    return status;
+}
+
+BindingStatus sendSeedToVold(const Executor& executor, const std::vector<uint8_t>& seed) {
+    int status = 0;
+
+    // we pass the seed value via environment variable in the forked process
+    setenv("SEED_VALUE", toHexString(seed).c_str(), 1);
+    int rc = executor.run({"/system/bin/vdc", "cryptfs", "bindkeys"}, &status);
+    unsetenv("SEED_VALUE");
+    LOG(INFO) << "rc: " << rc;
+    LOG(INFO) << "status: " << status;
+    if (rc != 0 || status != 0) {
+        LOG(ERROR) << "Error running vdc: " << rc << ", " << status;
+        return BindingStatus::ERROR;
+    }
+    return BindingStatus::OK;
+}
+
+}  // namespace
+
+bool DefaultCsrng::fill(void* buffer, size_t size) const {
+    int fd = TEMP_FAILURE_RETRY(open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOFOLLOW));
+    if (fd == -1) {
+        LOG(ERROR) << "Error opening urandom: " << errno;
+        return false;
+    }
+
+    ssize_t bytes_read;
+    uint8_t* bufptr = static_cast<uint8_t*>(buffer);
+    while ((bytes_read = TEMP_FAILURE_RETRY(read(fd, bufptr, size))) > 0) {
+        size -= bytes_read;
+        bufptr += bytes_read;
+    }
+
+    close(fd);
+
+    if (size != 0) {
+        LOG(ERROR) << "Unable to read " << size << " bytes from urandom";
+        return false;
+    }
+    return true;
+}
+
+int DefaultExecutor::run(const std::vector<std::string>& cmd_args, int* exit_code) const {
+    std::vector<const char*> argv;
+    argv.reserve(cmd_args.size());
+    for (auto& arg : cmd_args) {
+        argv.push_back(arg.c_str());
+    }
+    int status = 0;
+    return logwrap_fork_execvp(argv.size(), argv.data(), exit_code, false /*forward_signals*/,
+                               LOG_KLOG, true /*abbreviated*/, nullptr /*file_path*/);
+}
+
+BindingStatus setVehicleBindingSeed(sp<IVehicle> vehicle, const Executor& executor,
+                                    const Csrng& csrng) {
+    if (!isSeedVhalPropertySupported(vehicle)) {
+        LOG(WARNING) << "Vehicle binding seed is not supported by the VHAL.";
+        return BindingStatus::NOT_SUPPORTED;
+    }
+
+    std::vector<uint8_t> seed;
+    BindingStatus status = getSeedVhalProperty(vehicle, &seed);
+    if (status != BindingStatus::OK) {
+        LOG(ERROR) << "Unable to read the seed from the VHAL: " << static_cast<int>(status);
+        return status;
+    }
+
+    if (seed.empty()) {
+        seed = std::vector<uint8_t>(SEED_BYTE_SIZE);
+        if (!csrng.fill(seed.data(), seed.size())) {
+            LOG(ERROR) << "Error getting random seed: " << static_cast<int>(status);
+            return BindingStatus::ERROR;
+        }
+
+        status = setSeedVhalProperty(vehicle, seed);
+        if (status != BindingStatus::OK) {
+            LOG(ERROR) << "Error storing the seed in the VHAL: " << static_cast<int>(status);
+            return status;
+        }
+    }
+
+    status = sendSeedToVold(executor, seed);
+    if (status == BindingStatus::OK) {
+        LOG(INFO) << "Successfully bound vehicle storage to seed.";
+    }
+    return status;
+}
+
+}  // namespace security
+}  // namespace automotive
+}  // namespace android
diff --git a/cpp/security/vehicle_binding_util/src/VehicleBindingUtil.h b/cpp/security/vehicle_binding_util/src/VehicleBindingUtil.h
new file mode 100644
index 0000000..3fa89ec
--- /dev/null
+++ b/cpp/security/vehicle_binding_util/src/VehicleBindingUtil.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef CPP_SECURITY_VEHICLE_BINDING_UTIL_SRC_VEHICLEBINDINGUTIL_H_
+#define CPP_SECURITY_VEHICLE_BINDING_UTIL_SRC_VEHICLEBINDINGUTIL_H_
+
+#include "android/hardware/automotive/vehicle/2.0/types.h"
+
+#include <android/hardware/automotive/vehicle/2.0/IVehicle.h>
+#include <utils/StrongPointer.h>
+
+#include <cstdint>
+#include <vector>
+
+namespace android {
+namespace automotive {
+namespace security {
+
+constexpr size_t SEED_BYTE_SIZE = 16;
+
+// Possible results of attempting to set the vehicle binding seed.
+enum class BindingStatus {
+    OK,
+    NOT_SUPPORTED,
+    ERROR,
+};
+
+template <typename EnumT>
+constexpr auto toInt(const EnumT value) {
+    return static_cast<typename std::underlying_type<EnumT>::type>(value);
+}
+
+// Interface for getting cryptographically secure random byte strings
+class Csrng {
+public:
+    virtual ~Csrng() = default;
+
+    // Fill the given buffer with random bytes. Returns false if there is
+    // an unrecoverable error getting bits.
+    virtual bool fill(void* buffer, size_t size) const = 0;
+};
+
+// Csrng that relies on `/dev/urandom` to supply bits. We have to rely on
+// urandom so that we don't block boot-up. Devices that wish to supply very
+// high-quality random bits at boot should seed the linux PRNG at boot with
+// entropy.
+class DefaultCsrng : public Csrng {
+public:
+    bool fill(void* buffer, size_t size) const override;
+};
+
+// Interface for forking and executing a child process.
+class Executor {
+public:
+    virtual ~Executor() = default;
+
+    // Run the given command line and its arguments. Returns 0 on success, -1
+    // if an internal error occurred, and -ECHILD if the child process did not
+    // exit properly.
+    //
+    // On exit, `exit_code` is set to the child's exit status.
+    virtual int run(const std::vector<std::string>& cmd_args, int* exit_code) const = 0;
+};
+
+// Default Executor which forks, execs, and logs output from the child process.
+class DefaultExecutor : public Executor {
+    int run(const std::vector<std::string>& cmd_args, int* exit_code) const override;
+};
+
+// Set the seed in vold that is used to bind the encryption keys to the vehicle.
+// This is used to guard against headunit removal and subsequent scraping of
+// the filesystem for sensitive data (e.g. PII).
+//
+// The seed is read from the VHAL property STORAGE_ENCRYPTION_BINDING_SEED. If
+// the property has not yet been set, a random byte value is generated and
+// saved in the VHAL for reuse on future boots.
+BindingStatus setVehicleBindingSeed(
+        sp<::android::hardware::automotive::vehicle::V2_0::IVehicle> vehicle,
+        const Executor& executor, const Csrng& csrng);
+
+}  // namespace security
+}  // namespace automotive
+}  // namespace android
+
+#endif  // CPP_SECURITY_VEHICLE_BINDING_UTIL_SRC_VEHICLEBINDINGUTIL_H_
diff --git a/cpp/security/vehicle_binding_util/src/main.cpp b/cpp/security/vehicle_binding_util/src/main.cpp
new file mode 100644
index 0000000..cd82017
--- /dev/null
+++ b/cpp/security/vehicle_binding_util/src/main.cpp
@@ -0,0 +1,86 @@
+/*
+ * 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 "VehicleBindingUtil.h"
+
+#include <android-base/logging.h>
+#include <android/hardware/automotive/vehicle/2.0/IVehicle.h>
+#include <binder/IServiceManager.h>
+#include <binder/Status.h>
+
+#include <iostream>
+#include <map>
+#include <string>
+
+namespace {
+
+using android::defaultServiceManager;
+using android::automotive::security::BindingStatus;
+using android::automotive::security::DefaultCsrng;
+using android::automotive::security::DefaultExecutor;
+using android::hardware::automotive::vehicle::V2_0::IVehicle;
+
+static int printHelp(int argc, char* argv[]);
+static int setBinding(int /*argc*/, char*[] /*argv*/);
+
+// Avoid calling complex destructor on cleanup.
+const auto& subcommandTable = *new std::map<std::string, std::function<int(int, char*[])>>{
+        {"help", printHelp},
+        {"set_binding", setBinding},
+};
+
+static int setBinding(int /*argc*/, char*[] /*argv*/) {
+    auto status = setVehicleBindingSeed(IVehicle::getService(), DefaultExecutor{}, DefaultCsrng{});
+    if (status != BindingStatus::OK) {
+        LOG(ERROR) << "Unable to set the binding seed. Encryption keys are not "
+                   << "bound to the platform.";
+        return static_cast<int>(status);
+    }
+
+    return 0;
+}
+
+static int printHelp(int /*argc*/, char* argv[]) {
+    std::cout << "Usage: " << argv[0] << " <subcommand> [args]" << std::endl
+              << "Valid subcommands: " << std::endl;
+    for (const auto& i : subcommandTable) {
+        std::cout << "    " << i.first << std::endl;
+    }
+    return 0;
+}
+
+}  // namespace
+
+int main(int argc, char* argv[]) {
+    setenv("ANDROID_LOG_TAGS", "*:v", 1);
+    android::base::InitLogging(argv,
+                               (getppid() == 1) ? &android::base::KernelLogger
+                                                : &android::base::StderrLogger);
+    if (argc < 2) {
+        LOG(ERROR) << "Please specify a subcommand.";
+        printHelp(argc, argv);
+        return -1;
+    }
+
+    auto subcommand = subcommandTable.find(argv[1]);
+    if (subcommand == subcommandTable.end()) {
+        LOG(ERROR) << "Invalid subcommand: " << argv[1];
+        printHelp(argc, argv);
+        return -1;
+    }
+
+    return subcommand->second(argc, argv);
+}
diff --git a/cpp/security/vehicle_binding_util/tests/VehicleBindingUtilTests.cpp b/cpp/security/vehicle_binding_util/tests/VehicleBindingUtilTests.cpp
new file mode 100644
index 0000000..dc3ed6e
--- /dev/null
+++ b/cpp/security/vehicle_binding_util/tests/VehicleBindingUtilTests.cpp
@@ -0,0 +1,221 @@
+/*
+ * 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 "VehicleBindingUtil.h"
+
+#include <android/hardware/automotive/vehicle/2.0/IVehicle.h>
+#include <android/hardware/automotive/vehicle/2.0/types.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hidl/Status.h>
+#include <utils/SystemClock.h>
+
+#include <iterator>
+
+namespace android {
+namespace automotive {
+namespace security {
+namespace {
+
+using android::hardware::Void;
+using android::hardware::automotive::vehicle::V2_0::IVehicle;
+using android::hardware::automotive::vehicle::V2_0::IVehicleCallback;
+using android::hardware::automotive::vehicle::V2_0::StatusCode;
+using android::hardware::automotive::vehicle::V2_0::SubscribeOptions;
+using android::hardware::automotive::vehicle::V2_0::VehicleProperty;
+using android::hardware::automotive::vehicle::V2_0::VehiclePropValue;
+
+template <typename T>
+using hidl_vec = android::hardware::hidl_vec<T>;
+template <typename T>
+using VhalReturn = android::hardware::Return<T>;
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::ElementsAreArray;
+using ::testing::NotNull;
+using ::testing::Return;
+using ::testing::SetArgPointee;
+using ::testing::Test;
+
+class MockVehicle : public IVehicle {
+public:
+    MOCK_METHOD(VhalReturn<void>, getAllPropConfigs, (getAllPropConfigs_cb), (override));
+
+    MOCK_METHOD(VhalReturn<void>, getPropConfigs, (const hidl_vec<int32_t>&, getPropConfigs_cb),
+                (override));
+
+    MOCK_METHOD(VhalReturn<void>, get, (const VehiclePropValue&, get_cb), (override));
+
+    MOCK_METHOD(VhalReturn<StatusCode>, set, (const VehiclePropValue&), (override));
+
+    MOCK_METHOD(VhalReturn<StatusCode>, subscribe,
+                (const sp<IVehicleCallback>&, const hidl_vec<SubscribeOptions>&), (override));
+
+    MOCK_METHOD(VhalReturn<StatusCode>, unsubscribe, (const sp<IVehicleCallback>&, int32_t),
+                (override));
+
+    MOCK_METHOD(VhalReturn<void>, debugDump, (debugDump_cb), (override));
+};
+
+class MockCsrng : public Csrng {
+public:
+    MOCK_METHOD(bool, fill, (void*, size_t), (const override));
+};
+
+class MockExecutor : public Executor {
+public:
+    MOCK_METHOD(int, run, (const std::vector<std::string>&, int*), (const override));
+};
+
+class VehicleBindingUtilTests : public Test {
+protected:
+    void setMockVhalPropertySupported() {
+        hidl_vec<int32_t> expectedProps = {toInt(VehicleProperty::STORAGE_ENCRYPTION_BINDING_SEED)};
+        EXPECT_CALL(*mMockVehicle, getPropConfigs(expectedProps, _))
+                .WillOnce([](const hidl_vec<int32_t>&, IVehicle::getPropConfigs_cb callback) {
+                    callback(StatusCode::OK, {});
+                    return Void();
+                });
+    }
+
+    void setMockVhalPropertyValue(const std::vector<uint8_t>& seed) {
+        EXPECT_CALL(*mMockVehicle, get(_, _))
+                .WillOnce([seed](const VehiclePropValue& propValue, IVehicle::get_cb callback) {
+                    EXPECT_EQ(propValue.prop,
+                              toInt(VehicleProperty::STORAGE_ENCRYPTION_BINDING_SEED));
+                    VehiclePropValue value;
+                    value.prop = propValue.prop;
+                    value.value.bytes = hidl_vec<uint8_t>{seed.begin(), seed.end()};
+                    callback(StatusCode::OK, value);
+                    return Void();
+                });
+    }
+
+    void setTestRandomness(const char seed[SEED_BYTE_SIZE]) {
+        EXPECT_CALL(mMockCsrng, fill(NotNull(), SEED_BYTE_SIZE))
+                .WillOnce([seed](void* buf, size_t) {
+                    memcpy(buf, seed, SEED_BYTE_SIZE);
+                    return true;
+                });
+    }
+
+    static std::vector<uint8_t> toVector(const char seed[SEED_BYTE_SIZE]) {
+        return {seed, seed + SEED_BYTE_SIZE};
+    }
+
+    static std::vector<std::string> makeVdcArgs() {
+        return {"/system/bin/vdc", "cryptfs", "bindkeys"};
+    }
+
+    sp<MockVehicle> mMockVehicle{new MockVehicle};
+    MockExecutor mMockExecutor;
+    MockCsrng mMockCsrng;
+};
+
+// Verify that we fail as expected if the VHAL property is not supported. This
+// is not necessarily an error, and is expected on platforms that don't
+// implement the feature.
+TEST_F(VehicleBindingUtilTests, VhalPropertyUnsupported) {
+    hidl_vec<int32_t> expectedProps = {toInt(VehicleProperty::STORAGE_ENCRYPTION_BINDING_SEED)};
+    EXPECT_CALL(*mMockVehicle, getPropConfigs(expectedProps, _))
+            .WillOnce([](const hidl_vec<int32_t>&, IVehicle::getPropConfigs_cb callback) {
+                callback(StatusCode::INVALID_ARG, {});
+                return Void();
+            });
+
+    EXPECT_EQ(BindingStatus::NOT_SUPPORTED,
+              setVehicleBindingSeed(mMockVehicle, mMockExecutor, mMockCsrng));
+}
+
+// Verify that we properly handle an attempt to generate a random seed.
+TEST_F(VehicleBindingUtilTests, GetRandomnessFails) {
+    setMockVhalPropertySupported();
+    setMockVhalPropertyValue({});
+    EXPECT_CALL(mMockCsrng, fill(_, SEED_BYTE_SIZE)).WillOnce(Return(false));
+    EXPECT_EQ(BindingStatus::ERROR, setVehicleBindingSeed(mMockVehicle, mMockExecutor, mMockCsrng));
+}
+
+// Verify that we properly handle an attempt to generate a random seed.
+TEST_F(VehicleBindingUtilTests, GetSeedVhalPropertyFails) {
+    setMockVhalPropertySupported();
+    EXPECT_CALL(*mMockVehicle, get(_, _))
+            .WillOnce([&](const VehiclePropValue& propValue, IVehicle::get_cb callback) {
+                EXPECT_EQ(propValue.prop, toInt(VehicleProperty::STORAGE_ENCRYPTION_BINDING_SEED));
+                callback(StatusCode::NOT_AVAILABLE, {});
+                return Void();
+            });
+    EXPECT_EQ(BindingStatus::ERROR, setVehicleBindingSeed(mMockVehicle, mMockExecutor, mMockCsrng));
+}
+
+TEST_F(VehicleBindingUtilTests, SetSeedVhalPropertyFails) {
+    setMockVhalPropertySupported();
+    setMockVhalPropertyValue({});
+    setTestRandomness("I am not random");
+
+    EXPECT_CALL(*mMockVehicle, set(_)).WillOnce([](const VehiclePropValue&) {
+        return StatusCode::NOT_AVAILABLE;
+    });
+
+    EXPECT_EQ(BindingStatus::ERROR, setVehicleBindingSeed(mMockVehicle, mMockExecutor, mMockCsrng));
+}
+
+TEST_F(VehicleBindingUtilTests, SetSeedWithNewRandomSeed) {
+    setMockVhalPropertySupported();
+    setMockVhalPropertyValue({});
+    constexpr char SEED[SEED_BYTE_SIZE] = "Seed Value Here";
+    setTestRandomness(SEED);
+
+    EXPECT_CALL(*mMockVehicle, set(_)).WillOnce([&](const VehiclePropValue& value) {
+        EXPECT_EQ(value.prop, toInt(VehicleProperty::STORAGE_ENCRYPTION_BINDING_SEED));
+        EXPECT_THAT(value.value.bytes, testing::ElementsAreArray(SEED));
+        return StatusCode::OK;
+    });
+
+    EXPECT_CALL(mMockExecutor, run(ElementsAreArray(makeVdcArgs()), _)).WillOnce(Return(0));
+
+    EXPECT_EQ(BindingStatus::OK, setVehicleBindingSeed(mMockVehicle, mMockExecutor, mMockCsrng));
+}
+
+TEST_F(VehicleBindingUtilTests, SetSeedWithExistingProperty) {
+    setMockVhalPropertySupported();
+    const auto SEED = toVector("16 bytes of seed");
+    setMockVhalPropertyValue(SEED);
+    EXPECT_CALL(mMockExecutor, run(ElementsAreArray(makeVdcArgs()), _)).WillOnce(Return(0));
+    EXPECT_EQ(BindingStatus::OK, setVehicleBindingSeed(mMockVehicle, mMockExecutor, mMockCsrng));
+}
+
+TEST_F(VehicleBindingUtilTests, SetSeedVdcExecFails) {
+    setMockVhalPropertySupported();
+    const auto SEED = toVector("abcdefghijklmnop");
+    setMockVhalPropertyValue(SEED);
+    EXPECT_CALL(mMockExecutor, run(ElementsAreArray(makeVdcArgs()), _)).WillOnce(Return(-1));
+    EXPECT_EQ(BindingStatus::ERROR, setVehicleBindingSeed(mMockVehicle, mMockExecutor, mMockCsrng));
+}
+
+TEST_F(VehicleBindingUtilTests, SetSeedVdcExitsWithNonZeroStatus) {
+    setMockVhalPropertySupported();
+    const auto SEED = toVector("1123581321345589");
+    setMockVhalPropertyValue(SEED);
+    EXPECT_CALL(mMockExecutor, run(ElementsAreArray(makeVdcArgs()), _))
+            .WillOnce(DoAll(SetArgPointee<1>(-1), Return(0)));
+    EXPECT_EQ(BindingStatus::ERROR, setVehicleBindingSeed(mMockVehicle, mMockExecutor, mMockCsrng));
+}
+
+}  // namespace
+}  // namespace security
+}  // namespace automotive
+}  // namespace android
diff --git a/cpp/security/vehicle_binding_util/vehicle_binding_util.rc b/cpp/security/vehicle_binding_util/vehicle_binding_util.rc
new file mode 100644
index 0000000..cf73a23
--- /dev/null
+++ b/cpp/security/vehicle_binding_util/vehicle_binding_util.rc
@@ -0,0 +1,4 @@
+service vold_seed_binding /system/bin/vehicle_binding_util set_binding
+    oneshot
+    user root
+    group root
diff --git a/cpp/telemetry/proto/Android.bp b/cpp/telemetry/proto/Android.bp
index 862a537..53be1a4 100644
--- a/cpp/telemetry/proto/Android.bp
+++ b/cpp/telemetry/proto/Android.bp
@@ -24,3 +24,9 @@
         "evs.proto",
     ],
 }
+
+filegroup {
+    name: "cartelemetry-cardata-proto-srcs",
+    srcs: ["*.proto"],
+}
+
diff --git a/cpp/telemetry/proto/CarData.proto b/cpp/telemetry/proto/CarData.proto
index 96e8c2e..e5e9b38 100644
--- a/cpp/telemetry/proto/CarData.proto
+++ b/cpp/telemetry/proto/CarData.proto
@@ -31,13 +31,18 @@
 
 import "packages/services/Car/cpp/telemetry/proto/evs.proto";
 
+// Contains all the CarData messages to declare all the messages.
+// Unique protobuf number is used as an ID.
+// A message will be sent from writer clients to the cartelemetryd
+// wrapped in
+// frameworks/hardware/interfaces/automotive/telemetry/aidl/android/frameworks/automotive/telemetry/CarData.aidl
 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
+  // DO NOT USE field numbers above 10,000 in AOSP.
+  // Field numbers 10,000 - 19,999 are reserved for non-AOSP (e.g. OEMs) to
+  // use. Field numbers 20,000 and above are reserved for future use; do not
   // use them at all.
 }
diff --git a/cpp/telemetry/script_executor/Android.bp b/cpp/telemetry/script_executor/Android.bp
deleted file mode 100644
index 6eaec62..0000000
--- a/cpp/telemetry/script_executor/Android.bp
+++ /dev/null
@@ -1,77 +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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_defaults {
-    name: "scriptexecutor_defaults",
-    cflags: [
-        "-Wno-unused-parameter",
-    ],
-    static_libs: [
-        "libbase",
-        "liblog",
-        "liblua",
-    ],
-}
-
-cc_library {
-    name: "libscriptexecutor",
-    defaults: [
-        "scriptexecutor_defaults",
-    ],
-    srcs: [
-        "src/JniUtils.cpp",
-        "src/LuaEngine.cpp",
-        "src/ScriptExecutorListener.cpp",
-    ],
-    shared_libs: [
-        "libandroid_runtime",
-        "libnativehelper",
-    ],
-    // Allow dependents to use the header files.
-    export_include_dirs: [
-        "src",
-    ],
-}
-
-cc_library_shared {
-    name: "libscriptexecutorjniutils-test",
-    defaults: [
-        "scriptexecutor_defaults",
-    ],
-    srcs: [
-        "src/tests/JniUtilsTestHelper.cpp",
-    ],
-    shared_libs: [
-        "libnativehelper",
-        "libscriptexecutor",
-    ],
-}
-
-cc_library {
-    name: "libscriptexecutorjni",
-    defaults: [
-        "scriptexecutor_defaults",
-    ],
-    srcs: [
-        "src/ScriptExecutorJni.cpp",
-    ],
-    shared_libs: [
-        "libnativehelper",
-        "libscriptexecutor",
-    ],
-}
diff --git a/cpp/telemetry/script_executor/src/JniUtils.cpp b/cpp/telemetry/script_executor/src/JniUtils.cpp
deleted file mode 100644
index 93c1af8..0000000
--- a/cpp/telemetry/script_executor/src/JniUtils.cpp
+++ /dev/null
@@ -1,95 +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.
- */
-
-#include "JniUtils.h"
-
-namespace android {
-namespace automotive {
-namespace telemetry {
-namespace script_executor {
-
-void PushBundleToLuaTable(JNIEnv* env, LuaEngine* luaEngine, jobject bundle) {
-    lua_newtable(luaEngine->GetLuaState());
-    // null bundle object is allowed. We will treat it as an empty table.
-    if (bundle == nullptr) {
-        return;
-    }
-
-    // TODO(b/188832769): Consider caching some of these JNI references for
-    // performance reasons.
-    jclass bundleClass = env->FindClass("android/os/Bundle");
-    jmethodID getKeySetMethod = env->GetMethodID(bundleClass, "keySet", "()Ljava/util/Set;");
-    jobject keys = env->CallObjectMethod(bundle, getKeySetMethod);
-    jclass setClass = env->FindClass("java/util/Set");
-    jmethodID iteratorMethod = env->GetMethodID(setClass, "iterator", "()Ljava/util/Iterator;");
-    jobject keySetIteratorObject = env->CallObjectMethod(keys, iteratorMethod);
-
-    jclass iteratorClass = env->FindClass("java/util/Iterator");
-    jmethodID hasNextMethod = env->GetMethodID(iteratorClass, "hasNext", "()Z");
-    jmethodID nextMethod = env->GetMethodID(iteratorClass, "next", "()Ljava/lang/Object;");
-
-    jclass booleanClass = env->FindClass("java/lang/Boolean");
-    jclass integerClass = env->FindClass("java/lang/Integer");
-    jclass numberClass = env->FindClass("java/lang/Number");
-    jclass stringClass = env->FindClass("java/lang/String");
-    // TODO(b/188816922): Handle more types such as float and integer arrays,
-    // and perhaps nested Bundles.
-
-    jmethodID getMethod =
-            env->GetMethodID(bundleClass, "get", "(Ljava/lang/String;)Ljava/lang/Object;");
-
-    // Iterate over key set of the bundle one key at a time.
-    while (env->CallBooleanMethod(keySetIteratorObject, hasNextMethod)) {
-        // Read the value object that corresponds to this key.
-        jstring key = (jstring)env->CallObjectMethod(keySetIteratorObject, nextMethod);
-        jobject value = env->CallObjectMethod(bundle, getMethod, key);
-
-        // Get the value of the type, extract it accordingly from the bundle and
-        // push the extracted value and the key to the Lua table.
-        if (env->IsInstanceOf(value, booleanClass)) {
-            jmethodID boolMethod = env->GetMethodID(booleanClass, "booleanValue", "()Z");
-            bool boolValue = static_cast<bool>(env->CallBooleanMethod(value, boolMethod));
-            lua_pushboolean(luaEngine->GetLuaState(), boolValue);
-        } else if (env->IsInstanceOf(value, integerClass)) {
-            jmethodID intMethod = env->GetMethodID(integerClass, "intValue", "()I");
-            lua_pushinteger(luaEngine->GetLuaState(), env->CallIntMethod(value, intMethod));
-        } else if (env->IsInstanceOf(value, numberClass)) {
-            // Condense other numeric types using one class. Because lua supports only
-            // integer or double, and we handled integer in previous if clause.
-            jmethodID numberMethod = env->GetMethodID(numberClass, "doubleValue", "()D");
-            /* Pushes a double onto the stack */
-            lua_pushnumber(luaEngine->GetLuaState(), env->CallDoubleMethod(value, numberMethod));
-        } else if (env->IsInstanceOf(value, stringClass)) {
-            const char* rawStringValue = env->GetStringUTFChars((jstring)value, nullptr);
-            lua_pushstring(luaEngine->GetLuaState(), rawStringValue);
-            env->ReleaseStringUTFChars((jstring)value, rawStringValue);
-        } else {
-            // Other types are not implemented yet, skipping.
-            continue;
-        }
-
-        const char* rawKey = env->GetStringUTFChars(key, nullptr);
-        // table[rawKey] = value, where value is on top of the stack,
-        // and the table is the next element in the stack.
-        lua_setfield(luaEngine->GetLuaState(), /* idx= */ -2, rawKey);
-        env->ReleaseStringUTFChars(key, rawKey);
-    }
-}
-
-}  // namespace script_executor
-}  // namespace telemetry
-}  // namespace automotive
-}  // namespace android
diff --git a/cpp/telemetry/script_executor/src/JniUtils.h b/cpp/telemetry/script_executor/src/JniUtils.h
deleted file mode 100644
index c3ef677..0000000
--- a/cpp/telemetry/script_executor/src/JniUtils.h
+++ /dev/null
@@ -1,39 +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.
- */
-#ifndef CPP_TELEMETRY_SCRIPT_EXECUTOR_SRC_JNIUTILS_H_
-#define CPP_TELEMETRY_SCRIPT_EXECUTOR_SRC_JNIUTILS_H_
-
-#include "LuaEngine.h"
-#include "jni.h"
-
-namespace android {
-namespace automotive {
-namespace telemetry {
-namespace script_executor {
-
-// Helper function which takes android.os.Bundle object in "bundle" argument
-// and converts it to Lua table on top of Lua stack. All key-value pairs are
-// converted to the corresponding key-value pairs of the Lua table as long as
-// the Bundle value types are supported. At this point, we support boolean,
-// integer, double and String types in Java.
-void PushBundleToLuaTable(JNIEnv* env, LuaEngine* luaEngine, jobject bundle);
-
-}  // namespace script_executor
-}  // namespace telemetry
-}  // namespace automotive
-}  // namespace android
-
-#endif  // CPP_TELEMETRY_SCRIPT_EXECUTOR_SRC_JNIUTILS_H_
diff --git a/cpp/telemetry/script_executor/src/LuaEngine.cpp b/cpp/telemetry/script_executor/src/LuaEngine.cpp
deleted file mode 100644
index 1a074f2..0000000
--- a/cpp/telemetry/script_executor/src/LuaEngine.cpp
+++ /dev/null
@@ -1,95 +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.
- */
-
-#include "LuaEngine.h"
-
-#include <utility>
-
-extern "C" {
-#include "lauxlib.h"
-#include "lualib.h"
-}
-
-namespace android {
-namespace automotive {
-namespace telemetry {
-namespace script_executor {
-
-LuaEngine::LuaEngine() {
-    // Instantiate Lua environment
-    mLuaState = luaL_newstate();
-    luaL_openlibs(mLuaState);
-}
-
-LuaEngine::~LuaEngine() {
-    lua_close(mLuaState);
-}
-
-lua_State* LuaEngine::GetLuaState() {
-    return mLuaState;
-}
-
-void LuaEngine::ResetListener(ScriptExecutorListener* listener) {
-    mListener.reset(listener);
-}
-
-int LuaEngine::LoadScript(const char* scriptBody) {
-    // As the first step in Lua script execution we want to load
-    // the body of the script into Lua stack and have it processed by Lua
-    // to catch any errors.
-    // More on luaL_dostring: https://www.lua.org/manual/5.3/manual.html#lual_dostring
-    // If error, pushes the error object into the stack.
-    const auto status = luaL_dostring(mLuaState, scriptBody);
-    if (status) {
-        // Removes error object from the stack.
-        // Lua stack must be properly maintained due to its limited size,
-        // ~20 elements and its critical function because all interaction with
-        // Lua happens via the stack.
-        // Starting read about Lua stack: https://www.lua.org/pil/24.2.html
-        // TODO(b/192284232): add test case to trigger this.
-        lua_pop(mLuaState, 1);
-    }
-    return status;
-}
-
-bool LuaEngine::PushFunction(const char* functionName) {
-    // Interaction between native code and Lua happens via Lua stack.
-    // In such model, a caller first pushes the name of the function
-    // that needs to be called, followed by the function's input
-    // arguments, one input value pushed at a time.
-    // More info: https://www.lua.org/pil/24.2.html
-    lua_getglobal(mLuaState, functionName);
-    const auto status = lua_isfunction(mLuaState, /*idx= */ -1);
-    // TODO(b/192284785): add test case for wrong function name in Lua.
-    if (status == 0) lua_pop(mLuaState, 1);
-    return status;
-}
-
-int LuaEngine::Run() {
-    // Performs blocking call of the provided Lua function. Assumes all
-    // input arguments are in the Lua stack as well in proper order.
-    // On how to call Lua functions: https://www.lua.org/pil/25.2.html
-    // Doc on lua_pcall: https://www.lua.org/manual/5.3/manual.html#lua_pcall
-    // TODO(b/189241508): Once we implement publishedData parsing, nargs should
-    // change from 1 to 2.
-    // TODO(b/192284612): add test case for failed call.
-    return lua_pcall(mLuaState, /* nargs= */ 1, /* nresults= */ 0, /*errfunc= */ 0);
-}
-
-}  // namespace script_executor
-}  // namespace telemetry
-}  // namespace automotive
-}  // namespace android
diff --git a/cpp/telemetry/script_executor/src/LuaEngine.h b/cpp/telemetry/script_executor/src/LuaEngine.h
deleted file mode 100644
index a1d6e48..0000000
--- a/cpp/telemetry/script_executor/src/LuaEngine.h
+++ /dev/null
@@ -1,72 +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.
- */
-
-#ifndef CPP_TELEMETRY_SCRIPT_EXECUTOR_SRC_LUAENGINE_H_
-#define CPP_TELEMETRY_SCRIPT_EXECUTOR_SRC_LUAENGINE_H_
-
-#include "ScriptExecutorListener.h"
-
-#include <memory>
-
-extern "C" {
-#include "lua.h"
-}
-
-namespace android {
-namespace automotive {
-namespace telemetry {
-namespace script_executor {
-
-// Encapsulates Lua script execution environment.
-class LuaEngine {
-public:
-    LuaEngine();
-
-    virtual ~LuaEngine();
-
-    // Returns pointer to Lua state object.
-    lua_State* GetLuaState();
-
-    // Loads Lua script provided as scriptBody string.
-    // Returns 0 if successful. Otherwise returns non-zero Lua error code.
-    int LoadScript(const char* scriptBody);
-
-    // Pushes a Lua function under provided name into the stack.
-    // Returns true if successful.
-    bool PushFunction(const char* functionName);
-
-    // Invokes function with the inputs provided in the stack.
-    // Assumes that the script body has been already loaded and successully
-    // compiled and run, and all input arguments, and the function have been
-    // pushed to the stack.
-    // Returns 0 if successful. Otherwise returns non-zero Lua error code.
-    int Run();
-
-    // Updates stored listener and destroys the previous one.
-    void ResetListener(ScriptExecutorListener* listener);
-
-private:
-    lua_State* mLuaState;  // owned
-
-    std::unique_ptr<ScriptExecutorListener> mListener;
-};
-
-}  // namespace script_executor
-}  // namespace telemetry
-}  // namespace automotive
-}  // namespace android
-
-#endif  // CPP_TELEMETRY_SCRIPT_EXECUTOR_SRC_LUAENGINE_H_
diff --git a/cpp/telemetry/script_executor/src/ScriptExecutorJni.cpp b/cpp/telemetry/script_executor/src/ScriptExecutorJni.cpp
deleted file mode 100644
index 500b8e2..0000000
--- a/cpp/telemetry/script_executor/src/ScriptExecutorJni.cpp
+++ /dev/null
@@ -1,137 +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.
- */
-
-#include "JniUtils.h"
-#include "LuaEngine.h"
-#include "ScriptExecutorListener.h"
-#include "jni.h"
-
-#include <android-base/logging.h>
-
-#include <cstdint>
-
-namespace android {
-namespace automotive {
-namespace telemetry {
-namespace script_executor {
-
-extern "C" {
-
-JNIEXPORT jlong JNICALL
-Java_com_android_car_telemetry_ScriptExecutor_nativeInitLuaEngine(JNIEnv* env, jobject object) {
-    // Cast first to intptr_t to ensure int can hold the pointer without loss.
-    return static_cast<jlong>(reinterpret_cast<intptr_t>(new LuaEngine()));
-}
-
-JNIEXPORT void JNICALL Java_com_android_car_telemetry_ScriptExecutor_nativeDestroyLuaEngine(
-        JNIEnv* env, jobject object, jlong luaEnginePtr) {
-    delete reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
-}
-
-// Parses the inputs and loads them to Lua one at a time.
-// Loading of data into Lua also triggers checks on Lua side to verify the
-// inputs are valid. For example, pushing "functionName" into Lua stack verifies
-// that the function name actually exists in the previously loaded body of the
-// script.
-//
-// The steps are:
-// Step 1: Parse the inputs for obvious programming errors.
-// Step 2: Parse and load the body of the script.
-// Step 3: Parse and push function name we want to execute in the provided
-// script body to Lua stack. If the function name doesn't exist, we exit.
-// Step 4: Parse publishedData, convert it into Lua table and push it to the
-// stack.
-// Step 5: Parse savedState Bundle object, convert it into Lua table and push it
-// to the stack.
-// Any errors that occur at the stage above result in quick exit or crash.
-//
-// All interaction with Lua happens via Lua stack. Therefore, order of how the
-// inputs are parsed and processed is critical because Lua API methods such as
-// lua_pcall assume specific order between function name and the input arguments
-// on the stack.
-// More information about how to work with Lua stack: https://www.lua.org/pil/24.2.html
-// and how Lua functions are called via Lua API: https://www.lua.org/pil/25.2.html
-//
-// Finally, once parsing and pushing to Lua stack is complete, we do
-//
-// Step 6: attempt to run the provided function.
-JNIEXPORT void JNICALL Java_com_android_car_telemetry_ScriptExecutor_nativeInvokeScript(
-        JNIEnv* env, jobject object, jlong luaEnginePtr, jstring scriptBody, jstring functionName,
-        jobject publishedData, jobject savedState, jobject listener) {
-    if (!luaEnginePtr) {
-        env->FatalError("luaEnginePtr parameter cannot be nil");
-    }
-    if (scriptBody == nullptr) {
-        env->FatalError("scriptBody parameter cannot be null");
-    }
-    if (functionName == nullptr) {
-        env->FatalError("functionName parameter cannot be null");
-    }
-    if (listener == nullptr) {
-        env->FatalError("listener parameter cannot be null");
-    }
-
-    LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
-
-    // Load and parse the script
-    const char* scriptStr = env->GetStringUTFChars(scriptBody, nullptr);
-    auto status = engine->LoadScript(scriptStr);
-    env->ReleaseStringUTFChars(scriptBody, scriptStr);
-    // status == 0 if the script loads successfully.
-    if (status) {
-        env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"),
-                      "Failed to load the script.");
-        return;
-    }
-    engine->ResetListener(new ScriptExecutorListener(env, listener));
-
-    // Push the function name we want to invoke to Lua stack
-    const char* functionNameStr = env->GetStringUTFChars(functionName, nullptr);
-    status = engine->PushFunction(functionNameStr);
-    env->ReleaseStringUTFChars(functionName, functionNameStr);
-    // status == 1 if the name is indeed a function.
-    if (!status) {
-        env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"),
-                      "symbol functionName does not correspond to a function.");
-        return;
-    }
-
-    // TODO(b/189241508): Provide implementation to parse publishedData input,
-    // convert it into Lua table and push into Lua stack.
-    if (publishedData) {
-        env->ThrowNew(env->FindClass("java/lang/RuntimeException"),
-                      "Parsing of publishedData is not implemented yet.");
-        return;
-    }
-
-    // Unpack bundle in savedState, convert to Lua table and push it to Lua
-    // stack.
-    PushBundleToLuaTable(env, engine, savedState);
-
-    // Execute the function. This will block until complete or error.
-    if (engine->Run()) {
-        env->ThrowNew(env->FindClass("java/lang/RuntimeException"),
-                      "Runtime error occurred while running the function.");
-        return;
-    }
-}
-
-}  // extern "C"
-
-}  // namespace script_executor
-}  // namespace telemetry
-}  // namespace automotive
-}  // namespace android
diff --git a/cpp/telemetry/script_executor/src/ScriptExecutorListener.cpp b/cpp/telemetry/script_executor/src/ScriptExecutorListener.cpp
deleted file mode 100644
index 8c10aa4..0000000
--- a/cpp/telemetry/script_executor/src/ScriptExecutorListener.cpp
+++ /dev/null
@@ -1,47 +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.
- */
-
-#include "ScriptExecutorListener.h"
-
-#include <android-base/logging.h>
-#include <android_runtime/AndroidRuntime.h>
-
-namespace android {
-namespace automotive {
-namespace telemetry {
-namespace script_executor {
-
-ScriptExecutorListener::~ScriptExecutorListener() {
-    if (mScriptExecutorListener != NULL) {
-        JNIEnv* env = AndroidRuntime::getJNIEnv();
-        env->DeleteGlobalRef(mScriptExecutorListener);
-    }
-}
-
-ScriptExecutorListener::ScriptExecutorListener(JNIEnv* env, jobject script_executor_listener) {
-    mScriptExecutorListener = env->NewGlobalRef(script_executor_listener);
-}
-
-void ScriptExecutorListener::onError(const int errorType, const std::string& message,
-                                     const std::string& stackTrace) {
-    LOG(INFO) << "errorType: " << errorType << ", message: " << message
-              << ", stackTrace: " << stackTrace;
-}
-
-}  // namespace script_executor
-}  // namespace telemetry
-}  // namespace automotive
-}  // namespace android
diff --git a/cpp/telemetry/script_executor/src/ScriptExecutorListener.h b/cpp/telemetry/script_executor/src/ScriptExecutorListener.h
deleted file mode 100644
index 1e5c7d7..0000000
--- a/cpp/telemetry/script_executor/src/ScriptExecutorListener.h
+++ /dev/null
@@ -1,52 +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.
- */
-
-#ifndef CPP_TELEMETRY_SCRIPT_EXECUTOR_SRC_SCRIPTEXECUTORLISTENER_H_
-#define CPP_TELEMETRY_SCRIPT_EXECUTOR_SRC_SCRIPTEXECUTORLISTENER_H_
-
-#include "jni.h"
-
-#include <string>
-
-namespace android {
-namespace automotive {
-namespace telemetry {
-namespace script_executor {
-
-//  Wrapper class for IScriptExecutorListener.aidl.
-class ScriptExecutorListener {
-public:
-    ScriptExecutorListener(JNIEnv* jni, jobject script_executor_listener);
-
-    virtual ~ScriptExecutorListener();
-
-    void onScriptFinished() {}
-
-    void onSuccess() {}
-
-    void onError(const int errorType, const std::string& message, const std::string& stackTrace);
-
-private:
-    // Stores a jni global reference to Java Script Executor listener object.
-    jobject mScriptExecutorListener;
-};
-
-}  // namespace script_executor
-}  // namespace telemetry
-}  // namespace automotive
-}  // namespace android
-
-#endif  // CPP_TELEMETRY_SCRIPT_EXECUTOR_SRC_SCRIPTEXECUTORLISTENER_H_
diff --git a/cpp/telemetry/script_executor/src/tests/JniUtilsTestHelper.cpp b/cpp/telemetry/script_executor/src/tests/JniUtilsTestHelper.cpp
deleted file mode 100644
index 9e2c43a..0000000
--- a/cpp/telemetry/script_executor/src/tests/JniUtilsTestHelper.cpp
+++ /dev/null
@@ -1,138 +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.
- */
-
-#include "JniUtils.h"
-#include "LuaEngine.h"
-#include "jni.h"
-
-#include <cstdint>
-#include <cstring>
-
-namespace android {
-namespace automotive {
-namespace telemetry {
-namespace script_executor {
-namespace {
-
-extern "C" {
-
-#include "lua.h"
-
-JNIEXPORT jlong JNICALL
-Java_com_android_car_telemetry_JniUtilsTest_nativeCreateLuaEngine(JNIEnv* env, jobject object) {
-    // Cast first to intptr_t to ensure int can hold the pointer without loss.
-    return static_cast<jlong>(reinterpret_cast<intptr_t>(new LuaEngine()));
-}
-
-JNIEXPORT void JNICALL Java_com_android_car_telemetry_JniUtilsTest_nativeDestroyLuaEngine(
-        JNIEnv* env, jobject object, jlong luaEnginePtr) {
-    delete reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
-}
-
-JNIEXPORT void JNICALL Java_com_android_car_telemetry_JniUtilsTest_nativePushBundleToLuaTableCaller(
-        JNIEnv* env, jobject object, jlong luaEnginePtr, jobject bundle) {
-    PushBundleToLuaTable(env, reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr)),
-                         bundle);
-}
-
-JNIEXPORT jint JNICALL Java_com_android_car_telemetry_JniUtilsTest_nativeGetObjectSize(
-        JNIEnv* env, jobject object, jlong luaEnginePtr, jint index) {
-    LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
-    return lua_rawlen(engine->GetLuaState(), static_cast<int>(index));
-}
-
-JNIEXPORT bool JNICALL Java_com_android_car_telemetry_JniUtilsTest_nativeHasBooleanValue(
-        JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jboolean value) {
-    const char* rawKey = env->GetStringUTFChars(key, nullptr);
-    LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
-    auto* luaState = engine->GetLuaState();
-    lua_pushstring(luaState, rawKey);
-    env->ReleaseStringUTFChars(key, rawKey);
-    lua_gettable(luaState, -2);
-    bool result = false;
-    if (!lua_isboolean(luaState, -1))
-        result = false;
-    else
-        result = static_cast<bool>(lua_toboolean(luaState, -1)) == static_cast<bool>(value);
-    lua_pop(luaState, 1);
-    return result;
-}
-
-JNIEXPORT bool JNICALL Java_com_android_car_telemetry_JniUtilsTest_nativeHasIntValue(
-        JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jint value) {
-    const char* rawKey = env->GetStringUTFChars(key, nullptr);
-    LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
-    // Assumes the table is on top of the stack.
-    auto* luaState = engine->GetLuaState();
-    lua_pushstring(luaState, rawKey);
-    env->ReleaseStringUTFChars(key, rawKey);
-    lua_gettable(luaState, -2);
-    bool result = false;
-    if (!lua_isinteger(luaState, -1))
-        result = false;
-    else
-        result = lua_tointeger(luaState, -1) == static_cast<int>(value);
-    lua_pop(luaState, 1);
-    return result;
-}
-
-JNIEXPORT bool JNICALL Java_com_android_car_telemetry_JniUtilsTest_nativeHasDoubleValue(
-        JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jdouble value) {
-    const char* rawKey = env->GetStringUTFChars(key, nullptr);
-    LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
-    // Assumes the table is on top of the stack.
-    auto* luaState = engine->GetLuaState();
-    lua_pushstring(luaState, rawKey);
-    env->ReleaseStringUTFChars(key, rawKey);
-    lua_gettable(luaState, -2);
-    bool result = false;
-    if (!lua_isnumber(luaState, -1))
-        result = false;
-    else
-        result = static_cast<double>(lua_tonumber(luaState, -1)) == static_cast<double>(value);
-    lua_pop(luaState, 1);
-    return result;
-}
-
-JNIEXPORT bool JNICALL Java_com_android_car_telemetry_JniUtilsTest_nativeHasStringValue(
-        JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jstring value) {
-    const char* rawKey = env->GetStringUTFChars(key, nullptr);
-    LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
-    // Assumes the table is on top of the stack.
-    auto* luaState = engine->GetLuaState();
-    lua_pushstring(luaState, rawKey);
-    env->ReleaseStringUTFChars(key, rawKey);
-    lua_gettable(luaState, -2);
-    bool result = false;
-    if (!lua_isstring(luaState, -1)) {
-        result = false;
-    } else {
-        std::string s = lua_tostring(luaState, -1);
-        const char* rawValue = env->GetStringUTFChars(value, nullptr);
-        result = strcmp(lua_tostring(luaState, -1), rawValue) == 0;
-        env->ReleaseStringUTFChars(value, rawValue);
-    }
-    lua_pop(luaState, 1);
-    return result;
-}
-
-}  //  extern "C"
-
-}  //  namespace
-}  //  namespace script_executor
-}  //  namespace telemetry
-}  //  namespace automotive
-}  //  namespace android
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/ICarWatchdog.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/ICarWatchdog.aidl
index 57d82ca..c9ece21 100644
--- a/cpp/watchdog/aidl/android/automotive/watchdog/internal/ICarWatchdog.aidl
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/ICarWatchdog.aidl
@@ -128,4 +128,12 @@
    * @param actions              List of actions take on resource overusing packages.
    */
    void actionTakenOnResourceOveruse(in List<PackageResourceOveruseAction> actions);
+
+   /**
+    * Enable/disable the internal client health check process.
+    * Disabling would stop the ANR killing process.
+    *
+    * @param isEnabled            New enabled state.
+    */
+    void controlProcessHealthCheck(in boolean disable);
 }
diff --git a/cpp/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java b/cpp/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java
index 1810c92..e7229ba 100644
--- a/cpp/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java
+++ b/cpp/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java
@@ -291,6 +291,16 @@
         invokeDaemonMethod((daemon) -> daemon.actionTakenOnResourceOveruse(actions));
     }
 
+    /**
+     * Enable/disable the internal client health check process.
+     * Disabling would stop the ANR killing process.
+     *
+     * @param disable True to disable watchdog's health check process.
+     */
+    public void controlProcessHealthCheck(boolean disable) throws RemoteException {
+        invokeDaemonMethod((daemon) -> daemon.controlProcessHealthCheck(disable));
+    }
+
     private void invokeDaemonMethod(Invokable r) throws RemoteException {
         ICarWatchdog daemon;
         synchronized (mLock) {
diff --git a/cpp/watchdog/sepolicy/private/carwatchdog.te b/cpp/watchdog/sepolicy/private/carwatchdog.te
index 91620f5..5b18ebf 100644
--- a/cpp/watchdog/sepolicy/private/carwatchdog.te
+++ b/cpp/watchdog/sepolicy/private/carwatchdog.te
@@ -1,4 +1,4 @@
-# Car watchdog server
+# Car watchdog server.
 typeattribute carwatchdogd coredomain;
 typeattribute carwatchdogd mlstrustedsubject;
 
@@ -9,22 +9,26 @@
 binder_use(carwatchdogd)
 binder_service(carwatchdogd)
 
-# Configration to communicate with VHAL
+# Configration to communicate with VHAL.
 hwbinder_use(carwatchdogd)
 get_prop(carwatchdogd, hwservicemanager_prop)
 hal_client_domain(carwatchdogd, hal_vehicle)
 
-# Scan through /proc/pid for all processes
+# Scan through /proc/pid for all processes.
 r_dir_file(carwatchdogd, domain)
 
-# Read /proc/uid_io/stats
+# Read /proc/uid_io/stats.
 allow carwatchdogd proc_uid_io_stats:file r_file_perms;
 
-# Read /proc/stat file
+# Read /proc/stat file.
 allow carwatchdogd proc_stat:file r_file_perms;
 
-# Read /proc/diskstats file
+# Read /proc/diskstats file.
 allow carwatchdogd proc_diskstats:file r_file_perms;
 
 # List HALs to get pid of vehicle HAL.
 allow carwatchdogd hwservicemanager:hwservice_manager list;
+
+# R/W /data/system/car for resource overuse configurations.
+allow carwatchdogd system_car_data_file:dir create_dir_perms;
+allow carwatchdogd system_car_data_file:{ file lnk_file } create_file_perms;
diff --git a/cpp/watchdog/sepolicy/public/carwatchdog.te b/cpp/watchdog/sepolicy/public/carwatchdog.te
index 2cb9c5a..fd7ab3b 100644
--- a/cpp/watchdog/sepolicy/public/carwatchdog.te
+++ b/cpp/watchdog/sepolicy/public/carwatchdog.te
@@ -1,9 +1,9 @@
-# Car watchdog server
+# Car watchdog server.
 type carwatchdogd, domain;
 
 binder_call(carwatchdogd, carwatchdogclient_domain)
 binder_call(carwatchdogclient_domain, carwatchdogd)
 
-# Configuration for system_server
+# Configuration for system_server.
 allow system_server carwatchdogd_service:service_manager find;
 binder_call(carwatchdogd, system_server)
diff --git a/cpp/watchdog/server/src/IoOveruseConfigs.cpp b/cpp/watchdog/server/src/IoOveruseConfigs.cpp
index 6fb6633..2ff118a 100644
--- a/cpp/watchdog/server/src/IoOveruseConfigs.cpp
+++ b/cpp/watchdog/server/src/IoOveruseConfigs.cpp
@@ -83,12 +83,6 @@
     return output;
 }
 
-bool isZeroValueThresholds(const PerStateIoOveruseThreshold& thresholds) {
-    return thresholds.perStateWriteBytes.foregroundBytes == 0 &&
-            thresholds.perStateWriteBytes.backgroundBytes == 0 &&
-            thresholds.perStateWriteBytes.garageModeBytes == 0;
-}
-
 std::string toString(const PerStateIoOveruseThreshold& thresholds) {
     return StringPrintf("name=%s, foregroundBytes=%" PRId64 ", backgroundBytes=%" PRId64
                         ", garageModeBytes=%" PRId64,
@@ -102,23 +96,20 @@
         return Error() << "Doesn't contain threshold name";
     }
 
-    if (isZeroValueThresholds(thresholds)) {
-        return Error() << "Zero value thresholds for " << thresholds.name;
-    }
-
-    if (thresholds.perStateWriteBytes.foregroundBytes == 0 ||
-        thresholds.perStateWriteBytes.backgroundBytes == 0 ||
-        thresholds.perStateWriteBytes.garageModeBytes == 0) {
-        return Error() << "Some thresholds are zero: " << toString(thresholds);
+    if (thresholds.perStateWriteBytes.foregroundBytes <= 0 ||
+        thresholds.perStateWriteBytes.backgroundBytes <= 0 ||
+        thresholds.perStateWriteBytes.garageModeBytes <= 0) {
+        return Error() << "Some thresholds are less than or equal to zero: "
+                       << toString(thresholds);
     }
     return {};
 }
 
 Result<void> containsValidThreshold(const IoOveruseAlertThreshold& threshold) {
-    if (threshold.durationInSeconds == 0) {
+    if (threshold.durationInSeconds <= 0) {
         return Error() << "Duration must be greater than zero";
     }
-    if (threshold.writtenBytesPerSecond == 0) {
+    if (threshold.writtenBytesPerSecond <= 0) {
         return Error() << "Written bytes/second must be greater than zero";
     }
     return {};
@@ -238,6 +229,16 @@
     return {};
 }
 
+bool isSafeToKillAnyPackage(const std::vector<std::string>& packages,
+                            const std::unordered_set<std::string>& safeToKillPackages) {
+    for (const auto& packageName : packages) {
+        if (safeToKillPackages.find(packageName) != safeToKillPackages.end()) {
+            return true;
+        }
+    }
+    return false;
+}
+
 }  // namespace
 
 IoOveruseConfigs::ParseXmlFileFunction IoOveruseConfigs::sParseXmlFile =
@@ -724,11 +725,26 @@
     }
     switch (packageInfo.componentType) {
         case ComponentType::SYSTEM:
-            return mSystemConfig.mSafeToKillPackages.find(packageInfo.packageIdentifier.name) !=
-                    mSystemConfig.mSafeToKillPackages.end();
+            if (mSystemConfig.mSafeToKillPackages.find(packageInfo.packageIdentifier.name) !=
+                mSystemConfig.mSafeToKillPackages.end()) {
+                return true;
+            }
+            return isSafeToKillAnyPackage(packageInfo.sharedUidPackages,
+                                          mSystemConfig.mSafeToKillPackages);
         case ComponentType::VENDOR:
-            return mVendorConfig.mSafeToKillPackages.find(packageInfo.packageIdentifier.name) !=
-                    mVendorConfig.mSafeToKillPackages.end();
+            if (mVendorConfig.mSafeToKillPackages.find(packageInfo.packageIdentifier.name) !=
+                mVendorConfig.mSafeToKillPackages.end()) {
+                return true;
+            }
+            /*
+             * Packages under the vendor shared UID may contain system packages because when
+             * CarWatchdogService derives the shared component type it attributes system packages
+             * as vendor packages when there is at least one vendor package.
+             */
+            return isSafeToKillAnyPackage(packageInfo.sharedUidPackages,
+                                          mSystemConfig.mSafeToKillPackages) ||
+                    isSafeToKillAnyPackage(packageInfo.sharedUidPackages,
+                                           mVendorConfig.mSafeToKillPackages);
         default:
             return true;
     }
diff --git a/cpp/watchdog/server/src/PackageInfoResolver.cpp b/cpp/watchdog/server/src/PackageInfoResolver.cpp
index 2b30600..84d7c67 100644
--- a/cpp/watchdog/server/src/PackageInfoResolver.cpp
+++ b/cpp/watchdog/server/src/PackageInfoResolver.cpp
@@ -179,9 +179,23 @@
         if (id.name.empty()) {
             continue;
         }
-        if (const auto it = mPackagesToAppCategories.find(id.name);
-            packageInfo.uidType == UidType::APPLICATION && it != mPackagesToAppCategories.end()) {
-            packageInfo.appCategoryType = it->second;
+        if (packageInfo.uidType == UidType::APPLICATION) {
+            if (const auto it = mPackagesToAppCategories.find(id.name);
+                it != mPackagesToAppCategories.end()) {
+                packageInfo.appCategoryType = it->second;
+            } else if (!packageInfo.sharedUidPackages.empty()) {
+                /* The recommendation for the OEMs is to define the application category mapping
+                 * by the shared package names. However, this a fallback to catch if any mapping is
+                 * defined by the individual package name.
+                 */
+                for (const auto& packageName : packageInfo.sharedUidPackages) {
+                    if (const auto it = mPackagesToAppCategories.find(packageName);
+                        it != mPackagesToAppCategories.end()) {
+                        packageInfo.appCategoryType = it->second;
+                        break;
+                    }
+                }
+            }
         }
         mUidToPackageInfoMapping[id.uid] = packageInfo;
     }
diff --git a/cpp/watchdog/server/src/WatchdogInternalHandler.cpp b/cpp/watchdog/server/src/WatchdogInternalHandler.cpp
index 6da5fb5..be49ff9 100644
--- a/cpp/watchdog/server/src/WatchdogInternalHandler.cpp
+++ b/cpp/watchdog/server/src/WatchdogInternalHandler.cpp
@@ -268,6 +268,15 @@
     return Status::ok();
 }
 
+Status WatchdogInternalHandler::controlProcessHealthCheck(bool disable) {
+    Status status = checkSystemUser();
+    if (!status.isOk()) {
+        return status;
+    }
+    mWatchdogProcessService->setEnabled(!disable);
+    return Status::ok();
+}
+
 }  // namespace watchdog
 }  // namespace automotive
 }  // namespace android
diff --git a/cpp/watchdog/server/src/WatchdogInternalHandler.h b/cpp/watchdog/server/src/WatchdogInternalHandler.h
index a2b4892..e23620e 100644
--- a/cpp/watchdog/server/src/WatchdogInternalHandler.h
+++ b/cpp/watchdog/server/src/WatchdogInternalHandler.h
@@ -91,8 +91,9 @@
                     configs) override;
     android::binder::Status actionTakenOnResourceOveruse(
             const std::vector<
-                    android::automotive::watchdog::internal::PackageResourceOveruseAction>&
-                    actions);
+                    android::automotive::watchdog::internal::PackageResourceOveruseAction>& actions)
+            override;
+    android::binder::Status controlProcessHealthCheck(bool disable) override;
 
 protected:
     void terminate() {
diff --git a/cpp/watchdog/server/src/WatchdogProcessService.cpp b/cpp/watchdog/server/src/WatchdogProcessService.cpp
index 97a4047..fca1174 100644
--- a/cpp/watchdog/server/src/WatchdogProcessService.cpp
+++ b/cpp/watchdog/server/src/WatchdogProcessService.cpp
@@ -83,10 +83,11 @@
 const int32_t MSG_VHAL_WATCHDOG_ALIVE = static_cast<int>(TimeoutLength::TIMEOUT_NORMAL) + 1;
 const int32_t MSG_VHAL_HEALTH_CHECK = MSG_VHAL_WATCHDOG_ALIVE + 1;
 
-// VHAL sends heart beat every 3s. Car watchdog checks if there is the latest heart beat from VHAL
+// TODO(b/193742550): Restore the timeout to 3s after configuration by vendors is added.
+// VHAL sends heart beat every 6s. Car watchdog checks if there is the latest heart beat from VHAL
 // with 1s marginal time.
-constexpr std::chrono::nanoseconds kVhalHealthCheckDelayNs = 4s;
-constexpr int64_t kVhalHeartBeatIntervalMs = 3000;
+constexpr std::chrono::milliseconds kVhalHeartBeatIntervalMs = 6s;
+constexpr std::chrono::nanoseconds kVhalHealthCheckDelayNs = kVhalHeartBeatIntervalMs + 1s;
 
 constexpr const char kServiceName[] = "WatchdogProcessService";
 constexpr const char kVhalInterfaceName[] = "android.hardware.automotive.vehicle@2.0::IVehicle";
@@ -799,7 +800,7 @@
         Mutex::Autolock lock(mMutex);
         lastEventTime = mVhalHeartBeat.eventTime;
     }
-    if (currentUptime > lastEventTime + kVhalHeartBeatIntervalMs) {
+    if (currentUptime > lastEventTime + kVhalHeartBeatIntervalMs.count()) {
         ALOGW("VHAL failed to update heart beat within timeout. Terminating VHAL...");
         terminateVhal();
     }
diff --git a/cpp/watchdog/server/tests/IoOveruseConfigsTest.cpp b/cpp/watchdog/server/tests/IoOveruseConfigsTest.cpp
index 79b32fc..c03ad85 100644
--- a/cpp/watchdog/server/tests/IoOveruseConfigsTest.cpp
+++ b/cpp/watchdog/server/tests/IoOveruseConfigsTest.cpp
@@ -75,12 +75,14 @@
 
 PackageInfo constructPackageInfo(
         const char* packageName, const ComponentType componentType,
-        const ApplicationCategoryType appCategoryType = ApplicationCategoryType::OTHERS) {
+        const ApplicationCategoryType appCategoryType = ApplicationCategoryType::OTHERS,
+        const std::vector<std::string>& sharedUidPackages = std::vector<std::string>()) {
     PackageInfo packageInfo;
     packageInfo.packageIdentifier.name = packageName;
     packageInfo.uidType = UidType::APPLICATION;
     packageInfo.componentType = componentType;
     packageInfo.appCategoryType = appCategoryType;
+    packageInfo.sharedUidPackages = sharedUidPackages;
     return packageInfo;
 }
 
@@ -832,6 +834,28 @@
     EXPECT_THAT(actual, MEDIA_THRESHOLDS);
 }
 
+TEST_F(IoOveruseConfigsTest, TestFetchThresholdForSharedSystemPackages) {
+    const auto ioOveruseConfigs = sampleIoOveruseConfigs();
+    auto sampleSystemConfig = sampleUpdateSystemConfig();
+    auto& ioConfig = sampleSystemConfig.resourceSpecificConfigurations[0]
+                             .get<ResourceSpecificConfiguration::ioOveruseConfiguration>();
+    ioConfig.packageSpecificThresholds.push_back(
+            toPerStateIoOveruseThreshold("shared:systemSharedPackage",
+                                         toPerStateBytes(100, 200, 300)));
+
+    ioOveruseConfigs->update({sampleSystemConfig});
+
+    auto actual = ioOveruseConfigs->fetchThreshold(
+            constructPackageInfo("shared:systemSharedPackage", ComponentType::SYSTEM));
+
+    EXPECT_THAT(actual, toPerStateBytes(100, 200, 300));
+
+    actual = ioOveruseConfigs->fetchThreshold(
+            constructPackageInfo("systemSharedPackage", ComponentType::SYSTEM));
+
+    EXPECT_THAT(actual, SYSTEM_COMPONENT_LEVEL_THRESHOLDS);
+}
+
 TEST_F(IoOveruseConfigsTest, TestFetchThresholdForVendorPackages) {
     const auto ioOveruseConfigs = sampleIoOveruseConfigs();
 
@@ -853,6 +877,28 @@
     EXPECT_THAT(actual, MAPS_THRESHOLDS);
 }
 
+TEST_F(IoOveruseConfigsTest, TestFetchThresholdForSharedVendorPackages) {
+    const auto ioOveruseConfigs = sampleIoOveruseConfigs();
+    auto sampleVendorConfig = sampleUpdateVendorConfig();
+    auto& ioConfig = sampleVendorConfig.resourceSpecificConfigurations[0]
+                             .get<ResourceSpecificConfiguration::ioOveruseConfiguration>();
+    ioConfig.packageSpecificThresholds.push_back(
+            toPerStateIoOveruseThreshold("shared:vendorSharedPackage",
+                                         toPerStateBytes(100, 200, 300)));
+
+    ioOveruseConfigs->update({sampleVendorConfig});
+
+    auto actual = ioOveruseConfigs->fetchThreshold(
+            constructPackageInfo("shared:vendorSharedPackage", ComponentType::VENDOR));
+
+    EXPECT_THAT(actual, toPerStateBytes(100, 200, 300));
+
+    actual = ioOveruseConfigs->fetchThreshold(
+            constructPackageInfo("vendorSharedPackage", ComponentType::VENDOR));
+
+    EXPECT_THAT(actual, VENDOR_COMPONENT_LEVEL_THRESHOLDS);
+}
+
 TEST_F(IoOveruseConfigsTest, TestFetchThresholdForThirdPartyPackages) {
     const auto ioOveruseConfigs = sampleIoOveruseConfigs();
 
@@ -883,6 +929,36 @@
             constructPackageInfo("systemPackageA", ComponentType::SYSTEM)));
 }
 
+TEST_F(IoOveruseConfigsTest, TestIsSafeToKillSharedSystemPackages) {
+    auto sampleSystemConfig = sampleUpdateSystemConfig();
+    sampleSystemConfig.safeToKillPackages.push_back("sharedUidSystemPackageC");
+    sampleSystemConfig.safeToKillPackages.push_back("shared:systemSharedPackageD");
+    sp<IoOveruseConfigs> ioOveruseConfigs = new IoOveruseConfigs();
+
+    EXPECT_RESULT_OK(ioOveruseConfigs->update({sampleSystemConfig}));
+
+    PackageInfo packageInfo =
+            constructPackageInfo("systemSharedPackage", ComponentType::SYSTEM,
+                                 ApplicationCategoryType::OTHERS,
+                                 {"sharedUidSystemPackageA", "sharedUidSystemPackageB",
+                                  "sharedUidSystemPackageC"});
+
+    EXPECT_TRUE(ioOveruseConfigs->isSafeToKill(packageInfo))
+            << "Should be safe-to-kill when at least one package under shared UID is safe-to-kill";
+
+    packageInfo =
+            constructPackageInfo("shared:systemSharedPackageD", ComponentType::SYSTEM,
+                                 ApplicationCategoryType::OTHERS, {"sharedUidSystemPackageA"});
+    EXPECT_TRUE(ioOveruseConfigs->isSafeToKill(packageInfo))
+            << "Should be safe-to-kill when shared package is safe-to-kill";
+
+    packageInfo =
+            constructPackageInfo("systemSharedPackageD", ComponentType::SYSTEM,
+                                 ApplicationCategoryType::OTHERS, {"sharedUidSystemPackageA"});
+    EXPECT_FALSE(ioOveruseConfigs->isSafeToKill(packageInfo))
+            << "Shouldn't be safe-to-kill when the 'shared:' prefix is missing";
+}
+
 TEST_F(IoOveruseConfigsTest, TestIsSafeToKillVendorPackages) {
     const auto ioOveruseConfigs = sampleIoOveruseConfigs();
     EXPECT_FALSE(ioOveruseConfigs->isSafeToKill(
@@ -892,6 +968,40 @@
             constructPackageInfo("vendorPackageA", ComponentType::VENDOR)));
 }
 
+TEST_F(IoOveruseConfigsTest, TestIsSafeToKillSharedVendorPackages) {
+    auto sampleVendorConfig = sampleUpdateVendorConfig();
+    sampleVendorConfig.safeToKillPackages.push_back("sharedUidVendorPackageC");
+    sampleVendorConfig.safeToKillPackages.push_back("shared:vendorSharedPackageD");
+
+    auto sampleSystemConfig = sampleUpdateSystemConfig();
+    sampleSystemConfig.safeToKillPackages.push_back("sharedUidSystemPackageC");
+
+    sp<IoOveruseConfigs> ioOveruseConfigs = new IoOveruseConfigs();
+
+    EXPECT_RESULT_OK(ioOveruseConfigs->update({sampleSystemConfig, sampleVendorConfig}));
+
+    PackageInfo packageInfo =
+            constructPackageInfo("vendorSharedPackage", ComponentType::VENDOR,
+                                 ApplicationCategoryType::OTHERS,
+                                 {"sharedUidVendorPackageA", "sharedUidVendorPackageB",
+                                  "sharedUidVendorPackageC"});
+
+    EXPECT_TRUE(ioOveruseConfigs->isSafeToKill(packageInfo))
+            << "Should be safe-to-kill when at least one package under shared UID is safe-to-kill";
+
+    packageInfo =
+            constructPackageInfo("shared:vendorSharedPackageD", ComponentType::VENDOR,
+                                 ApplicationCategoryType::OTHERS, {"sharedUidVendorPackageA"});
+    EXPECT_TRUE(ioOveruseConfigs->isSafeToKill(packageInfo))
+            << "Should be safe-to-kill when shared package is safe-to-kill";
+
+    packageInfo =
+            constructPackageInfo("shared:vendorSharedPackageE", ComponentType::VENDOR,
+                                 ApplicationCategoryType::OTHERS, {"sharedUidVendorPackageA"});
+    EXPECT_FALSE(ioOveruseConfigs->isSafeToKill(packageInfo))
+            << "Shouldn't be safe-to-kill when the 'shared:' prefix is missing";
+}
+
 TEST_F(IoOveruseConfigsTest, TestIsSafeToKillThirdPartyPackages) {
     const auto ioOveruseConfigs = sampleIoOveruseConfigs();
     EXPECT_TRUE(ioOveruseConfigs->isSafeToKill(
@@ -931,6 +1041,32 @@
                 UnorderedElementsAre("vendorPackage", "vendorPkgB"));
 }
 
+TEST_F(IoOveruseConfigsTest, TestVendorPackagePrefixesWithSharedPackages) {
+    auto sampleVendorConfig = sampleUpdateVendorConfig();
+    sampleVendorConfig.vendorPackagePrefixes.push_back("shared:vendorSharedPackage");
+    sampleVendorConfig.safeToKillPackages.push_back("sharedUidVendorPackageD");
+    sampleVendorConfig.safeToKillPackages.push_back("shared:vendorSharedPackageE");
+    sampleVendorConfig.safeToKillPackages.push_back("shared:vndrSharedPkgF");
+
+    auto& ioConfig = sampleVendorConfig.resourceSpecificConfigurations[0]
+                             .get<ResourceSpecificConfiguration::ioOveruseConfiguration>();
+
+    ioConfig.packageSpecificThresholds.push_back(
+            toPerStateIoOveruseThreshold("shared:vendorSharedPackageG",
+                                         VENDOR_PACKAGE_A_THRESHOLDS));
+    ioConfig.packageSpecificThresholds.push_back(
+            toPerStateIoOveruseThreshold("shared:vndrSharedPkgH", VENDOR_PACKAGE_A_THRESHOLDS));
+
+    sp<IoOveruseConfigs> ioOveruseConfigs = new IoOveruseConfigs();
+
+    EXPECT_RESULT_OK(ioOveruseConfigs->update({sampleVendorConfig}));
+
+    EXPECT_THAT(ioOveruseConfigs->vendorPackagePrefixes(),
+                UnorderedElementsAre("vendorPackage", "vendorPkgB", "shared:vendorSharedPackage",
+                                     "sharedUidVendorPackageD", "shared:vndrSharedPkgF",
+                                     "shared:vndrSharedPkgH"));
+}
+
 TEST_F(IoOveruseConfigsTest, TestPackagesToAppCategoriesWithSystemConfig) {
     IoOveruseConfigs ioOveruseConfigs;
     const auto resourceOveruseConfig = sampleUpdateSystemConfig();
diff --git a/cpp/watchdog/server/tests/PackageInfoResolverTest.cpp b/cpp/watchdog/server/tests/PackageInfoResolverTest.cpp
index f2fe3a6..cf282be 100644
--- a/cpp/watchdog/server/tests/PackageInfoResolverTest.cpp
+++ b/cpp/watchdog/server/tests/PackageInfoResolverTest.cpp
@@ -195,6 +195,8 @@
             // system.package.B is native package so this should be ignored.
             {"system.package.B", ApplicationCategoryType::MAPS},
             {"vendor.package.A", ApplicationCategoryType::MEDIA},
+            {"shared:vendor.package.C", ApplicationCategoryType::MEDIA},
+            {"vendor.package.shared.uid.D", ApplicationCategoryType::MAPS},
     };
     peer.setPackageConfigurations({"vendor.pkg"}, packagesToAppCategories);
     /*
@@ -213,23 +215,38 @@
                                   ApplicationCategoryType::OTHERS)},
             {15100,
              constructPackageInfo("vendor.package.A", 15100, UidType::APPLICATION,
-                                  ComponentType::VENDOR, ApplicationCategoryType::MEDIA)},
+                                  ComponentType::VENDOR, ApplicationCategoryType::OTHERS)},
             {16700,
              constructPackageInfo("vendor.pkg", 16700, UidType::NATIVE, ComponentType::VENDOR,
                                   ApplicationCategoryType::OTHERS)},
+            {18100,
+             constructPackageInfo("shared:vendor.package.C", 18100, UidType::APPLICATION,
+                                  ComponentType::VENDOR, ApplicationCategoryType::OTHERS)},
+            {19100,
+             constructPackageInfo("shared:vendor.package.D", 19100, UidType::APPLICATION,
+                                  ComponentType::VENDOR, ApplicationCategoryType::OTHERS,
+                                  {"vendor.package.shared.uid.D"})},
     };
 
-    std::vector<int32_t> expectedUids = {6100, 7700, 15100, 16700};
+    std::vector<int32_t> expectedUids = {6100, 7700, 15100, 16700, 18100, 19100};
     std::vector<std::string> expectedPrefixes = {"vendor.pkg"};
     std::vector<PackageInfo> injectPackageInfos = {expectedMappings.at(6100),
                                                    expectedMappings.at(7700),
                                                    expectedMappings.at(15100),
-                                                   expectedMappings.at(16700)};
+                                                   expectedMappings.at(16700),
+                                                   expectedMappings.at(18100),
+                                                   expectedMappings.at(19100)};
+
+    expectedMappings.at(15100).appCategoryType = ApplicationCategoryType::MEDIA;
+    expectedMappings.at(18100).appCategoryType = ApplicationCategoryType::MEDIA;
+    expectedMappings.at(19100).appCategoryType = ApplicationCategoryType::MAPS;
+
     EXPECT_CALL(*peer.mockWatchdogServiceHelper,
                 getPackageInfosForUids(expectedUids, expectedPrefixes, _))
             .WillOnce(DoAll(SetArgPointee<2>(injectPackageInfos), Return(binder::Status::ok())));
 
-    auto actualMappings = packageInfoResolver->getPackageInfosForUids({6100, 7700, 15100, 16700});
+    auto actualMappings =
+            packageInfoResolver->getPackageInfosForUids({6100, 7700, 15100, 16700, 18100, 19100});
 
     EXPECT_THAT(actualMappings, UnorderedElementsAreArray(expectedMappings))
             << "Expected: " << toString(expectedMappings)
diff --git a/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp b/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp
index efa9eef..4646a11 100644
--- a/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp
+++ b/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp
@@ -470,6 +470,19 @@
     ASSERT_FALSE(status.isOk()) << status;
 }
 
+TEST_F(WatchdogInternalHandlerTest, TestControlProcessHealthCheck) {
+    setSystemCallingUid();
+    EXPECT_CALL(*mMockWatchdogProcessService, setEnabled(/*isEnabled=*/true)).Times(1);
+    Status status = mWatchdogInternalHandler->controlProcessHealthCheck(false);
+    ASSERT_TRUE(status.isOk()) << status;
+}
+
+TEST_F(WatchdogInternalHandlerTest, TestErrorOnControlProcessHealthCheckWithNonSystemCallingUid) {
+    EXPECT_CALL(*mMockWatchdogProcessService, setEnabled(_)).Times(0);
+    Status status = mWatchdogInternalHandler->controlProcessHealthCheck(false);
+    ASSERT_FALSE(status.isOk()) << status;
+}
+
 }  // namespace watchdog
 }  // namespace automotive
 }  // namespace android
diff --git a/packages/CarDeveloperOptions/Android.bp b/packages/CarDeveloperOptions/Android.bp
index e3b559a..4394ed2 100644
--- a/packages/CarDeveloperOptions/Android.bp
+++ b/packages/CarDeveloperOptions/Android.bp
@@ -38,4 +38,8 @@
     // TODO(b/176240706): "org.apache.http.legacy" is used by Settings-core,
     // get rid of this dependency and remove the "uses_libs" property.
     uses_libs: ["org.apache.http.legacy"],
+    optional_uses_libs: [
+        "androidx.window.extensions",
+        "androidx.window.sidecar",
+    ],
 }
diff --git a/packages/CarDeveloperOptions/AndroidManifest.xml b/packages/CarDeveloperOptions/AndroidManifest.xml
index cd61380..db784d2 100644
--- a/packages/CarDeveloperOptions/AndroidManifest.xml
+++ b/packages/CarDeveloperOptions/AndroidManifest.xml
@@ -25,6 +25,9 @@
                  tools:node="merge"
                  tools:replace="android:label">
 
+        <uses-library android:name="androidx.window.extensions" android:required="false"/>
+        <uses-library android:name="androidx.window.sidecar" android:required="false"/>
+
         <activity
             android:name=".CarDevelopmentSettingsDashboardActivity"
             android:enabled="false"
diff --git a/packages/ScriptExecutor/Android.bp b/packages/ScriptExecutor/Android.bp
new file mode 100644
index 0000000..af0d132
--- /dev/null
+++ b/packages/ScriptExecutor/Android.bp
@@ -0,0 +1,126 @@
+// 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"],
+}
+
+cc_defaults {
+    name: "scriptexecutor_defaults",
+
+    cflags: [
+        "-Wno-unused-parameter",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+
+    static_libs: [
+        "liblua",
+    ],
+}
+
+cc_library {
+    name: "libscriptexecutor",
+
+    defaults: [
+        "scriptexecutor_defaults",
+    ],
+
+    srcs: [
+        ":iscriptexecutorconstants_aidl",
+        "src/BundleWrapper.cpp",
+        "src/JniUtils.cpp",
+        "src/LuaEngine.cpp",
+        "src/ScriptExecutorListener.cpp",
+    ],
+
+    shared_libs: [
+        "libbinder",
+        "libnativehelper",
+        "libutils",
+    ],
+
+    // Allow dependents to use the header files.
+    export_include_dirs: ["src"],
+}
+
+cc_library {
+    name: "libscriptexecutorjni",
+
+    defaults: [
+        "scriptexecutor_defaults",
+    ],
+
+    srcs: [
+        "src/ScriptExecutorJni.cpp",
+    ],
+
+    shared_libs: [
+        "libnativehelper",
+        "libscriptexecutor",
+    ],
+}
+
+android_app {
+    name: "ScriptExecutor",
+
+    srcs: [
+        ":iscriptexecutor_aidl",
+        "src/**/*.java"
+    ],
+
+    resource_dirs: ["res"],
+
+    // TODO(197006437): Make this build against sdk_version: "module_current" instead.
+    platform_apis: true,
+
+    privileged: false,
+
+    // TODO(b/196053524): Enable optimization.
+    optimize: {
+        enabled: false,
+    },
+
+    aidl: {
+        include_dirs: [
+            // TODO(b/198195711): Remove once we compile against SDK.
+	    "frameworks/native/aidl/binder", // For PersistableBundle.aidl
+	],
+    },
+
+    jni_libs: [
+        "libscriptexecutorjni",
+    ]
+}
+
+java_test_helper_library {
+    name: "scriptexecutor-test-lib",
+
+    srcs: [
+        ":iscriptexecutor_aidl",
+        ":iscriptexecutorconstants_aidl",
+        "src/**/*.java",
+    ],
+
+    aidl: {
+        include_dirs: [
+            // TODO(b/198195711): Remove once we compile against SDK.
+	    "frameworks/native/aidl/binder", // For PersistableBundle.aidl
+	],
+    },
+}
+
diff --git a/packages/ScriptExecutor/AndroidManifest.xml b/packages/ScriptExecutor/AndroidManifest.xml
new file mode 100644
index 0000000..51595fa
--- /dev/null
+++ b/packages/ScriptExecutor/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.car.scriptexecutor">
+
+    <application android:label="@string/app_title"
+         android:allowBackup="false">
+
+        <service android:name=".ScriptExecutor"
+            android:exported="true"
+            android:isolatedProcess="true"/>
+    </application>
+</manifest>
diff --git a/packages/ScriptExecutor/res/values/strings.xml b/packages/ScriptExecutor/res/values/strings.xml
new file mode 100644
index 0000000..39ca8b2
--- /dev/null
+++ b/packages/ScriptExecutor/res/values/strings.xml
@@ -0,0 +1,18 @@
+<?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>
+    <string name="app_title" translatable="false">Script executor service</string>
+</resources>
diff --git a/packages/ScriptExecutor/src/BundleWrapper.cpp b/packages/ScriptExecutor/src/BundleWrapper.cpp
new file mode 100644
index 0000000..d447d34
--- /dev/null
+++ b/packages/ScriptExecutor/src/BundleWrapper.cpp
@@ -0,0 +1,79 @@
+/*
+ * 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 "BundleWrapper.h"
+
+#include <android-base/logging.h>
+
+namespace com {
+namespace android {
+namespace car {
+namespace scriptexecutor {
+
+BundleWrapper::BundleWrapper(JNIEnv* env) {
+    mJNIEnv = env;
+    mBundleClass = static_cast<jclass>(
+            mJNIEnv->NewGlobalRef(mJNIEnv->FindClass("android/os/PersistableBundle")));
+    jmethodID bundleConstructor = mJNIEnv->GetMethodID(mBundleClass, "<init>", "()V");
+    mBundle = mJNIEnv->NewGlobalRef(mJNIEnv->NewObject(mBundleClass, bundleConstructor));
+}
+
+BundleWrapper::~BundleWrapper() {
+    // Delete global JNI references.
+    if (mBundle != NULL) {
+        mJNIEnv->DeleteGlobalRef(mBundle);
+    }
+    if (mBundleClass != NULL) {
+        mJNIEnv->DeleteGlobalRef(mBundleClass);
+    }
+}
+
+void BundleWrapper::putBoolean(const char* key, bool value) {
+    // TODO(b/188832769): consider caching the references.
+    jmethodID putBooleanMethod =
+            mJNIEnv->GetMethodID(mBundleClass, "putBoolean", "(Ljava/lang/String;Z)V");
+    mJNIEnv->CallVoidMethod(mBundle, putBooleanMethod, mJNIEnv->NewStringUTF(key),
+                            static_cast<jboolean>(value));
+}
+
+void BundleWrapper::putInteger(const char* key, int value) {
+    jmethodID putIntMethod = mJNIEnv->GetMethodID(mBundleClass, "putInt", "(Ljava/lang/String;I)V");
+    mJNIEnv->CallVoidMethod(mBundle, putIntMethod, mJNIEnv->NewStringUTF(key),
+                            static_cast<jint>(value));
+}
+
+void BundleWrapper::putDouble(const char* key, double value) {
+    jmethodID putDoubleMethod =
+            mJNIEnv->GetMethodID(mBundleClass, "putDouble", "(Ljava/lang/String;D)V");
+    mJNIEnv->CallVoidMethod(mBundle, putDoubleMethod, mJNIEnv->NewStringUTF(key),
+                            static_cast<jdouble>(value));
+}
+
+void BundleWrapper::putString(const char* key, const char* value) {
+    jmethodID putStringMethod = mJNIEnv->GetMethodID(mBundleClass, "putString",
+                                                     "(Ljava/lang/String;Ljava/lang/String;)V");
+    mJNIEnv->CallVoidMethod(mBundle, putStringMethod, mJNIEnv->NewStringUTF(key),
+                            mJNIEnv->NewStringUTF(value));
+}
+
+jobject BundleWrapper::getBundle() {
+    return mBundle;
+}
+
+}  // namespace scriptexecutor
+}  // namespace car
+}  // namespace android
+}  // namespace com
diff --git a/packages/ScriptExecutor/src/BundleWrapper.h b/packages/ScriptExecutor/src/BundleWrapper.h
new file mode 100644
index 0000000..4a0f9bb
--- /dev/null
+++ b/packages/ScriptExecutor/src/BundleWrapper.h
@@ -0,0 +1,64 @@
+/*
+ * 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 PACKAGES_SCRIPTEXECUTOR_SRC_BUNDLEWRAPPER_H_
+#define PACKAGES_SCRIPTEXECUTOR_SRC_BUNDLEWRAPPER_H_
+
+#include "jni.h"
+
+namespace com {
+namespace android {
+namespace car {
+namespace scriptexecutor {
+
+// Used to create a java bundle object and populate its fields one at a time.
+class BundleWrapper {
+public:
+    explicit BundleWrapper(JNIEnv* env);
+    // BundleWrapper is not copyable.
+    BundleWrapper(const BundleWrapper&) = delete;
+    BundleWrapper& operator=(const BundleWrapper&) = delete;
+
+    virtual ~BundleWrapper();
+
+    // Family of methods that puts the provided 'value' into the PersistableBundle
+    // under provided 'key'.
+    void putBoolean(const char* key, bool value);
+    void putInteger(const char* key, int value);
+    void putDouble(const char* key, double value);
+    void putString(const char* key, const char* value);
+
+    jobject getBundle();
+
+private:
+    // The class asks Java to create PersistableBundle object and stores the reference.
+    // When the instance of this class is destroyed the actual Java PersistableBundle object behind
+    // this reference stays on and is managed by Java.
+    jobject mBundle;
+
+    // Reference to java PersistableBundle class cached for performance reasons.
+    jclass mBundleClass;
+
+    // Stores a JNIEnv* pointer.
+    JNIEnv* mJNIEnv;  // not owned
+};
+
+}  // namespace scriptexecutor
+}  // namespace car
+}  // namespace android
+}  // namespace com
+
+#endif  // PACKAGES_SCRIPTEXECUTOR_SRC_BUNDLEWRAPPER_H_
diff --git a/packages/ScriptExecutor/src/JniUtils.cpp b/packages/ScriptExecutor/src/JniUtils.cpp
new file mode 100644
index 0000000..e943129
--- /dev/null
+++ b/packages/ScriptExecutor/src/JniUtils.cpp
@@ -0,0 +1,96 @@
+/*
+ * 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 "JniUtils.h"
+
+namespace com {
+namespace android {
+namespace car {
+namespace scriptexecutor {
+
+void pushBundleToLuaTable(JNIEnv* env, LuaEngine* luaEngine, jobject bundle) {
+    lua_newtable(luaEngine->getLuaState());
+    // null bundle object is allowed. We will treat it as an empty table.
+    if (bundle == nullptr) {
+        return;
+    }
+
+    // TODO(b/188832769): Consider caching some of these JNI references for
+    // performance reasons.
+    jclass persistableBundleClass = env->FindClass("android/os/PersistableBundle");
+    jmethodID getKeySetMethod =
+            env->GetMethodID(persistableBundleClass, "keySet", "()Ljava/util/Set;");
+    jobject keys = env->CallObjectMethod(bundle, getKeySetMethod);
+    jclass setClass = env->FindClass("java/util/Set");
+    jmethodID iteratorMethod = env->GetMethodID(setClass, "iterator", "()Ljava/util/Iterator;");
+    jobject keySetIteratorObject = env->CallObjectMethod(keys, iteratorMethod);
+
+    jclass iteratorClass = env->FindClass("java/util/Iterator");
+    jmethodID hasNextMethod = env->GetMethodID(iteratorClass, "hasNext", "()Z");
+    jmethodID nextMethod = env->GetMethodID(iteratorClass, "next", "()Ljava/lang/Object;");
+
+    jclass booleanClass = env->FindClass("java/lang/Boolean");
+    jclass integerClass = env->FindClass("java/lang/Integer");
+    jclass numberClass = env->FindClass("java/lang/Number");
+    jclass stringClass = env->FindClass("java/lang/String");
+    // TODO(b/188816922): Handle more types such as float and integer arrays,
+    // and perhaps nested Bundles.
+
+    jmethodID getMethod = env->GetMethodID(persistableBundleClass, "get",
+                                           "(Ljava/lang/String;)Ljava/lang/Object;");
+
+    // Iterate over key set of the bundle one key at a time.
+    while (env->CallBooleanMethod(keySetIteratorObject, hasNextMethod)) {
+        // Read the value object that corresponds to this key.
+        jstring key = (jstring)env->CallObjectMethod(keySetIteratorObject, nextMethod);
+        jobject value = env->CallObjectMethod(bundle, getMethod, key);
+
+        // Get the value of the type, extract it accordingly from the bundle and
+        // push the extracted value and the key to the Lua table.
+        if (env->IsInstanceOf(value, booleanClass)) {
+            jmethodID boolMethod = env->GetMethodID(booleanClass, "booleanValue", "()Z");
+            bool boolValue = static_cast<bool>(env->CallBooleanMethod(value, boolMethod));
+            lua_pushboolean(luaEngine->getLuaState(), boolValue);
+        } else if (env->IsInstanceOf(value, integerClass)) {
+            jmethodID intMethod = env->GetMethodID(integerClass, "intValue", "()I");
+            lua_pushinteger(luaEngine->getLuaState(), env->CallIntMethod(value, intMethod));
+        } else if (env->IsInstanceOf(value, numberClass)) {
+            // Condense other numeric types using one class. Because lua supports only
+            // integer or double, and we handled integer in previous if clause.
+            jmethodID numberMethod = env->GetMethodID(numberClass, "doubleValue", "()D");
+            /* Pushes a double onto the stack */
+            lua_pushnumber(luaEngine->getLuaState(), env->CallDoubleMethod(value, numberMethod));
+        } else if (env->IsInstanceOf(value, stringClass)) {
+            const char* rawStringValue = env->GetStringUTFChars((jstring)value, nullptr);
+            lua_pushstring(luaEngine->getLuaState(), rawStringValue);
+            env->ReleaseStringUTFChars((jstring)value, rawStringValue);
+        } else {
+            // Other types are not implemented yet, skipping.
+            continue;
+        }
+
+        const char* rawKey = env->GetStringUTFChars(key, nullptr);
+        // table[rawKey] = value, where value is on top of the stack,
+        // and the table is the next element in the stack.
+        lua_setfield(luaEngine->getLuaState(), /* idx= */ -2, rawKey);
+        env->ReleaseStringUTFChars(key, rawKey);
+    }
+}
+
+}  // namespace scriptexecutor
+}  // namespace car
+}  // namespace android
+}  // namespace com
diff --git a/packages/ScriptExecutor/src/JniUtils.h b/packages/ScriptExecutor/src/JniUtils.h
new file mode 100644
index 0000000..57aeb9f
--- /dev/null
+++ b/packages/ScriptExecutor/src/JniUtils.h
@@ -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.
+ */
+#ifndef PACKAGES_SCRIPTEXECUTOR_SRC_JNIUTILS_H_
+#define PACKAGES_SCRIPTEXECUTOR_SRC_JNIUTILS_H_
+
+#include "LuaEngine.h"
+#include "jni.h"
+
+namespace com {
+namespace android {
+namespace car {
+namespace scriptexecutor {
+
+// Helper function which takes android.os.Bundle object in "bundle" argument
+// and converts it to Lua table on top of Lua stack. All key-value pairs are
+// converted to the corresponding key-value pairs of the Lua table as long as
+// the Bundle value types are supported. At this point, we support boolean,
+// integer, double and String types in Java.
+void pushBundleToLuaTable(JNIEnv* env, LuaEngine* luaEngine, jobject bundle);
+
+}  // namespace scriptexecutor
+}  // namespace car
+}  // namespace android
+}  // namespace com
+
+#endif  // PACKAGES_SCRIPTEXECUTOR_SRC_JNIUTILS_H_
diff --git a/packages/ScriptExecutor/src/LuaEngine.cpp b/packages/ScriptExecutor/src/LuaEngine.cpp
new file mode 100644
index 0000000..cf418bf
--- /dev/null
+++ b/packages/ScriptExecutor/src/LuaEngine.cpp
@@ -0,0 +1,209 @@
+/*
+ * 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 "LuaEngine.h"
+
+#include "BundleWrapper.h"
+
+#include <android-base/logging.h>
+#include <com/android/car/telemetry/scriptexecutorinterface/IScriptExecutorConstants.h>
+
+#include <utility>
+
+extern "C" {
+#include "lauxlib.h"
+#include "lua.h"
+#include "lualib.h"
+}
+
+namespace com {
+namespace android {
+namespace car {
+namespace scriptexecutor {
+
+using ::com::android::car::telemetry::scriptexecutorinterface::IScriptExecutorConstants;
+
+namespace {
+
+enum LuaNumReturnedResults {
+    ZERO_RETURNED_RESULTS = 0,
+};
+
+// Helper method that goes over Lua table fields one by one and populates PersistableBundle
+// object wrapped in BundleWrapper.
+// It is assumed that Lua table is located on top of the Lua stack.
+void convertLuaTableToBundle(lua_State* lua, BundleWrapper* bundleWrapper) {
+    // Iterate over Lua table which is expected to be at the top of Lua stack.
+    // lua_next call pops the key from the top of the stack and finds the next
+    // key-value pair. It returns 0 if the next pair was not found.
+    // More on lua_next in: https://www.lua.org/manual/5.3/manual.html#lua_next
+    lua_pushnil(lua);  // First key is a null value.
+    while (lua_next(lua, /* index = */ -2) != 0) {
+        //  'key' is at index -2 and 'value' is at index -1
+        // -1 index is the top of the stack.
+        // remove 'value' and keep 'key' for next iteration
+        // Process each key-value depending on a type and push it to Java PersistableBundle.
+        const char* key = lua_tostring(lua, /* index = */ -2);
+        if (lua_isboolean(lua, /* index = */ -1)) {
+            bundleWrapper->putBoolean(key, static_cast<bool>(lua_toboolean(lua, /* index = */ -1)));
+        } else if (lua_isinteger(lua, /* index = */ -1)) {
+            bundleWrapper->putInteger(key, static_cast<int>(lua_tointeger(lua, /* index = */ -1)));
+        } else if (lua_isnumber(lua, /* index = */ -1)) {
+            bundleWrapper->putDouble(key, static_cast<double>(lua_tonumber(lua, /* index = */ -1)));
+        } else if (lua_isstring(lua, /* index = */ -1)) {
+            bundleWrapper->putString(key, lua_tostring(lua, /* index = */ -1));
+        } else {
+            // not supported yet...
+            LOG(WARNING) << "key=" << key << " has a Lua type which is not supported yet. "
+                         << "The bundle object will not have this key-value pair.";
+        }
+        // Pop value from the stack, keep the key for the next iteration.
+        lua_pop(lua, 1);
+        // The key is at index -1, the table is at index -2 now.
+    }
+}
+
+}  // namespace
+
+ScriptExecutorListener* LuaEngine::sListener = nullptr;
+
+LuaEngine::LuaEngine() {
+    // Instantiate Lua environment
+    mLuaState = luaL_newstate();
+    luaL_openlibs(mLuaState);
+}
+
+LuaEngine::~LuaEngine() {
+    lua_close(mLuaState);
+}
+
+lua_State* LuaEngine::getLuaState() {
+    return mLuaState;
+}
+
+void LuaEngine::resetListener(ScriptExecutorListener* listener) {
+    if (sListener != nullptr) {
+        delete sListener;
+    }
+    sListener = listener;
+}
+
+int LuaEngine::loadScript(const char* scriptBody) {
+    // As the first step in Lua script execution we want to load
+    // the body of the script into Lua stack and have it processed by Lua
+    // to catch any errors.
+    // More on luaL_dostring: https://www.lua.org/manual/5.3/manual.html#lual_dostring
+    // If error, pushes the error object into the stack.
+    const auto status = luaL_dostring(mLuaState, scriptBody);
+    if (status) {
+        // Removes error object from the stack.
+        // Lua stack must be properly maintained due to its limited size,
+        // ~20 elements and its critical function because all interaction with
+        // Lua happens via the stack.
+        // Starting read about Lua stack: https://www.lua.org/pil/24.2.html
+        // TODO(b/192284232): add test case to trigger this.
+        lua_pop(mLuaState, 1);
+        return status;
+    }
+
+    // Register limited set of reserved methods for Lua to call native side.
+    lua_register(mLuaState, "on_success", LuaEngine::onSuccess);
+    lua_register(mLuaState, "on_script_finished", LuaEngine::onScriptFinished);
+    lua_register(mLuaState, "on_error", LuaEngine::onError);
+    return status;
+}
+
+bool LuaEngine::pushFunction(const char* functionName) {
+    // Interaction between native code and Lua happens via Lua stack.
+    // In such model, a caller first pushes the name of the function
+    // that needs to be called, followed by the function's input
+    // arguments, one input value pushed at a time.
+    // More info: https://www.lua.org/pil/24.2.html
+    lua_getglobal(mLuaState, functionName);
+    const auto status = lua_isfunction(mLuaState, /*idx= */ -1);
+    // TODO(b/192284785): add test case for wrong function name in Lua.
+    if (status == 0) lua_pop(mLuaState, 1);
+    return status;
+}
+
+int LuaEngine::run() {
+    // Performs blocking call of the provided Lua function. Assumes all
+    // input arguments are in the Lua stack as well in proper order.
+    // On how to call Lua functions: https://www.lua.org/pil/25.2.html
+    // Doc on lua_pcall: https://www.lua.org/manual/5.3/manual.html#lua_pcall
+    // TODO(b/189241508): Once we implement publishedData parsing, nargs should
+    // change from 1 to 2.
+    // TODO(b/192284612): add test case for failed call.
+    return lua_pcall(mLuaState, /* nargs= */ 1, /* nresults= */ 0, /*errfunc= */ 0);
+}
+
+int LuaEngine::onSuccess(lua_State* lua) {
+    // Any script we run can call on_success only with a single argument of Lua table type.
+    if (lua_gettop(lua) != 1 || !lua_istable(lua, /* index =*/-1)) {
+        sListener->onError(IScriptExecutorConstants::ERROR_TYPE_LUA_SCRIPT_ERROR,
+                           "on_success can push only a single parameter from Lua - a Lua table",
+                           "");
+        return ZERO_RETURNED_RESULTS;
+    }
+
+    // Helper object to create and populate Java PersistableBundle object.
+    BundleWrapper bundleWrapper(sListener->getCurrentJNIEnv());
+    convertLuaTableToBundle(lua, &bundleWrapper);
+
+    // Forward the populated Bundle object to Java callback.
+    sListener->onSuccess(bundleWrapper.getBundle());
+    // We explicitly must tell Lua how many results we return, which is 0 in this case.
+    // More on the topic: https://www.lua.org/manual/5.3/manual.html#lua_CFunction
+    return ZERO_RETURNED_RESULTS;
+}
+
+int LuaEngine::onScriptFinished(lua_State* lua) {
+    // Any script we run can call on_success only with a single argument of Lua table type.
+    if (lua_gettop(lua) != 1 || !lua_istable(lua, /* index =*/-1)) {
+        sListener->onError(IScriptExecutorConstants::ERROR_TYPE_LUA_SCRIPT_ERROR,
+                           "on_script_finished can push only a single parameter from Lua - a Lua "
+                           "table",
+                           "");
+        return ZERO_RETURNED_RESULTS;
+    }
+
+    // Helper object to create and populate Java PersistableBundle object.
+    BundleWrapper bundleWrapper(sListener->getCurrentJNIEnv());
+    convertLuaTableToBundle(lua, &bundleWrapper);
+
+    // Forward the populated Bundle object to Java callback.
+    sListener->onScriptFinished(bundleWrapper.getBundle());
+    // We explicitly must tell Lua how many results we return, which is 0 in this case.
+    // More on the topic: https://www.lua.org/manual/5.3/manual.html#lua_CFunction
+    return ZERO_RETURNED_RESULTS;
+}
+
+int LuaEngine::onError(lua_State* lua) {
+    // Any script we run can call on_error only with a single argument of Lua string type.
+    if (lua_gettop(lua) != 1 || !lua_isstring(lua, /* index = */ -1)) {
+        sListener->onError(IScriptExecutorConstants::ERROR_TYPE_LUA_SCRIPT_ERROR,
+                           "on_error can push only a single string parameter from Lua", "");
+        return ZERO_RETURNED_RESULTS;
+    }
+    sListener->onError(IScriptExecutorConstants::ERROR_TYPE_LUA_SCRIPT_ERROR,
+                       lua_tostring(lua, /* index = */ -1), /* stackTrace =*/"");
+    return ZERO_RETURNED_RESULTS;
+}
+
+}  // namespace scriptexecutor
+}  // namespace car
+}  // namespace android
+}  // namespace com
diff --git a/packages/ScriptExecutor/src/LuaEngine.h b/packages/ScriptExecutor/src/LuaEngine.h
new file mode 100644
index 0000000..0774108
--- /dev/null
+++ b/packages/ScriptExecutor/src/LuaEngine.h
@@ -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.
+ */
+
+#ifndef PACKAGES_SCRIPTEXECUTOR_SRC_LUAENGINE_H_
+#define PACKAGES_SCRIPTEXECUTOR_SRC_LUAENGINE_H_
+
+#include "ScriptExecutorListener.h"
+
+#include <memory>
+
+extern "C" {
+#include "lua.h"
+}
+
+namespace com {
+namespace android {
+namespace car {
+namespace scriptexecutor {
+
+// Encapsulates Lua script execution environment.
+class LuaEngine {
+public:
+    LuaEngine();
+
+    virtual ~LuaEngine();
+
+    // Returns pointer to Lua state object.
+    lua_State* getLuaState();
+
+    // Loads Lua script provided as scriptBody string.
+    // Returns 0 if successful. Otherwise returns non-zero Lua error code.
+    int loadScript(const char* scriptBody);
+
+    // Pushes a Lua function under provided name into the stack.
+    // Returns true if successful.
+    bool pushFunction(const char* functionName);
+
+    // Invokes function with the inputs provided in the stack.
+    // Assumes that the script body has been already loaded and successfully
+    // compiled and run, and all input arguments, and the function have been
+    // pushed to the stack.
+    // Returns 0 if successful. Otherwise returns non-zero Lua error code.
+    int run();
+
+    // Updates stored listener and destroys the previous one.
+    static void resetListener(ScriptExecutorListener* listener);
+
+private:
+    // Invoked by a running Lua script to store intermediate results.
+    // The script will provide the results as a Lua table.
+    // We currently support only non-nested fields in the table and the fields can be the following
+    // Lua types: boolean, number, integer, and string.
+    // The result pushed by Lua is converted to PersistableBundle and forwarded to
+    // ScriptExecutor service via callback interface.
+    // This method returns 0 to indicate that no results were pushed to Lua stack according
+    // to Lua C function calling convention.
+    // More info: https://www.lua.org/manual/5.3/manual.html#lua_CFunction
+    static int onSuccess(lua_State* lua);
+
+    // Invoked by a running Lua script to effectively mark the completion of the script's lifecycle,
+    // and send the final results to CarTelemetryService and then to the user.
+    // The script will provide the final results as a Lua table.
+    // We currently support only non-nested fields in the table and the fields can be the following
+    // Lua types: boolean, number, integer, and string.
+    // The result pushed by Lua is converted to Android PersistableBundle and forwarded to
+    // ScriptExecutor service via callback interface.
+    // This method returns 0 to indicate that no results were pushed to Lua stack according
+    // to Lua C function calling convention.
+    // More info: https://www.lua.org/manual/5.3/manual.html#lua_CFunction
+    static int onScriptFinished(lua_State* lua);
+
+    // Invoked by a running Lua script to indicate than an error occurred. This is the mechanism to
+    // for a script author to receive error logs. The caller script encapsulates all the information
+    // about the error that the author wants to provide in a single string parameter.
+    // This method returns 0 to indicate that no results were pushed to Lua stack according
+    // to Lua C function calling convention.
+    // More info: https://www.lua.org/manual/5.3/manual.html#lua_CFunction
+    static int onError(lua_State* lua);
+
+    // Points to the current listener object.
+    // Lua cannot call non-static class methods. We need to access listener object instance in
+    // Lua callbacks. Therefore, callbacks callable by Lua are static class methods and the pointer
+    // to a listener object needs to be static, since static methods cannot access non-static
+    // members.
+    // Only one listener is supported at any given time.
+    // Since listeners are heap-allocated, the destructor does not need to run at shutdown
+    // of the service because the memory allocated to the current listener object will be
+    // reclaimed by the OS.
+    static ScriptExecutorListener* sListener;
+
+    lua_State* mLuaState;  // owned
+};
+
+}  // namespace scriptexecutor
+}  // namespace car
+}  // namespace android
+}  // namespace com
+
+#endif  // PACKAGES_SCRIPTEXECUTOR_SRC_LUAENGINE_H_
diff --git a/packages/ScriptExecutor/src/ScriptExecutorJni.cpp b/packages/ScriptExecutor/src/ScriptExecutorJni.cpp
new file mode 100644
index 0000000..6cb0670
--- /dev/null
+++ b/packages/ScriptExecutor/src/ScriptExecutorJni.cpp
@@ -0,0 +1,136 @@
+/*
+ * 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 "JniUtils.h"
+#include "LuaEngine.h"
+#include "ScriptExecutorListener.h"
+#include "jni.h"
+
+#include <android-base/logging.h>
+
+#include <cstdint>
+
+namespace com {
+namespace android {
+namespace car {
+namespace scriptexecutor {
+
+extern "C" {
+
+JNIEXPORT jlong JNICALL Java_com_android_car_scriptexecutor_ScriptExecutor_nativeInitLuaEngine(
+        JNIEnv* env, jobject object) {
+    // Cast first to intptr_t to ensure int can hold the pointer without loss.
+    return static_cast<jlong>(reinterpret_cast<intptr_t>(new LuaEngine()));
+}
+
+JNIEXPORT void JNICALL Java_com_android_car_scriptexecutor_ScriptExecutor_nativeDestroyLuaEngine(
+        JNIEnv* env, jobject object, jlong luaEnginePtr) {
+    delete reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+}
+
+// Parses the inputs and loads them to Lua one at a time.
+// Loading of data into Lua also triggers checks on Lua side to verify the
+// inputs are valid. For example, pushing "functionName" into Lua stack verifies
+// that the function name actually exists in the previously loaded body of the
+// script.
+//
+// The steps are:
+// Step 1: Parse the inputs for obvious programming errors.
+// Step 2: Parse and load the body of the script.
+// Step 3: Parse and push function name we want to execute in the provided
+// script body to Lua stack. If the function name doesn't exist, we exit.
+// Step 4: Parse publishedData, convert it into Lua table and push it to the
+// stack.
+// Step 5: Parse savedState Bundle object, convert it into Lua table and push it
+// to the stack.
+// Any errors that occur at the stage above result in quick exit or crash.
+//
+// All interaction with Lua happens via Lua stack. Therefore, order of how the
+// inputs are parsed and processed is critical because Lua API methods such as
+// lua_pcall assume specific order between function name and the input arguments
+// on the stack.
+// More information about how to work with Lua stack: https://www.lua.org/pil/24.2.html
+// and how Lua functions are called via Lua API: https://www.lua.org/pil/25.2.html
+//
+// Finally, once parsing and pushing to Lua stack is complete, we go on to the final step,
+// Step 6: Attempt to run the provided function.
+JNIEXPORT void JNICALL Java_com_android_car_scriptexecutor_ScriptExecutor_nativeInvokeScript(
+        JNIEnv* env, jobject object, jlong luaEnginePtr, jstring scriptBody, jstring functionName,
+        jobject publishedData, jobject savedState, jobject listener) {
+    if (!luaEnginePtr) {
+        env->FatalError("luaEnginePtr parameter cannot be nil");
+    }
+    if (scriptBody == nullptr) {
+        env->FatalError("scriptBody parameter cannot be null");
+    }
+    if (functionName == nullptr) {
+        env->FatalError("functionName parameter cannot be null");
+    }
+    if (listener == nullptr) {
+        env->FatalError("listener parameter cannot be null");
+    }
+
+    LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+
+    // Load and parse the script
+    const char* scriptStr = env->GetStringUTFChars(scriptBody, nullptr);
+    auto status = engine->loadScript(scriptStr);
+    env->ReleaseStringUTFChars(scriptBody, scriptStr);
+    // status == 0 if the script loads successfully.
+    if (status) {
+        env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"),
+                      "Failed to load the script.");
+        return;
+    }
+    LuaEngine::resetListener(new ScriptExecutorListener(env, listener));
+
+    // Push the function name we want to invoke to Lua stack
+    const char* functionNameStr = env->GetStringUTFChars(functionName, nullptr);
+    status = engine->pushFunction(functionNameStr);
+    env->ReleaseStringUTFChars(functionName, functionNameStr);
+    // status == 1 if the name is indeed a function.
+    if (!status) {
+        env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"),
+                      "symbol functionName does not correspond to a function.");
+        return;
+    }
+
+    // TODO(b/189241508): Provide implementation to parse publishedData input,
+    // convert it into Lua table and push into Lua stack.
+    if (publishedData) {
+        env->ThrowNew(env->FindClass("java/lang/RuntimeException"),
+                      "Parsing of publishedData is not implemented yet.");
+        return;
+    }
+
+    // Unpack bundle in savedState, convert to Lua table and push it to Lua
+    // stack.
+    pushBundleToLuaTable(env, engine, savedState);
+
+    // Execute the function. This will block until complete or error.
+    if (engine->run()) {
+        env->ThrowNew(env->FindClass("java/lang/RuntimeException"),
+                      "Runtime error occurred while running the function.");
+        return;
+    }
+}
+
+}  // extern "C"
+
+}  // namespace scriptexecutor
+}  // namespace car
+}  // namespace android
+}  // namespace com
diff --git a/packages/ScriptExecutor/src/ScriptExecutorListener.cpp b/packages/ScriptExecutor/src/ScriptExecutorListener.cpp
new file mode 100644
index 0000000..739c71d
--- /dev/null
+++ b/packages/ScriptExecutor/src/ScriptExecutorListener.cpp
@@ -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.
+ */
+
+#include "ScriptExecutorListener.h"
+
+#include <android-base/logging.h>
+
+namespace com {
+namespace android {
+namespace car {
+namespace scriptexecutor {
+
+ScriptExecutorListener::~ScriptExecutorListener() {
+    JNIEnv* env = getCurrentJNIEnv();
+    if (mScriptExecutorListener != nullptr) {
+        env->DeleteGlobalRef(mScriptExecutorListener);
+    }
+}
+
+ScriptExecutorListener::ScriptExecutorListener(JNIEnv* env, jobject script_executor_listener) {
+    mScriptExecutorListener = env->NewGlobalRef(script_executor_listener);
+    env->GetJavaVM(&mJavaVM);
+}
+
+void ScriptExecutorListener::onSuccess(jobject bundle) {
+    JNIEnv* env = getCurrentJNIEnv();
+    jclass listenerClass = env->GetObjectClass(mScriptExecutorListener);
+    jmethodID onSuccessMethod =
+            env->GetMethodID(listenerClass, "onSuccess", "(Landroid/os/PersistableBundle;)V");
+    env->CallVoidMethod(mScriptExecutorListener, onSuccessMethod, bundle);
+}
+
+void ScriptExecutorListener::onScriptFinished(jobject bundle) {
+    JNIEnv* env = getCurrentJNIEnv();
+    jclass listenerClass = env->GetObjectClass(mScriptExecutorListener);
+    jmethodID onScriptFinished = env->GetMethodID(listenerClass, "onScriptFinished",
+                                                  "(Landroid/os/PersistableBundle;)V");
+    env->CallVoidMethod(mScriptExecutorListener, onScriptFinished, bundle);
+}
+
+void ScriptExecutorListener::onError(const int errorType, const char* message,
+                                     const char* stackTrace) {
+    JNIEnv* env = getCurrentJNIEnv();
+    jclass listenerClass = env->GetObjectClass(mScriptExecutorListener);
+    jmethodID onErrorMethod =
+            env->GetMethodID(listenerClass, "onError", "(ILjava/lang/String;Ljava/lang/String;)V");
+
+    env->CallVoidMethod(mScriptExecutorListener, onErrorMethod, errorType,
+                        env->NewStringUTF(message), env->NewStringUTF(stackTrace));
+}
+
+JNIEnv* ScriptExecutorListener::getCurrentJNIEnv() {
+    JNIEnv* env;
+    if (mJavaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        LOG(FATAL) << "Unable to return JNIEnv from JavaVM";
+    }
+    return env;
+}
+
+}  // namespace scriptexecutor
+}  // namespace car
+}  // namespace android
+}  // namespace com
diff --git a/packages/ScriptExecutor/src/ScriptExecutorListener.h b/packages/ScriptExecutor/src/ScriptExecutorListener.h
new file mode 100644
index 0000000..392bc77
--- /dev/null
+++ b/packages/ScriptExecutor/src/ScriptExecutorListener.h
@@ -0,0 +1,59 @@
+/*
+ * 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 PACKAGES_SCRIPTEXECUTOR_SRC_SCRIPTEXECUTORLISTENER_H_
+#define PACKAGES_SCRIPTEXECUTOR_SRC_SCRIPTEXECUTORLISTENER_H_
+
+#include "jni.h"
+
+#include <string>
+
+namespace com {
+namespace android {
+namespace car {
+namespace scriptexecutor {
+
+//  Wrapper class for IScriptExecutorListener.aidl.
+class ScriptExecutorListener {
+public:
+    ScriptExecutorListener(JNIEnv* jni, jobject script_executor_listener);
+
+    virtual ~ScriptExecutorListener();
+
+    void onScriptFinished(jobject bundle);
+
+    void onSuccess(jobject bundle);
+
+    void onError(const int errorType, const char* message, const char* stackTrace);
+
+    JNIEnv* getCurrentJNIEnv();
+
+private:
+    // Stores a jni global reference to Java Script Executor listener object.
+    jobject mScriptExecutorListener;
+
+    // Stores JavaVM pointer in order to be able to get JNIEnv pointer.
+    // This is done because JNIEnv cannot be shared between threads.
+    // https://developer.android.com/training/articles/perf-jni.html#javavm-and-jnienv
+    JavaVM* mJavaVM;
+};
+
+}  // namespace scriptexecutor
+}  // namespace car
+}  // namespace android
+}  // namespace com
+
+#endif  // PACKAGES_SCRIPTEXECUTOR_SRC_SCRIPTEXECUTORLISTENER_H_
diff --git a/packages/ScriptExecutor/src/com/android/car/scriptexecutor/ScriptExecutor.java b/packages/ScriptExecutor/src/com/android/car/scriptexecutor/ScriptExecutor.java
new file mode 100644
index 0000000..bd364bc
--- /dev/null
+++ b/packages/ScriptExecutor/src/com/android/car/scriptexecutor/ScriptExecutor.java
@@ -0,0 +1,114 @@
+/*
+ * 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.scriptexecutor;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.PersistableBundle;
+
+import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutor;
+import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutorListener;
+
+/**
+ * Executes Lua code in an isolated process with provided source code
+ * and input arguments.
+ */
+public final class ScriptExecutor extends Service {
+
+    static {
+        System.loadLibrary("scriptexecutorjni");
+    }
+
+    private static final String TAG = ScriptExecutor.class.getSimpleName();
+
+    // Dedicated "worker" thread to handle all calls related to native code.
+    private HandlerThread mNativeHandlerThread;
+    // Handler associated with the native worker thread.
+    private Handler mNativeHandler;
+
+    private final class IScriptExecutorImpl extends IScriptExecutor.Stub {
+        @Override
+        public void invokeScript(String scriptBody, String functionName,
+                PersistableBundle publishedData, PersistableBundle savedState,
+                IScriptExecutorListener listener) {
+            mNativeHandler.post(() ->
+                    nativeInvokeScript(mLuaEnginePtr, scriptBody, functionName, publishedData,
+                            savedState, listener));
+        }
+    }
+
+    private IScriptExecutorImpl mScriptExecutorBinder;
+
+    // Memory location of Lua Engine object which is allocated in native code.
+    private long mLuaEnginePtr;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        mNativeHandlerThread = new HandlerThread(ScriptExecutor.class.getSimpleName());
+        mNativeHandlerThread.start();
+        mNativeHandler = new Handler(mNativeHandlerThread.getLooper());
+
+        mLuaEnginePtr = nativeInitLuaEngine();
+        mScriptExecutorBinder = new IScriptExecutorImpl();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        nativeDestroyLuaEngine(mLuaEnginePtr);
+        mNativeHandlerThread.quit();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mScriptExecutorBinder;
+    }
+
+    /**
+     * Initializes Lua Engine.
+     *
+     * <p>Returns memory location of Lua Engine.
+     */
+    private native long nativeInitLuaEngine();
+
+    /**
+     * Destroys LuaEngine at the provided memory address.
+     */
+    private native void nativeDestroyLuaEngine(long luaEnginePtr);
+
+    /**
+     * Calls provided Lua function.
+     *
+     * @param luaEnginePtr  memory address of the stored LuaEngine instance.
+     * @param scriptBody    complete body of Lua script that also contains the function to be
+     *                      invoked.
+     * @param functionName  the name of the function to execute.
+     * @param publishedData input data provided by the source which the function handles.
+     * @param savedState    key-value pairs preserved from the previous invocation of the function.
+     * @param listener      callback for the sandboxed environent to report back script execution
+     *                      results
+     *                      and errors.
+     */
+    private native void nativeInvokeScript(long luaEnginePtr, String scriptBody,
+            String functionName, PersistableBundle publishedData, PersistableBundle savedState,
+            IScriptExecutorListener listener);
+}
diff --git a/packages/ScriptExecutor/tests/unit/Android.bp b/packages/ScriptExecutor/tests/unit/Android.bp
new file mode 100644
index 0000000..fbbe750
--- /dev/null
+++ b/packages/ScriptExecutor/tests/unit/Android.bp
@@ -0,0 +1,63 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "ScriptExecutorUnitTest",
+
+    srcs: ["src/**/*.java"],
+
+    platform_apis: true,
+
+    certificate: "platform",
+
+    instrumentation_for: "ScriptExecutor",
+
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.runner",
+        "junit",
+        "scriptexecutor-test-lib",
+        "truth-prebuilt",
+    ],
+
+    test_suites: ["general-tests"],
+
+    jni_libs: [
+        "libscriptexecutorjni",
+        "libscriptexecutorjniutils-test",
+    ],
+}
+
+cc_library_shared {
+    name: "libscriptexecutorjniutils-test",
+
+    defaults: [
+        "scriptexecutor_defaults",
+    ],
+
+    srcs: [
+        "src/com/android/car/scriptexecutor/JniUtilsTestHelper.cpp",
+    ],
+
+    shared_libs: [
+        "libnativehelper",
+        "libscriptexecutor",
+    ],
+}
diff --git a/packages/ScriptExecutor/tests/unit/AndroidManifest.xml b/packages/ScriptExecutor/tests/unit/AndroidManifest.xml
new file mode 100644
index 0000000..f393408
--- /dev/null
+++ b/packages/ScriptExecutor/tests/unit/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.car.scriptexecutor_test">
+
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+
+    <queries>
+        <package android:name="com.android.car.scriptexecutor" />
+    </queries>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.car.scriptexecutor_test"
+                     android:label="Tests for ScriptExecutor"/>
+</manifest>
diff --git a/packages/ScriptExecutor/tests/unit/README.md b/packages/ScriptExecutor/tests/unit/README.md
new file mode 100644
index 0000000..8c5a67a
--- /dev/null
+++ b/packages/ScriptExecutor/tests/unit/README.md
@@ -0,0 +1,52 @@
+<!--
+  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
+  -->
+
+# How to run unit tests for ScriptExecutor
+
+**1. Navigate to the root of the repo and do full build:**
+
+`m -j`
+
+**2. Flash the device with this build:**
+
+`aae flash`
+
+**3. Run the tests. For example**
+
+`atest ScriptExecutorUnitTest:ScriptExecutorTest`
+
+
+## How to rerun the tests after changes
+Sometimes a test needs to be modified. These are the steps to do incremental update instead of full
+device flash.
+
+**1. Navigate to ScriptExecutor unit test location and build its targets:**
+`cd packages/services/Car/packages/ScriptExecutor/tests/unit`
+
+`mm -j`
+
+**2. Sync the device with all the files that need to be updated:**
+
+`adb root`
+
+`adb remount`
+
+`adb sync && adb shell stop && adb shell start`
+
+**3. At this point we are ready to run the tests again. For example:**
+
+`atest ScriptExecutorUnitTest:ScriptExecutorTest`
+
diff --git a/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/JniUtilsTest.java b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/JniUtilsTest.java
new file mode 100644
index 0000000..a1a5979
--- /dev/null
+++ b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/JniUtilsTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.scriptexecutor;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.PersistableBundle;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class JniUtilsTest {
+
+    private static final String TAG = JniUtilsTest.class.getSimpleName();
+
+    private static final String BOOLEAN_KEY = "boolean_key";
+    private static final String INT_KEY = "int_key";
+    private static final String STRING_KEY = "string_key";
+    private static final String NUMBER_KEY = "number_key";
+
+    private static final boolean BOOLEAN_VALUE = true;
+    private static final double NUMBER_VALUE = 0.1;
+    private static final int INT_VALUE = 10;
+    private static final String STRING_VALUE = "test";
+
+    // Pointer to Lua Engine instantiated in native space.
+    private long mLuaEnginePtr = 0;
+
+    static {
+        System.loadLibrary("scriptexecutorjniutils-test");
+    }
+
+    @Before
+    public void setUp() {
+        mLuaEnginePtr = nativeCreateLuaEngine();
+    }
+
+    @After
+    public void tearDown() {
+        nativeDestroyLuaEngine(mLuaEnginePtr);
+    }
+
+    // Simply invokes PushBundleToLuaTable native method under test.
+    private native void nativePushBundleToLuaTableCaller(
+            long luaEnginePtr, PersistableBundle bundle);
+
+    // Creates an instance of LuaEngine on the heap and returns the pointer.
+    private native long nativeCreateLuaEngine();
+
+    // Destroys instance of LuaEngine on the native side at provided memory address.
+    private native void nativeDestroyLuaEngine(long luaEnginePtr);
+
+    // Returns size of a Lua object located at the specified position on the stack.
+    private native int nativeGetObjectSize(long luaEnginePtr, int index);
+
+    /*
+     * Family of methods to check if the table on top of the stack has
+     * the given value under provided key.
+     */
+    private native boolean nativeHasBooleanValue(long luaEnginePtr, String key, boolean value);
+    private native boolean nativeHasStringValue(long luaEnginePtr, String key, String value);
+    private native boolean nativeHasIntValue(long luaEnginePtr, String key, int value);
+    private native boolean nativeHasDoubleValue(long luaEnginePtr, String key, double value);
+
+    @Test
+    public void pushBundleToLuaTable_nullBundleMakesEmptyLuaTable() {
+        Log.d(TAG, "Using Lua Engine with address " + mLuaEnginePtr);
+        nativePushBundleToLuaTableCaller(mLuaEnginePtr, null);
+        // Get the size of the object on top of the stack,
+        // which is where our table is supposed to be.
+        assertThat(nativeGetObjectSize(mLuaEnginePtr, 1)).isEqualTo(0);
+    }
+
+    @Test
+    public void pushBundleToLuaTable_valuesOfDifferentTypes() {
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putBoolean(BOOLEAN_KEY, BOOLEAN_VALUE);
+        bundle.putInt(INT_KEY, INT_VALUE);
+        bundle.putDouble(NUMBER_KEY, NUMBER_VALUE);
+        bundle.putString(STRING_KEY, STRING_VALUE);
+
+        // Invokes the corresponding helper method to convert the bundle
+        // to Lua table on Lua stack.
+        nativePushBundleToLuaTableCaller(mLuaEnginePtr, bundle);
+
+        // Check contents of Lua table.
+        assertThat(nativeHasBooleanValue(mLuaEnginePtr, BOOLEAN_KEY, BOOLEAN_VALUE)).isTrue();
+        assertThat(nativeHasIntValue(mLuaEnginePtr, INT_KEY, INT_VALUE)).isTrue();
+        assertThat(nativeHasDoubleValue(mLuaEnginePtr, NUMBER_KEY, NUMBER_VALUE)).isTrue();
+        assertThat(nativeHasStringValue(mLuaEnginePtr, STRING_KEY, STRING_VALUE)).isTrue();
+    }
+
+
+    @Test
+    public void pushBundleToLuaTable_wrongKey() {
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putBoolean(BOOLEAN_KEY, BOOLEAN_VALUE);
+
+        // Invokes the corresponding helper method to convert the bundle
+        // to Lua table on Lua stack.
+        nativePushBundleToLuaTableCaller(mLuaEnginePtr, bundle);
+
+        // Check contents of Lua table.
+        assertThat(nativeHasBooleanValue(mLuaEnginePtr, "wrong key", BOOLEAN_VALUE)).isFalse();
+    }
+}
diff --git a/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/JniUtilsTestHelper.cpp b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/JniUtilsTestHelper.cpp
new file mode 100644
index 0000000..eae1377
--- /dev/null
+++ b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/JniUtilsTestHelper.cpp
@@ -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.
+ */
+
+#include "JniUtils.h"
+#include "LuaEngine.h"
+#include "jni.h"
+
+#include <cstdint>
+#include <cstring>
+
+namespace com {
+namespace android {
+namespace car {
+namespace scriptexecutor {
+namespace {
+
+extern "C" {
+
+#include "lua.h"
+
+JNIEXPORT jlong JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeCreateLuaEngine(
+        JNIEnv* env, jobject object) {
+    // Cast first to intptr_t to ensure int can hold the pointer without loss.
+    return static_cast<jlong>(reinterpret_cast<intptr_t>(new LuaEngine()));
+}
+
+JNIEXPORT void JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeDestroyLuaEngine(
+        JNIEnv* env, jobject object, jlong luaEnginePtr) {
+    delete reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+}
+
+JNIEXPORT void JNICALL
+Java_com_android_car_scriptexecutor_JniUtilsTest_nativePushBundleToLuaTableCaller(
+        JNIEnv* env, jobject object, jlong luaEnginePtr, jobject bundle) {
+    pushBundleToLuaTable(env, reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr)),
+                         bundle);
+}
+
+JNIEXPORT jint JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeGetObjectSize(
+        JNIEnv* env, jobject object, jlong luaEnginePtr, jint index) {
+    LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+    return lua_rawlen(engine->getLuaState(), static_cast<int>(index));
+}
+
+JNIEXPORT bool JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeHasBooleanValue(
+        JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jboolean value) {
+    const char* rawKey = env->GetStringUTFChars(key, nullptr);
+    LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+    auto* luaState = engine->getLuaState();
+    lua_pushstring(luaState, rawKey);
+    env->ReleaseStringUTFChars(key, rawKey);
+    lua_gettable(luaState, -2);
+    bool result = false;
+    if (!lua_isboolean(luaState, -1))
+        result = false;
+    else
+        result = static_cast<bool>(lua_toboolean(luaState, -1)) == static_cast<bool>(value);
+    lua_pop(luaState, 1);
+    return result;
+}
+
+JNIEXPORT bool JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeHasIntValue(
+        JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jint value) {
+    const char* rawKey = env->GetStringUTFChars(key, nullptr);
+    LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+    // Assumes the table is on top of the stack.
+    auto* luaState = engine->getLuaState();
+    lua_pushstring(luaState, rawKey);
+    env->ReleaseStringUTFChars(key, rawKey);
+    lua_gettable(luaState, -2);
+    bool result = false;
+    if (!lua_isinteger(luaState, -1))
+        result = false;
+    else
+        result = lua_tointeger(luaState, -1) == static_cast<int>(value);
+    lua_pop(luaState, 1);
+    return result;
+}
+
+JNIEXPORT bool JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeHasDoubleValue(
+        JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jdouble value) {
+    const char* rawKey = env->GetStringUTFChars(key, nullptr);
+    LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+    // Assumes the table is on top of the stack.
+    auto* luaState = engine->getLuaState();
+    lua_pushstring(luaState, rawKey);
+    env->ReleaseStringUTFChars(key, rawKey);
+    lua_gettable(luaState, -2);
+    bool result = false;
+    if (!lua_isnumber(luaState, -1))
+        result = false;
+    else
+        result = static_cast<double>(lua_tonumber(luaState, -1)) == static_cast<double>(value);
+    lua_pop(luaState, 1);
+    return result;
+}
+
+JNIEXPORT bool JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeHasStringValue(
+        JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jstring value) {
+    const char* rawKey = env->GetStringUTFChars(key, nullptr);
+    LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+    // Assumes the table is on top of the stack.
+    auto* luaState = engine->getLuaState();
+    lua_pushstring(luaState, rawKey);
+    env->ReleaseStringUTFChars(key, rawKey);
+    lua_gettable(luaState, -2);
+    bool result = false;
+    if (!lua_isstring(luaState, -1)) {
+        result = false;
+    } else {
+        std::string s = lua_tostring(luaState, -1);
+        const char* rawValue = env->GetStringUTFChars(value, nullptr);
+        result = strcmp(lua_tostring(luaState, -1), rawValue) == 0;
+        env->ReleaseStringUTFChars(value, rawValue);
+    }
+    lua_pop(luaState, 1);
+    return result;
+}
+
+}  //  extern "C"
+
+}  // namespace
+}  // namespace scriptexecutor
+}  // namespace car
+}  // namespace android
+}  // namespace com
diff --git a/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/ScriptExecutorTest.java b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/ScriptExecutorTest.java
new file mode 100644
index 0000000..37760f0
--- /dev/null
+++ b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/ScriptExecutorTest.java
@@ -0,0 +1,492 @@
+/*
+ * 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.scriptexecutor;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutor;
+import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutorConstants;
+import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutorListener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+public final class ScriptExecutorTest {
+
+    private IScriptExecutor mScriptExecutor;
+    private ScriptExecutor mInstance;
+    private Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+
+    private static final class ScriptExecutorListener extends IScriptExecutorListener.Stub {
+        public PersistableBundle mSavedBundle;
+        public PersistableBundle mFinalResult;
+        public int mErrorType;
+        public String mMessage;
+        public String mStackTrace;
+        public final CountDownLatch mResponseLatch = new CountDownLatch(1);
+
+        @Override
+        public void onScriptFinished(PersistableBundle result) {
+            mFinalResult = result;
+            mResponseLatch.countDown();
+        }
+
+        @Override
+        public void onSuccess(PersistableBundle stateToPersist) {
+            mSavedBundle = stateToPersist;
+            mResponseLatch.countDown();
+        }
+
+        @Override
+        public void onError(int errorType, String message, String stackTrace) {
+            mErrorType = errorType;
+            mMessage = message;
+            mStackTrace = stackTrace;
+            mResponseLatch.countDown();
+        }
+    }
+
+    private final ScriptExecutorListener mFakeScriptExecutorListener =
+            new ScriptExecutorListener();
+
+    // TODO(b/189241508). Parsing of publishedData is not implemented yet.
+    // Null is the only accepted input.
+    private final PersistableBundle mPublishedData = null;
+    private final PersistableBundle mSavedState = new PersistableBundle();
+
+    private static final String LUA_SCRIPT =
+            "function hello(state)\n"
+                    + "    print(\"Hello World\")\n"
+                    + "end\n";
+
+    private static final String LUA_METHOD = "hello";
+
+    private final CountDownLatch mBindLatch = new CountDownLatch(1);
+
+    private static final int BIND_SERVICE_TIMEOUT_SEC = 5;
+    private static final int SCRIPT_PROCESSING_TIMEOUT_SEC = 10;
+
+
+    private final ServiceConnection mScriptExecutorConnection =
+            new ServiceConnection() {
+                @Override
+                public void onServiceConnected(ComponentName className, IBinder service) {
+                    mScriptExecutor = IScriptExecutor.Stub.asInterface(service);
+                    mBindLatch.countDown();
+                }
+
+                @Override
+                public void onServiceDisconnected(ComponentName className) {
+                    fail("Service unexpectedly disconnected");
+                }
+            };
+
+    // Helper method to invoke the script and wait for it to complete and return a response.
+    private void runScriptAndWaitForResponse(String script, String function,
+            PersistableBundle previousState)
+            throws RemoteException {
+        mScriptExecutor.invokeScript(script, function, mPublishedData, previousState,
+                mFakeScriptExecutorListener);
+        try {
+            if (!mFakeScriptExecutorListener.mResponseLatch.await(SCRIPT_PROCESSING_TIMEOUT_SEC,
+                    TimeUnit.SECONDS)) {
+                fail("Failed to get the callback method called by the script on time");
+            }
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+
+    private void runScriptAndWaitForError(String script, String function) throws RemoteException {
+        runScriptAndWaitForResponse(script, function, new PersistableBundle());
+    }
+
+    @Before
+    public void setUp() throws InterruptedException {
+        Intent intent = new Intent();
+        intent.setComponent(new ComponentName("com.android.car.scriptexecutor",
+                "com.android.car.scriptexecutor.ScriptExecutor"));
+        mContext.bindServiceAsUser(intent, mScriptExecutorConnection, Context.BIND_AUTO_CREATE,
+                UserHandle.SYSTEM);
+        if (!mBindLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+            fail("Failed to bind to ScriptExecutor service");
+        }
+    }
+
+    @Test
+    public void invokeScript_helloWorld() throws RemoteException {
+        // Expect to load "hello world" script successfully and push the function.
+        mScriptExecutor.invokeScript(LUA_SCRIPT, LUA_METHOD, mPublishedData, mSavedState,
+                mFakeScriptExecutorListener);
+        // Sleep, otherwise the test case will complete before the script loads
+        // because invokeScript is non-blocking.
+        try {
+            // TODO(b/192285332): Replace sleep logic with waiting for specific callbacks
+            // to be called once they are implemented. Otherwise, this could be a flaky test.
+            TimeUnit.SECONDS.sleep(10);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    @Test
+    public void invokeScript_returnsResult() throws RemoteException {
+        String returnResultScript =
+                "function hello(state)\n"
+                        + "    result = {hello=\"world\"}\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+
+        runScriptAndWaitForResponse(returnResultScript, "hello", mSavedState);
+
+        // Expect to get back a bundle with a single string key: string value pair:
+        // {"hello": "world"}
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(1);
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.getString("hello")).isEqualTo("world");
+    }
+
+    @Test
+    public void invokeScript_allSupportedTypes() throws RemoteException {
+        String script =
+                "function knows(state)\n"
+                        + "    result = {string=\"hello\", boolean=true, integer=1, number=1.1}\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+
+        runScriptAndWaitForResponse(script, "knows", mSavedState);
+
+        // Expect to get back a bundle with 4 keys, each corresponding to a distinct supported type.
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(4);
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.getString("string")).isEqualTo("hello");
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.getBoolean("boolean")).isEqualTo(true);
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.getInt("integer")).isEqualTo(1);
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.getDouble("number")).isEqualTo(1.1);
+    }
+
+    @Test
+    public void invokeScript_skipsUnsupportedTypes() throws RemoteException {
+        String script =
+                "function nested(state)\n"
+                        + "    result = {string=\"hello\", boolean=true, integer=1, number=1.1}\n"
+                        + "    result.nested_table = {x=0, y=0}\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+
+        runScriptAndWaitForResponse(script, "nested", mSavedState);
+
+        // Bundle does not contain any value under "nested_table" key, because nested tables are
+        // not supported yet.
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(4);
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.getString("nested_table")).isNull();
+    }
+
+    @Test
+    public void invokeScript_emptyBundle() throws RemoteException {
+        String script =
+                "function empty(state)\n"
+                        + "    result = {}\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+
+        runScriptAndWaitForResponse(script, "empty", mSavedState);
+
+        // If a script returns empty table as the result, we get an empty bundle.
+        assertThat(mFakeScriptExecutorListener.mSavedBundle).isNotNull();
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(0);
+    }
+
+    @Test
+    public void invokeScript_processPreviousStateAndReturnResult() throws RemoteException {
+        // Here we verify that the script actually processes provided state from a previous run
+        // and makes calculation based on that and returns the result.
+        // TODO(b/189241508): update function signatures.
+        String script =
+                "function update(state)\n"
+                        + "    result = {y = state.x+1}\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+        PersistableBundle previousState = new PersistableBundle();
+        previousState.putInt("x", 1);
+
+
+        runScriptAndWaitForResponse(script, "update", previousState);
+
+        // Verify that y = 2, because y = x + 1 and x = 1.
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(1);
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.getInt("y")).isEqualTo(2);
+    }
+
+    @Test
+    public void invokeScript_allSupportedTypesWorkRoundTripWithKeyNamesPreserved()
+            throws RemoteException {
+        // Here we verify that all supported types in supplied previous state Bundle are interpreted
+        // by the script as expected.
+        // TODO(b/189241508): update function signatures.
+        String script =
+                "function update_all(state)\n"
+                        + "    result = {}\n"
+                        + "    result.integer = state.integer + 1\n"
+                        + "    result.number = state.number + 0.1\n"
+                        + "    result.boolean = not state.boolean\n"
+                        + "    result.string = state.string .. \"CADABRA\"\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+        PersistableBundle previousState = new PersistableBundle();
+        previousState.putInt("integer", 1);
+        previousState.putDouble("number", 0.1);
+        previousState.putBoolean("boolean", false);
+        previousState.putString("string", "ABRA");
+
+
+        runScriptAndWaitForResponse(script, "update_all", previousState);
+
+        // Verify that keys are preserved but the values are modified as expected.
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(4);
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.getInt("integer")).isEqualTo(2);
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.getDouble("number")).isEqualTo(0.2);
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.getBoolean("boolean")).isEqualTo(true);
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.getString("string")).isEqualTo(
+                "ABRACADABRA");
+    }
+
+    @Test
+    public void invokeScript_scriptCallsOnError() throws RemoteException {
+        String script =
+                "function calls_on_error()\n"
+                        + "    if 1 ~= 2 then\n"
+                        + "        on_error(\"one is not equal to two\")\n"
+                        + "        return\n"
+                        + "    end\n"
+                        + "end\n";
+
+        runScriptAndWaitForError(script, "calls_on_error");
+
+        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
+                IScriptExecutorConstants.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo("one is not equal to two");
+    }
+
+    @Test
+    public void invokeScript_tooManyParametersInOnError() throws RemoteException {
+        String script =
+                "function too_many_params_in_on_error()\n"
+                        + "    if 1 ~= 2 then\n"
+                        + "        on_error(\"param1\", \"param2\")\n"
+                        + "        return\n"
+                        + "    end\n"
+                        + "end\n";
+
+        runScriptAndWaitForError(script, "too_many_params_in_on_error");
+
+        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
+                IScriptExecutorConstants.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
+                "on_error can push only a single string parameter from Lua");
+    }
+
+    @Test
+    public void invokeScript_onErrorOnlyAcceptsString() throws RemoteException {
+        String script =
+                "function only_string()\n"
+                        + "    if 1 ~= 2 then\n"
+                        + "        on_error(false)\n"
+                        + "        return\n"
+                        + "    end\n"
+                        + "end\n";
+
+        runScriptAndWaitForError(script, "only_string");
+
+        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
+                IScriptExecutorConstants.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
+                "on_error can push only a single string parameter from Lua");
+    }
+
+    @Test
+    public void invokeScript_returnsFinalResult() throws RemoteException {
+        String returnFinalResultScript =
+                "function script_finishes(state)\n"
+                        + "    result = {data = state.input + 1}\n"
+                        + "    on_script_finished(result)\n"
+                        + "end\n";
+        PersistableBundle previousState = new PersistableBundle();
+        previousState.putInt("input", 1);
+
+        runScriptAndWaitForResponse(returnFinalResultScript, "script_finishes", previousState);
+
+        // Expect to get back a bundle with a single key-value pair {"data": 2}
+        // because data = state.input + 1 as in the script body above.
+        assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(1);
+        assertThat(mFakeScriptExecutorListener.mFinalResult.getInt("data")).isEqualTo(2);
+    }
+
+    @Test
+    public void invokeScript_allSupportedTypesForReturningFinalResult()
+            throws RemoteException {
+        // Here we verify that all supported types are present in the returned final result
+        // bundle are present.
+        // TODO(b/189241508): update function signatures.
+        String script =
+                "function finalize_all(state)\n"
+                        + "    result = {}\n"
+                        + "    result.integer = state.integer + 1\n"
+                        + "    result.number = state.number + 0.1\n"
+                        + "    result.boolean = not state.boolean\n"
+                        + "    result.string = state.string .. \"CADABRA\"\n"
+                        + "    on_script_finished(result)\n"
+                        + "end\n";
+        PersistableBundle previousState = new PersistableBundle();
+        previousState.putInt("integer", 1);
+        previousState.putDouble("number", 0.1);
+        previousState.putBoolean("boolean", false);
+        previousState.putString("string", "ABRA");
+
+
+        runScriptAndWaitForResponse(script, "finalize_all", previousState);
+
+        // Verify that keys are preserved but the values are modified as expected.
+        assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(4);
+        assertThat(mFakeScriptExecutorListener.mFinalResult.getInt("integer")).isEqualTo(2);
+        assertThat(mFakeScriptExecutorListener.mFinalResult.getDouble("number")).isEqualTo(0.2);
+        assertThat(mFakeScriptExecutorListener.mFinalResult.getBoolean("boolean")).isEqualTo(true);
+        assertThat(mFakeScriptExecutorListener.mFinalResult.getString("string")).isEqualTo(
+                "ABRACADABRA");
+    }
+
+    @Test
+    public void invokeScript_emptyFinalResultBundle() throws RemoteException {
+        String script =
+                "function empty_final_result(state)\n"
+                        + "    result = {}\n"
+                        + "    on_script_finished(result)\n"
+                        + "end\n";
+
+
+        runScriptAndWaitForResponse(script, "empty_final_result", mSavedState);
+
+        // If a script returns empty table as the final result, we get an empty bundle.
+        assertThat(mFakeScriptExecutorListener.mFinalResult).isNotNull();
+        assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(0);
+    }
+
+    @Test
+    public void invokeScript_wrongNumberOfCallbackInputsInOnScriptFinished()
+            throws RemoteException {
+        String script =
+                "function wrong_number_of_outputs_in_on_script_finished(state)\n"
+                        + "    result = {}\n"
+                        + "    extra = 1\n"
+                        + "    on_script_finished(result, extra)\n"
+                        + "end\n";
+
+
+        runScriptAndWaitForResponse(script, "wrong_number_of_outputs_in_on_script_finished",
+                mSavedState);
+
+        // We expect to get an error here because we expect only 1 input parameter in
+        // on_script_finished.
+        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
+                IScriptExecutorConstants.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
+                "on_script_finished can push only a single parameter from Lua - a Lua table");
+    }
+
+    @Test
+    public void invokeScript_wrongNumberOfCallbackInputsInOnSuccess() throws RemoteException {
+        String script =
+                "function wrong_number_of_outputs_in_on_success(state)\n"
+                        + "    result = {}\n"
+                        + "    extra = 1\n"
+                        + "    on_success(result, extra)\n"
+                        + "end\n";
+
+
+        runScriptAndWaitForResponse(script, "wrong_number_of_outputs_in_on_success", mSavedState);
+
+        // We expect to get an error here because we expect only 1 input parameter in on_success.
+        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
+                IScriptExecutorConstants.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
+                "on_success can push only a single parameter from Lua - a Lua table");
+    }
+
+    @Test
+    public void invokeScript_wrongTypeInOnSuccess() throws RemoteException {
+        String script =
+                "function wrong_type_in_on_success(state)\n"
+                        + "    result = 1\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+
+        runScriptAndWaitForResponse(script, "wrong_type_in_on_success", mSavedState);
+
+        // We expect to get an error here because the type of the input parameter for on_success
+        // must be a Lua table.
+        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
+                IScriptExecutorConstants.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
+                "on_success can push only a single parameter from Lua - a Lua table");
+    }
+
+    @Test
+    public void invokeScript_wrongTypeInOnScriptFinished() throws RemoteException {
+        String script =
+                "function wrong_type_in_on_script_finished(state)\n"
+                        + "    result = 1\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+
+        runScriptAndWaitForResponse(script, "wrong_type_in_on_script_finished", mSavedState);
+
+        // We expect to get an error here because the type of the input parameter for
+        // on_script_finished must be a Lua table.
+        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
+                IScriptExecutorConstants.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
+                "on_success can push only a single parameter from Lua - a Lua table");
+    }
+}
+
diff --git a/service/Android.bp b/service/Android.bp
index 709c928..7f4a329 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -29,11 +29,13 @@
 }
 
 car_service_sources = [
+    ":iscriptexecutor_aidl",
     "src/**/*.java",
     ":statslog-Car-java-gen",
 ]
 
 common_lib_deps = [
+    "android.automotive.telemetry.internal-java",  // ICarTelemetryInternal
     "android.car.cluster.navigation",
     "android.car.userlib",
     "android.car.watchdoglib",
@@ -85,9 +87,14 @@
 
     jni_libs: [
         "libcarservicejni",
-        "libscriptexecutorjni",
     ],
 
+    aidl: {
+        include_dirs: [
+	    "frameworks/native/aidl/binder", // For PersistableBundle.aidl
+	],
+    },
+
     required: ["allowed_privapp_com.android.car"],
 
     // Disable build in PDK, missing aidl import breaks build
@@ -138,6 +145,12 @@
 
     static_libs: common_lib_deps,
 
+    aidl: {
+        include_dirs: [
+	    "frameworks/native/aidl/binder", // For PersistableBundle.aidl
+	],
+    },
+
     product_variables: {
         pdk: {
             enabled: false,
@@ -162,9 +175,32 @@
         "car-frameworks-service",
     ],
 
+    aidl: {
+        include_dirs: [
+	    "frameworks/native/aidl/binder", // For PersistableBundle.aidl
+	],
+    },
+
     product_variables: {
         pdk: {
             enabled: false,
         },
     },
 }
+
+filegroup {
+    name: "iscriptexecutor_aidl",
+    srcs: [
+        "src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutor.aidl",
+        "src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutorListener.aidl",
+    ],
+    path: "src",
+}
+
+filegroup {
+    name: "iscriptexecutorconstants_aidl",
+    srcs: [
+        "src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutorConstants.aidl",
+    ],
+    path: "src",
+}
diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml
index fabe8e2..f8a5e9c 100644
--- a/service/AndroidManifest.xml
+++ b/service/AndroidManifest.xml
@@ -936,9 +936,18 @@
         </service>
         <service android:name=".PerUserCarService"
             android:exported="false"/>
-        <service android:name=".telemetry.ScriptExecutor"
-            android:exported="false"
-            android:isolatedProcess="true"/>
+        <service
+            android:name="com.android.car.pm.CarSafetyAccessibilityService"
+            android:singleUser="true"
+            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="android.accessibilityservice.AccessibilityService" />
+            </intent-filter>
+            <meta-data
+                android:name="android.accessibilityservice"
+                android:resource="@xml/car_safety_accessibility_service_config" />
+        </service>
 
         <activity android:name="com.android.car.pm.ActivityBlockingActivity"
              android:documentLaunchMode="always"
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index f0c611c..ae487da 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -413,4 +413,14 @@
     <!-- A configuration flag to adjust Wifi for suspend. -->
     <bool name="config_wifiAdjustmentForSuspend">false</bool>
 
+    <!-- A configuration flag to prevent the templated apps from showing dialogs. This is done in
+         the view of driver safety as templated apps can potentially show a dialog with custom UI
+         which can be a distraction hazard for the driver. -->
+    <bool name="config_preventTemplatedAppsFromShowingDialog">true</bool>
+
+    <!-- The class name of the templated activities. This is used to detect currently running
+         templated activity.-->
+    <string name="config_template_activity_class_name">
+        androidx.car.app.activity.CarAppActivity
+    </string>
 </resources>
diff --git a/service/res/xml/car_safety_accessibility_service_config.xml b/service/res/xml/car_safety_accessibility_service_config.xml
new file mode 100644
index 0000000..9029ec2
--- /dev/null
+++ b/service/res/xml/car_safety_accessibility_service_config.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ 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.
+  -->
+
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+    android:accessibilityEventTypes="typeWindowStateChanged"
+    android:accessibilityFlags="flagDefault"
+    android:accessibilityFeedbackType="feedbackAllMask"/>
\ No newline at end of file
diff --git a/service/src/com/android/car/AppFocusService.java b/service/src/com/android/car/AppFocusService.java
index 477529d..6b1c7b4 100644
--- a/service/src/com/android/car/AppFocusService.java
+++ b/service/src/com/android/car/AppFocusService.java
@@ -15,11 +15,14 @@
  */
 package com.android.car;
 
+import android.car.Car;
 import android.car.CarAppFocusManager;
 import android.car.IAppFocus;
 import android.car.IAppFocusListener;
 import android.car.IAppFocusOwnershipCallback;
 import android.content.Context;
+import android.content.PermissionChecker;
+import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -36,6 +39,7 @@
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
@@ -77,9 +81,11 @@
             getClass().getSimpleName());
     private final DispatchHandler mDispatchHandler = new DispatchHandler(mHandlerThread.getLooper(),
             this);
+    private final Context mContext;
 
     public AppFocusService(Context context,
             SystemActivityMonitoringService systemActivityMonitoringService) {
+        mContext = context;
         mSystemActivityMonitoringService = systemActivityMonitoringService;
         mAllChangeClients = new ClientHolder(mAllBinderEventHandler);
         mAllOwnershipClients = new OwnershipClientHolder(this);
@@ -121,6 +127,22 @@
     }
 
     @Override
+    public List<String> getAppTypeOwner(@CarAppFocusManager.AppFocusType int appType) {
+        OwnershipClientInfo owner;
+        synchronized (mLock) {
+            owner = mFocusOwners.get(appType);
+        }
+        if (owner == null) {
+            return null;
+        }
+        String[] packageNames = mContext.getPackageManager().getPackagesForUid(owner.getUid());
+        if (packageNames == null) {
+            return null;
+        }
+        return Arrays.asList(packageNames);
+    }
+
+    @Override
     public boolean isOwningFocus(IAppFocusOwnershipCallback callback, int appType) {
         OwnershipClientInfo info;
         synchronized (mLock) {
@@ -146,10 +168,10 @@
             if (!alreadyOwnedAppTypes.contains(appType)) {
                 OwnershipClientInfo ownerInfo = mFocusOwners.get(appType);
                 if (ownerInfo != null && ownerInfo != info) {
-                    if (mSystemActivityMonitoringService.isInForeground(
-                            ownerInfo.getPid(), ownerInfo.getUid())
-                            && !mSystemActivityMonitoringService.isInForeground(
-                            info.getPid(), info.getUid())) {
+                    // Allow receiving focus if the requester has a foreground activity OR if the
+                    // requester is privileged service.
+                    if (isInForeground(ownerInfo) && !isInForeground(info)
+                            && !hasPrivilegedPermission()) {
                         Slog.w(CarLog.TAG_APP_FOCUS, "Focus request failed for non-foreground app("
                                 + "pid=" + info.getPid() + ", uid=" + info.getUid() + ")."
                                 + "Foreground app (pid=" + ownerInfo.getPid() + ", uid="
@@ -190,6 +212,15 @@
         return CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED;
     }
 
+    private boolean isInForeground(OwnershipClientInfo info) {
+        return mSystemActivityMonitoringService.isInForeground(info.getPid(), info.getUid());
+    }
+
+    private boolean hasPrivilegedPermission() {
+        return mContext.checkCallingOrSelfPermission(Car.PERMISSION_CAR_DISPLAY_IN_CLUSTER)
+                == PermissionChecker.PERMISSION_GRANTED;
+    }
+
     @Override
     public void abandonAppFocus(IAppFocusOwnershipCallback callback, int appType) {
         synchronized (mLock) {
diff --git a/service/src/com/android/car/CarInputService.java b/service/src/com/android/car/CarInputService.java
index 5870b32..f63a6e0 100644
--- a/service/src/com/android/car/CarInputService.java
+++ b/service/src/com/android/car/CarInputService.java
@@ -60,6 +60,7 @@
 import com.android.car.hal.InputHalService;
 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.car.internal.common.UserHelperLite;
+import com.android.car.pm.CarSafetyAccessibilityService;
 import com.android.car.user.CarUserService;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -69,6 +70,7 @@
 import com.android.server.utils.Slogf;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collections;
 import java.util.List;
@@ -81,7 +83,8 @@
  */
 public class CarInputService extends ICarInput.Stub
         implements CarServiceBase, InputHalService.InputListener {
-
+    public static final String ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ":";
+    private static final int MAX_RETRIES_FOR_ENABLING_ACCESSIBILITY_SERVICES = 5;
     private static final String TAG = CarLog.TAG_INPUT;
 
     /** An interface to receive {@link KeyEvent}s as they occur. */
@@ -232,7 +235,7 @@
     private final CarUserManager.UserLifecycleListener mUserLifecycleListener = event -> {
         Slogf.d(TAG, "CarInputService.onEvent(%s)", event);
         if (CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING == event.getEventType()) {
-            updateRotaryServiceSettings(event.getUserId());
+            updateCarAccessibilityServicesSettings(event.getUserId());
         }
     };
 
@@ -327,9 +330,7 @@
                         mBluetoothProfileServiceListener, BluetoothProfile.HEADSET_CLIENT);
             });
         }
-        if (!TextUtils.isEmpty(mRotaryServiceComponentName)) {
-            mUserService.addUserLifecycleListener(mUserLifecycleListener);
-        }
+        mUserService.addUserLifecycleListener(mUserLifecycleListener);
     }
 
     @Override
@@ -344,9 +345,7 @@
                 mBluetoothHeadsetClient = null;
             }
         }
-        if (!TextUtils.isEmpty(mRotaryServiceComponentName)) {
-            mUserService.removeUserLifecycleListener(mUserLifecycleListener);
-        }
+        mUserService.removeUserLifecycleListener(mUserLifecycleListener);
     }
 
     @Override
@@ -450,6 +449,12 @@
                 InputDevice.SOURCE_CLASS_BUTTON);
     }
 
+    /**
+     * Requests capturing of input event for the specified display for all requested input types.
+     *
+     * Currently this method requires {@code android.car.permission.CAR_MONITOR_INPUT} or
+     * {@code android.permission.MONITOR_INPUT} permissions (any of them will be acceptable).
+     */
     @Override
     public int requestInputEventCapture(ICarInputCallback callback,
             @DisplayTypeEnum int targetDisplayType,
@@ -458,6 +463,13 @@
                 requestFlags);
     }
 
+    /**
+     * Overloads #requestInputEventCapture(int, int[], int, CarInputCaptureCallback) by providing
+     * a {@link java.util.concurrent.Executor} to be used when invoking the callback argument.
+     *
+     * Currently this method requires {@code android.car.permission.CAR_MONITOR_INPUT} or
+     * {@code android.permission.MONITOR_INPUT} permissions (any of them will be acceptable).
+     */
     @Override
     public void releaseInputEventCapture(ICarInputCallback callback,
             @DisplayTypeEnum int targetDisplayType) {
@@ -691,6 +703,26 @@
         return true;
     }
 
+    private List<String> getAccessibilityServicesToBeEnabled() {
+        String carSafetyAccessibilityServiceComponentName = mContext.getPackageName()
+                + "/"
+                + CarSafetyAccessibilityService.class.getName();
+        ArrayList<String> accessibilityServicesToBeEnabled = new ArrayList<>();
+        accessibilityServicesToBeEnabled.add(carSafetyAccessibilityServiceComponentName);
+        if (!TextUtils.isEmpty(mRotaryServiceComponentName)) {
+            accessibilityServicesToBeEnabled.add(mRotaryServiceComponentName);
+        }
+        return accessibilityServicesToBeEnabled;
+    }
+
+    private static List<String> createServiceListFromSettingsString(
+            String accessibilityServicesString) {
+        return TextUtils.isEmpty(accessibilityServicesString)
+                ? new ArrayList<>()
+                : Arrays.asList(accessibilityServicesString.split(
+                        ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR));
+    }
+
     @Override
     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     public void dump(IndentingPrintWriter writer) {
@@ -701,15 +733,44 @@
         mCaptureController.dump(writer);
     }
 
-    private void updateRotaryServiceSettings(@UserIdInt int userId) {
+    private void updateCarAccessibilityServicesSettings(@UserIdInt int userId) {
         if (UserHelperLite.isHeadlessSystemUser(userId)) {
             return;
         }
+        List<String> accessibilityServicesToBeEnabled = getAccessibilityServicesToBeEnabled();
         ContentResolver contentResolver = mContext.getContentResolver();
-        Settings.Secure.putStringForUser(contentResolver,
-                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
-                mRotaryServiceComponentName,
-                userId);
+        List<String> alreadyEnabledServices = createServiceListFromSettingsString(
+                Settings.Secure.getStringForUser(contentResolver,
+                        Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                        userId));
+
+        int retry = 0;
+        while (!alreadyEnabledServices.containsAll(accessibilityServicesToBeEnabled)
+                && retry <= MAX_RETRIES_FOR_ENABLING_ACCESSIBILITY_SERVICES) {
+            ArrayList<String> enabledServicesList = new ArrayList<>(alreadyEnabledServices);
+            int numAccessibilityServicesToBeEnabled = accessibilityServicesToBeEnabled.size();
+            for (int i = 0; i < numAccessibilityServicesToBeEnabled; i++) {
+                String serviceToBeEnabled = accessibilityServicesToBeEnabled.get(i);
+                if (!enabledServicesList.contains(serviceToBeEnabled)) {
+                    enabledServicesList.add(serviceToBeEnabled);
+                }
+            }
+            Settings.Secure.putStringForUser(contentResolver,
+                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                    String.join(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR, enabledServicesList),
+                    userId);
+            // Read again to account for any race condition with other parts of the code that might
+            // be enabling other accessibility services.
+            alreadyEnabledServices = createServiceListFromSettingsString(
+                    Settings.Secure.getStringForUser(contentResolver,
+                            Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                            userId));
+            retry++;
+        }
+        if (!alreadyEnabledServices.containsAll(accessibilityServicesToBeEnabled)) {
+            Slogf.e(TAG, "Failed to enable accessibility services");
+        }
+
         Settings.Secure.putStringForUser(contentResolver,
                 Settings.Secure.ACCESSIBILITY_ENABLED,
                 "1",
diff --git a/service/src/com/android/car/CarMediaService.java b/service/src/com/android/car/CarMediaService.java
index 8cee561..1f6375e 100644
--- a/service/src/com/android/car/CarMediaService.java
+++ b/service/src/com/android/car/CarMediaService.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.car.Car;
 import android.car.hardware.power.CarPowerPolicy;
@@ -60,6 +61,7 @@
 import android.os.UserManager;
 import android.service.media.MediaBrowserService;
 import android.text.TextUtils;
+import android.util.DebugUtils;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.Slog;
@@ -68,6 +70,7 @@
 import com.android.car.user.CarUserService;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.utils.Slogf;
 
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -76,6 +79,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.stream.Collectors;
 
 /**
@@ -87,7 +91,9 @@
  * it were being browsed only. However, that source is still considered the active source, and
  * should be the source displayed in any Media related UIs (Media Center, home screen, etc).
  */
-public class CarMediaService extends ICarMedia.Stub implements CarServiceBase {
+public final class CarMediaService extends ICarMedia.Stub implements CarServiceBase {
+
+    private static final boolean DEBUG = false;
 
     private static final String SOURCE_KEY = "media_source_component";
     private static final String SOURCE_KEY_SEPARATOR = "_";
@@ -96,6 +102,7 @@
     private static final String COMPONENT_NAME_SEPARATOR = ",";
     private static final String MEDIA_CONNECTION_ACTION = "com.android.car.media.MEDIA_CONNECTION";
     private static final String EXTRA_AUTOPLAY = "com.android.car.media.autoplay";
+    private static final String LAST_UPDATE_KEY = "last_update";
 
     private static final int MEDIA_SOURCE_MODES = 2;
 
@@ -126,6 +133,8 @@
     private boolean mWasPreviouslyDisabledByPowerPolicy;
     @GuardedBy("mLock")
     private boolean mWasPlayingBeforeDisabled;
+
+    // NOTE: must use getSharedPrefsForWriting() to write to it
     private SharedPreferences mSharedPrefs;
     private SessionChangedListener mSessionsListener;
     private int mPlayOnMediaSourceChangedConfig;
@@ -151,6 +160,7 @@
     private ComponentName[] mRemovedMediaSourceComponents = new ComponentName[MEDIA_SOURCE_MODES];
 
     private final IntentFilter mPackageUpdateFilter;
+    @GuardedBy("mLock")
     private boolean mIsPackageUpdateReceiverRegistered;
 
     /**
@@ -230,7 +240,7 @@
                 maybeInitUser(event.getUserId());
                 break;
             case CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED:
-                onUserUnlock(event.getUserId());
+                onUserUnlocked(event.getUserId());
                 break;
         }
     };
@@ -309,8 +319,9 @@
     @Override
     // This method is called from ICarImpl after CarMediaService is created.
     public void init() {
-        int currentUser = ActivityManager.getCurrentUser();
-        maybeInitUser(currentUser);
+        int currentUserId = ActivityManager.getCurrentUser();
+        Slog.d(CarLog.TAG_MEDIA, "init(): currentUser=" + currentUserId);
+        maybeInitUser(currentUserId);
         setPowerPolicyListener();
     }
 
@@ -325,21 +336,16 @@
         }
     }
 
-    private void initUser(int userId) {
-        // SharedPreferences are shared among different users thus only need initialized once. And
-        // they should be initialized after user 0 is unlocked because SharedPreferences in
-        // credential encrypted storage are not available until after user 0 is unlocked.
-        // initUser() is called when the current foreground user is unlocked, and by that time user
-        // 0 has been unlocked already, so initializing SharedPreferences in initUser() is fine.
-        synchronized (mLock) {
-            if (mSharedPrefs == null) {
-                mSharedPrefs = mContext.getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE);
-            }
+    private void initUser(@UserIdInt int userId) {
+        Slog.d(CarLog.TAG_MEDIA, "initUser(): userId=" + userId + ", mSharedPrefs=" + mSharedPrefs);
+        UserHandle currentUser = new UserHandle(userId);
 
+        maybeInitSharedPrefs(userId);
+
+        synchronized (mLock) {
             if (mIsPackageUpdateReceiverRegistered) {
                 mContext.unregisterReceiver(mPackageUpdateReceiver);
             }
-            UserHandle currentUser = new UserHandle(userId);
             mContext.registerReceiverAsUser(mPackageUpdateReceiver, currentUser,
                     mPackageUpdateFilter, null, null);
             mIsPackageUpdateReceiverRegistered = true;
@@ -358,6 +364,30 @@
         }
     }
 
+    private void maybeInitSharedPrefs(@UserIdInt int userId) {
+        // SharedPreferences are shared among different users thus only need initialized once. And
+        // they should be initialized after user 0 is unlocked because SharedPreferences in
+        // credential encrypted storage are not available until after user 0 is unlocked.
+        // initUser() is called when the current foreground user is unlocked, and by that time user
+        // 0 has been unlocked already, so initializing SharedPreferences in initUser() is fine.
+        if (mSharedPrefs != null) {
+            Slog.i(CarLog.TAG_MEDIA, "Shared preferences already set (on directory "
+                    + mContext.getDataDir() + ") when initializing user " + userId);
+            return;
+        }
+        Slog.i(CarLog.TAG_MEDIA, "Getting shared preferences when initializing user "
+                + userId);
+        mSharedPrefs = mContext.getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE);
+
+        // Try to access the properties to make sure they were properly open
+        if (DEBUG) {
+            Slogf.i(CarLog.TAG_MEDIA, "Number of prefs: %d", mSharedPrefs.getAll().size());
+
+        } else if (Log.isLoggable(CarLog.TAG_MEDIA, Log.DEBUG)) {
+            Slogf.d(CarLog.TAG_MEDIA, "Number of prefs: %d", mSharedPrefs.getAll().size());
+        }
+    }
+
     /**
      * Starts a service on the current user that binds to the media browser of the current media
      * source. We start a new service because this one runs on user 0, and MediaBrowser doesn't
@@ -373,20 +403,19 @@
     }
 
     private boolean sharedPrefsInitialized() {
-        if (mSharedPrefs == null) {
-            // It shouldn't reach this but let's be cautious.
-            Slog.e(CarLog.TAG_MEDIA, "SharedPreferences are not initialized!");
-            String className = getClass().getName();
-            for (StackTraceElement ste : Thread.currentThread().getStackTrace()) {
-                // Let's print the useful logs only.
-                String log = ste.toString();
-                if (log.contains(className)) {
-                    Slog.e(CarLog.TAG_MEDIA, log);
-                }
+        if (mSharedPrefs != null) return true;
+
+        // It shouldn't reach this but let's be cautious.
+        Slog.e(CarLog.TAG_MEDIA, "SharedPreferences are not initialized!");
+        String className = getClass().getName();
+        for (StackTraceElement ste : Thread.currentThread().getStackTrace()) {
+            // Let's print the useful logs only.
+            String log = ste.toString();
+            if (log.contains(className)) {
+                Slog.e(CarLog.TAG_MEDIA, log);
             }
-            return false;
         }
-        return true;
+        return false;
     }
 
     private boolean isCurrentUserEphemeral() {
@@ -414,43 +443,85 @@
 
     @Override
     public void dump(IndentingPrintWriter writer) {
+        writer.println("*CarMediaService*");
+        writer.increaseIndent();
+
+        writer.printf("Pending init: %b\n", mPendingInit);
+        boolean hasSharedPrefs;
         synchronized (mLock) {
-            writer.println("*CarMediaService*");
-            writer.increaseIndent();
-            writer.printf("Current playback media component: %s\n",
-                    mPrimaryMediaComponents[MEDIA_SOURCE_MODE_PLAYBACK] == null ? "-"
-                    : mPrimaryMediaComponents[MEDIA_SOURCE_MODE_PLAYBACK].flattenToString());
-            writer.printf("Current browse media component: %s\n",
-                    mPrimaryMediaComponents[MEDIA_SOURCE_MODE_BROWSE] == null ? "-"
-                    : mPrimaryMediaComponents[MEDIA_SOURCE_MODE_BROWSE].flattenToString());
+            hasSharedPrefs = mSharedPrefs != null;
+            dumpCurrentMediaComponent(writer, "playback", MEDIA_SOURCE_MODE_PLAYBACK);
+            dumpCurrentMediaComponent(writer, "browse", MEDIA_SOURCE_MODE_BROWSE);
             if (mActiveUserMediaController != null) {
                 writer.printf("Current media controller: %s\n",
                         mActiveUserMediaController.getPackageName());
                 writer.printf("Current browse service extra: %s\n",
                         getClassName(mActiveUserMediaController));
+            } else {
+                writer.println("no active user media controller");
             }
-            writer.printf("Number of active media sessions: %s\n", mMediaSessionManager
-                    .getActiveSessionsForUser(null,
-                            new UserHandle(ActivityManager.getCurrentUser())).size());
+            int userId = ActivityManager.getCurrentUser();
+            writer.printf("Number of active media sessions (for current user %d): %d\n", userId,
+                    mMediaSessionManager.getActiveSessionsForUser(/* notificationListener= */ null,
+                            new UserHandle(userId)).size());
 
-            writer.println("Playback media source history:");
-            writer.increaseIndent();
-            for (ComponentName name : getLastMediaSources(MEDIA_SOURCE_MODE_PLAYBACK)) {
-                writer.println(name.flattenToString());
-            }
-            writer.decreaseIndent();
-            writer.println("Browse media source history:");
-            writer.increaseIndent();
-            for (ComponentName name : getLastMediaSources(MEDIA_SOURCE_MODE_BROWSE)) {
-                writer.println(name.flattenToString());
-            }
-            writer.decreaseIndent();
             writer.printf("Disabled by power policy: %s\n", mIsDisabledByPowerPolicy);
             if (mIsDisabledByPowerPolicy) {
                 writer.printf("Before being disabled by power policy, audio was %s\n",
                         mWasPlayingBeforeDisabled ? "active" : "inactive");
             }
         }
+
+        if (hasSharedPrefs) {
+            dumpLastMediaSources(writer, "Playback", MEDIA_SOURCE_MODE_PLAYBACK);
+            dumpLastMediaSources(writer, "Browse", MEDIA_SOURCE_MODE_BROWSE);
+            dumpSharedPrefs(writer);
+        } else {
+            writer.println("No shared preferences");
+        }
+
+        writer.decreaseIndent();
+    }
+
+    private void dumpCurrentMediaComponent(IndentingPrintWriter writer, String name,
+            @CarMediaManager.MediaSourceMode int mode) {
+        ComponentName componentName = mPrimaryMediaComponents[mode];
+        writer.printf("Current %s media component: %s\n", name, componentName == null
+                ? "-"
+                : componentName.flattenToString());
+    }
+
+    private void dumpLastMediaSources(IndentingPrintWriter writer, String name,
+            @CarMediaManager.MediaSourceMode int mode) {
+        writer.printf("%s media source history:\n", name);
+        writer.increaseIndent();
+        List<ComponentName> lastMediaSources = getLastMediaSources(mode);
+        for (int i = 0; i < lastMediaSources.size(); i++) {
+            ComponentName componentName = lastMediaSources.get(i);
+            if (componentName == null) {
+                Slogf.e(CarLog.TAG_MEDIA, "dump(): empty last media source of %s at index %d: %s",
+                        mediaModeToString(mode), i, lastMediaSources);
+                continue;
+            }
+            writer.println(componentName.flattenToString());
+        }
+        writer.decreaseIndent();
+    }
+
+    private void dumpSharedPrefs(IndentingPrintWriter writer) {
+        Map<String, ?> allPrefs = mSharedPrefs.getAll();
+        writer.printf("%d shared preferences (saved on directory %s)",
+                allPrefs.size(), mContext.getDataDir());
+        if (!Log.isLoggable(CarLog.TAG_MEDIA, Log.VERBOSE) || allPrefs.isEmpty()) {
+            writer.println();
+            return;
+        }
+        writer.println(':');
+        writer.increaseIndent();
+        for (Entry<String, ?> pref : allPrefs.entrySet()) {
+            writer.printf("%s = %s\n", pref.getKey(), pref.getValue());
+        }
+        writer.decreaseIndent();
     }
 
     /**
@@ -531,10 +602,12 @@
     }
 
     // TODO(b/153115826): this method was used to be called from the ICar binder thread, but it's
-    // now called by UserCarService. Currently UserCarServie is calling every listener in one
+    // now called by UserCarService. Currently UserCarService is calling every listener in one
     // non-main thread, but it's not clear how the final behavior will be. So, for now it's ok
     // to post it to mMainHandler, but once b/145689885 is fixed, we might not need it.
-    private void onUserUnlock(int userId) {
+    private void onUserUnlocked(@UserIdInt int userId) {
+        Slog.d(CarLog.TAG_MEDIA, "onUserUnlocked(): userId=" + userId
+                + ", mPendingInit=" + mPendingInit);
         mMainHandler.post(() -> {
             // No need to handle system user, non current foreground user.
             if (userId == UserHandle.USER_SYSTEM
@@ -904,13 +977,28 @@
         String componentName = component.flattenToString();
         String key = getMediaSourceKey(mode);
         String serialized = mSharedPrefs.getString(key, null);
+        String modeName = null;
+        boolean debug = DEBUG || Log.isLoggable(CarLog.TAG_MEDIA, Log.DEBUG);
+        if (debug) {
+            modeName = mediaModeToString(mode);
+        }
+
         if (serialized == null) {
-            mSharedPrefs.edit().putString(key, componentName).apply();
+            if (debug) {
+                Slogf.d(CarLog.TAG_MEDIA, "saveLastMediaSource(%s, %s): no value for key %s",
+                        componentName, modeName, key);
+            }
+            getSharedPrefsForWriting().putString(key, componentName).apply();
         } else {
             Deque<String> componentNames = new ArrayDeque<>(getComponentNameList(serialized));
             componentNames.remove(componentName);
             componentNames.addFirst(componentName);
-            mSharedPrefs.edit().putString(key, serializeComponentNameList(componentNames)).apply();
+            String newSerialized = serializeComponentNameList(componentNames);
+            if (debug) {
+                Slogf.d(CarLog.TAG_MEDIA, "saveLastMediaSource(%s, %s): updating %s from %s to %s",
+                        componentName, modeName,  key, serialized, newSerialized);
+            }
+            getSharedPrefsForWriting().putString(key, newSerialized).apply();
         }
     }
 
@@ -960,7 +1048,8 @@
             mCurrentPlaybackState = state;
         }
         String key = getPlaybackStateKey();
-        mSharedPrefs.edit().putInt(key, state).apply();
+        Slogf.d(CarLog.TAG_MEDIA, "savePlaybackState(): %s = %d)", key, state);
+        getSharedPrefsForWriting().putInt(key, state).apply();
     }
 
     /**
@@ -1031,6 +1120,15 @@
         }
     }
 
+    /**
+     * Gets the editor used to update shared preferences.
+     */
+    private SharedPreferences.Editor getSharedPrefsForWriting() {
+        long now = System.currentTimeMillis();
+        Slogf.i(CarLog.TAG_MEDIA, "Updating %s to %d", LAST_UPDATE_KEY, now);
+        return mSharedPrefs.edit().putLong(LAST_UPDATE_KEY, now);
+    }
+
     @NonNull
     private static String getClassName(@NonNull MediaController controller) {
         Bundle sessionExtras = controller.getExtras();
@@ -1039,4 +1137,8 @@
                         Car.CAR_EXTRA_BROWSE_SERVICE_FOR_SESSION);
         return value != null ? value : "";
     }
+
+    private static String mediaModeToString(@CarMediaManager.MediaSourceMode int mode) {
+        return DebugUtils.constantToString(CarMediaManager.class, "MEDIA_SOURCE_", mode);
+    }
 }
diff --git a/service/src/com/android/car/CarShellCommand.java b/service/src/com/android/car/CarShellCommand.java
index bad6b71..2e16d10 100644
--- a/service/src/com/android/car/CarShellCommand.java
+++ b/service/src/com/android/car/CarShellCommand.java
@@ -18,6 +18,7 @@
 import static android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME;
 import static android.car.Car.PERMISSION_CAR_POWER;
 import static android.car.Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG;
+import static android.car.Car.PERMISSION_USE_CAR_WATCHDOG;
 import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue.ASSOCIATE_CURRENT_USER;
 import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue.DISASSOCIATE_ALL_USERS;
 import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue.DISASSOCIATE_CURRENT_USER;
@@ -171,6 +172,10 @@
     private static final String COMMAND_APPLY_POWER_POLICY = "apply-power-policy";
     private static final String COMMAND_DEFINE_POWER_POLICY_GROUP = "define-power-policy-group";
     private static final String COMMAND_SET_POWER_POLICY_GROUP = "set-power-policy-group";
+    private static final String COMMAND_APPLY_CTS_VERIFIER_POWER_OFF_POLICY =
+            "apply-cts-verifier-power-off-policy";
+    private static final String COMMAND_APPLY_CTS_VERIFIER_POWER_ON_POLICY =
+            "apply-cts-verifier-power-on-policy";
     private static final String COMMAND_POWER_OFF = "power-off";
     private static final String POWER_OFF_SKIP_GARAGEMODE = "--skip-garagemode";
     private static final String POWER_OFF_SHUTDOWN = "--shutdown";
@@ -197,6 +202,8 @@
             "watchdog-io-set-3p-foreground-bytes";
     private static final String COMMAND_WATCHDOG_IO_GET_3P_FOREGROUND_BYTES =
             "watchdog-io-get-3p-foreground-bytes";
+    private static final String COMMAND_WATCHDOG_CONTROL_PROCESS_HEALTH_CHECK =
+            "watchdog-control-health-check";
 
     private static final String[] CREATE_OR_MANAGE_USERS_PERMISSIONS = new String[] {
             android.Manifest.permission.CREATE_USERS,
@@ -249,6 +256,10 @@
                 android.Manifest.permission.DEVICE_POWER);
         USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_SET_POWER_POLICY_GROUP,
                 android.Manifest.permission.DEVICE_POWER);
+        USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_APPLY_CTS_VERIFIER_POWER_OFF_POLICY,
+                android.Manifest.permission.DEVICE_POWER);
+        USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_APPLY_CTS_VERIFIER_POWER_ON_POLICY,
+                android.Manifest.permission.DEVICE_POWER);
         USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_SILENT_MODE,
                 PERMISSION_CAR_POWER);
         USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_GET_INITIAL_USER,
@@ -269,6 +280,8 @@
                 PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG);
         USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_WATCHDOG_IO_GET_3P_FOREGROUND_BYTES,
                 PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG);
+        USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_WATCHDOG_CONTROL_PROCESS_HEALTH_CHECK,
+                PERMISSION_USE_CAR_WATCHDOG);
     }
 
     private static final String PARAM_DAY_MODE = "day";
@@ -594,6 +607,14 @@
         pw.println("\t  Sets power policy group which is defined in /vendor/etc/power_policy.xml ");
         pw.printf("\t  or by %s command\n", COMMAND_DEFINE_POWER_POLICY_GROUP);
 
+        pw.printf("\t%s\n", COMMAND_APPLY_CTS_VERIFIER_POWER_OFF_POLICY);
+        pw.println("\t  Define and apply the cts_verifier_off power policy with "
+                + "--disable WIFI,LOCATION,BLUETOOTH");
+
+        pw.printf("\t%s\n", COMMAND_APPLY_CTS_VERIFIER_POWER_ON_POLICY);
+        pw.println("\t  Define and apply the cts_verifier_on power policy with "
+                + "--enable WIFI,LOCATION,BLUETOOTH");
+
         pw.printf("\t%s [%s] [%s]\n", COMMAND_POWER_OFF, POWER_OFF_SKIP_GARAGEMODE,
                 POWER_OFF_SHUTDOWN);
         pw.println("\t  Powers off the car.");
@@ -612,6 +633,10 @@
 
         pw.printf("\t%s\n", COMMAND_WATCHDOG_IO_GET_3P_FOREGROUND_BYTES);
         pw.println("\t  Gets third-party apps foreground I/O overuse threshold");
+
+        pw.printf("\t%s enable|disable\n", COMMAND_WATCHDOG_CONTROL_PROCESS_HEALTH_CHECK);
+        pw.println("\t  Enables/disables car watchdog process health check.");
+        pw.println("\t  Set to true to disable the process health check.");
     }
 
     private static int showInvalidArguments(IndentingPrintWriter pw) {
@@ -925,6 +950,10 @@
                 return definePowerPolicyGroup(args, writer);
             case COMMAND_SET_POWER_POLICY_GROUP:
                 return setPowerPolicyGroup(args, writer);
+            case COMMAND_APPLY_CTS_VERIFIER_POWER_OFF_POLICY:
+                return applyCtsVerifierPowerOffPolicy(args, writer);
+            case COMMAND_APPLY_CTS_VERIFIER_POWER_ON_POLICY:
+                return applyCtsVerifierPowerOnPolicy(args, writer);
             case COMMAND_POWER_OFF:
                 powerOff(args, writer);
                 break;
@@ -940,7 +969,9 @@
             case COMMAND_WATCHDOG_IO_GET_3P_FOREGROUND_BYTES:
                 getWatchdogIoThirdPartyForegroundBytes(writer);
                 break;
-
+            case COMMAND_WATCHDOG_CONTROL_PROCESS_HEALTH_CHECK:
+                controlWatchdogProcessHealthCheck(args, writer);
+                break;
             default:
                 writer.println("Unknown command: \"" + cmd + "\"");
                 showHelp(writer);
@@ -1984,6 +2015,29 @@
         return RESULT_ERROR;
     }
 
+    private int applyCtsVerifierPowerPolicy(String policyId, String ops, String cmdName,
+            IndentingPrintWriter writer) {
+        String[] defArgs = {"define-power-policy", policyId, ops, "WIFI,BLUETOOTH,LOCATION"};
+        mCarPowerManagementService.definePowerPolicyFromCommand(defArgs, writer);
+
+        String[] appArgs = {"apply-power-policy", policyId};
+        boolean result = mCarPowerManagementService.applyPowerPolicyFromCommand(appArgs, writer);
+        if (result) return RESULT_OK;
+
+        writer.printf("\nUsage: cmd car_service %s\n", cmdName);
+        return RESULT_ERROR;
+    }
+
+    private int applyCtsVerifierPowerOffPolicy(String[] unusedArgs, IndentingPrintWriter writer) {
+        return applyCtsVerifierPowerPolicy("cts_verifier_off", "--disable",
+                COMMAND_APPLY_CTS_VERIFIER_POWER_OFF_POLICY, writer);
+    }
+
+    private int applyCtsVerifierPowerOnPolicy(String[] unusedArgs, IndentingPrintWriter writer) {
+        return applyCtsVerifierPowerPolicy("cts_verifier_on", "--enable",
+                COMMAND_APPLY_CTS_VERIFIER_POWER_ON_POLICY, writer);
+    }
+
     private void powerOff(String[] args, IndentingPrintWriter writer) {
         int index = 1;
         boolean skipGarageMode = false;
@@ -2200,6 +2254,19 @@
                 .setIoOveruseConfiguration(configuration.getIoOveruseConfiguration());
     }
 
+    private void controlWatchdogProcessHealthCheck(String[] args, IndentingPrintWriter writer) {
+        if (args.length != 2) {
+            showInvalidArguments(writer);
+            return;
+        }
+        if (!args[1].equals("enable") && !args[1].equals("disable")) {
+            writer.println("Failed to parse argument. Valid arguments: enable | disable");
+            return;
+        }
+        mCarWatchdogService.controlProcessHealthCheck(args[1].equals("disable"));
+        writer.printf("Watchdog health checking is now %sd \n", args[1]);
+    }
+
     // 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 df600de..d590083 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -354,7 +354,7 @@
         }
 
         if (mFeatureController.isFeatureEnabled(Car.CAR_TELEMETRY_SERVICE)) {
-            mCarTelemetryService = new CarTelemetryService(serviceContext);
+            mCarTelemetryService = new CarTelemetryService(serviceContext, mCarPropertyService);
         } else {
             mCarTelemetryService = null;
         }
diff --git a/service/src/com/android/car/InputCaptureClientController.java b/service/src/com/android/car/InputCaptureClientController.java
index 7719b24..5f9ebfa 100644
--- a/service/src/com/android/car/InputCaptureClientController.java
+++ b/service/src/com/android/car/InputCaptureClientController.java
@@ -246,7 +246,8 @@
     public int requestInputEventCapture(ICarInputCallback callback,
             @DisplayTypeEnum int targetDisplayType,
             int[] inputTypes, int requestFlags) {
-        ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_MONITOR_INPUT);
+        ICarImpl.assertAnyPermission(mContext, Car.PERMISSION_CAR_MONITOR_INPUT,
+                android.Manifest.permission.MONITOR_INPUT);
 
         Preconditions.checkArgument(SUPPORTED_DISPLAY_TYPES.contains(targetDisplayType),
                 "Display not supported yet:" + targetDisplayType);
diff --git a/service/src/com/android/car/audio/CarAudioPolicyVolumeCallback.java b/service/src/com/android/car/audio/CarAudioPolicyVolumeCallback.java
index 6fccbf9..14b1a6f 100644
--- a/service/src/com/android/car/audio/CarAudioPolicyVolumeCallback.java
+++ b/service/src/com/android/car/audio/CarAudioPolicyVolumeCallback.java
@@ -31,6 +31,8 @@
 
 import com.android.car.audio.CarAudioContext.AudioContext;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.utils.Slogf;
+
 
 import java.util.Objects;
 
@@ -67,25 +69,32 @@
 
         int zoneId = PRIMARY_AUDIO_ZONE;
         int groupId = mCarAudioService.getVolumeGroupIdForAudioContext(zoneId, suggestedContext);
+        boolean isMuted = isMuted(zoneId, groupId);
 
-        if (Log.isLoggable(TAG_AUDIO, VERBOSE)) {
-            Slog.v(TAG_AUDIO, "onVolumeAdjustment: "
+        if (Slogf.isLoggable(TAG_AUDIO, VERBOSE)) {
+            Slogf.v(TAG_AUDIO, "onVolumeAdjustment: "
                     + AudioManager.adjustToString(adjustment) + " suggested audio context: "
                     + CarAudioContext.toString(suggestedContext) + " suggested volume group: "
-                    + groupId);
+                    + groupId + " is muted " + isMuted);
         }
 
         final int currentVolume = mCarAudioService.getGroupVolume(zoneId, groupId);
         final int flags = AudioManager.FLAG_FROM_KEY | AudioManager.FLAG_SHOW_UI;
+        int minGain = mCarAudioService.getGroupMinVolume(zoneId, groupId);
         switch (adjustment) {
             case AudioManager.ADJUST_LOWER:
-                int minValue = Math.max(currentVolume - 1,
-                        mCarAudioService.getGroupMinVolume(zoneId, groupId));
+                int minValue = Math.max(currentVolume - 1, minGain);
+                if (isMuted)  {
+                    minValue = minGain;
+                }
                 mCarAudioService.setGroupVolume(zoneId, groupId, minValue, flags);
                 break;
             case AudioManager.ADJUST_RAISE:
                 int maxValue = Math.min(currentVolume + 1,
                         mCarAudioService.getGroupMaxVolume(zoneId, groupId));
+                if (isMuted)  {
+                    maxValue = minGain;
+                }
                 mCarAudioService.setGroupVolume(zoneId, groupId, maxValue, flags);
                 break;
             case AudioManager.ADJUST_MUTE:
@@ -93,7 +102,7 @@
                 setMute(adjustment == AudioManager.ADJUST_MUTE, groupId, flags);
                 break;
             case AudioManager.ADJUST_TOGGLE_MUTE:
-                toggleMute(groupId, flags);
+                setMute(!isMuted, groupId, flags);
                 break;
             case AudioManager.ADJUST_SAME:
             default:
@@ -101,13 +110,11 @@
         }
     }
 
-    private void toggleMute(int groupId, int flags) {
+    private boolean isMuted(int zoneId, int groupId) {
         if (mUseCarVolumeGroupMuting) {
-            setMute(!mCarAudioService.isVolumeGroupMuted(PRIMARY_AUDIO_ZONE, groupId), groupId,
-                    flags);
-            return;
+            return mCarAudioService.isVolumeGroupMuted(zoneId, groupId);
         }
-        setMute(!mAudioManager.isMasterMute(), groupId, flags);
+        return mAudioManager.isMasterMute();
     }
 
     private void setMute(boolean mute, int groupId, int flags) {
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index 62be1fd..1673f61 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -69,7 +69,6 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
-import android.view.KeyEvent;
 
 import com.android.car.CarLocalServices;
 import com.android.car.CarLog;
@@ -436,25 +435,8 @@
     void setMasterMute(boolean mute, int flags) {
         mAudioManager.setMasterMute(mute, flags);
 
-        // Master Mute only appliers to primary zone
+        // Master Mute only applies to primary zone
         callbackMasterMuteChange(PRIMARY_AUDIO_ZONE, flags);
-
-        // When the master mute is turned ON, we want the playing app to get a "pause" command.
-        // When the volume is unmuted, we want to resume playback.
-        int keycode = mute ? KeyEvent.KEYCODE_MEDIA_PAUSE : KeyEvent.KEYCODE_MEDIA_PLAY;
-
-        dispatchMediaKeyEvent(keycode);
-    }
-
-    private void dispatchMediaKeyEvent(int keycode) {
-        long currentTime = SystemClock.uptimeMillis();
-        KeyEvent keyDown = new KeyEvent(/* downTime= */ currentTime, /* eventTime= */ currentTime,
-                KeyEvent.ACTION_DOWN, keycode, /* repeat= */ 0);
-        mAudioManager.dispatchMediaKeyEvent(keyDown);
-
-        KeyEvent keyUp = new KeyEvent(/* downTime= */ currentTime, /* eventTime= */ currentTime,
-                KeyEvent.ACTION_UP, keycode, /* repeat= */ 0);
-        mAudioManager.dispatchMediaKeyEvent(keyUp);
     }
 
     void callbackMasterMuteChange(int zoneId, int flags) {
@@ -550,9 +532,9 @@
                 AudioManager.GET_DEVICES_INPUTS);
     }
 
+    @GuardedBy("mImplLock")
     private SparseArray<CarAudioZone> loadCarAudioConfigurationLocked(
-            List<CarAudioDeviceInfo> carAudioDeviceInfos) {
-        AudioDeviceInfo[] inputDevices = getAllInputDevices();
+            List<CarAudioDeviceInfo> carAudioDeviceInfos, AudioDeviceInfo[] inputDevices) {
         try (InputStream inputStream = new FileInputStream(mCarAudioConfigurationPath)) {
             CarAudioZonesHelper zonesHelper = new CarAudioZonesHelper(mCarAudioSettings,
                     inputStream, carAudioDeviceInfos, inputDevices, mUseCarVolumeGroupMuting);
@@ -564,8 +546,9 @@
         }
     }
 
+    @GuardedBy("mImplLock")
     private SparseArray<CarAudioZone> loadVolumeGroupConfigurationWithAudioControlLocked(
-            List<CarAudioDeviceInfo> carAudioDeviceInfos) {
+            List<CarAudioDeviceInfo> carAudioDeviceInfos, AudioDeviceInfo[] inputDevices) {
         AudioControlWrapper audioControlWrapper = getAudioControlWrapperLocked();
         if (!(audioControlWrapper instanceof AudioControlWrapperV1)) {
             throw new IllegalStateException(
@@ -574,20 +557,22 @@
         }
         CarAudioZonesHelperLegacy legacyHelper = new CarAudioZonesHelperLegacy(mContext,
                 R.xml.car_volume_groups, carAudioDeviceInfos,
-                (AudioControlWrapperV1) audioControlWrapper, mCarAudioSettings);
+                (AudioControlWrapperV1) audioControlWrapper, mCarAudioSettings, inputDevices);
         return legacyHelper.loadAudioZones();
     }
 
     @GuardedBy("mImplLock")
     private void loadCarAudioZonesLocked() {
         List<CarAudioDeviceInfo> carAudioDeviceInfos = generateCarAudioDeviceInfos();
+        AudioDeviceInfo[] inputDevices = getAllInputDevices();
 
         mCarAudioConfigurationPath = getAudioConfigurationPath();
         if (mCarAudioConfigurationPath != null) {
-            mCarAudioZones = loadCarAudioConfigurationLocked(carAudioDeviceInfos);
+            mCarAudioZones = loadCarAudioConfigurationLocked(carAudioDeviceInfos, inputDevices);
         } else {
-            mCarAudioZones = loadVolumeGroupConfigurationWithAudioControlLocked(
-                    carAudioDeviceInfos);
+            mCarAudioZones =
+                    loadVolumeGroupConfigurationWithAudioControlLocked(carAudioDeviceInfos,
+                            inputDevices);
         }
 
         CarAudioZonesValidator.validate(mCarAudioZones);
diff --git a/service/src/com/android/car/audio/CarAudioUtils.java b/service/src/com/android/car/audio/CarAudioUtils.java
index b787efb..ad74587 100644
--- a/service/src/com/android/car/audio/CarAudioUtils.java
+++ b/service/src/com/android/car/audio/CarAudioUtils.java
@@ -16,6 +16,10 @@
 
 package com.android.car.audio;
 
+import static android.media.AudioDeviceInfo.TYPE_BUILTIN_MIC;
+
+import android.media.AudioDeviceInfo;
+
 final class CarAudioUtils {
     private CarAudioUtils() {
     }
@@ -23,4 +27,8 @@
     static boolean hasExpired(long startTimeMs, long currentTimeMs, int timeoutMs) {
         return (currentTimeMs - startTimeMs) > timeoutMs;
     }
+
+    static boolean isMicrophoneInputDevice(AudioDeviceInfo device) {
+        return device.getType() == TYPE_BUILTIN_MIC;
+    }
 }
diff --git a/service/src/com/android/car/audio/CarAudioZonesHelper.java b/service/src/com/android/car/audio/CarAudioZonesHelper.java
index 7956ea2..2e25325 100644
--- a/service/src/com/android/car/audio/CarAudioZonesHelper.java
+++ b/service/src/com/android/car/audio/CarAudioZonesHelper.java
@@ -17,10 +17,14 @@
 
 import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE;
 
+import static com.android.car.audio.CarAudioUtils.isMicrophoneInputDevice;
+
 import android.annotation.NonNull;
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.Xml;
@@ -35,8 +39,6 @@
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -73,7 +75,7 @@
     private static final Map<String, Integer> CONTEXT_NAME_MAP;
 
     static {
-        CONTEXT_NAME_MAP = new HashMap<>(CarAudioContext.CONTEXTS.length);
+        CONTEXT_NAME_MAP = new ArrayMap<>(CarAudioContext.CONTEXTS.length);
         CONTEXT_NAME_MAP.put("music", CarAudioContext.MUSIC);
         CONTEXT_NAME_MAP.put("navigation", CarAudioContext.NAVIGATION);
         CONTEXT_NAME_MAP.put("voice_command", CarAudioContext.VOICE_COMMAND);
@@ -128,11 +130,11 @@
 
     private final CarAudioSettings mCarAudioSettings;
     private final Map<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo;
-    private final Map<String, AudioDeviceInfo> mAddressToInputAudioDeviceInfo;
+    private final Map<String, AudioDeviceInfo> mAddressToInputAudioDeviceInfoForAllInputDevices;
     private final InputStream mInputStream;
     private final SparseIntArray mZoneIdToOccupantZoneIdMapping;
     private final Set<Integer> mAudioZoneIds;
-    private final Set<String> mInputAudioDevices;
+    private final Set<String> mAssignedInputAudioDevices;
     private final boolean mUseCarVolumeGroupMute;
 
     private int mNextSecondaryZoneId;
@@ -152,12 +154,12 @@
         Objects.requireNonNull(inputDeviceInfo);
         mAddressToCarAudioDeviceInfo = CarAudioZonesHelper.generateAddressToInfoMap(
                 carAudioDeviceInfos);
-        mAddressToInputAudioDeviceInfo =
+        mAddressToInputAudioDeviceInfoForAllInputDevices =
                 CarAudioZonesHelper.generateAddressToInputAudioDeviceInfoMap(inputDeviceInfo);
         mNextSecondaryZoneId = PRIMARY_AUDIO_ZONE + 1;
         mZoneIdToOccupantZoneIdMapping = new SparseIntArray();
-        mAudioZoneIds = new HashSet<>();
-        mInputAudioDevices = new HashSet<>();
+        mAudioZoneIds = new ArraySet<>();
+        mAssignedInputAudioDevices = new ArraySet<>();
         mUseCarVolumeGroupMute = useCarVolumeGroupMute;
     }
 
@@ -178,8 +180,8 @@
 
     private static Map<String, AudioDeviceInfo> generateAddressToInputAudioDeviceInfoMap(
             @NonNull AudioDeviceInfo[] inputAudioDeviceInfos) {
-        HashMap<String, AudioDeviceInfo> deviceAddressToInputDeviceMap =
-                new HashMap<>(inputAudioDeviceInfos.length);
+        Map<String, AudioDeviceInfo> deviceAddressToInputDeviceMap =
+                new ArrayMap<>(inputAudioDeviceInfos.length);
         for (int i = 0; i < inputAudioDeviceInfos.length; ++i) {
             AudioDeviceInfo device = inputAudioDeviceInfos[i];
             if (device.isSource()) {
@@ -238,9 +240,20 @@
         }
 
         verifyPrimaryZonePresent(carAudioZones);
+        addRemainingMicrophonesToPrimaryZone(carAudioZones);
         return carAudioZones;
     }
 
+    private void addRemainingMicrophonesToPrimaryZone(SparseArray<CarAudioZone> carAudioZones) {
+        CarAudioZone primaryAudioZone = carAudioZones.get(PRIMARY_AUDIO_ZONE);
+        for (AudioDeviceInfo info : mAddressToInputAudioDeviceInfoForAllInputDevices.values()) {
+            if (!mAssignedInputAudioDevices.contains(info.getAddress())
+                    && isMicrophoneInputDevice(info)) {
+                primaryAudioZone.addInputAudioDevice(new AudioDeviceAttributes(info));
+            }
+        }
+    }
+
     private void verifyOnlyOnePrimaryZone(CarAudioZone newZone, SparseArray<CarAudioZone> zones) {
         if (newZone.getId() == PRIMARY_AUDIO_ZONE && zones.contains(PRIMARY_AUDIO_ZONE)) {
             throw new RuntimeException("More than one zone parsed with primary audio zone ID: "
@@ -347,7 +360,8 @@
                 String audioDeviceAddress =
                         parser.getAttributeValue(NAMESPACE, ATTR_DEVICE_ADDRESS);
                 validateInputAudioDeviceAddress(audioDeviceAddress);
-                AudioDeviceInfo info = mAddressToInputAudioDeviceInfo.get(audioDeviceAddress);
+                AudioDeviceInfo info =
+                        mAddressToInputAudioDeviceInfoForAllInputDevices.get(audioDeviceAddress);
                 Preconditions.checkArgument(info != null,
                         "%s %s of %s does not exist, add input device to"
                                 + " audio_policy_configuration.xml.",
@@ -364,11 +378,11 @@
         Preconditions.checkArgument(!audioDeviceAddress.isEmpty(),
                 "%s %s attribute can not be empty.",
                 TAG_INPUT_DEVICE, ATTR_DEVICE_ADDRESS);
-        if (mInputAudioDevices.contains(audioDeviceAddress)) {
+        if (mAssignedInputAudioDevices.contains(audioDeviceAddress)) {
             throw new IllegalArgumentException(TAG_INPUT_DEVICE + " " + audioDeviceAddress
                     + " repeats, " + TAG_INPUT_DEVICES + " can not repeat.");
         }
-        mInputAudioDevices.add(audioDeviceAddress);
+        mAssignedInputAudioDevices.add(audioDeviceAddress);
     }
 
     private void validateOccupantZoneIdIsUnique(int occupantZoneId) {
diff --git a/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java b/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
index 6bbd6c9..7ce2538 100644
--- a/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
+++ b/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
@@ -17,6 +17,7 @@
 
 import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE;
 
+import static com.android.car.audio.CarAudioUtils.isMicrophoneInputDevice;
 import static com.android.car.audio.CarAudioZonesHelper.LEGACY_CONTEXTS;
 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DEPRECATED_CODE;
 
@@ -25,6 +26,8 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
 import android.util.AttributeSet;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -62,15 +65,21 @@
     private final SparseIntArray mLegacyAudioContextToBus;
     private final SparseArray<CarAudioDeviceInfo> mBusToCarAudioDeviceInfo;
     private final CarAudioSettings mCarAudioSettings;
+    private final AudioDeviceInfo[] mInputDevices;
 
-    CarAudioZonesHelperLegacy(@NonNull  Context context, @XmlRes int xmlConfiguration,
+    CarAudioZonesHelperLegacy(@NonNull Context context, @XmlRes int xmlConfiguration,
             @NonNull List<CarAudioDeviceInfo> carAudioDeviceInfos,
             @NonNull AudioControlWrapperV1 audioControlWrapper,
-            @NonNull CarAudioSettings carAudioSettings) {
-        Objects.requireNonNull(context);
-        Objects.requireNonNull(carAudioDeviceInfos);
-        Objects.requireNonNull(audioControlWrapper);
-        mCarAudioSettings = Objects.requireNonNull(carAudioSettings);
+            @NonNull CarAudioSettings carAudioSettings,
+            AudioDeviceInfo[] inputDevices) {
+        Objects.requireNonNull(context, "Context must not be null.");
+        Objects.requireNonNull(carAudioDeviceInfos,
+                "Car Audio Device Info must not be null.");
+        Objects.requireNonNull(audioControlWrapper,
+                "Car Audio Control must not be null.");
+        Objects.requireNonNull(inputDevices, "Input Devices must not be null.");
+        mCarAudioSettings = Objects.requireNonNull(carAudioSettings,
+                "Car Audio Settings can not be null.");
         mContext = context;
         mXmlConfiguration = xmlConfiguration;
         mBusToCarAudioDeviceInfo =
@@ -78,6 +87,7 @@
 
         mLegacyAudioContextToBus =
                 loadBusesForLegacyContexts(audioControlWrapper);
+        mInputDevices = inputDevices;
     }
 
     /* Loads mapping from {@link CarAudioContext} values to bus numbers
@@ -135,7 +145,9 @@
             zone.addVolumeGroup(volumeGroup);
         }
         SparseArray<CarAudioZone> carAudioZones = new SparseArray<>();
+        addMicrophonesToPrimaryZone(zone);
         carAudioZones.put(PRIMARY_AUDIO_ZONE, zone);
+
         return carAudioZones;
     }
 
@@ -221,6 +233,15 @@
         return contexts;
     }
 
+    private void addMicrophonesToPrimaryZone(CarAudioZone primaryAudioZone) {
+        for (int index = 0; index < mInputDevices.length; index++) {
+            AudioDeviceInfo info = mInputDevices[index];
+            if (isMicrophoneInputDevice(info)) {
+                primaryAudioZone.addInputAudioDevice(new AudioDeviceAttributes(info));
+            }
+        }
+    }
+
     /**
      * Parse device address. Expected format is BUS%d_%s, address, usage hint
      *
diff --git a/service/src/com/android/car/audio/CarAudioZonesValidator.java b/service/src/com/android/car/audio/CarAudioZonesValidator.java
index 5fe495d..6b7bbb9 100644
--- a/service/src/com/android/car/audio/CarAudioZonesValidator.java
+++ b/service/src/com/android/car/audio/CarAudioZonesValidator.java
@@ -16,9 +16,16 @@
 package com.android.car.audio;
 
 
+import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE;
+import static android.media.AudioDeviceInfo.TYPE_BUILTIN_MIC;
+
+import android.media.AudioDeviceAttributes;
 import android.util.SparseArray;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 final class CarAudioZonesValidator {
@@ -29,6 +36,20 @@
         validateAtLeastOneZoneDefined(carAudioZones);
         validateVolumeGroupsForEachZone(carAudioZones);
         validateEachAddressAppearsAtMostOnce(carAudioZones);
+        validatePrimaryZoneHasInputDevice(carAudioZones);
+    }
+
+    private static void validatePrimaryZoneHasInputDevice(SparseArray<CarAudioZone> carAudioZones) {
+        CarAudioZone primaryZone = carAudioZones.get(PRIMARY_AUDIO_ZONE);
+        List<AudioDeviceAttributes> devices = primaryZone.getInputAudioDevices();
+        Preconditions.checkCollectionNotEmpty(devices, "Primary Zone Input Devices");
+        for (int index = 0; index < devices.size(); index++) {
+            AudioDeviceAttributes device = devices.get(index);
+            if (device.getType() == TYPE_BUILTIN_MIC) {
+                return;
+            }
+        }
+        throw new RuntimeException("Primary Zone must have at least one microphone input device");
     }
 
     private static void validateAtLeastOneZoneDefined(SparseArray<CarAudioZone> carAudioZones) {
diff --git a/service/src/com/android/car/audio/CarDuckingUtils.java b/service/src/com/android/car/audio/CarDuckingUtils.java
index f386d62..e0998fc 100644
--- a/service/src/com/android/car/audio/CarDuckingUtils.java
+++ b/service/src/com/android/car/audio/CarDuckingUtils.java
@@ -126,6 +126,7 @@
 
     static List<String> getAddressesToDuck(int[] usages, CarAudioZone zone) {
         Set<Integer> uniqueContexts = CarAudioContext.getUniqueContextsForUsages(usages);
+        uniqueContexts.remove(INVALID);
         Set<Integer> contextsToDuck = getContextsToDuck(uniqueContexts);
         Set<String> addressesToDuck = getAddressesForContexts(contextsToDuck, zone);
 
diff --git a/service/src/com/android/car/audio/CarVolumeGroup.java b/service/src/com/android/car/audio/CarVolumeGroup.java
index 9c40725..8e98fea 100644
--- a/service/src/com/android/car/audio/CarVolumeGroup.java
+++ b/service/src/com/android/car/audio/CarVolumeGroup.java
@@ -150,30 +150,43 @@
 
     int getCurrentGainIndex() {
         synchronized (mLock) {
-            return mCurrentGainIndex;
+            if (mIsMuted) {
+                return getIndexForGain(mMinGain);
+            }
+            return getCurrentGainIndexLocked();
         }
     }
 
+    private int getCurrentGainIndexLocked() {
+        return mCurrentGainIndex;
+    }
+
     /**
      * Sets the gain on this group, gain will be set on all devices within volume group.
      */
     void setCurrentGainIndex(int gainIndex) {
-        int gainInMillibels = getGainForIndex(gainIndex);
         Preconditions.checkArgument(isValidGainIndex(gainIndex),
-                "Gain out of range (%d:%d) %d index %d", mMinGain, mMaxGain,
-                gainInMillibels, gainIndex);
+                "Gain out of range (%d:%d) index %d", mMinGain, mMaxGain, gainIndex);
         synchronized (mLock) {
-            for (String address : mAddressToCarAudioDeviceInfo.keySet()) {
-                CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.get(address);
-                info.setCurrentGain(gainInMillibels);
+            if (mIsMuted) {
+                setMuteLocked(false);
             }
-
-            mCurrentGainIndex = gainIndex;
-
-            storeGainIndexForUserLocked(mCurrentGainIndex, mUserId);
+            setCurrentGainIndexLocked(gainIndex);
         }
     }
 
+    private void setCurrentGainIndexLocked(int gainIndex) {
+        int gainInMillibels = getGainForIndex(gainIndex);
+        for (String address : mAddressToCarAudioDeviceInfo.keySet()) {
+            CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.get(address);
+            info.setCurrentGain(gainInMillibels);
+        }
+
+        mCurrentGainIndex = gainIndex;
+
+        storeGainIndexForUserLocked(mCurrentGainIndex, mUserId);
+    }
+
     @Nullable
     AudioDevicePort getAudioDevicePortForContext(int carAudioContext) {
         final String address = mContextToAddress.get(carAudioContext);
@@ -233,18 +246,22 @@
             updateUserIdLocked(userId);
             //Update the current gain index
             updateCurrentGainIndexLocked();
+            setCurrentGainIndexLocked(getCurrentGainIndexLocked());
             //Reset devices with current gain index
             updateGroupMuteLocked();
         }
-        setCurrentGainIndex(getCurrentGainIndex());
     }
 
     void setMute(boolean mute) {
         synchronized (mLock) {
-            mIsMuted = mute;
-            if (mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId)) {
-                mSettingsManager.storeVolumeGroupMuteForUser(mUserId, mZoneId, mId, mute);
-            }
+            setMuteLocked(mute);
+        }
+    }
+
+    void setMuteLocked(boolean mute) {
+        mIsMuted = mute;
+        if (mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId)) {
+            mSettingsManager.storeVolumeGroupMuteForUser(mUserId, mZoneId, mId, mute);
         }
     }
 
diff --git a/service/src/com/android/car/garagemode/Controller.java b/service/src/com/android/car/garagemode/Controller.java
index 81c8c10..fdf0c05 100644
--- a/service/src/com/android/car/garagemode/Controller.java
+++ b/service/src/com/android/car/garagemode/Controller.java
@@ -16,6 +16,8 @@
 
 package com.android.car.garagemode;
 
+import static com.android.car.internal.testing.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
+
 import android.app.job.JobScheduler;
 import android.car.hardware.power.CarPowerManager;
 import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
@@ -28,6 +30,7 @@
 
 import com.android.car.CarLocalServices;
 import com.android.car.CarLog;
+import com.android.car.internal.testing.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.car.systeminterface.SystemInterface;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.utils.Slogf;
@@ -116,6 +119,7 @@
     /**
      * Prints Garage Mode's status, including what jobs it is waiting for
      */
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     void dump(PrintWriter writer) {
         mGarageMode.dump(writer);
     }
diff --git a/service/src/com/android/car/garagemode/GarageMode.java b/service/src/com/android/car/garagemode/GarageMode.java
index f315dca..5936391 100644
--- a/service/src/com/android/car/garagemode/GarageMode.java
+++ b/service/src/com/android/car/garagemode/GarageMode.java
@@ -16,6 +16,8 @@
 
 package com.android.car.garagemode;
 
+import static com.android.car.internal.testing.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
+
 import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
 import android.app.job.JobSnapshot;
@@ -30,6 +32,7 @@
 import com.android.car.CarLocalServices;
 import com.android.car.CarLog;
 import com.android.car.CarStatsLogHelper;
+import com.android.car.internal.testing.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.car.power.CarPowerManagementService;
 import com.android.car.user.CarUserService;
 import com.android.internal.annotations.GuardedBy;
@@ -242,6 +245,7 @@
         }
     }
 
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     void dump(PrintWriter writer) {
         if (!mGarageModeActive) {
             return;
diff --git a/service/src/com/android/car/hal/VehicleHal.java b/service/src/com/android/car/hal/VehicleHal.java
index 5de5a81..726f34f 100644
--- a/service/src/com/android/car/hal/VehicleHal.java
+++ b/service/src/com/android/car/hal/VehicleHal.java
@@ -39,6 +39,7 @@
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType;
+import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
@@ -86,8 +87,8 @@
     public static final int NO_AREA = -1;
     public static final float NO_SAMPLE_RATE = -1;
 
-    private final HandlerThread mHandlerThread = CarServiceUtils.getHandlerThread(
-            VehicleHal.class.getSimpleName());
+    private final HandlerThread mHandlerThread;
+    private final Handler mHandler;
     private final PowerHalService mPowerHal;
     private final PropertyHalService mPropertyHal;
     private final InputHalService mInputHal;
@@ -124,6 +125,9 @@
      * both passed as parameters.
      */
     public VehicleHal(Context context, IVehicle vehicle) {
+        mHandlerThread = CarServiceUtils.getHandlerThread(
+                VehicleHal.class.getSimpleName());
+        mHandler = new Handler(mHandlerThread.getLooper());
         mPowerHal = new PowerHalService(this);
         mPropertyHal = new PropertyHalService(this);
         mInputHal = new InputHalService(this);
@@ -156,7 +160,10 @@
             UserHalService userHal,
             DiagnosticHalService diagnosticHal,
             ClusterHalService clusterHalService,
-            HalClient halClient) {
+            HalClient halClient,
+            HandlerThread handlerThread) {
+        mHandlerThread = handlerThread;
+        mHandler = new Handler(mHandlerThread.getLooper());
         mPowerHal = powerHal;
         mPropertyHal = propertyHal;
         mInputHal = inputHal;
@@ -581,6 +588,7 @@
 
     private final ArraySet<HalServiceBase> mServicesToDispatch = new ArraySet<>();
 
+    // should be posted to the mHandlerThread
     @Override
     public void onPropertyEvent(ArrayList<VehiclePropValue> propValues) {
         synchronized (mLock) {
@@ -614,6 +622,7 @@
         // No need to handle on-property-set events in HAL service yet.
     }
 
+    // should be posted to the mHandlerThread
     @Override
     public void onPropertySetError(@CarPropertyManager.CarSetPropertyErrorCode int errorCode,
             int propId, int areaId) {
@@ -800,7 +809,7 @@
         }
         // update timestamp
         v.timestamp = SystemClock.elapsedRealtimeNanos() + TimeUnit.SECONDS.toNanos(delayTime);
-        onPropertyEvent(Lists.newArrayList(v));
+        mHandler.post(() -> onPropertyEvent(Lists.newArrayList(v)));
     }
 
     /**
@@ -838,7 +847,7 @@
                     // Avoid the fake events be covered by real Event
                     v.timestamp = SystemClock.elapsedRealtimeNanos()
                             + TimeUnit.SECONDS.toNanos(timeDurationInSec);
-                    onPropertyEvent(Lists.newArrayList(v));
+                    mHandler.post(() -> onPropertyEvent(Lists.newArrayList(v)));
                 }
             }
         }, /* delay= */0, period);
diff --git a/service/src/com/android/car/pm/CarPackageManagerService.java b/service/src/com/android/car/pm/CarPackageManagerService.java
index 8c8a105..10288b1 100644
--- a/service/src/com/android/car/pm/CarPackageManagerService.java
+++ b/service/src/com/android/car/pm/CarPackageManagerService.java
@@ -50,10 +50,15 @@
 import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.ParcelFileDescriptor;
 import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.LocalLog;
@@ -73,9 +78,13 @@
 import com.android.car.SystemActivityMonitoringService.TopTaskInfoContainer;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.utils.Slogf;
 
 import com.google.android.collect.Sets;
 
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -96,12 +105,14 @@
     private static final String PACKAGE_DELIMITER = ",";
     private static final String PACKAGE_ACTIVITY_DELIMITER = "/";
     private static final int LOG_SIZE = 20;
+    private static final String[] WINDOW_DUMP_ARGUMENTS = new String[]{"windows"};
 
     private final Context mContext;
     private final SystemActivityMonitoringService mSystemActivityMonitoringService;
     private final PackageManager mPackageManager;
     private final ActivityManager mActivityManager;
     private final DisplayManager mDisplayManager;
+    private final IBinder mWindowManagerBinder;
 
     private final HandlerThread mHandlerThread = CarServiceUtils.getHandlerThread(
             getClass().getSimpleName());
@@ -122,6 +133,9 @@
 
     private final List<String> mAllowedAppInstallSources;
 
+    @GuardedBy("mLock")
+    private final SparseArray<ComponentName> mTopActivityWithDialogPerDisplay = new SparseArray<>();
+
     /**
      * Hold policy set from policy service or client.
      * Key: packageName of policy service
@@ -142,6 +156,8 @@
     private final CarUxRestrictionsManagerService mCarUxRestrictionsService;
     private boolean mEnableActivityBlocking;
     private final ComponentName mActivityBlockingActivity;
+    private final boolean mPreventTemplatedAppsFromShowingDialog;
+    private final String mTemplateActivityClassName;
 
     private final ActivityLaunchListener mActivityLaunchListener = new ActivityLaunchListener();
 
@@ -204,6 +220,7 @@
         mPackageManager = mContext.getPackageManager();
         mActivityManager = mContext.getSystemService(ActivityManager.class);
         mDisplayManager = mContext.getSystemService(DisplayManager.class);
+        mWindowManagerBinder = ServiceManager.getService(Context.WINDOW_SERVICE);
         Resources res = context.getResources();
         mEnableActivityBlocking = res.getBoolean(R.bool.enableActivityBlockingForSafety);
         String blockingActivity = res.getString(R.string.activityBlockingActivity);
@@ -212,6 +229,9 @@
                 res.getStringArray(R.array.allowedAppInstallSources));
         mVendorServiceController = new VendorServiceController(
                 mContext, mHandler.getLooper());
+        mPreventTemplatedAppsFromShowingDialog =
+                res.getBoolean(R.bool.config_preventTemplatedAppsFromShowingDialog);
+        mTemplateActivityClassName = res.getString(R.string.config_template_activity_class_name);
     }
 
 
@@ -282,6 +302,15 @@
                 Slog.d(TAG, "isActivityDistractionOptimized" + dumpPoliciesLocked(false));
             }
 
+            for (int i = mTopActivityWithDialogPerDisplay.size() - 1; i >= 0; i--) {
+                ComponentName activityWithDialog = mTopActivityWithDialogPerDisplay.get(
+                        mTopActivityWithDialogPerDisplay.keyAt(i));
+                if (activityWithDialog.getClassName().equals(className)
+                        && activityWithDialog.getPackageName().equals(packageName)) {
+                    return false;
+                }
+            }
+
             if (searchFromClientPolicyBlocklistsLocked(packageName)) {
                 return false;
             }
@@ -997,6 +1026,9 @@
         synchronized (mLock) {
             writer.println("*CarPackageManagerService*");
             writer.println("mEnableActivityBlocking:" + mEnableActivityBlocking);
+            writer.println("mPreventTemplatedAppsFromShowingDialog"
+                    + mPreventTemplatedAppsFromShowingDialog);
+            writer.println("mTemplateActivityClassName" + mTemplateActivityClassName);
             List<String> restrictions = new ArrayList<>(mUxRestrictionsListeners.size());
             for (int i = 0; i < mUxRestrictionsListeners.size(); i++) {
                 int displayId = mUxRestrictionsListeners.keyAt(i);
@@ -1102,6 +1134,14 @@
     }
 
     private void blockTopActivityIfNecessary(TopTaskInfoContainer topTask) {
+        synchronized (mLock) {
+            if (mTopActivityWithDialogPerDisplay.contains(topTask.displayId)
+                    && !topTask.topActivity.equals(
+                            mTopActivityWithDialogPerDisplay.get(topTask.displayId))) {
+                // Clear top activity-with-dialog if the activity has changed on this display.
+                mTopActivityWithDialogPerDisplay.remove(topTask.displayId);
+            }
+        }
         if (isUxRestrictedOnDisplay(topTask.displayId)) {
             doBlockTopActivityIfNotAllowed(topTask);
         }
@@ -1111,10 +1151,7 @@
         if (topTask.topActivity == null) {
             return;
         }
-
-        boolean allowed = isActivityDistractionOptimized(
-                topTask.topActivity.getPackageName(),
-                topTask.topActivity.getClassName());
+        boolean allowed = isActivityAllowed(topTask);
         if (Log.isLoggable(TAG, Log.DEBUG)) {
             Slog.d(TAG, "new activity:" + topTask.toString() + " allowed:" + allowed);
         }
@@ -1149,9 +1186,10 @@
 
         boolean isRootDO = false;
         if (taskRootActivity != null) {
-            ComponentName componentName = ComponentName.unflattenFromString(taskRootActivity);
+            ComponentName taskRootComponentName =
+                    ComponentName.unflattenFromString(taskRootActivity);
             isRootDO = isActivityDistractionOptimized(
-                    componentName.getPackageName(), componentName.getClassName());
+                    taskRootComponentName.getPackageName(), taskRootComponentName.getClassName());
         }
 
         Intent newActivityIntent = createBlockingActivityIntent(
@@ -1168,6 +1206,82 @@
         mSystemActivityMonitoringService.blockActivity(topTask, newActivityIntent);
     }
 
+    private boolean isActivityAllowed(TopTaskInfoContainer topTaskInfoContainer) {
+        ComponentName activityName = topTaskInfoContainer.topActivity;
+        boolean isDistractionOptimized = isActivityDistractionOptimized(
+                activityName.getPackageName(),
+                activityName.getClassName());
+        if (!isDistractionOptimized) {
+            return false;
+        }
+        return !(mPreventTemplatedAppsFromShowingDialog
+                && isTemplateActivity(activityName)
+                && isActivityShowingADialogOnDisplay(activityName, topTaskInfoContainer.displayId));
+    }
+
+    private boolean isTemplateActivity(ComponentName activityName) {
+        // TODO(b/191263486): Finalise on how to detect the templated activities.
+        return activityName.getClassName().equals(mTemplateActivityClassName);
+    }
+
+    private boolean isActivityShowingADialogOnDisplay(ComponentName activityName, int displayId) {
+        String output = dumpWindows();
+        List<WindowDumpParser.Window> appWindows =
+                WindowDumpParser.getParsedAppWindows(output, activityName.getPackageName());
+        // TODO(b/192354699): Handle case where an activity can have multiple instances on the same
+        //  display.
+        int totalAppWindows = appWindows.size();
+        String firstActivityRecord = null;
+        int numTopActivityAppWindowsOnDisplay = 0;
+        for (int i = 0; i < totalAppWindows; i++) {
+            WindowDumpParser.Window appWindow = appWindows.get(i);
+            if (appWindow.getDisplayId() != displayId) {
+                continue;
+            }
+            if (TextUtils.isEmpty(appWindow.getActivityRecord())) {
+                continue;
+            }
+            if (firstActivityRecord == null) {
+                firstActivityRecord = appWindow.getActivityRecord();
+            }
+            if (firstActivityRecord.equals(appWindow.getActivityRecord())) {
+                numTopActivityAppWindowsOnDisplay++;
+            }
+        }
+        Slogf.d(TAG, "Top activity =  " + activityName);
+        Slogf.d(TAG, "Number of app widows of top activity = " + numTopActivityAppWindowsOnDisplay);
+        boolean isShowingADialog = numTopActivityAppWindowsOnDisplay > 1;
+        synchronized (mLock) {
+            if (isShowingADialog) {
+                mTopActivityWithDialogPerDisplay.put(displayId, activityName);
+            } else {
+                mTopActivityWithDialogPerDisplay.remove(displayId);
+            }
+        }
+        return isShowingADialog;
+    }
+
+    private String dumpWindows() {
+        try {
+            ParcelFileDescriptor[] fileDescriptors = ParcelFileDescriptor.createSocketPair();
+            mWindowManagerBinder.dump(
+                    fileDescriptors[0].getFileDescriptor(), WINDOW_DUMP_ARGUMENTS);
+            fileDescriptors[0].close();
+            StringBuilder outputBuilder = new StringBuilder();
+            BufferedReader reader = new BufferedReader(
+                    new FileReader(fileDescriptors[1].getFileDescriptor()));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                outputBuilder.append(line).append("\n");
+            }
+            reader.close();
+            fileDescriptors[1].close();
+            return outputBuilder.toString();
+        } catch (IOException | RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     /**
      * Creates an intent to start blocking activity.
      *
@@ -1175,6 +1289,7 @@
      * @param blockedActivity  the activity being blocked
      * @param blockedTaskId    the blocked task id, which contains the blocked activity
      * @param taskRootActivity root activity of the blocked task
+     * @param isRootDo         denotes if the root activity is distraction optimised
      * @return an intent to launch the blocking activity.
      */
     private static Intent createBlockingActivityIntent(ComponentName blockingActivity,
@@ -1463,6 +1578,15 @@
     }
 
     /**
+     * Called when a window change event is received by the {@link CarSafetyAccessibilityService}.
+     */
+    @VisibleForTesting
+    void onWindowChangeEvent() {
+        Slogf.d(TAG, "onWindowChange event received");
+        mHandlerThread.getThreadHandler().post(() -> blockTopActivitiesIfNecessary());
+    }
+
+    /**
      * Listens to the package install/uninstall events to know when to initiate parsing
      * installed packages.
      */
diff --git a/service/src/com/android/car/pm/CarSafetyAccessibilityService.java b/service/src/com/android/car/pm/CarSafetyAccessibilityService.java
new file mode 100644
index 0000000..76a6246
--- /dev/null
+++ b/service/src/com/android/car/pm/CarSafetyAccessibilityService.java
@@ -0,0 +1,41 @@
+/*
+ * 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.pm;
+
+import android.accessibilityservice.AccessibilityService;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.car.CarLocalServices;
+
+/**
+ * An accessibility service to notify the Car Service of any change in the window state. The car
+ * safety related code can read the events sent from this service and take the necessary actions.
+ */
+public class CarSafetyAccessibilityService extends AccessibilityService {
+    @Override
+    public void onAccessibilityEvent(AccessibilityEvent event) {
+        CarPackageManagerService carPackageManagerService =
+                CarLocalServices.getService(CarPackageManagerService.class);
+        if (carPackageManagerService != null) {
+            carPackageManagerService.onWindowChangeEvent();
+        }
+    }
+
+    @Override
+    public void onInterrupt() {
+    }
+}
diff --git a/service/src/com/android/car/pm/TEST_MAPPING b/service/src/com/android/car/pm/TEST_MAPPING
index d37fe3d..1c32824 100644
--- a/service/src/com/android/car/pm/TEST_MAPPING
+++ b/service/src/com/android/car/pm/TEST_MAPPING
@@ -19,6 +19,12 @@
         },
         {
           "include-filter": "com.android.car.pm.VendorServiceInfoTest"
+        },
+        {
+          "include-filter": "com.android.car.pm.CarSafetyAccessibilityServiceTest"
+        },
+        {
+          "include-filter": "com.android.car.pm.WindowDumpParserTest"
         }
       ]
     },
diff --git a/service/src/com/android/car/pm/WindowDumpParser.java b/service/src/com/android/car/pm/WindowDumpParser.java
new file mode 100644
index 0000000..8387ce7
--- /dev/null
+++ b/service/src/com/android/car/pm/WindowDumpParser.java
@@ -0,0 +1,124 @@
+/*
+ * 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.pm;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A utility class to parse the window dump.
+ */
+class WindowDumpParser {
+    private static final String WINDOW_TYPE_APPLICATION_STARTING = "APPLICATION_STARTING";
+
+    /**
+     * Parses the provided window dump and returns the list of windows only for a particular app.
+     *
+     * @param dump the window dump in string format as returned from `dumpsys window
+     *         windows`.
+     * @param appPackageName the package name of the app, which the windows are being
+     *         requested for.
+     * @return a list of parsed {@link Window} objects.
+     */
+    public static List<Window> getParsedAppWindows(String dump, String appPackageName) {
+        Pattern dumpSplitter = Pattern.compile("(Window #)|\\n\\n");
+        // \\n\\n to separate out the Global dump from the windows list.
+
+        Pattern windowDetailsPattern = Pattern.compile("\\d*.*\\n"
+                        + ".*mDisplayId=(\\S*).*\\n"
+                        + ".*package=(\\S*).*\\n"
+                        + ".*ty=(\\S*)"
+                        + "((.*ActivityRecord\\{(.*?)\\}.*\\n)|(.*\\n))*"
+                // (.*\\n) is required for skipping the lines before the line containing
+                // ActivityRecord{}.
+        );
+        List<Window> windows = new ArrayList<>();
+
+        String[] windowDumps = dumpSplitter.split(dump);
+        for (int i = 1; i < windowDumps.length - 1; i++) {
+            Matcher m = windowDetailsPattern.matcher(windowDumps[i]);
+            if (m.find()) {
+                // Only consider windows for the given appPackageName which are not the splash
+                // screen windows.
+                // TODO(b/192355798): Revisit this logic as window type can be changed.
+                if (Objects.equals(m.group(2), appPackageName)
+                        && !Objects.equals(m.group(3), WINDOW_TYPE_APPLICATION_STARTING)) {
+                    windows.add(new Window(
+                            /* packageName = */ m.group(2),
+                            /* displayId = */ Integer.parseInt(m.group(1)),
+                            /* activityRecord = */ m.group(6)
+                    ));
+                }
+            }
+        }
+        return windows;
+    }
+
+    /**
+     * A holder class that represents an app's window.
+     */
+    static class Window {
+        private final String mPackageName;
+        private final int mDisplayId;
+        private final String mActivityRecord;
+
+        Window(String packageName, int displayId, String activityRecord) {
+            mPackageName = packageName;
+            mDisplayId = displayId;
+            mActivityRecord = activityRecord;
+        }
+
+        public String getPackageName() {
+            return mPackageName;
+        }
+
+        public int getDisplayId() {
+            return mDisplayId;
+        }
+
+        public String getActivityRecord() {
+            return mActivityRecord;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof Window)) return false;
+            Window window = (Window) o;
+            return mDisplayId == window.mDisplayId
+                    && mPackageName.equals(window.mPackageName)
+                    && Objects.equals(mActivityRecord, window.mActivityRecord);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mPackageName, mDisplayId, mActivityRecord);
+        }
+
+        @Override
+        public String toString() {
+            return "Window{"
+                    + "mPackageName=" + mPackageName
+                    + ", mDisplayId=" + mDisplayId
+                    + ", mActivityRecord={" + mActivityRecord + "}"
+                    + "}";
+        }
+    }
+}
diff --git a/service/src/com/android/car/power/PowerComponentHandler.java b/service/src/com/android/car/power/PowerComponentHandler.java
index dcf22e8..f6f1b3b 100644
--- a/service/src/com/android/car/power/PowerComponentHandler.java
+++ b/service/src/com/android/car/power/PowerComponentHandler.java
@@ -113,13 +113,8 @@
                 mComponentStates.put(component, false);
                 PowerComponentMediator mediator = factory.createPowerComponent(component);
                 String componentName = powerComponentToString(component);
-                if (mediator == null) {
-                    Slogf.w(TAG, "Power component(%s) is not valid or doesn't need a mediator",
-                            componentName);
-                    continue;
-                }
-                if (!mediator.isComponentAvailable()) {
-                    Slogf.w(TAG, "Power component(%s) is not available", componentName);
+                if (mediator == null || !mediator.isComponentAvailable()) {
+                    // We don't not associate a mediator with the component.
                     continue;
                 }
                 mPowerComponentMediators.put(component, mediator);
@@ -237,7 +232,6 @@
 
         PowerComponentMediator mediator = mPowerComponentMediators.get(component);
         if (mediator == null) {
-            Slogf.w(TAG, "%s doesn't have a mediator", powerComponentToString(component));
             return true;
         }
 
diff --git a/service/src/com/android/car/telemetry/CarTelemetryService.java b/service/src/com/android/car/telemetry/CarTelemetryService.java
index 695e25e..5b78e3b 100644
--- a/service/src/com/android/car/telemetry/CarTelemetryService.java
+++ b/service/src/com/android/car/telemetry/CarTelemetryService.java
@@ -15,28 +15,41 @@
  */
 package com.android.car.telemetry;
 
-import static android.car.telemetry.CarTelemetryManager.ERROR_NEWER_MANIFEST_EXISTS;
-import static android.car.telemetry.CarTelemetryManager.ERROR_NONE;
-import static android.car.telemetry.CarTelemetryManager.ERROR_PARSE_MANIFEST_FAILED;
-import static android.car.telemetry.CarTelemetryManager.ERROR_SAME_MANIFEST_EXISTS;
+import static android.car.telemetry.CarTelemetryManager.ERROR_METRICS_CONFIG_PARSE_FAILED;
 
 import android.annotation.NonNull;
+import android.app.StatsManager;
 import android.car.Car;
-import android.car.telemetry.CarTelemetryManager.AddManifestError;
 import android.car.telemetry.ICarTelemetryService;
 import android.car.telemetry.ICarTelemetryServiceListener;
-import android.car.telemetry.ManifestKey;
+import android.car.telemetry.MetricsConfigKey;
 import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.RemoteException;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
+import com.android.car.CarLocalServices;
+import com.android.car.CarLog;
+import com.android.car.CarPropertyService;
 import com.android.car.CarServiceBase;
-import com.android.internal.annotations.GuardedBy;
+import com.android.car.CarServiceUtils;
+import com.android.car.systeminterface.SystemInterface;
+import com.android.car.telemetry.databroker.DataBroker;
+import com.android.car.telemetry.databroker.DataBrokerController;
+import com.android.car.telemetry.databroker.DataBrokerImpl;
+import com.android.car.telemetry.publisher.PublisherFactory;
+import com.android.car.telemetry.publisher.StatsManagerImpl;
+import com.android.car.telemetry.publisher.StatsManagerProxy;
+import com.android.car.telemetry.systemmonitor.SystemMonitor;
+import com.android.internal.annotations.VisibleForTesting;
 
 import com.google.protobuf.InvalidProtocolBufferException;
 
-import java.util.HashMap;
-import java.util.Map;
+import java.io.File;
+import java.util.List;
 
 /**
  * CarTelemetryService manages OEM telemetry collection, processing and communication
@@ -44,32 +57,62 @@
  */
 public class CarTelemetryService extends ICarTelemetryService.Stub implements CarServiceBase {
 
-    // TODO(b/189340793): Rename Manifest to MetricsConfig
-
     private static final boolean DEBUG = false;
-    private static final int DEFAULT_VERSION = 0;
-    private static final String TAG = CarTelemetryService.class.getSimpleName();
+    public static final String TELEMETRY_DIR = "telemetry";
 
     private final Context mContext;
-    @GuardedBy("mLock")
-    private final Map<String, Integer> mNameVersionMap = new HashMap<>();
-    private final Object mLock = new Object();
+    private final CarPropertyService mCarPropertyService;
+    private final File mRootDirectory;
+    private final HandlerThread mTelemetryThread = CarServiceUtils.getHandlerThread(
+            CarTelemetryService.class.getSimpleName());
+    private final Handler mTelemetryHandler = new Handler(mTelemetryThread.getLooper());
 
-    @GuardedBy("mLock")
     private ICarTelemetryServiceListener mListener;
+    private DataBroker mDataBroker;
+    private DataBrokerController mDataBrokerController;
+    private MetricsConfigStore mMetricsConfigStore;
+    private PublisherFactory mPublisherFactory;
+    private ResultStore mResultStore;
+    private SharedPreferences mSharedPrefs;
+    private StatsManagerProxy mStatsManagerProxy;
+    private SystemMonitor mSystemMonitor;
 
-    public CarTelemetryService(Context context) {
+    public CarTelemetryService(Context context, CarPropertyService carPropertyService) {
         mContext = context;
+        mCarPropertyService = carPropertyService;
+        SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class);
+        // full root directory path is /data/system/car/telemetry
+        mRootDirectory = new File(systemInterface.getSystemCarDir(), TELEMETRY_DIR);
     }
 
     @Override
     public void init() {
-        // nothing to do
+        mTelemetryHandler.post(() -> {
+            // initialize all necessary components
+            mMetricsConfigStore = new MetricsConfigStore(mRootDirectory);
+            mResultStore = new ResultStore(mRootDirectory);
+            mStatsManagerProxy = new StatsManagerImpl(
+                    mContext.getSystemService(StatsManager.class));
+            // TODO(b/197968695): delay initialization of stats publisher
+            mPublisherFactory = new PublisherFactory(mCarPropertyService, mTelemetryHandler,
+                    mStatsManagerProxy, null);
+            mDataBroker = new DataBrokerImpl(mContext, mPublisherFactory, mResultStore);
+            mSystemMonitor = SystemMonitor.create(mContext, mTelemetryHandler);
+            mDataBrokerController = new DataBrokerController(mDataBroker, mSystemMonitor);
+
+            // start collecting data. once data is sent by publisher, scripts will be able to run
+            List<TelemetryProto.MetricsConfig> activeConfigs =
+                    mMetricsConfigStore.getActiveMetricsConfigs();
+            for (TelemetryProto.MetricsConfig config : activeConfigs) {
+                mDataBroker.addMetricsConfiguration(config);
+            }
+        });
     }
 
     @Override
     public void release() {
-        // nothing to do
+        // TODO(b/197969149): prevent threading issue, block main thread
+        mTelemetryHandler.post(() -> mResultStore.flushToDisk());
     }
 
     @Override
@@ -85,9 +128,12 @@
         // TODO(b/184890506): verify that only a hardcoded app can set the listener
         mContext.enforceCallingOrSelfPermission(
                 Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
-        synchronized (mLock) {
-            setListenerLocked(listener);
-        }
+        mTelemetryHandler.post(() -> {
+            if (DEBUG) {
+                Slog.d(CarLog.TAG_TELEMETRY, "Setting the listener for car telemetry service");
+            }
+            mListener = listener;
+        });
     }
 
     /**
@@ -96,50 +142,80 @@
     @Override
     public void clearListener() {
         mContext.enforceCallingOrSelfPermission(
-                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
-        synchronized (mLock) {
-            clearListenerLocked();
-        }
+                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "clearListener");
+        mTelemetryHandler.post(() -> {
+            if (DEBUG) {
+                Slog.d(CarLog.TAG_TELEMETRY, "Clearing the listener for car telemetry service");
+            }
+            mListener = null;
+        });
     }
 
     /**
-     * Allows client to send telemetry manifests.
+     * Send a telemetry metrics config to the service.
      *
-     * @param key    the unique key to identify the manifest.
-     * @param config the serialized bytes of a Manifest object.
-     * @return {@link AddManifestError} the error code.
+     * @param key    the unique key to identify the MetricsConfig.
+     * @param config the serialized bytes of a MetricsConfig object.
      */
     @Override
-    public @AddManifestError int addManifest(@NonNull ManifestKey key, @NonNull byte[] config) {
+    public void addMetricsConfig(@NonNull MetricsConfigKey key, @NonNull byte[] config) {
         mContext.enforceCallingOrSelfPermission(
-                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
-        synchronized (mLock) {
-            return addManifestLocked(key, config);
-        }
+                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "addMetricsConfig");
+        // TODO(b/198797367): if an older version exists, delete its script result/error
+        mTelemetryHandler.post(() -> {
+            TelemetryProto.MetricsConfig metricsConfig;
+            int status;
+            try {
+                metricsConfig = TelemetryProto.MetricsConfig.parseFrom(config);
+                status = mMetricsConfigStore.addMetricsConfig(metricsConfig);
+            } catch (InvalidProtocolBufferException e) {
+                Slog.e(CarLog.TAG_TELEMETRY, "Failed to parse MetricsConfig.", e);
+                status = ERROR_METRICS_CONFIG_PARSE_FAILED;
+            }
+            try {
+                mListener.onAddMetricsConfigStatus(key, status);
+            } catch (RemoteException e) {
+                Slog.d(CarLog.TAG_TELEMETRY, "error with ICarTelemetryServiceListener", e);
+            }
+        });
     }
 
     /**
      * Removes a manifest based on the key.
      */
     @Override
-    public boolean removeManifest(@NonNull ManifestKey key) {
+    public void removeMetricsConfig(@NonNull MetricsConfigKey key) {
         mContext.enforceCallingOrSelfPermission(
-                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
-        synchronized (mLock) {
-            return removeManifestLocked(key);
-        }
+                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "removeMetricsConfig");
+        // TODO(b/198797367): Delete script result/error associated with this MetricsConfig
+        mTelemetryHandler.post(() -> {
+            if (DEBUG) {
+                Slog.d(CarLog.TAG_TELEMETRY, "Removing manifest " + key.getName()
+                        + " from car telemetry service");
+            }
+            // TODO(b/198792767): Check both config name and config version for deletion
+            boolean success = mMetricsConfigStore.deleteMetricsConfig(key.getName());
+            try {
+                mListener.onRemoveMetricsConfigStatus(key, success);
+            } catch (RemoteException e) {
+                Slog.d(CarLog.TAG_TELEMETRY, "error with ICarTelemetryServiceListener", e);
+            }
+        });
     }
 
     /**
      * Removes all manifests.
      */
     @Override
-    public void removeAllManifests() {
+    public void removeAllMetricsConfigs() {
         mContext.enforceCallingOrSelfPermission(
-                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
-        synchronized (mLock) {
-            removeAllManifestsLocked();
-        }
+                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "removeAllMetricsConfig");
+        mTelemetryHandler.post(() -> {
+            if (DEBUG) {
+                Slog.d(CarLog.TAG_TELEMETRY, "Removing all manifest from car telemetry service");
+            }
+            // TODO(b/184087869): Implement
+        });
     }
 
     /**
@@ -147,99 +223,30 @@
      * {@link ICarTelemetryServiceListener}.
      */
     @Override
-    public void sendFinishedReports(@NonNull ManifestKey key) {
+    public void sendFinishedReports(@NonNull MetricsConfigKey key) {
         // TODO(b/184087869): Implement
         mContext.enforceCallingOrSelfPermission(
-                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
+                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "sendFinishedReports");
         if (DEBUG) {
-            Slog.d(TAG, "Flushing reports for a manifest");
+            Slog.d(CarLog.TAG_TELEMETRY, "Flushing reports for a manifest");
         }
     }
 
     /**
-     * Sends all script results associated using the {@link ICarTelemetryServiceListener}.
+     * Sends all script results or errors using the {@link ICarTelemetryServiceListener}.
      */
     @Override
     public void sendAllFinishedReports() {
         // TODO(b/184087869): Implement
         mContext.enforceCallingOrSelfPermission(
-                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
+                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "sendAllFinishedReports");
         if (DEBUG) {
-            Slog.d(TAG, "Flushing all reports");
+            Slog.d(CarLog.TAG_TELEMETRY, "Flushing all reports");
         }
     }
 
-    /**
-     * Sends all errors using the {@link ICarTelemetryServiceListener}.
-     */
-    @Override
-    public void sendScriptExecutionErrors() {
-        // TODO(b/184087869): Implement
-        mContext.enforceCallingOrSelfPermission(
-                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
-        if (DEBUG) {
-            Slog.d(TAG, "Flushing script execution errors");
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void setListenerLocked(@NonNull ICarTelemetryServiceListener listener) {
-        if (DEBUG) {
-            Slog.d(TAG, "Setting the listener for car telemetry service");
-        }
-        mListener = listener;
-    }
-
-    @GuardedBy("mLock")
-    private void clearListenerLocked() {
-        if (DEBUG) {
-            Slog.d(TAG, "Clearing listener");
-        }
-        mListener = null;
-    }
-
-    @GuardedBy("mLock")
-    private @AddManifestError int addManifestLocked(ManifestKey key, byte[] configProto) {
-        if (DEBUG) {
-            Slog.d(TAG, "Adding MetricsConfig to car telemetry service");
-        }
-        int currentVersion = mNameVersionMap.getOrDefault(key.getName(), DEFAULT_VERSION);
-        if (currentVersion > key.getVersion()) {
-            return ERROR_NEWER_MANIFEST_EXISTS;
-        } else if (currentVersion == key.getVersion()) {
-            return ERROR_SAME_MANIFEST_EXISTS;
-        }
-
-        TelemetryProto.MetricsConfig metricsConfig;
-        try {
-            metricsConfig = TelemetryProto.MetricsConfig.parseFrom(configProto);
-        } catch (InvalidProtocolBufferException e) {
-            Slog.e(TAG, "Failed to parse MetricsConfig.", e);
-            return ERROR_PARSE_MANIFEST_FAILED;
-        }
-        mNameVersionMap.put(key.getName(), key.getVersion());
-
-        // TODO(b/186047142): Store the MetricsConfig to disk
-        // TODO(b/186047142): Send metricsConfig to a script manager or a queue
-        return ERROR_NONE;
-    }
-
-    @GuardedBy("mLock")
-    private boolean removeManifestLocked(@NonNull ManifestKey key) {
-        Integer version = mNameVersionMap.remove(key.getName());
-        if (version == null) {
-            return false;
-        }
-        // TODO(b/186047142): Delete manifest from disk and remove it from queue
-        return true;
-    }
-
-    @GuardedBy("mLock")
-    private void removeAllManifestsLocked() {
-        if (DEBUG) {
-            Slog.d(TAG, "Removing all manifest from car telemetry service");
-        }
-        mNameVersionMap.clear();
-        // TODO(b/186047142): Delete all manifests from disk & queue
+    @VisibleForTesting
+    Handler getTelemetryHandler() {
+        return mTelemetryHandler;
     }
 }
diff --git a/service/src/com/android/car/telemetry/MetricsConfigStore.java b/service/src/com/android/car/telemetry/MetricsConfigStore.java
new file mode 100644
index 0000000..9802e0e
--- /dev/null
+++ b/service/src/com/android/car/telemetry/MetricsConfigStore.java
@@ -0,0 +1,114 @@
+/*
+ * 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 static android.car.telemetry.CarTelemetryManager.ERROR_METRICS_CONFIG_ALREADY_EXISTS;
+import static android.car.telemetry.CarTelemetryManager.ERROR_METRICS_CONFIG_NONE;
+import static android.car.telemetry.CarTelemetryManager.ERROR_METRICS_CONFIG_UNKNOWN;
+import static android.car.telemetry.CarTelemetryManager.ERROR_METRICS_CONFIG_VERSION_TOO_OLD;
+
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.car.CarLog;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class is responsible for storing, retrieving, and deleting {@link
+ * TelemetryProto.MetricsConfig}. All of the methods are blocking so the class should only be
+ * accessed on the telemetry thread.
+ */
+class MetricsConfigStore {
+    @VisibleForTesting
+    static final String METRICS_CONFIG_DIR = "metrics_configs";
+
+    private final File mConfigDirectory;
+    private Map<String, TelemetryProto.MetricsConfig> mActiveConfigs;
+    private Map<String, Integer> mNameVersionMap;
+
+    MetricsConfigStore(File rootDirectory) {
+        mConfigDirectory = new File(rootDirectory, METRICS_CONFIG_DIR);
+        mConfigDirectory.mkdirs();
+        mActiveConfigs = new ArrayMap<>();
+        mNameVersionMap = new ArrayMap<>();
+        // TODO(b/197336485): Add expiration date check for MetricsConfig
+        for (File file : mConfigDirectory.listFiles()) {
+            try {
+                byte[] serializedConfig = Files.readAllBytes(file.toPath());
+                TelemetryProto.MetricsConfig config =
+                        TelemetryProto.MetricsConfig.parseFrom(serializedConfig);
+                mActiveConfigs.put(config.getName(), config);
+                mNameVersionMap.put(config.getName(), config.getVersion());
+            } catch (IOException e) {
+                // TODO(b/197336655): record failure
+                file.delete();
+            }
+        }
+    }
+
+    /**
+     * Returns all active {@link TelemetryProto.MetricsConfig} from disk.
+     */
+    List<TelemetryProto.MetricsConfig> getActiveMetricsConfigs() {
+        return new ArrayList<>(mActiveConfigs.values());
+    }
+
+    /**
+     * Stores the MetricsConfig if it is valid.
+     *
+     * @param metricsConfig the config to be persisted to disk.
+     * @return true if the MetricsConfig should start receiving data, false otherwise.
+     */
+    int addMetricsConfig(TelemetryProto.MetricsConfig metricsConfig) {
+        // TODO(b/198823862): Validate config version
+        // TODO(b/197336485): Check expiration date for MetricsConfig
+        int currentVersion = mNameVersionMap.getOrDefault(metricsConfig.getName(), -1);
+        if (currentVersion > metricsConfig.getVersion()) {
+            return ERROR_METRICS_CONFIG_VERSION_TOO_OLD;
+        } else if (currentVersion == metricsConfig.getVersion()) {
+            return ERROR_METRICS_CONFIG_ALREADY_EXISTS;
+        }
+        mNameVersionMap.put(metricsConfig.getName(), metricsConfig.getVersion());
+        try {
+            Files.write(
+                    new File(mConfigDirectory, metricsConfig.getName()).toPath(),
+                    metricsConfig.toByteArray());
+        } catch (IOException e) {
+            // TODO(b/197336655): record failure
+            Slog.w(CarLog.TAG_TELEMETRY, "Failed to write metrics config to disk", e);
+            return ERROR_METRICS_CONFIG_UNKNOWN;
+        }
+        return ERROR_METRICS_CONFIG_NONE;
+    }
+
+    /** Deletes the MetricsConfig from disk. Returns the success status. */
+    boolean deleteMetricsConfig(String metricsConfigName) {
+        mActiveConfigs.remove(metricsConfigName);
+        return new File(mConfigDirectory, metricsConfigName).delete();
+    }
+
+    void deleteAllMetricsConfig() {
+        // TODO(b/198784116): implement
+    }
+}
diff --git a/service/src/com/android/car/telemetry/ResultStore.java b/service/src/com/android/car/telemetry/ResultStore.java
new file mode 100644
index 0000000..976ee8e
--- /dev/null
+++ b/service/src/com/android/car/telemetry/ResultStore.java
@@ -0,0 +1,214 @@
+/*
+ * 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.PersistableBundle;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.car.CarLog;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Disk storage for interim and final metrics statistics.
+ * All methods in this class should be invoked from the telemetry thread.
+ */
+public class ResultStore {
+
+    private static final long STALE_THRESHOLD_MILLIS =
+            TimeUnit.MILLISECONDS.convert(30, TimeUnit.DAYS);
+    @VisibleForTesting
+    static final String INTERIM_RESULT_DIR = "interim";
+    @VisibleForTesting
+    static final String FINAL_RESULT_DIR = "final";
+
+    /** Map keys are MetricsConfig names, which are also the file names in disk. */
+    private final Map<String, InterimResult> mInterimResultCache = new ArrayMap<>();
+
+    private final File mInterimResultDirectory;
+    private final File mFinalResultDirectory;
+
+    ResultStore(File rootDirectory) {
+        mInterimResultDirectory = new File(rootDirectory, INTERIM_RESULT_DIR);
+        mFinalResultDirectory = new File(rootDirectory, FINAL_RESULT_DIR);
+        mInterimResultDirectory.mkdirs();
+        mFinalResultDirectory.mkdirs();
+        // load results into memory to reduce the frequency of disk access
+        loadInterimResultsIntoMemory();
+    }
+
+    /** Reads interim results into memory for faster access. */
+    private void loadInterimResultsIntoMemory() {
+        for (File file : mInterimResultDirectory.listFiles()) {
+            try (FileInputStream fis = new FileInputStream(file)) {
+                mInterimResultCache.put(
+                        file.getName(),
+                        new InterimResult(PersistableBundle.readFromStream(fis)));
+            } catch (IOException e) {
+                Slog.w(CarLog.TAG_TELEMETRY, "Failed to read result from disk.", e);
+                // TODO(b/195422219): record failure
+            }
+        }
+    }
+
+    /**
+     * Retrieves interim metrics for the given
+     * {@link com.android.car.telemetry.TelemetryProto.MetricsConfig}.
+     */
+    public PersistableBundle getInterimResult(String metricsConfigName) {
+        if (!mInterimResultCache.containsKey(metricsConfigName)) {
+            return null;
+        }
+        return mInterimResultCache.get(metricsConfigName).getBundle();
+    }
+
+    /**
+     * Stores interim metrics results in memory for the given
+     * {@link com.android.car.telemetry.TelemetryProto.MetricsConfig}.
+     */
+    public void putInterimResult(String metricsConfigName, PersistableBundle result) {
+        mInterimResultCache.put(metricsConfigName, new InterimResult(result, /* dirty = */ true));
+    }
+
+    /**
+     * Retrieves final metrics for the given
+     * {@link com.android.car.telemetry.TelemetryProto.MetricsConfig}.
+     *
+     * @param metricsConfigName name of the MetricsConfig.
+     * @param deleteResult      if true, the final result will be deleted from disk.
+     * @param callback          for receiving the metrics output. If result does not exist, it will
+     *                          receive a null value.
+     */
+    public void getFinalResult(
+            String metricsConfigName, boolean deleteResult, FinalResultCallback callback) {
+        File file = new File(mFinalResultDirectory, metricsConfigName);
+        // if no final result exists for this metrics config, return immediately
+        if (!file.exists()) {
+            callback.onFinalResult(metricsConfigName, null);
+            return;
+        }
+        try (FileInputStream fis = new FileInputStream(file)) {
+            PersistableBundle bundle = PersistableBundle.readFromStream(fis);
+            callback.onFinalResult(metricsConfigName, bundle);
+        } catch (IOException e) {
+            Slog.w(CarLog.TAG_TELEMETRY, "Failed to get final result from disk.", e);
+            callback.onFinalResult(metricsConfigName, null);
+        }
+        if (deleteResult) {
+            file.delete();
+        }
+    }
+
+    /**
+     * Stores final metrics in memory for the given
+     * {@link com.android.car.telemetry.TelemetryProto.MetricsConfig}.
+     */
+    public void putFinalResult(String metricsConfigName, PersistableBundle result) {
+        writePersistableBundleToFile(mFinalResultDirectory, metricsConfigName, result);
+        deleteFileInDirectory(mInterimResultDirectory, metricsConfigName);
+        mInterimResultCache.remove(metricsConfigName);
+    }
+
+    /** Persists data to disk. */
+    public void flushToDisk() {
+        writeInterimResultsToFile();
+        deleteAllStaleData(mInterimResultDirectory, mFinalResultDirectory);
+    }
+
+    /** Writes dirty interim results to disk. */
+    private void writeInterimResultsToFile() {
+        mInterimResultCache.forEach((metricsConfigName, interimResult) -> {
+            // only write dirty data
+            if (!interimResult.isDirty()) {
+                return;
+            }
+            writePersistableBundleToFile(
+                    mInterimResultDirectory, metricsConfigName, interimResult.getBundle());
+        });
+    }
+
+    /** Deletes data that are older than some threshold in the given directories. */
+    private void deleteAllStaleData(File... dirs) {
+        long currTimeMs = System.currentTimeMillis();
+        for (File dir : dirs) {
+            for (File file : dir.listFiles()) {
+                // delete stale data
+                if (file.lastModified() + STALE_THRESHOLD_MILLIS < currTimeMs) {
+                    file.delete();
+                }
+            }
+        }
+    }
+
+    /**
+     * Converts a {@link PersistableBundle} into byte array and saves the results to a file.
+     */
+    private void writePersistableBundleToFile(
+            File dir, String metricsConfigName, PersistableBundle result) {
+        try (FileOutputStream os = new FileOutputStream(new File(dir, metricsConfigName))) {
+            result.writeToStream(os);
+        } catch (IOException e) {
+            Slog.w(CarLog.TAG_TELEMETRY, "Failed to write result to file", e);
+            // TODO(b/195422219): record failure
+        }
+    }
+
+    /** Deletes a the given file in the given directory if it exists. */
+    private void deleteFileInDirectory(File interimResultDirectory, String metricsConfigName) {
+        File file = new File(interimResultDirectory, metricsConfigName);
+        file.delete();
+    }
+
+    /** Callback for receiving final metrics output. */
+    interface FinalResultCallback {
+        void onFinalResult(String metricsConfigName, PersistableBundle result);
+    }
+
+    /** Wrapper around a result and whether the result should be written to disk. */
+    static final class InterimResult {
+        private final PersistableBundle mBundle;
+        private final boolean mDirty;
+
+        InterimResult(PersistableBundle bundle) {
+            mBundle = bundle;
+            mDirty = false;
+        }
+
+        InterimResult(PersistableBundle bundle, boolean dirty) {
+            mBundle = bundle;
+            mDirty = dirty;
+        }
+
+        PersistableBundle getBundle() {
+            return mBundle;
+        }
+
+        boolean isDirty() {
+            return mDirty;
+        }
+    }
+
+    // TODO(b/195422227): Implement storing error results
+    // TODO(b/195422227): Implement deletion of interim results after MetricsConfig is removed
+}
diff --git a/service/src/com/android/car/telemetry/ScriptExecutor.java b/service/src/com/android/car/telemetry/ScriptExecutor.java
deleted file mode 100644
index d791481..0000000
--- a/service/src/com/android/car/telemetry/ScriptExecutor.java
+++ /dev/null
@@ -1,115 +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.app.Service;
-import android.car.telemetry.IScriptExecutor;
-import android.car.telemetry.IScriptExecutorListener;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.util.Log;
-
-import com.android.car.CarServiceUtils;
-
-/**
- * Executes Lua code in an isolated process with provided source code
- * and input arguments.
- */
-public final class ScriptExecutor extends Service {
-
-    static {
-        System.loadLibrary("scriptexecutorjni");
-    }
-
-    private static final String TAG = ScriptExecutor.class.getSimpleName();
-
-    // Dedicated "worker" thread to handle all calls related to native code.
-    private HandlerThread mNativeHandlerThread;
-    // Handler associated with the native worker thread.
-    private Handler mNativeHandler;
-
-    private final class IScriptExecutorImpl extends IScriptExecutor.Stub {
-        @Override
-        public void invokeScript(String scriptBody, String functionName, Bundle publishedData,
-                Bundle savedState, IScriptExecutorListener listener) {
-            mNativeHandler.post(() ->
-                    nativeInvokeScript(mLuaEnginePtr, scriptBody, functionName, publishedData,
-                            savedState, listener));
-        }
-    }
-    private IScriptExecutorImpl mScriptExecutorBinder;
-
-    // Memory location of Lua Engine object which is allocated in native code.
-    private long mLuaEnginePtr;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-
-        mNativeHandlerThread = CarServiceUtils.getHandlerThread(
-            ScriptExecutor.class.getSimpleName());
-        // TODO(b/192284628): Remove this once start handling all recoverable errors via onError
-        // callback.
-        mNativeHandlerThread.setUncaughtExceptionHandler((t, e) -> Log.e(TAG, e.getMessage()));
-        mNativeHandler = new Handler(mNativeHandlerThread.getLooper());
-
-        mLuaEnginePtr = nativeInitLuaEngine();
-        mScriptExecutorBinder = new IScriptExecutorImpl();
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        nativeDestroyLuaEngine(mLuaEnginePtr);
-        mNativeHandlerThread.quit();
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        return mScriptExecutorBinder;
-    }
-
-    /**
-    * Initializes Lua Engine.
-    *
-    * <p>Returns memory location of Lua Engine.
-    */
-    private native long nativeInitLuaEngine();
-
-    /**
-     * Destroys LuaEngine at the provided memory address.
-     */
-    private native void nativeDestroyLuaEngine(long luaEnginePtr);
-
-    /**
-     * Calls provided Lua function.
-     *
-     * @param luaEnginePtr memory address of the stored LuaEngine instance.
-     * @param scriptBody complete body of Lua script that also contains the function to be invoked.
-     * @param functionName the name of the function to execute.
-     * @param publishedData input data provided by the source which the function handles.
-     * @param savedState key-value pairs preserved from the previous invocation of the function.
-     * @param listener callback for the sandboxed environent to report back script execution results
-     * and errors.
-     */
-    private native void nativeInvokeScript(long luaEnginePtr, String scriptBody,
-            String functionName, Bundle publishedData, Bundle savedState,
-            IScriptExecutorListener listener);
-}
diff --git a/service/src/com/android/car/telemetry/databroker/DataBroker.java b/service/src/com/android/car/telemetry/databroker/DataBroker.java
index 44f2f61..d7cc2e6 100644
--- a/service/src/com/android/car/telemetry/databroker/DataBroker.java
+++ b/service/src/com/android/car/telemetry/databroker/DataBroker.java
@@ -22,6 +22,18 @@
 public interface DataBroker {
 
     /**
+     * Interface for receiving notification that script finished.
+     */
+    interface ScriptFinishedCallback {
+        /**
+         * Listens to script finished event.
+         *
+         * @param configName the name of the config whose script finished.
+         */
+        void onScriptFinished(String configName);
+    }
+
+    /**
      * Adds an active {@link com.android.car.telemetry.TelemetryProto.MetricsConfig} that is pending
      * execution. When updating the MetricsConfig to a newer version, the caller must call
      * {@link #removeMetricsConfiguration(TelemetryProto.MetricsConfig)} first to clear the old
@@ -29,29 +41,40 @@
      * 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.
      */
-    boolean addMetricsConfiguration(TelemetryProto.MetricsConfig metricsConfig);
+    void 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.
      */
-    boolean removeMetricsConfiguration(TelemetryProto.MetricsConfig metricsConfig);
+    void removeMetricsConfiguration(TelemetryProto.MetricsConfig metricsConfig);
+
+    /**
+     * Adds a {@link ScriptExecutionTask} to the priority queue. This method will schedule the
+     * next task if a task is not currently running.
+     */
+    void addTaskToQueue(ScriptExecutionTask task);
+
+    /**
+     * Checks system health state and executes a task if condition allows.
+     */
+    void scheduleNextTask();
 
     /**
      * Sets callback for notifying script finished.
      *
      * @param callback script finished callback.
      */
-    void setOnScriptFinishedCallback(DataBrokerController.ScriptFinishedCallback callback);
+    void setOnScriptFinishedCallback(ScriptFinishedCallback callback);
 
     /**
-     * Invoked by controller to indicate system health state and which subscribers can be consumed.
-     * A smaller priority number indicates higher priority. Range 1 - 100.
+     * Sets the priority which affects which subscribers can consume data. Invoked by controller to
+     * indicate system health state and which subscribers can be consumed. If controller does not
+     * set the priority, it will be defaulted to 1. A smaller priority number indicates higher
+     * priority. Range 1 - 100.
      */
     void setTaskExecutionPriority(int priority);
 }
diff --git a/service/src/com/android/car/telemetry/databroker/DataBrokerController.java b/service/src/com/android/car/telemetry/databroker/DataBrokerController.java
index 7663667..a5fa1cb 100644
--- a/service/src/com/android/car/telemetry/databroker/DataBrokerController.java
+++ b/service/src/com/android/car/telemetry/databroker/DataBrokerController.java
@@ -34,18 +34,6 @@
     private final DataBroker mDataBroker;
     private final SystemMonitor mSystemMonitor;
 
-    /**
-     * Interface for receiving notification that script finished.
-     */
-    public interface ScriptFinishedCallback {
-        /**
-         * Listens to script finished event.
-         *
-         * @param configName the name of the config whose script finished.
-         */
-        void onScriptFinished(String configName);
-    }
-
     public DataBrokerController(DataBroker dataBroker, SystemMonitor systemMonitor) {
         mDataBroker = dataBroker;
         mDataBroker.setOnScriptFinishedCallback(this::onScriptFinished);
diff --git a/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java b/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java
index 86d725e..55c7685 100644
--- a/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java
+++ b/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java
@@ -16,55 +16,189 @@
 
 package com.android.car.telemetry.databroker;
 
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Slog;
 
 import com.android.car.CarLog;
+import com.android.car.CarServiceUtils;
+import com.android.car.telemetry.CarTelemetryService;
+import com.android.car.telemetry.ResultStore;
 import com.android.car.telemetry.TelemetryProto;
 import com.android.car.telemetry.TelemetryProto.MetricsConfig;
 import com.android.car.telemetry.publisher.AbstractPublisher;
 import com.android.car.telemetry.publisher.PublisherFactory;
+import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutor;
+import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutorListener;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.PriorityBlockingQueue;
 
 /**
  * Implementation of the data path component of CarTelemetryService. Forwards the published data
  * from publishers to consumers subject to the Controller's decision.
+ * TODO(b/187743369): Handle thread-safety of member variables.
  */
 public class DataBrokerImpl implements DataBroker {
 
-    // Maps MetricsConfig's name to its subscriptions. This map is useful when removing a
-    // MetricsConfig.
+    private static final int MSG_HANDLE_TASK = 1;
+    private static final int MSG_BIND_TO_SCRIPT_EXECUTOR = 2;
+
+    private static final String SCRIPT_EXECUTOR_PACKAGE = "com.android.car.scriptexecutor";
+    private static final String SCRIPT_EXECUTOR_CLASS =
+            "com.android.car.scriptexecutor.ScriptExecutor";
+
+    private final Context mContext;
+    private final PublisherFactory mPublisherFactory;
+    private final ResultStore mResultStore;
+    private final ScriptExecutorListener mScriptExecutorListener;
+    private final HandlerThread mTelemetryThread = CarServiceUtils.getHandlerThread(
+            CarTelemetryService.class.getSimpleName());
+    private final Handler mTelemetryHandler = new TaskHandler(mTelemetryThread.getLooper());
+
+    /** Thread-safe priority queue for scheduling tasks. */
+    private final PriorityBlockingQueue<ScriptExecutionTask> mTaskQueue =
+            new PriorityBlockingQueue<>();
+
+    /**
+     * Maps MetricsConfig's name to its subscriptions. This map is useful when removing a
+     * MetricsConfig.
+     */
     private final Map<String, List<DataSubscriber>> mSubscriptionMap = new ArrayMap<>();
 
-    private DataBrokerController.ScriptFinishedCallback mScriptFinishedCallback;
-    private final PublisherFactory mPublisherFactory;
+    /**
+     * If something irrecoverable happened, DataBroker should enter into a disabled state to prevent
+     * doing futile work.
+     */
+    private boolean mDisabled = false;
 
-    public DataBrokerImpl(PublisherFactory publisherFactory) {
+    /** Priority of current system to determine if a {@link ScriptExecutionTask} can run. */
+    private int mPriority = 1;
+
+    /**
+     * Name of the script that's currently running. If no script is running, value is null.
+     * A non-null script name indicates a script is running, which means DataBroker should not
+     * make another ScriptExecutor binder call.
+     */
+    private String mCurrentScriptName;
+    private IScriptExecutor mScriptExecutor;
+    private ScriptFinishedCallback mScriptFinishedCallback;
+
+    private final ServiceConnection mServiceConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            mTelemetryHandler.post(() -> {
+                mScriptExecutor = IScriptExecutor.Stub.asInterface(service);
+                scheduleNextTask();
+            });
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            // TODO(b/198684473): clean up the state after script executor disconnects
+            mTelemetryHandler.post(() -> {
+                mScriptExecutor = null;
+                unbindScriptExecutor();
+            });
+        }
+    };
+
+    public DataBrokerImpl(
+            Context context, PublisherFactory publisherFactory, ResultStore resultStore) {
+        mContext = context;
         mPublisherFactory = publisherFactory;
+        mResultStore = resultStore;
+        mScriptExecutorListener = new ScriptExecutorListener(this);
+        mPublisherFactory.setFailureConsumer(this::onPublisherFailure);
     }
 
-    // current task priority, used to determine which data can be processed
-    private int mTaskExecutionPriority;
+    private void onPublisherFailure(AbstractPublisher publisher, Throwable error) {
+        // TODO(b/193680465): disable MetricsConfig and log the error
+        Slog.w(CarLog.TAG_TELEMETRY, "publisher failed", error);
+    }
+
+    private void bindScriptExecutor() {
+        // do not re-bind if broker is in a disabled state or if script executor is nonnull
+        if (mDisabled || mScriptExecutor != null) {
+            return;
+        }
+        Intent intent = new Intent();
+        intent.setComponent(new ComponentName(SCRIPT_EXECUTOR_PACKAGE, SCRIPT_EXECUTOR_CLASS));
+        boolean success = mContext.bindServiceAsUser(
+                intent,
+                mServiceConnection,
+                Context.BIND_AUTO_CREATE,
+                UserHandle.SYSTEM);
+        if (!success) {
+            Slog.w(CarLog.TAG_TELEMETRY, "failed to get valid connection to ScriptExecutor");
+            unbindScriptExecutor();
+            disableBroker();
+        }
+    }
+
+    /**
+     * Unbinds {@link ScriptExecutor} to release the connection. This method should be called from
+     * the telemetry thread.
+     */
+    private void unbindScriptExecutor() {
+        // TODO(b/198648763): unbind from script executor when there is no work to do
+        mCurrentScriptName = null;
+        try {
+            mContext.unbindService(mServiceConnection);
+        } catch (IllegalArgumentException e) {
+            // If ScriptExecutor is gone before unbinding, it will throw this exception
+            Slog.w(CarLog.TAG_TELEMETRY, "Failed to unbind from ScriptExecutor", e);
+        }
+    }
+
+    /** Enters into a disabled state because something irrecoverable happened. */
+    private void disableBroker() {
+        mDisabled = true;
+        // remove all MetricConfigs, disable all publishers, stop receiving data
+        for (String metricsConfigName : mSubscriptionMap.keySet()) {
+            // get the metrics config from the DataSubscriber and remove the metrics config
+            if (mSubscriptionMap.get(metricsConfigName).size() != 0) {
+                removeMetricsConfiguration(mSubscriptionMap.get(metricsConfigName).get(0)
+                        .getMetricsConfig());
+            }
+        }
+        mSubscriptionMap.clear();
+    }
 
     @Override
-    public boolean addMetricsConfiguration(MetricsConfig metricsConfig) {
-        // if metricsConfig already exists, it should not be added again
-        if (mSubscriptionMap.containsKey(metricsConfig.getName())) {
-            return false;
+    public void addMetricsConfiguration(MetricsConfig metricsConfig) {
+        // TODO(b/187743369): pass status back to caller
+        // if broker is disabled or metricsConfig already exists, do nothing
+        if (mDisabled || mSubscriptionMap.containsKey(metricsConfig.getName())) {
+            return;
         }
         // Create the subscribers for this metrics configuration
-        List<DataSubscriber> dataSubscribers = new ArrayList<>();
+        List<DataSubscriber> dataSubscribers = new ArrayList<>(
+                metricsConfig.getSubscribersList().size());
         for (TelemetryProto.Subscriber subscriber : metricsConfig.getSubscribersList()) {
             // protobuf publisher to a concrete Publisher
             AbstractPublisher publisher = mPublisherFactory.getPublisher(
                     subscriber.getPublisher().getPublisherCase());
-
             // create DataSubscriber from TelemetryProto.Subscriber
-            DataSubscriber dataSubscriber = new DataSubscriber(metricsConfig, subscriber);
+            DataSubscriber dataSubscriber = new DataSubscriber(
+                    this,
+                    metricsConfig,
+                    subscriber);
             dataSubscribers.add(dataSubscriber);
 
             try {
@@ -73,17 +207,17 @@
                 publisher.addDataSubscriber(dataSubscriber);
             } catch (IllegalArgumentException e) {
                 Slog.w(CarLog.TAG_TELEMETRY, "Invalid config", e);
-                return false;
+                return;
             }
         }
         mSubscriptionMap.put(metricsConfig.getName(), dataSubscribers);
-        return true;
     }
 
     @Override
-    public boolean removeMetricsConfiguration(MetricsConfig metricsConfig) {
+    public void removeMetricsConfiguration(MetricsConfig metricsConfig) {
+        // TODO(b/187743369): pass status back to caller
         if (!mSubscriptionMap.containsKey(metricsConfig.getName())) {
-            return false;
+            return;
         }
         // get the subscriptions associated with this MetricsConfig, remove it from the map
         List<DataSubscriber> dataSubscribers = mSubscriptionMap.remove(metricsConfig.getName());
@@ -97,23 +231,202 @@
                 // It shouldn't happen, but if happens, let's just log it.
                 Slog.w(CarLog.TAG_TELEMETRY, "Failed to remove subscriber from publisher", e);
             }
-            // TODO(b/187743369): remove related tasks from the queue
         }
-        return true;
+        // Remove all the tasks associated with this metrics config. The underlying impl uses the
+        // weakly consistent iterator, which is thread-safe but does not freeze the collection while
+        // iterating, so it may or may not reflect any updates since the iterator was created.
+        // But since adding & polling from queue should happen in the same thread, the task queue
+        // should not be changed while tasks are being iterated and removed.
+        mTaskQueue.removeIf(task -> task.isAssociatedWithMetricsConfig(metricsConfig));
     }
 
     @Override
-    public void setOnScriptFinishedCallback(DataBrokerController.ScriptFinishedCallback callback) {
+    public void addTaskToQueue(ScriptExecutionTask task) {
+        if (mDisabled) {
+            return;
+        }
+        mTaskQueue.add(task);
+        scheduleNextTask();
+    }
+
+    /**
+     * This method can be called from any thread. It is thread-safe because atomic values and the
+     * blocking queue are thread-safe. It is possible for this method to be invoked from different
+     * threads at the same time, but it is not possible to schedule the same task twice, because
+     * the handler handles message in the order they come in, this means the task will be polled
+     * sequentially instead of concurrently. Every task that is scheduled and run will be distinct.
+     * TODO(b/187743369): If the threading behavior in DataSubscriber changes, ScriptExecutionTask
+     *  will also have different threading behavior. Update javadoc when the behavior is decided.
+     */
+    @Override
+    public void scheduleNextTask() {
+        if (mDisabled || mTelemetryHandler.hasMessages(MSG_HANDLE_TASK)) {
+            return;
+        }
+        mTelemetryHandler.sendEmptyMessage(MSG_HANDLE_TASK);
+    }
+
+    @Override
+    public void setOnScriptFinishedCallback(ScriptFinishedCallback callback) {
+        if (mDisabled) {
+            return;
+        }
         mScriptFinishedCallback = callback;
     }
 
     @Override
     public void setTaskExecutionPriority(int priority) {
-        mTaskExecutionPriority = priority;
+        if (mDisabled) {
+            return;
+        }
+        mPriority = priority;
+        scheduleNextTask(); // when priority updates, schedule a task which checks task queue
     }
 
     @VisibleForTesting
     Map<String, List<DataSubscriber>> getSubscriptionMap() {
-        return mSubscriptionMap;
+        return new ArrayMap<>((ArrayMap<String, List<DataSubscriber>>) mSubscriptionMap);
+    }
+
+    @VisibleForTesting
+    Handler getTelemetryHandler() {
+        return mTelemetryHandler;
+    }
+
+    @VisibleForTesting
+    PriorityBlockingQueue<ScriptExecutionTask> getTaskQueue() {
+        return mTaskQueue;
+    }
+
+    /**
+     * Polls and runs a task from the head of the priority queue if the queue is nonempty and the
+     * head of the queue has priority higher than or equal to the current priority. A higher
+     * priority is denoted by a lower priority number, so head of the queue should have equal or
+     * lower priority number to be polled.
+     */
+    private void pollAndExecuteTask() {
+        // check databroker state is ready to run script
+        if (mDisabled || mCurrentScriptName != null) {
+            return;
+        }
+        // check task is valid and ready to be run
+        ScriptExecutionTask task = mTaskQueue.peek();
+        if (task == null || task.getPriority() > mPriority) {
+            return;
+        }
+        mTaskQueue.poll(); // remove task from queue
+        try {
+            if (mScriptExecutor == null) {
+                Slog.w(CarLog.TAG_TELEMETRY,
+                        "script executor is null, cannot execute task");
+                mTaskQueue.add(task);
+                // upon successful binding, a task will be scheduled to run if there are any
+                mTelemetryHandler.sendEmptyMessage(MSG_BIND_TO_SCRIPT_EXECUTOR);
+            } else {
+                // update current name because a script is currently running
+                mCurrentScriptName = task.getMetricsConfig().getName();
+                mScriptExecutor.invokeScript(
+                        task.getMetricsConfig().getScript(),
+                        task.getHandlerName(),
+                        task.getData(),
+                        mResultStore.getInterimResult(mCurrentScriptName),
+                        mScriptExecutorListener);
+            }
+        } catch (RemoteException e) {
+            Slog.d(CarLog.TAG_TELEMETRY, "remote exception occurred invoking script", e);
+            mTaskQueue.add(task); // will not trigger scheduleNextTask()
+            mCurrentScriptName = null;
+        }
+    }
+
+    /** Stores final metrics and schedules the next task. */
+    private void onScriptFinished(PersistableBundle result) {
+        mTelemetryHandler.post(() -> {
+            mResultStore.putFinalResult(mCurrentScriptName, result);
+            mCurrentScriptName = null;
+            scheduleNextTask();
+        });
+    }
+
+    /** Stores interim metrics and schedules the next task. */
+    private void onScriptSuccess(PersistableBundle stateToPersist) {
+        mTelemetryHandler.post(() -> {
+            mResultStore.putInterimResult(mCurrentScriptName, stateToPersist);
+            mCurrentScriptName = null;
+            scheduleNextTask();
+        });
+    }
+
+    /** Stores telemetry error and schedules the next task. */
+    private void onScriptError(int errorType, String message, String stackTrace) {
+        mTelemetryHandler.post(() -> {
+            // TODO(b/197005294): create error object
+            mCurrentScriptName = null;
+            scheduleNextTask();
+        });
+    }
+
+    /** Listens for script execution status. Methods are called on the binder thread. */
+    private static final class ScriptExecutorListener extends IScriptExecutorListener.Stub {
+        private final WeakReference<DataBrokerImpl> mWeakDataBroker;
+
+        private ScriptExecutorListener(DataBrokerImpl dataBroker) {
+            mWeakDataBroker = new WeakReference<>(dataBroker);
+        }
+
+        @Override
+        public void onScriptFinished(PersistableBundle result) {
+            DataBrokerImpl dataBroker = mWeakDataBroker.get();
+            if (dataBroker == null) {
+                return;
+            }
+            dataBroker.onScriptFinished(result);
+        }
+
+        @Override
+        public void onSuccess(PersistableBundle stateToPersist) {
+            DataBrokerImpl dataBroker = mWeakDataBroker.get();
+            if (dataBroker == null) {
+                return;
+            }
+            dataBroker.onScriptSuccess(stateToPersist);
+        }
+
+        @Override
+        public void onError(int errorType, String message, String stackTrace) {
+            DataBrokerImpl dataBroker = mWeakDataBroker.get();
+            if (dataBroker == null) {
+                return;
+            }
+            dataBroker.onScriptError(errorType, message, stackTrace);
+        }
+    }
+
+    /** Callback handler to handle scheduling and rescheduling of {@link ScriptExecutionTask}s. */
+    class TaskHandler extends Handler {
+        TaskHandler(Looper looper) {
+            super(looper);
+        }
+
+        /**
+         * Handles a message depending on the message ID.
+         * If the msg ID is MSG_HANDLE_TASK, it polls a task from the priority queue and executing a
+         * {@link ScriptExecutionTask}. There are multiple places where this message is sent: when
+         * priority updates, when a new task is added to the priority queue, and when a task
+         * finishes running.
+         */
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_HANDLE_TASK:
+                    pollAndExecuteTask(); // run the next task
+                    break;
+                case MSG_BIND_TO_SCRIPT_EXECUTOR:
+                    bindScriptExecutor();
+                    break;
+                default:
+                    Slog.w(CarLog.TAG_TELEMETRY, "TaskHandler received unknown message.");
+            }
+        }
     }
 }
diff --git a/service/src/com/android/car/telemetry/databroker/DataSubscriber.java b/service/src/com/android/car/telemetry/databroker/DataSubscriber.java
index 0c713c5..8af2c6f 100644
--- a/service/src/com/android/car/telemetry/databroker/DataSubscriber.java
+++ b/service/src/com/android/car/telemetry/databroker/DataSubscriber.java
@@ -16,31 +16,68 @@
 
 package com.android.car.telemetry.databroker;
 
-import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.os.SystemClock;
 
 import com.android.car.telemetry.TelemetryProto;
 
 /**
- * Subscriber class that receive published data and schedules tasks for execution on the data.
+ * Subscriber class that receives published data and schedules tasks for execution.
+ * All methods of this class must be accessed on telemetry thread.
+ *
+ * <p>TODO(b/187743369): implement equals() and hash() functions, as they are used in publishers
+ *                       to check equality of subscribers.
  */
 public class DataSubscriber {
+
+    private final DataBroker mDataBroker;
+    private final TelemetryProto.MetricsConfig mMetricsConfig;
     private final TelemetryProto.Subscriber mSubscriber;
 
-    public DataSubscriber(TelemetryProto.MetricsConfig metricsConfig,
+    public DataSubscriber(
+            DataBroker dataBroker,
+            TelemetryProto.MetricsConfig metricsConfig,
             TelemetryProto.Subscriber subscriber) {
+        mDataBroker = dataBroker;
+        mMetricsConfig = metricsConfig;
         mSubscriber = subscriber;
     }
 
+    /** Returns the handler function name for this subscriber. */
+    public String getHandlerName() {
+        return mSubscriber.getHandler();
+    }
+
     /**
-     * Returns the publisher param {@link com.android.car.telemetry.TelemetryProto.Publisher} that
+     * Returns the publisher param {@link TelemetryProto.Publisher} that
      * contains the data source and the config.
      */
     public TelemetryProto.Publisher getPublisherParam() {
         return mSubscriber.getPublisher();
     }
 
-    /** Pushes data to the subscriber. */
-    public void push(Bundle data) {
-        // TODO(b/187743369): implement
+    /**
+     * Creates a {@link ScriptExecutionTask} and pushes it to the priority queue where the task
+     * will be pending execution.
+     */
+    public void push(PersistableBundle data) {
+        ScriptExecutionTask task = new ScriptExecutionTask(
+                this, data, SystemClock.elapsedRealtime());
+        mDataBroker.addTaskToQueue(task);
+    }
+
+    /** Returns the {@link TelemetryProto.MetricsConfig}. */
+    public TelemetryProto.MetricsConfig getMetricsConfig() {
+        return mMetricsConfig;
+    }
+
+    /** Returns the {@link TelemetryProto.Subscriber}. */
+    public TelemetryProto.Subscriber getSubscriber() {
+        return mSubscriber;
+    }
+
+    /** Returns the priority of subscriber. */
+    public int getPriority() {
+        return mSubscriber.getPriority();
     }
 }
diff --git a/service/src/com/android/car/telemetry/databroker/ScriptExecutionTask.java b/service/src/com/android/car/telemetry/databroker/ScriptExecutionTask.java
new file mode 100644
index 0000000..a1fb522
--- /dev/null
+++ b/service/src/com/android/car/telemetry/databroker/ScriptExecutionTask.java
@@ -0,0 +1,85 @@
+/*
+ * 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 android.os.PersistableBundle;
+
+import com.android.car.telemetry.TelemetryProto;
+
+/**
+ * A wrapper class containing all the necessary information to invoke the ScriptExecutor API. It
+ * is enqueued into the priority queue where it pends execution by {@link DataBroker}.
+ * It implements the {@link Comparable} interface so it has a natural ordering by priority and
+ * creation timestamp in the priority queue.
+ * The object can be accessed from any thread. See {@link DataSubscriber} for thread-safety.
+ */
+public class ScriptExecutionTask implements Comparable<ScriptExecutionTask> {
+    private final long mTimestampMillis;
+    private final DataSubscriber mSubscriber;
+    private final PersistableBundle mData;
+
+    ScriptExecutionTask(DataSubscriber subscriber, PersistableBundle data,
+            long elapsedRealtimeMillis) {
+        mTimestampMillis = elapsedRealtimeMillis;
+        mSubscriber = subscriber;
+        mData = data;
+    }
+
+    /** Returns the priority of the task. */
+    public int getPriority() {
+        return mSubscriber.getPriority();
+    }
+
+    /** Returns the creation timestamp of the task. */
+    public long getCreationTimestampMillis() {
+        return mTimestampMillis;
+    }
+
+    public TelemetryProto.MetricsConfig getMetricsConfig() {
+        return mSubscriber.getMetricsConfig();
+    }
+
+    public String getHandlerName() {
+        return mSubscriber.getHandlerName();
+    }
+
+    public PersistableBundle getData() {
+        return mData;
+    }
+
+    /**
+     * Indicates whether the task is associated with the given
+     * {@link com.android.car.telemetry.TelemetryProto.MetricsConfig).
+     */
+    public boolean isAssociatedWithMetricsConfig(TelemetryProto.MetricsConfig metricsConfig) {
+        return mSubscriber.getMetricsConfig().equals(metricsConfig);
+    }
+
+    @Override
+    public int compareTo(ScriptExecutionTask other) {
+        if (getPriority() < other.getPriority()) {
+            return -1;
+        } else if (getPriority() > other.getPriority()) {
+            return 1;
+        }
+        // if equal priority, compare creation timestamps
+        if (getCreationTimestampMillis() < other.getCreationTimestampMillis()) {
+            return -1;
+        }
+        return 1;
+    }
+}
diff --git a/service/src/com/android/car/telemetry/proto/Android.bp b/service/src/com/android/car/telemetry/proto/Android.bp
index 8ffcaa2..89e09c5 100644
--- a/service/src/com/android/car/telemetry/proto/Android.bp
+++ b/service/src/com/android/car/telemetry/proto/Android.bp
@@ -16,11 +16,16 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-java_library_static {
+java_library {
     name: "cartelemetry-protos",
-    host_supported: true,
     proto: {
         type: "lite",
     },
-    srcs: ["*.proto"],
+    srcs: [
+        ":cartelemetry-cardata-proto-srcs",
+        "atoms.proto",
+        "stats_log.proto",
+        "statsd_config.proto",
+        "telemetry.proto",
+    ],
 }
diff --git a/service/src/com/android/car/telemetry/proto/atoms.proto b/service/src/com/android/car/telemetry/proto/atoms.proto
new file mode 100644
index 0000000..225785e
--- /dev/null
+++ b/service/src/com/android/car/telemetry/proto/atoms.proto
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+// Partial clone of frameworks/proto_logging/stats/atoms.proto. CarTelemetryService only uses
+// small number of atoms.
+
+syntax = "proto2";
+
+package android.car.telemetry.statsd;
+option java_package = "com.android.car.telemetry";
+option java_outer_classname = "AtomsProto";
+
+message Atom {
+  oneof pushed {
+    AppStartMemoryStateCaptured app_start_memory_state_captured = 55;
+  }
+}
+
+message AppStartMemoryStateCaptured {
+  // The uid if available. -1 means not available.
+  optional int32 uid = 1;
+  optional string process_name = 2;
+  optional string activity_name = 3;
+  optional int64 page_fault = 4;
+  optional int64 page_major_fault = 5;
+  optional int64 rss_in_bytes = 6;
+  optional int64 cache_in_bytes = 7;
+  optional int64 swap_in_bytes = 8;
+}
diff --git a/service/src/com/android/car/telemetry/proto/stats_log.proto b/service/src/com/android/car/telemetry/proto/stats_log.proto
new file mode 100644
index 0000000..a3ec1ef
--- /dev/null
+++ b/service/src/com/android/car/telemetry/proto/stats_log.proto
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+
+// Partial clone packages/modules/StatsD/statsd/src/stats_log.proto. Unused messages are not copied
+// here.
+
+syntax = "proto2";
+
+package android.car.telemetry.statsd;
+
+option java_package = "com.android.car.telemetry";
+option java_outer_classname = "StatsLogProto";
+
+import "packages/services/Car/service/src/com/android/car/telemetry/proto/atoms.proto";
+
+message AggregatedAtomInfo {
+  optional Atom atom = 1;
+  repeated int64 elapsed_timestamp_nanos = 2;
+}
+
+message EventMetricData {
+  optional int64 elapsed_timestamp_nanos = 1;
+  optional Atom atom = 2;
+  optional AggregatedAtomInfo aggregated_atom_info = 4;
+  reserved 3;
+}
+
+message StatsLogReport {
+  optional int64 metric_id = 1;
+
+  message EventMetricDataWrapper {
+    repeated EventMetricData data = 1;
+  }
+
+  oneof data {
+    EventMetricDataWrapper event_metrics = 4;
+  }
+
+  optional bool is_active = 14;
+
+  reserved 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16;
+}
+
+message ConfigMetricsReport {
+  repeated StatsLogReport metrics = 1;
+
+  enum DumpReportReason {
+    DEVICE_SHUTDOWN = 1;
+    CONFIG_UPDATED = 2;
+    CONFIG_REMOVED = 3;
+    GET_DATA_CALLED = 4;
+    ADB_DUMP = 5;
+    CONFIG_RESET = 6;
+    STATSCOMPANION_DIED = 7;
+    TERMINATION_SIGNAL_RECEIVED = 8;
+  }
+  optional DumpReportReason dump_report_reason = 8;
+
+  reserved 2, 3, 4, 5, 6, 7, 9;
+}
+
+message ConfigMetricsReportList {
+  message ConfigKey {
+    optional int32 uid = 1;
+    optional int64 id = 2;
+  }
+  optional ConfigKey config_key = 1;
+
+  repeated ConfigMetricsReport reports = 2;
+
+  reserved 10;
+}
diff --git a/service/src/com/android/car/telemetry/proto/statsd_config.proto b/service/src/com/android/car/telemetry/proto/statsd_config.proto
new file mode 100644
index 0000000..fe92f28
--- /dev/null
+++ b/service/src/com/android/car/telemetry/proto/statsd_config.proto
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+// Partial clone of packages/modules/StatsD/statsd/src/statsd_config.proto. The service only
+// uses some parameters when configuring StatsD.
+
+syntax = "proto2";
+
+package android.car.telemetry.statsd;
+
+option java_package = "com.android.car.telemetry";
+option java_outer_classname = "StatsdConfigProto";
+
+enum TimeUnit {
+  TIME_UNIT_UNSPECIFIED = 0;
+  FIVE_MINUTES = 2;
+  TEN_MINUTES = 3;
+  THIRTY_MINUTES = 4;
+  ONE_HOUR = 5;
+  reserved 1, 6, 7, 8, 9, 10, 1000;
+}
+
+message SimpleAtomMatcher {
+  optional int32 atom_id = 1;
+
+  reserved 2;
+}
+
+message AtomMatcher {
+  optional int64 id = 1;
+
+  oneof contents {
+    SimpleAtomMatcher simple_atom_matcher = 2;
+  }
+
+  reserved 3;
+}
+
+message EventMetric {
+  optional int64 id = 1;
+  optional int64 what = 2;
+  optional int64 condition = 3;
+
+  reserved 4;
+  reserved 100;
+  reserved 101;
+}
+
+message PullAtomPackages {
+  optional int32 atom_id = 1;
+
+  repeated string packages = 2;
+}
+
+message StatsdConfig {
+  optional int64 id = 1;
+
+  repeated EventMetric event_metric = 2;
+
+  repeated AtomMatcher atom_matcher = 7;
+
+  repeated string allowed_log_source = 12;
+
+  optional int64 ttl_in_seconds = 15;
+
+  optional bool hash_strings_in_metric_report = 16 [default = true];
+
+  optional bool persist_locally = 20 [default = false];
+
+  repeated PullAtomPackages pull_atom_packages = 23;
+
+  repeated int32 whitelisted_atom_ids = 24;
+
+  reserved 3, 4, 5, 6, 8, 9, 10, 11, 13, 14, 17, 18, 19, 21, 22, 25;
+
+  // Do not use.
+  reserved 1000, 1001;
+}
diff --git a/service/src/com/android/car/telemetry/proto/telemetry.proto b/service/src/com/android/car/telemetry/proto/telemetry.proto
index 5787d62..768b31b 100644
--- a/service/src/com/android/car/telemetry/proto/telemetry.proto
+++ b/service/src/com/android/car/telemetry/proto/telemetry.proto
@@ -16,7 +16,7 @@
 
 syntax = "proto2";
 
-package com.android.car.telemetry;
+package android.car.telemetry;
 
 option java_package = "com.android.car.telemetry";
 option java_outer_classname = "TelemetryProto";
@@ -30,16 +30,22 @@
 message MetricsConfig {
   // Required.
   // The name of the configuration. Must be unique within a device.
+  //
+  // Changing the name of the config should be done carefully, by first removing the config
+  // with the old name, and creating a new config with a new name.
+  // The name is used to for persisting the configs and other internal state, not removing the
+  // configs with old name will result dangling data in the system.
+  //
   // Only alphanumeric and _ characters are allowed. Minimum length is 3 chars.
   optional string name = 1;
 
   // Required.
-  // Version number of the configuration.
+  // Version number of the configuration. Must be more than 0.
   optional int32 version = 2;
 
   // Required.
-  // A script that is executed in devices. Must contain all the handler functions
-  // defined in the listeners below.
+  // A script that is executed at the device side. Must contain all the handler
+  // functions defined in the listeners below.
   // The script functions must be `pure` functions.
   optional string script = 3;
 
@@ -60,19 +66,53 @@
   optional float read_rate = 2;
 }
 
+// Parameters for cartelemetryd publisher.
+// See packages/services/Car/cpp/telemetry for CarData proto and docs.
+message CarTelemetrydPublisher {
+  // Required.
+  // CarData id to subscribe to.
+  // See packages/services/Car/cpp/telemetry/proto/CarData.proto for all the
+  // messages and IDs.
+  optional int32 id = 1;
+}
+
+// Publisher for various system metrics and stats.
+// It pushes metrics to the subscribers periodically or when garage mode starts.
+message StatsPublisher {
+  enum SystemMetric {
+    UNDEFINED = 0;  // default value, not used
+    // Collects all the app start events with the initial used RSS/CACHE/SWAP memory.
+    APP_START_MEMORY_STATE_CAPTURED = 1;
+  }
+
+  // Required.
+  // System metric for the publisher.
+  optional SystemMetric system_metric = 1;
+}
+
 // Specifies data publisher and its parameters.
 message Publisher {
   oneof publisher {
     VehiclePropertyPublisher vehicle_property = 1;
+    CarTelemetrydPublisher cartelemetryd = 2;
+    StatsPublisher stats = 3;
   }
 }
 
 // Specifies publisher with its parameters and the handler function that's invoked
 // when data is received. The format of the data depends on the Publisher.
 message Subscriber {
-  // Name of the script function that's invoked when this subscriber is triggered.
+  // Required.
+  // Name of the function that handles the published data. Must be defined
+  // in the script.
   optional string handler = 1;
 
-  // Publisher and its parameters.
+  // Required.
+  // Publisher definition.
   optional Publisher publisher = 2;
+
+  // Required.
+  // Priority of the subscriber, which affects the order of when it receives data.
+  // Ranges from 0 to 100. 0 being highest priority and 100 being lowest.
+  optional int32 priority = 3;
 }
diff --git a/service/src/com/android/car/telemetry/publisher/AbstractPublisher.java b/service/src/com/android/car/telemetry/publisher/AbstractPublisher.java
index f58a6fa..e91831e 100644
--- a/service/src/com/android/car/telemetry/publisher/AbstractPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/AbstractPublisher.java
@@ -18,9 +18,7 @@
 
 import com.android.car.telemetry.databroker.DataSubscriber;
 
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
+import java.util.function.BiConsumer;
 
 /**
  * Abstract class for publishers. It is 1-1 with data source and manages sending data to
@@ -30,54 +28,51 @@
  * configuration. Single publisher instance can send data as several
  * {@link com.android.car.telemetry.TelemetryProto.Publisher} to subscribers.
  *
- * <p>Not thread-safe.
+ * <p>Child classes must be called from the telemetry thread.
  */
 public abstract class AbstractPublisher {
-    private final HashSet<DataSubscriber> mDataSubscribers = new HashSet<>();
+    // TODO(b/199211673): provide list of bad MetricsConfigs to failureConsumer.
+    /** Consumes the publisher failures, such as failing to connect to a underlying service. */
+    private final BiConsumer<AbstractPublisher, Throwable> mFailureConsumer;
+
+    AbstractPublisher(BiConsumer<AbstractPublisher, Throwable> failureConsumer) {
+        mFailureConsumer = failureConsumer;
+    }
 
     /**
      * Adds a subscriber that listens for data produced by this publisher.
      *
+     * <p>DataBroker may call this method when a new {@code MetricsConfig} is added,
+     * {@code CarTelemetryService} is restarted or the device is restarted.
+     *
      * @param subscriber a subscriber to receive data
-     * @throws IllegalArgumentException if an invalid subscriber was provided.
+     * @throws IllegalArgumentException if the subscriber is invalid.
+     * @throws IllegalStateException if there are internal errors.
      */
-    public void addDataSubscriber(DataSubscriber subscriber) {
-        // This method can throw exceptions if data is invalid - do now swap these 2 lines.
-        onDataSubscriberAdded(subscriber);
-        mDataSubscribers.add(subscriber);
-    }
+    public abstract void addDataSubscriber(DataSubscriber subscriber);
 
     /**
      * Removes the subscriber from the publisher. Publisher stops if necessary.
      *
-     * @throws IllegalArgumentException if the subscriber was not found.
+     * <p>It does nothing if subscriber is not found.
      */
-    public void removeDataSubscriber(DataSubscriber subscriber) {
-        if (!mDataSubscribers.remove(subscriber)) {
-            throw new IllegalArgumentException("Failed to remove, subscriber not found");
-        }
-        onDataSubscribersRemoved(Collections.singletonList(subscriber));
-    }
+    public abstract void removeDataSubscriber(DataSubscriber subscriber);
 
     /**
      * Removes all the subscribers from the publisher. The publisher may stop.
+     *
+     * <p>This method also cleans-up internal publisher and the data source persisted state.
      */
-    public void removeAllDataSubscribers() {
-        onDataSubscribersRemoved(mDataSubscribers);
-        mDataSubscribers.clear();
-    }
+    public abstract void removeAllDataSubscribers();
+
+    /** Returns true if the publisher already has this data subscriber. */
+    public abstract boolean hasDataSubscriber(DataSubscriber subscriber);
 
     /**
-     * Called when a new subscriber is added to the publisher.
-     *
-     * @throws IllegalArgumentException if the invalid subscriber was provided.
+     * Notifies the failure consumer that this publisher cannot recover from the hard failure.
+     * For example, it cannot connect to the underlying service.
      */
-    protected abstract void onDataSubscriberAdded(DataSubscriber subscriber);
-
-    /** Called when subscribers are removed from the publisher. */
-    protected abstract void onDataSubscribersRemoved(Collection<DataSubscriber> subscribers);
-
-    protected HashSet<DataSubscriber> getDataSubscribers() {
-        return mDataSubscribers;
+    protected void notifyFailureConsumer(Throwable error) {
+        mFailureConsumer.accept(this, error);
     }
 }
diff --git a/service/src/com/android/car/telemetry/publisher/CarTelemetrydPublisher.java b/service/src/com/android/car/telemetry/publisher/CarTelemetrydPublisher.java
new file mode 100644
index 0000000..490ab83
--- /dev/null
+++ b/service/src/com/android/car/telemetry/publisher/CarTelemetrydPublisher.java
@@ -0,0 +1,191 @@
+/*
+ * 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.publisher;
+
+import android.automotive.telemetry.internal.CarDataInternal;
+import android.automotive.telemetry.internal.ICarDataListener;
+import android.automotive.telemetry.internal.ICarTelemetryInternal;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+import com.android.automotive.telemetry.CarDataProto;
+import com.android.car.CarLog;
+import com.android.car.telemetry.TelemetryProto;
+import com.android.car.telemetry.databroker.DataSubscriber;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+import com.android.server.utils.Slogf;
+
+import java.util.ArrayList;
+import java.util.function.BiConsumer;
+
+/**
+ * Publisher for cartelemtryd service (aka ICarTelemetry).
+ *
+ * <p>When a subscriber is added, the publisher binds to ICarTelemetryInternal and starts listening
+ * for incoming CarData. The matching CarData will be pushed to the subscriber. It unbinds itself
+ * from ICarTelemetryInternal if there are no subscribers.
+ *
+ * <p>See {@code packages/services/Car/cpp/telemetry/cartelemetryd} to learn more about the service.
+ */
+public class CarTelemetrydPublisher extends AbstractPublisher {
+    private static final boolean DEBUG = false;  // STOPSHIP if true
+    private static final String SERVICE_NAME = ICarTelemetryInternal.DESCRIPTOR + "/default";
+    private static final int BINDER_FLAGS = 0;
+
+    private ICarTelemetryInternal mCarTelemetryInternal;
+
+    private final ArrayList<DataSubscriber> mSubscribers = new ArrayList<>();
+
+    // All the methods in this class are expected to be called on this handler's thread.
+    private final Handler mTelemetryHandler;
+
+    private final ICarDataListener mListener = new ICarDataListener.Stub() {
+        @Override
+        public void onCarDataReceived(final CarDataInternal[] dataList) throws RemoteException {
+            if (DEBUG) {
+                Slog.d(CarLog.TAG_TELEMETRY,
+                        "Received " + dataList.length + " CarData from cartelemetryd");
+            }
+            // TODO(b/189142577): Create custom Handler and post message to improve performance
+            mTelemetryHandler.post(() -> onCarDataListReceived(dataList));
+        }
+    };
+
+    CarTelemetrydPublisher(BiConsumer<AbstractPublisher, Throwable> failureConsumer,
+            Handler telemetryHandler) {
+        super(failureConsumer);
+        this.mTelemetryHandler = telemetryHandler;
+    }
+
+    /** Called when binder for ICarTelemetry service is died. */
+    private void onBinderDied() {
+        // TODO(b/189142577): Create custom Handler and post message to improve performance
+        mTelemetryHandler.post(() -> {
+            if (mCarTelemetryInternal != null) {
+                mCarTelemetryInternal.asBinder().unlinkToDeath(this::onBinderDied, BINDER_FLAGS);
+                mCarTelemetryInternal = null;
+            }
+            notifyFailureConsumer(new IllegalStateException("ICarTelemetryInternal binder died"));
+        });
+    }
+
+    /** Connects to ICarTelemetryInternal service and starts listening for CarData. */
+    private void connectToCarTelemetryd() {
+        if (mCarTelemetryInternal != null) {
+            return;
+        }
+        IBinder binder = ServiceManager.checkService(SERVICE_NAME);
+        if (binder == null) {
+            notifyFailureConsumer(new IllegalStateException(
+                    "Failed to connect to the ICarTelemetryInternal: service is not ready"));
+            return;
+        }
+        try {
+            binder.linkToDeath(this::onBinderDied, BINDER_FLAGS);
+        } catch (RemoteException e) {
+            notifyFailureConsumer(new IllegalStateException(
+                    "Failed to connect to the ICarTelemetryInternal: linkToDeath failed", e));
+            return;
+        }
+        mCarTelemetryInternal = ICarTelemetryInternal.Stub.asInterface(binder);
+        try {
+            mCarTelemetryInternal.setListener(mListener);
+        } catch (RemoteException e) {
+            binder.unlinkToDeath(this::onBinderDied, BINDER_FLAGS);
+            mCarTelemetryInternal = null;
+            notifyFailureConsumer(new IllegalStateException(
+                    "Failed to connect to the ICarTelemetryInternal: Cannot set CarData listener",
+                    e));
+        }
+    }
+
+    /**
+     * Disconnects from ICarTelemetryInternal service.
+     *
+     * @throws IllegalStateException if fails to clear the listener.
+     */
+    private void disconnectFromCarTelemetryd() {
+        if (mCarTelemetryInternal == null) {
+            return;  // already disconnected
+        }
+        try {
+            mCarTelemetryInternal.clearListener();
+        } catch (RemoteException e) {
+            Slog.w(CarLog.TAG_TELEMETRY, "Failed to remove ICarTelemetryInternal listener", e);
+        }
+        mCarTelemetryInternal.asBinder().unlinkToDeath(this::onBinderDied, BINDER_FLAGS);
+        mCarTelemetryInternal = null;
+    }
+
+    @VisibleForTesting
+    boolean isConnectedToCarTelemetryd() {
+        return mCarTelemetryInternal != null;
+    }
+
+    @Override
+    public void addDataSubscriber(DataSubscriber subscriber) {
+        TelemetryProto.Publisher publisherParam = subscriber.getPublisherParam();
+        Preconditions.checkArgument(
+                publisherParam.getPublisherCase()
+                        == TelemetryProto.Publisher.PublisherCase.CARTELEMETRYD,
+                "Subscribers only with CarTelemetryd publisher are supported by this class.");
+        int carDataId = publisherParam.getCartelemetryd().getId();
+        CarDataProto.CarData.PushedCase carDataCase =
+                CarDataProto.CarData.PushedCase.forNumber(carDataId);
+        Preconditions.checkArgument(
+                carDataCase != null
+                        && carDataCase != CarDataProto.CarData.PushedCase.PUSHED_NOT_SET,
+                "Invalid CarData ID " + carDataId
+                        + ". Please see CarData.proto for the list of available IDs.");
+
+        mSubscribers.add(subscriber);
+
+        connectToCarTelemetryd();
+
+        Slogf.d(CarLog.TAG_TELEMETRY, "Subscribing to CarDat.id=%d", carDataId);
+    }
+
+    @Override
+    public void removeDataSubscriber(DataSubscriber subscriber) {
+        mSubscribers.remove(subscriber);
+        if (mSubscribers.isEmpty()) {
+            disconnectFromCarTelemetryd();
+        }
+    }
+
+    @Override
+    public void removeAllDataSubscribers() {
+        mSubscribers.clear();
+        disconnectFromCarTelemetryd();
+    }
+
+    @Override
+    public boolean hasDataSubscriber(DataSubscriber subscriber) {
+        return mSubscribers.contains(subscriber);
+    }
+
+    /**
+     * Called when publisher receives new car data list. It's executed on the telemetry thread.
+     */
+    private void onCarDataListReceived(CarDataInternal[] dataList) {
+        // TODO(b/189142577): implement
+    }
+}
diff --git a/service/src/com/android/car/telemetry/publisher/HashUtils.java b/service/src/com/android/car/telemetry/publisher/HashUtils.java
new file mode 100644
index 0000000..62eb12c
--- /dev/null
+++ b/service/src/com/android/car/telemetry/publisher/HashUtils.java
@@ -0,0 +1,63 @@
+/*
+ * 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.publisher;
+
+import android.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Utility class for computing hash code.
+ *
+ * <p>Most of the methods are copied from {@code external/guava/}.
+ */
+public class HashUtils {
+
+    /**
+     * Returns the hash code of the given string using SHA-256 algorithm. Returns only the first
+     * 8 bytes if the hash code, as SHA-256 is uniformly distributed.
+     */
+    static long sha256(@NonNull String data) {
+        try {
+            return asLong(MessageDigest.getInstance("SHA-256").digest(data.getBytes()));
+        } catch (NoSuchAlgorithmException e) {
+            // unreachable
+            throw new RuntimeException("SHA-256 algorithm not found.", e);
+        }
+    }
+
+    /**
+     * Returns the first eight bytes of {@code hashCode}, converted to a {@code long} value in
+     * little-endian order.
+     *
+     * <p>Copied from Guava's {@code HashCode#asLong()}.
+     *
+     * @throws IllegalStateException if {@code hashCode bytes < 8}
+     */
+    private static long asLong(byte[] hashCode) {
+        Preconditions.checkState(hashCode.length >= 8, "requires >= 8 bytes (it only has %s bytes)",
+                hashCode.length);
+        long retVal = (hashCode[0] & 0xFF);
+        for (int i = 1; i < Math.min(hashCode.length, 8); i++) {
+            retVal |= (hashCode[i] & 0xFFL) << (i * 8);
+        }
+        return retVal;
+    }
+}
diff --git a/service/src/com/android/car/telemetry/publisher/PublisherFactory.java b/service/src/com/android/car/telemetry/publisher/PublisherFactory.java
index 8d0975a..1009d25 100644
--- a/service/src/com/android/car/telemetry/publisher/PublisherFactory.java
+++ b/service/src/com/android/car/telemetry/publisher/PublisherFactory.java
@@ -16,39 +16,82 @@
 
 package com.android.car.telemetry.publisher;
 
+import android.content.SharedPreferences;
+import android.os.Handler;
+
 import com.android.car.CarPropertyService;
 import com.android.car.telemetry.TelemetryProto;
 
+import java.util.function.BiConsumer;
+
 /**
- * Factory class for Publishers. It's expected to have a single factory instance.
+ * Lazy factory class for Publishers. It's expected to have a single factory instance.
+ * Must be called from the telemetry thread.
+ *
+ * <p>It doesn't instantiate all the publishers right away, as in some cases some publishers are
+ * not needed.
  *
  * <p>Thread-safe.
  */
 public class PublisherFactory {
     private final Object mLock = new Object();
     private final CarPropertyService mCarPropertyService;
+    private final Handler mTelemetryHandler;
+    private final StatsManagerProxy mStatsManager;
+    private final SharedPreferences mSharedPreferences;
     private VehiclePropertyPublisher mVehiclePropertyPublisher;
+    private CarTelemetrydPublisher mCarTelemetrydPublisher;
+    private StatsPublisher mStatsPublisher;
 
-    public PublisherFactory(CarPropertyService carPropertyService) {
+    private BiConsumer<AbstractPublisher, Throwable> mFailureConsumer;
+
+    public PublisherFactory(
+            CarPropertyService carPropertyService,
+            Handler handler,
+            StatsManagerProxy statsManager,
+            SharedPreferences sharedPreferences) {
         mCarPropertyService = carPropertyService;
+        mTelemetryHandler = handler;
+        mStatsManager = statsManager;
+        mSharedPreferences = sharedPreferences;
     }
 
-    /** Returns publisher by given type. */
-    public AbstractPublisher getPublisher(
-            TelemetryProto.Publisher.PublisherCase type) {
+    /** Returns the publisher by given type. */
+    public AbstractPublisher getPublisher(TelemetryProto.Publisher.PublisherCase type) {
         // No need to optimize locks, as this method is infrequently called.
         synchronized (mLock) {
             switch (type.getNumber()) {
                 case TelemetryProto.Publisher.VEHICLE_PROPERTY_FIELD_NUMBER:
                     if (mVehiclePropertyPublisher == null) {
                         mVehiclePropertyPublisher = new VehiclePropertyPublisher(
-                                mCarPropertyService);
+                                mCarPropertyService, mFailureConsumer, mTelemetryHandler);
                     }
                     return mVehiclePropertyPublisher;
+                case TelemetryProto.Publisher.CARTELEMETRYD_FIELD_NUMBER:
+                    if (mCarTelemetrydPublisher == null) {
+                        mCarTelemetrydPublisher = new CarTelemetrydPublisher(
+                                mFailureConsumer, mTelemetryHandler);
+                    }
+                    return mCarTelemetrydPublisher;
+                case TelemetryProto.Publisher.STATS_FIELD_NUMBER:
+                    if (mStatsPublisher == null) {
+                        mStatsPublisher = new StatsPublisher(
+                                mFailureConsumer, mStatsManager, mSharedPreferences);
+                    }
+                    return mStatsPublisher;
                 default:
                     throw new IllegalArgumentException(
                             "Publisher type " + type + " is not supported");
             }
         }
     }
+
+    /**
+     * Sets the publisher failure consumer for all the publishers. This is expected to be called
+     * before {@link #getPublisher} method. This is not the best approach, but it suits for this
+     * case.
+     */
+    public void setFailureConsumer(BiConsumer<AbstractPublisher, Throwable> consumer) {
+        mFailureConsumer = consumer;
+    }
 }
diff --git a/service/src/com/android/car/telemetry/publisher/StatsManagerImpl.java b/service/src/com/android/car/telemetry/publisher/StatsManagerImpl.java
new file mode 100644
index 0000000..b184f28
--- /dev/null
+++ b/service/src/com/android/car/telemetry/publisher/StatsManagerImpl.java
@@ -0,0 +1,44 @@
+/*
+ * 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.publisher;
+
+import android.app.StatsManager;
+import android.app.StatsManager.StatsUnavailableException;
+
+/** Implementation for {@link StatsManagerProxy} */
+public class StatsManagerImpl implements StatsManagerProxy {
+    private final StatsManager mStatsManager;
+
+    public StatsManagerImpl(StatsManager statsManager) {
+        mStatsManager = statsManager;
+    }
+
+    @Override
+    public byte[] getReports(long configKey) throws StatsUnavailableException {
+        return mStatsManager.getReports(configKey);
+    }
+
+    @Override
+    public void addConfig(long configKey, byte[] data) throws StatsUnavailableException {
+        mStatsManager.addConfig(configKey, data);
+    }
+
+    @Override
+    public void removeConfig(long configKey) throws StatsUnavailableException {
+        mStatsManager.removeConfig(configKey);
+    }
+}
diff --git a/service/src/com/android/car/telemetry/publisher/StatsManagerProxy.java b/service/src/com/android/car/telemetry/publisher/StatsManagerProxy.java
new file mode 100644
index 0000000..046ac44
--- /dev/null
+++ b/service/src/com/android/car/telemetry/publisher/StatsManagerProxy.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.telemetry.publisher;
+
+import android.app.StatsManager;
+import android.app.StatsManager.StatsUnavailableException;
+
+/** Proxy for {@link StatsManager}, as it's marked as final and can't be used in tests. */
+public interface StatsManagerProxy {
+    /** See {@link StatsManager#getReports(long)}. */
+    byte[] getReports(long configKey) throws StatsUnavailableException;
+
+    /** See {@link StatsManager#addConfig(long, byte[])}. */
+    void addConfig(long configKey, byte[] data) throws StatsUnavailableException;
+
+    /** See {@link StatsManager#removeConfig(long)}. */
+    void removeConfig(long configKey) throws StatsUnavailableException;
+}
diff --git a/service/src/com/android/car/telemetry/publisher/StatsPublisher.java b/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
new file mode 100644
index 0000000..8d5f7ea
--- /dev/null
+++ b/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
@@ -0,0 +1,365 @@
+/*
+ * 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.publisher;
+
+import android.app.StatsManager.StatsUnavailableException;
+import android.content.SharedPreferences;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.util.LongSparseArray;
+import android.util.Slog;
+
+import com.android.car.CarLog;
+import com.android.car.telemetry.StatsLogProto;
+import com.android.car.telemetry.StatsdConfigProto;
+import com.android.car.telemetry.StatsdConfigProto.StatsdConfig;
+import com.android.car.telemetry.TelemetryProto;
+import com.android.car.telemetry.TelemetryProto.Publisher.PublisherCase;
+import com.android.car.telemetry.databroker.DataSubscriber;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiConsumer;
+
+/**
+ * Publisher for {@link TelemetryProto.StatsPublisher}.
+ *
+ * <p>The publisher adds subscriber configurations in StatsD and they persist between reboots and
+ * CarTelemetryService restarts. Please use {@link #removeAllDataSubscribers} to clean-up these
+ * configs from StatsD store.
+ */
+public class StatsPublisher extends AbstractPublisher {
+    // These IDs are used in StatsdConfig and ConfigMetricsReport.
+    @VisibleForTesting
+    static final int APP_START_MEMORY_STATE_CAPTURED_ATOM_MATCHER_ID = 1;
+    @VisibleForTesting
+    static final int APP_START_MEMORY_STATE_CAPTURED_EVENT_METRIC_ID = 2;
+
+    // The atom ids are from frameworks/proto_logging/stats/atoms.proto
+    @VisibleForTesting
+    static final int ATOM_APP_START_MEMORY_STATE_CAPTURED_ID = 55;
+
+    private static final Duration PULL_REPORTS_PERIOD = Duration.ofMinutes(10);
+
+    private static final String SHARED_PREF_CONFIG_KEY_PREFIX = "statsd-publisher-config-id-";
+    private static final String SHARED_PREF_CONFIG_VERSION_PREFIX =
+            "statsd-publisher-config-version-";
+
+    // TODO(b/197766340): remove unnecessary lock
+    private final Object mLock = new Object();
+
+    private final StatsManagerProxy mStatsManager;
+    private final SharedPreferences mSharedPreferences;
+    private final Handler mTelemetryHandler;
+
+    // True if the publisher is periodically pulling reports from StatsD.
+    private final AtomicBoolean mIsPullingReports = new AtomicBoolean(false);
+
+    /** Assign the method to {@link Runnable}, otherwise the handler fails to remove it. */
+    private final Runnable mPullReportsPeriodically = this::pullReportsPeriodically;
+
+    // LongSparseArray is memory optimized, but they can be bit slower for more
+    // than 100 items. We're expecting much less number of subscribers, so these data structures
+    // are ok.
+    // Maps config_key to the set of DataSubscriber.
+    @GuardedBy("mLock")
+    private final LongSparseArray<DataSubscriber> mConfigKeyToSubscribers = new LongSparseArray<>();
+
+    // TODO(b/198331078): Use telemetry thread
+    StatsPublisher(
+            BiConsumer<AbstractPublisher, Throwable> failureConsumer,
+            StatsManagerProxy statsManager,
+            SharedPreferences sharedPreferences) {
+        this(failureConsumer, statsManager, sharedPreferences, new Handler(Looper.myLooper()));
+    }
+
+    @VisibleForTesting
+    StatsPublisher(
+            BiConsumer<AbstractPublisher, Throwable> failureConsumer,
+            StatsManagerProxy statsManager,
+            SharedPreferences sharedPreferences,
+            Handler handler) {
+        super(failureConsumer);
+        mStatsManager = statsManager;
+        mSharedPreferences = sharedPreferences;
+        mTelemetryHandler = handler;
+    }
+
+    @Override
+    public void addDataSubscriber(DataSubscriber subscriber) {
+        TelemetryProto.Publisher publisherParam = subscriber.getPublisherParam();
+        Preconditions.checkArgument(
+                publisherParam.getPublisherCase() == PublisherCase.STATS,
+                "Subscribers only with StatsPublisher are supported by this class.");
+
+        synchronized (mLock) {
+            long configKey = addStatsConfigLocked(subscriber);
+            mConfigKeyToSubscribers.put(configKey, subscriber);
+        }
+
+        if (!mIsPullingReports.getAndSet(true)) {
+            mTelemetryHandler.postDelayed(mPullReportsPeriodically, PULL_REPORTS_PERIOD.toMillis());
+        }
+    }
+
+    private void processReport(long configKey, StatsLogProto.ConfigMetricsReportList report) {
+        // TODO(b/197269115): parse the report
+        Slog.i(CarLog.TAG_TELEMETRY, "Received reports: " + report.getReportsCount());
+        if (report.getReportsCount() > 0) {
+            PersistableBundle data = new PersistableBundle();
+            // TODO(b/197269115): parse the report
+            data.putInt("reportsCount", report.getReportsCount());
+            DataSubscriber subscriber = getSubscriberByConfigKey(configKey);
+            if (subscriber != null) {
+                subscriber.push(data);
+            }
+        }
+    }
+
+    private void pullReportsPeriodically() {
+        for (long configKey : getActiveConfigKeys()) {
+            try {
+                processReport(configKey, StatsLogProto.ConfigMetricsReportList.parseFrom(
+                        mStatsManager.getReports(configKey)));
+            } catch (StatsUnavailableException e) {
+                // If the StatsD is not available, retry in the next pullReportsPeriodically call.
+                break;
+            } catch (InvalidProtocolBufferException e) {
+                // This case should never happen.
+                Slog.w(CarLog.TAG_TELEMETRY,
+                        "Failed to parse report from statsd, configKey=" + configKey);
+            }
+        }
+
+        if (mIsPullingReports.get()) {
+            mTelemetryHandler.postDelayed(mPullReportsPeriodically, PULL_REPORTS_PERIOD.toMillis());
+        }
+    }
+
+    private List<Long> getActiveConfigKeys() {
+        ArrayList<Long> result = new ArrayList<>();
+        synchronized (mLock) {
+            mSharedPreferences.getAll().forEach((key, value) -> {
+                if (!key.startsWith(SHARED_PREF_CONFIG_KEY_PREFIX)) {
+                    return;
+                }
+                result.add((long) value);
+            });
+        }
+        return result;
+    }
+
+    /**
+     * Removes the subscriber from the publisher and removes StatsdConfig from StatsD service.
+     * If StatsdConfig is present in Statsd, it removes it even if the subscriber is not present
+     * in the publisher (it happens when subscriber was added before and CarTelemetryService was
+     * restarted and lost publisher state).
+     */
+    @Override
+    public void removeDataSubscriber(DataSubscriber subscriber) {
+        TelemetryProto.Publisher publisherParam = subscriber.getPublisherParam();
+        if (publisherParam.getPublisherCase() != PublisherCase.STATS) {
+            Slog.w(CarLog.TAG_TELEMETRY,
+                    "Expected STATS publisher, but received "
+                            + publisherParam.getPublisherCase().name());
+            return;
+        }
+        synchronized (mLock) {
+            long configKey = removeStatsConfigLocked(subscriber);
+            mConfigKeyToSubscribers.remove(configKey);
+        }
+
+        if (mConfigKeyToSubscribers.size() == 0) {
+            mIsPullingReports.set(false);
+            mTelemetryHandler.removeCallbacks(mPullReportsPeriodically);
+        }
+    }
+
+    /** Removes all the subscribers from the publisher removes StatsdConfigs from StatsD service. */
+    @Override
+    public void removeAllDataSubscribers() {
+        synchronized (mLock) {
+            SharedPreferences.Editor editor = mSharedPreferences.edit();
+            mSharedPreferences.getAll().forEach((key, value) -> {
+                if (!key.startsWith(SHARED_PREF_CONFIG_KEY_PREFIX)) {
+                    return;
+                }
+                long configKey = (long) value;
+                try {
+                    mStatsManager.removeConfig(configKey);
+                    String sharedPrefVersion = buildSharedPrefConfigVersionKey(configKey);
+                    editor.remove(key).remove(sharedPrefVersion);
+                } catch (StatsUnavailableException e) {
+                    Slog.w(CarLog.TAG_TELEMETRY, "Failed to remove config " + configKey
+                            + ". Ignoring the failure. Will retry removing again when"
+                            + " removeAllDataSubscribers() is called.", e);
+                    // If it cannot remove statsd config, it's less likely it can delete it even if
+                    // retry. So we will just ignore the failures. The next call of this method
+                    // will ry deleting StatsD configs again.
+                }
+            });
+            editor.apply();
+            mConfigKeyToSubscribers.clear();
+        }
+        mIsPullingReports.set(false);
+        mTelemetryHandler.removeCallbacks(mPullReportsPeriodically);
+    }
+
+    @Override
+    public boolean hasDataSubscriber(DataSubscriber subscriber) {
+        TelemetryProto.Publisher publisherParam = subscriber.getPublisherParam();
+        if (publisherParam.getPublisherCase() != PublisherCase.STATS) {
+            return false;
+        }
+        long configKey = buildConfigKey(subscriber);
+        synchronized (mLock) {
+            return mConfigKeyToSubscribers.indexOfKey(configKey) >= 0;
+        }
+    }
+
+    /** Returns a subscriber for the given statsd config key. Returns null if not found. */
+    private DataSubscriber getSubscriberByConfigKey(long configKey) {
+        synchronized (mLock) {
+            return mConfigKeyToSubscribers.get(configKey);
+        }
+    }
+
+    /**
+     * Returns the key for SharedPreferences to store/retrieve configKey associated with the
+     * subscriber.
+     */
+    private static String buildSharedPrefConfigKey(DataSubscriber subscriber) {
+        return SHARED_PREF_CONFIG_KEY_PREFIX + subscriber.getMetricsConfig().getName() + "-"
+                + subscriber.getSubscriber().getHandler();
+    }
+
+    /**
+     * Returns the key for SharedPreferences to store/retrieve {@link TelemetryProto.MetricsConfig}
+     * version associated with the configKey (which is generated per DataSubscriber).
+     */
+    private static String buildSharedPrefConfigVersionKey(long configKey) {
+        return SHARED_PREF_CONFIG_VERSION_PREFIX + configKey;
+    }
+
+    /**
+     * This method can be called even if StatsdConfig was added to StatsD service before. It stores
+     * previously added config_keys in the shared preferences and only updates StatsD when
+     * the MetricsConfig (of CarTelemetryService) has a new version.
+     */
+    @GuardedBy("mLock")
+    private long addStatsConfigLocked(DataSubscriber subscriber) {
+        long configKey = buildConfigKey(subscriber);
+        // Store MetricsConfig (of CarTelemetryService) version per handler_function.
+        String sharedPrefVersion = buildSharedPrefConfigVersionKey(configKey);
+        if (mSharedPreferences.contains(sharedPrefVersion)) {
+            int currentVersion = mSharedPreferences.getInt(sharedPrefVersion, 0);
+            if (currentVersion >= subscriber.getMetricsConfig().getVersion()) {
+                // It's trying to add current or older MetricsConfig version, just ignore it.
+                return configKey;
+            }  // if the subscriber's MetricsConfig version is newer, it will replace the old one.
+        }
+        String sharedPrefConfigKey = buildSharedPrefConfigKey(subscriber);
+        StatsdConfig config = buildStatsdConfig(subscriber, configKey);
+        try {
+            // It doesn't throw exception if the StatsdConfig is invalid. But it shouldn't happen,
+            // as we generate well-tested StatsdConfig in this service.
+            mStatsManager.addConfig(configKey, config.toByteArray());
+            mSharedPreferences.edit()
+                    .putInt(sharedPrefVersion, subscriber.getMetricsConfig().getVersion())
+                    .putLong(sharedPrefConfigKey, configKey)
+                    .apply();
+        } catch (StatsUnavailableException e) {
+            Slog.w(CarLog.TAG_TELEMETRY, "Failed to add config" + configKey, e);
+            // TODO(b/189143813): if StatsManager is not ready, retry N times and hard fail after
+            //                    by notifying DataBroker.
+            // We will notify the failure immediately, as we're expecting StatsManager to be stable.
+            notifyFailureConsumer(
+                    new IllegalStateException("Failed to add config " + configKey, e));
+        }
+        return configKey;
+    }
+
+    /** Removes StatsdConfig and returns configKey. */
+    @GuardedBy("mLock")
+    private long removeStatsConfigLocked(DataSubscriber subscriber) {
+        String sharedPrefConfigKey = buildSharedPrefConfigKey(subscriber);
+        long configKey = buildConfigKey(subscriber);
+        // Store MetricsConfig (of CarTelemetryService) version per handler_function.
+        String sharedPrefVersion = buildSharedPrefConfigVersionKey(configKey);
+        try {
+            mStatsManager.removeConfig(configKey);
+            mSharedPreferences.edit().remove(sharedPrefVersion).remove(sharedPrefConfigKey).apply();
+        } catch (StatsUnavailableException e) {
+            Slog.w(CarLog.TAG_TELEMETRY, "Failed to remove config " + configKey
+                    + ". Ignoring the failure. Will retry removing again when"
+                    + " removeAllDataSubscribers() is called.", e);
+            // If it cannot remove statsd config, it's less likely it can delete it even if we
+            // retry. So we will just ignore the failures. The next call of this method will
+            // try deleting StatsD configs again.
+        }
+        return configKey;
+    }
+
+    /**
+     * Builds StatsdConfig id (aka config_key) using subscriber handler name.
+     *
+     * <p>StatsD uses ConfigKey struct to uniquely identify StatsdConfigs. StatsD ConfigKey consists
+     * of two parts: client uid and config_key number. The StatsdConfig is added to StatsD from
+     * CarService - which has uid=1000. Currently there is no client under uid=1000 and there will
+     * not be config_key collision.
+     */
+    private static long buildConfigKey(DataSubscriber subscriber) {
+        // Not to be confused with statsd metric, this one is a global CarTelemetry metric name.
+        String metricConfigName = subscriber.getMetricsConfig().getName();
+        String handlerFnName = subscriber.getSubscriber().getHandler();
+        return HashUtils.sha256(metricConfigName + "-" + handlerFnName);
+    }
+
+    /** Builds {@link StatsdConfig} proto for given subscriber. */
+    @VisibleForTesting
+    static StatsdConfig buildStatsdConfig(DataSubscriber subscriber, long configId) {
+        TelemetryProto.StatsPublisher.SystemMetric metric =
+                subscriber.getPublisherParam().getStats().getSystemMetric();
+        if (metric == TelemetryProto.StatsPublisher.SystemMetric.APP_START_MEMORY_STATE_CAPTURED) {
+            return StatsdConfig.newBuilder()
+                    // This id is not used in StatsD, but let's make it the same as config_key
+                    // just in case.
+                    .setId(configId)
+                    .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
+                            // The id must be unique within StatsdConfig/matchers
+                            .setId(APP_START_MEMORY_STATE_CAPTURED_ATOM_MATCHER_ID)
+                            .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
+                                    .setAtomId(ATOM_APP_START_MEMORY_STATE_CAPTURED_ID)))
+                    .addEventMetric(StatsdConfigProto.EventMetric.newBuilder()
+                            // The id must be unique within StatsdConfig/metrics
+                            .setId(APP_START_MEMORY_STATE_CAPTURED_EVENT_METRIC_ID)
+                            .setWhat(APP_START_MEMORY_STATE_CAPTURED_ATOM_MATCHER_ID))
+                    .addAllowedLogSource("AID_SYSTEM")
+                    .build();
+        } else {
+            throw new IllegalArgumentException("Unsupported metric " + metric.name());
+        }
+    }
+}
diff --git a/service/src/com/android/car/telemetry/publisher/VehiclePropertyPublisher.java b/service/src/com/android/car/telemetry/publisher/VehiclePropertyPublisher.java
index ed0b19b..dbfc002 100644
--- a/service/src/com/android/car/telemetry/publisher/VehiclePropertyPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/VehiclePropertyPublisher.java
@@ -20,24 +20,28 @@
 import android.car.hardware.CarPropertyConfig;
 import android.car.hardware.property.CarPropertyEvent;
 import android.car.hardware.property.ICarPropertyEventListener;
-import android.os.Bundle;
+import android.os.Handler;
+import android.os.PersistableBundle;
 import android.os.RemoteException;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.car.CarLog;
 import com.android.car.CarPropertyService;
 import com.android.car.telemetry.TelemetryProto;
+import com.android.car.telemetry.TelemetryProto.Publisher.PublisherCase;
 import com.android.car.telemetry.databroker.DataSubscriber;
 import com.android.internal.util.Preconditions;
 
-import java.util.Collection;
 import java.util.List;
+import java.util.function.BiConsumer;
 
 /**
  * Publisher for Vehicle Property changes, aka {@code CarPropertyService}.
  *
- * <p>TODO(b/187525360): Add car property listener logic
+ * <p> When a subscriber is added, it registers a car property change listener for the
+ * property id of the subscriber and starts pushing the change events to the subscriber.
  */
 public class VehiclePropertyPublisher extends AbstractPublisher {
     private static final boolean DEBUG = false;  // STOPSHIP if true
@@ -46,8 +50,18 @@
     public static final String CAR_PROPERTY_EVENT_KEY = "car_property_event";
 
     private final CarPropertyService mCarPropertyService;
+    private final Handler mTelemetryHandler;
+
+    // The class only reads, no need to synchronize this object.
+    // Maps property_id to CarPropertyConfig.
     private final SparseArray<CarPropertyConfig> mCarPropertyList;
 
+    // SparseArray and ArraySet are memory optimized, but they can be bit slower for more
+    // than 100 items. We're expecting much less number of subscribers, so these DS are ok.
+    // Maps property_id to the set of DataSubscriber.
+    private final SparseArray<ArraySet<DataSubscriber>> mCarPropertyToSubscribers =
+            new SparseArray<>();
+
     private final ICarPropertyEventListener mCarPropertyEventListener =
             new ICarPropertyEventListener.Stub() {
                 @Override
@@ -62,17 +76,21 @@
                 }
             };
 
-    public VehiclePropertyPublisher(CarPropertyService carPropertyService) {
+    public VehiclePropertyPublisher(CarPropertyService carPropertyService,
+            BiConsumer<AbstractPublisher, Throwable> failureConsumer, Handler handler) {
+        super(failureConsumer);
         mCarPropertyService = carPropertyService;
+        mTelemetryHandler = handler;
         // Load car property list once, as the list doesn't change runtime.
-        mCarPropertyList = new SparseArray<>();
-        for (CarPropertyConfig property : mCarPropertyService.getPropertyList()) {
+        List<CarPropertyConfig> propertyList = mCarPropertyService.getPropertyList();
+        mCarPropertyList = new SparseArray<>(propertyList.size());
+        for (CarPropertyConfig property : propertyList) {
             mCarPropertyList.append(property.getPropertyId(), property);
         }
     }
 
     @Override
-    protected void onDataSubscriberAdded(DataSubscriber subscriber) {
+    public void addDataSubscriber(DataSubscriber subscriber) {
         TelemetryProto.Publisher publisherParam = subscriber.getPublisherParam();
         Preconditions.checkArgument(
                 publisherParam.getPublisherCase()
@@ -88,31 +106,80 @@
                         || config.getAccess()
                         == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
                 "No access. Cannot read " + VehiclePropertyIds.toString(propertyId) + ".");
-        mCarPropertyService.registerListener(
-                propertyId,
-                publisherParam.getVehicleProperty().getReadRate(),
-                mCarPropertyEventListener);
+
+        ArraySet<DataSubscriber> subscribers = mCarPropertyToSubscribers.get(propertyId);
+        if (subscribers == null) {
+            subscribers = new ArraySet<>();
+            mCarPropertyToSubscribers.put(propertyId, subscribers);
+            // Register the listener only once per propertyId.
+            mCarPropertyService.registerListener(
+                    propertyId,
+                    publisherParam.getVehicleProperty().getReadRate(),
+                    mCarPropertyEventListener);
+        }
+        subscribers.add(subscriber);
     }
 
     @Override
-    protected void onDataSubscribersRemoved(Collection<DataSubscriber> subscribers) {
-        // TODO(b/190230611): Remove car property listener
+    public void removeDataSubscriber(DataSubscriber subscriber) {
+        TelemetryProto.Publisher publisherParam = subscriber.getPublisherParam();
+        if (publisherParam.getPublisherCase() != PublisherCase.VEHICLE_PROPERTY) {
+            Slog.w(CarLog.TAG_TELEMETRY,
+                    "Expected VEHICLE_PROPERTY publisher, but received "
+                            + publisherParam.getPublisherCase().name());
+            return;
+        }
+        int propertyId = publisherParam.getVehicleProperty().getVehiclePropertyId();
+
+        ArraySet<DataSubscriber> subscribers = mCarPropertyToSubscribers.get(propertyId);
+        if (subscribers == null) {
+            return;
+        }
+        subscribers.remove(subscriber);
+        if (subscribers.isEmpty()) {
+            mCarPropertyToSubscribers.remove(propertyId);
+            // Doesn't throw exception as listener is not null. mCarPropertyService and
+            // local mCarPropertyToSubscribers will not get out of sync.
+            mCarPropertyService.unregisterListener(propertyId, mCarPropertyEventListener);
+        }
+    }
+
+    @Override
+    public void removeAllDataSubscribers() {
+        for (int i = 0; i < mCarPropertyToSubscribers.size(); i++) {
+            int propertyId = mCarPropertyToSubscribers.keyAt(i);
+            // Doesn't throw exception as listener is not null. mCarPropertyService and
+            // local mCarPropertyToSubscribers will not get out of sync.
+            mCarPropertyService.unregisterListener(propertyId, mCarPropertyEventListener);
+        }
+        mCarPropertyToSubscribers.clear();
+    }
+
+    @Override
+    public boolean hasDataSubscriber(DataSubscriber subscriber) {
+        TelemetryProto.Publisher publisherParam = subscriber.getPublisherParam();
+        if (publisherParam.getPublisherCase() != PublisherCase.VEHICLE_PROPERTY) {
+            return false;
+        }
+        int propertyId = publisherParam.getVehicleProperty().getVehiclePropertyId();
+        ArraySet<DataSubscriber> subscribers = mCarPropertyToSubscribers.get(propertyId);
+        return subscribers != null && subscribers.contains(subscriber);
     }
 
     /**
-     * Called when publisher receives new events. It's called on CarPropertyService's worker
-     * thread.
+     * Called when publisher receives new event. It's executed on a CarPropertyService's
+     * worker thread.
      */
     private void onVehicleEvent(CarPropertyEvent event) {
-        Bundle bundle = new Bundle();
-        bundle.putParcelable(CAR_PROPERTY_EVENT_KEY, event);
-        for (DataSubscriber subscriber : getDataSubscribers()) {
-            TelemetryProto.Publisher publisherParam = subscriber.getPublisherParam();
-            if (event.getCarPropertyValue().getPropertyId()
-                    != publisherParam.getVehicleProperty().getVehiclePropertyId()) {
-                continue;
+        // move the work from CarPropertyService's worker thread to the telemetry thread
+        mTelemetryHandler.post(() -> {
+            // TODO(b/197269115): convert CarPropertyEvent into PersistableBundle
+            PersistableBundle bundle = new PersistableBundle();
+            ArraySet<DataSubscriber> subscribersClone = new ArraySet<>(
+                    mCarPropertyToSubscribers.get(event.getCarPropertyValue().getPropertyId()));
+            for (DataSubscriber subscriber : subscribersClone) {
+                subscriber.push(bundle);
             }
-            subscriber.push(bundle);
-        }
+        });
     }
 }
diff --git a/service/src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutor.aidl b/service/src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutor.aidl
new file mode 100644
index 0000000..c768161
--- /dev/null
+++ b/service/src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutor.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.scriptexecutorinterface;
+
+import android.os.PersistableBundle;
+import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutorListener;
+
+/**
+ * An internal API provided by isolated Script Executor process
+ * for executing Lua scripts in a sandboxed environment
+ */
+interface IScriptExecutor {
+  /**
+   * Executes a specified function in provided Lua script with given input arguments.
+   *
+   * @param scriptBody complete body of Lua script that also contains the function to be invoked
+   * @param functionName the name of the function to execute
+   * @param publishedData input data provided by the source which the function handles
+   * @param savedState key-value pairs preserved from the previous invocation of the function
+   * @param listener callback for the sandboxed environent to report back script execution results, errors, and logs
+   */
+  void invokeScript(String scriptBody,
+                    String functionName,
+                    in PersistableBundle publishedData,
+                    in @nullable PersistableBundle savedState,
+                    in IScriptExecutorListener listener);
+}
diff --git a/service/src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutorConstants.aidl b/service/src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutorConstants.aidl
new file mode 100644
index 0000000..52f4cbe
--- /dev/null
+++ b/service/src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutorConstants.aidl
@@ -0,0 +1,45 @@
+/*
+ * 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.scriptexecutorinterface;
+
+// TODO(b/194324369): Investigate if we could combine it
+// with IScriptExecutorListener.aidl
+
+interface IScriptExecutorConstants {
+  /**
+   * Default error type.
+   */
+  const int ERROR_TYPE_UNSPECIFIED = 0;
+
+  /**
+   * Used when an error occurs in the ScriptExecutor code.
+   */
+  const int ERROR_TYPE_SCRIPT_EXECUTOR_ERROR = 1;
+
+  /**
+   * Used when an error occurs while executing the Lua script (such as
+   * errors returned by lua_pcall)
+   */
+  const int ERROR_TYPE_LUA_RUNTIME_ERROR = 2;
+
+  /**
+   * Used to log errors by a script itself, for instance, when a script received
+   * inputs outside of expected range.
+   */
+  const int ERROR_TYPE_LUA_SCRIPT_ERROR = 3;
+}
+
diff --git a/service/src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutorListener.aidl b/service/src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutorListener.aidl
new file mode 100644
index 0000000..dc64732
--- /dev/null
+++ b/service/src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutorListener.aidl
@@ -0,0 +1,56 @@
+/*
+ * 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.scriptexecutorinterface;
+
+import android.os.PersistableBundle;
+
+/**
+ * Listener for {@code IScriptExecutor#invokeScript}.
+ *
+ * An invocation of a script by Script Executor will result in a call of only one
+ * of the three methods below. If a script fully completes its objective, onScriptFinished
+ * is called. If a script's invocation completes normally, onSuccess is called.
+ * onError is called if any error happens before or during script execution and we
+ * should abandon this run of the script.
+ */
+interface IScriptExecutorListener {
+  /**
+   * Called by ScriptExecutor when the script declares itself as "finished".
+   *
+   * @param result final results of the script that will be uploaded.
+   */
+  void onScriptFinished(in PersistableBundle result);
+
+  /**
+   * Called by ScriptExecutor when a function completes successfully and also provides
+   * optional state that the script wants CarTelemetryService to persist.
+   *
+   * @param stateToPersist key-value pairs to persist
+   */
+  void onSuccess(in @nullable PersistableBundle stateToPersist);
+
+  /**
+   * Called by ScriptExecutor to report errors that prevented the script
+   * from running or completing execution successfully.
+   *
+   * @param errorType type of the error message as defined in this aidl file.
+   * @param messsage the human-readable message containing information helpful for analysis or debugging.
+   * @param stackTrace the stack trace of the error if available.
+   */
+  void onError(int errorType, String message, @nullable String stackTrace);
+}
+
diff --git a/service/src/com/android/car/telemetry/systemmonitor/SystemMonitor.java b/service/src/com/android/car/telemetry/systemmonitor/SystemMonitor.java
index 65bc45b..0dc5563 100644
--- a/service/src/com/android/car/telemetry/systemmonitor/SystemMonitor.java
+++ b/service/src/com/android/car/telemetry/systemmonitor/SystemMonitor.java
@@ -16,13 +16,45 @@
 
 package com.android.car.telemetry.systemmonitor;
 
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityManager.MemoryInfo;
+import android.content.Context;
+import android.os.Handler;
+import android.util.Slog;
+
+import com.android.car.CarLog;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+
 /**
  * SystemMonitor monitors system states and report to listeners when there are
  * important changes.
+ * All methods in this class should be invoked from the telemetry thread.
  */
 public class SystemMonitor {
 
-    private SystemMonitorCallback mCallback;
+    private static final int NUM_LOADAVG_VALS = 3;
+    private static final float HI_CPU_LOAD_PER_CORE_BASE_LEVEL = 1.0f;
+    private static final float MED_CPU_LOAD_PER_CORE_BASE_LEVEL = 0.5f;
+    private static final float HI_MEM_LOAD_BASE_LEVEL = 0.95f;
+    private static final float MED_MEM_LOAD_BASE_LEVEL = 0.80f;
+    private static final String LOADAVG_PATH = "/proc/loadavg";
+
+    private static final int POLL_INTERVAL_MILLIS = 60000;
+
+    private final Handler mTelemetryHandler;
+
+    private final Context mContext;
+    private final ActivityManager mActivityManager;
+    private final String mLoadavgPath;
+    private final Runnable mSystemLoadRunnable = this::getSystemLoadRepeated;
+
+    @Nullable private SystemMonitorCallback mCallback;
+    private boolean mSystemMonitorRunning = false;
 
     /**
      * Interface for receiving notifications about system monitor changes.
@@ -37,11 +69,162 @@
     }
 
     /**
-     * Sets the callback to notify of system state changes.
+     * Creates a SystemMonitor instance set with default loadavg path.
+     *
+     * @param context the context this is running in.
+     * @param workerHandler a handler for running monitoring jobs.
+     * @return SystemMonitor instance.
+     */
+    public static SystemMonitor create(Context context, Handler workerHandler) {
+        return new SystemMonitor(context, workerHandler, LOADAVG_PATH);
+    }
+
+    @VisibleForTesting
+    SystemMonitor(Context context, Handler telemetryHandler, String loadavgPath) {
+        mContext = context;
+        mTelemetryHandler = telemetryHandler;
+        mActivityManager = (ActivityManager)
+                mContext.getSystemService(Context.ACTIVITY_SERVICE);
+        mLoadavgPath = loadavgPath;
+    }
+
+    /**
+     * Sets the {@link SystemMonitorCallback} to notify of system state changes.
      *
      * @param callback the callback to nofify state changes on.
      */
     public void setSystemMonitorCallback(SystemMonitorCallback callback) {
         mCallback = callback;
+        if (!mSystemMonitorRunning) {
+            startSystemLoadMonitoring();
+        }
+    }
+
+    /**
+     * Unsets the {@link SystemMonitorCallback}.
+     */
+    public void unsetSystemMonitorCallback() {
+        mTelemetryHandler.removeCallbacks(mSystemLoadRunnable);
+        mSystemMonitorRunning = false;
+        mCallback = null;
+    }
+
+    /**
+     * Gets the loadavg data from /proc/loadavg, getting the first 3 averages,
+     * which are 1-min, 5-min and 15-min moving averages respectively.
+     *
+     * Requires Selinux permissions 'open', 'read, 'getattr' to proc_loadavg,
+     * which is set in Car/car_product/sepolicy/private/carservice_app.te.
+     *
+     * @return the {@link CpuLoadavg}.
+     */
+    @VisibleForTesting
+    @Nullable
+    CpuLoadavg getCpuLoad() {
+        try (BufferedReader reader = new BufferedReader(new FileReader(mLoadavgPath))) {
+            String line = reader.readLine();
+            String[] vals = line.split("\\s+", NUM_LOADAVG_VALS + 1);
+            if (vals.length < NUM_LOADAVG_VALS) {
+                Slog.w(CarLog.TAG_TELEMETRY, "Loadavg wrong format");
+                return null;
+            }
+            CpuLoadavg cpuLoadavg = new CpuLoadavg();
+            cpuLoadavg.mOneMinuteVal = Float.parseFloat(vals[0]);
+            cpuLoadavg.mFiveMinutesVal = Float.parseFloat(vals[1]);
+            cpuLoadavg.mFifteenMinutesVal = Float.parseFloat(vals[2]);
+            return cpuLoadavg;
+        } catch (IOException | NumberFormatException ex) {
+            Slog.w(CarLog.TAG_TELEMETRY, "Failed to read loadavg file.", ex);
+            return null;
+        }
+    }
+
+    /**
+     * Gets the {@link ActivityManager.MemoryInfo} for system memory pressure.
+     *
+     * Of the MemoryInfo fields, we will only be using availMem and totalMem,
+     * since lowMemory and threshold are likely deprecated.
+     *
+     * @return {@link MemoryInfo} for the system.
+     */
+    private MemoryInfo getMemoryLoad() {
+        MemoryInfo mi = new ActivityManager.MemoryInfo();
+        mActivityManager.getMemoryInfo(mi);
+        return mi;
+    }
+
+    /**
+     * Sets the CPU usage level for a {@link SystemMonitorEvent}.
+     *
+     * @param event the {@link SystemMonitorEvent}.
+     * @param cpuLoadPerCore the CPU load average per CPU core.
+     */
+    @VisibleForTesting
+    void setEventCpuUsageLevel(SystemMonitorEvent event, double cpuLoadPerCore) {
+        if (cpuLoadPerCore > HI_CPU_LOAD_PER_CORE_BASE_LEVEL) {
+            event.setCpuUsageLevel(SystemMonitorEvent.USAGE_LEVEL_HI);
+        } else if (cpuLoadPerCore > MED_CPU_LOAD_PER_CORE_BASE_LEVEL
+                   && cpuLoadPerCore <= HI_CPU_LOAD_PER_CORE_BASE_LEVEL) {
+            event.setCpuUsageLevel(SystemMonitorEvent.USAGE_LEVEL_MED);
+        } else {
+            event.setCpuUsageLevel(SystemMonitorEvent.USAGE_LEVEL_LOW);
+        }
+    }
+
+    /**
+     * Sets the memory usage level for a {@link SystemMonitorEvent}.
+     *
+     * @param event the {@link SystemMonitorEvent}.
+     * @param memLoadRatio ratio of used memory to total memory.
+     */
+    @VisibleForTesting
+    void setEventMemUsageLevel(SystemMonitorEvent event, double memLoadRatio) {
+        if (memLoadRatio > HI_MEM_LOAD_BASE_LEVEL) {
+            event.setMemoryUsageLevel(SystemMonitorEvent.USAGE_LEVEL_HI);
+        } else if (memLoadRatio > MED_MEM_LOAD_BASE_LEVEL
+                   && memLoadRatio <= HI_MEM_LOAD_BASE_LEVEL) {
+            event.setMemoryUsageLevel(SystemMonitorEvent.USAGE_LEVEL_MED);
+        } else {
+            event.setMemoryUsageLevel(SystemMonitorEvent.USAGE_LEVEL_LOW);
+        }
+    }
+
+    /**
+     * The Runnable to repeatedly getting system load data with some interval.
+     */
+    private void getSystemLoadRepeated() {
+        try {
+            CpuLoadavg cpuLoadAvg = getCpuLoad();
+            if (cpuLoadAvg == null) {
+                return;
+            }
+            int numProcessors = Runtime.getRuntime().availableProcessors();
+
+            MemoryInfo memInfo = getMemoryLoad();
+
+            SystemMonitorEvent event = new SystemMonitorEvent();
+            setEventCpuUsageLevel(event, cpuLoadAvg.mOneMinuteVal / numProcessors);
+            setEventMemUsageLevel(event, 1 - memInfo.availMem / memInfo.totalMem);
+
+            mCallback.onSystemMonitorEvent(event);
+        } finally {
+            if (mSystemMonitorRunning) {
+                mTelemetryHandler.postDelayed(mSystemLoadRunnable, POLL_INTERVAL_MILLIS);
+            }
+        }
+    }
+
+    /**
+     * Starts system load monitoring.
+     */
+    private void startSystemLoadMonitoring() {
+        mTelemetryHandler.post(mSystemLoadRunnable);
+        mSystemMonitorRunning = true;
+    }
+
+    static final class CpuLoadavg {
+        float mOneMinuteVal;
+        float mFiveMinutesVal;
+        float mFifteenMinutesVal;
     }
 }
diff --git a/service/src/com/android/car/user/AppLifecycleListener.java b/service/src/com/android/car/user/AppLifecycleListener.java
new file mode 100644
index 0000000..153ca85
--- /dev/null
+++ b/service/src/com/android/car/user/AppLifecycleListener.java
@@ -0,0 +1,77 @@
+/*
+ * 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.user;
+
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+
+import com.android.car.CarLog;
+import com.android.internal.os.IResultReceiver;
+import com.android.server.utils.Slogf;
+
+import java.io.PrintWriter;
+
+/**
+ * Helper DTO to hold info about an app-based {@code UserLifecycleListener}
+ */
+final class AppLifecycleListener {
+
+    private static final String TAG = CarLog.tagFor(AppLifecycleListener.class);
+
+    private final DeathRecipient mDeathRecipient;
+
+    public final int uid;
+    public final String packageName;
+    public final IResultReceiver receiver;
+
+    AppLifecycleListener(int uid, String packageName, IResultReceiver receiver,
+            BinderDeathCallback binderDeathCallback) {
+        this.uid = uid;
+        this.packageName = packageName;
+        this.receiver = receiver;
+
+        mDeathRecipient = () -> binderDeathCallback.onBinderDeath(this);
+        Slogf.v(TAG, "linking death recipient %s", mDeathRecipient);
+        try {
+            receiver.asBinder().linkToDeath(mDeathRecipient, /* flags= */ 0);
+        } catch (RemoteException e) {
+            Slogf.wtf(TAG, "Cannot listen to death of %s", mDeathRecipient);
+        }
+    }
+
+    void onDestroy() {
+        Slogf.v(TAG, "onDestroy(): unlinking death recipient %s", mDeathRecipient);
+        receiver.asBinder().unlinkToDeath(mDeathRecipient, /* flags= */ 0);
+    }
+
+    void dump(PrintWriter writer) {
+        writer.printf("uid=%d, pkg=%s\n", uid, packageName);
+    }
+
+    String toShortString() {
+        return uid + "-" + packageName;
+    }
+
+    @Override
+    public String toString() {
+        return "AppLifecycleListener[uid=" + uid + ", pkg=" + packageName + "]";
+    }
+
+    interface BinderDeathCallback {
+        void onBinderDeath(AppLifecycleListener listener);
+    }
+}
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index 1fde509..00cb893 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -77,6 +77,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.Trace;
@@ -85,11 +86,11 @@
 import android.provider.Settings;
 import android.sysprop.CarProperties;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.EventLog;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.TimingsTraceLog;
 import android.view.Display;
@@ -195,16 +196,19 @@
     private final Handler mHandler;
 
     /**
-     * List of listeners to be notified on new user activities events.
-     * This collection should be accessed and manipulated by mHandlerThread only.
+     * Internal listeners to be notified on new user activities events.
+     *
+     * <p>This collection should be accessed and manipulated by {@code mHandlerThread} only.
      */
     private final List<UserLifecycleListener> mUserLifecycleListeners = new ArrayList<>();
 
     /**
-     * List of lifecycle listeners by uid.
-     * This collection should be accessed and manipulated by mHandlerThread only.
+     * App listeners to be notified on new user activities events.
+     *
+     * <p>This collection should be accessed and manipulated by {@code mHandlerThread} only.
      */
-    private final SparseArray<IResultReceiver> mAppLifecycleListeners = new SparseArray<>();
+    private final ArrayMap<IBinder, AppLifecycleListener> mAppLifecycleListeners =
+            new ArrayMap<>();
 
     /**
      * User Id for the user switch in process, if any.
@@ -359,8 +363,7 @@
         checkHasDumpPermissionGranted("dump()");
 
         writer.println("*CarUserService*");
-        String indent = "  ";
-        handleDumpListeners(writer, indent);
+        handleDumpListeners(writer);
         writer.printf("User switch UI receiver %s\n", mUserSwitchUiReceiver);
         synchronized (mLockUser) {
             writer.println("User0Unlocked: " + mUser0Unlocked);
@@ -380,9 +383,10 @@
         List<UserInfo> allDrivers = getAllDrivers();
         int driversSize = allDrivers.size();
         writer.println("NumberOfDrivers: " + driversSize);
+        writer.increaseIndent();
         for (int i = 0; i < driversSize; i++) {
             int driverId = allDrivers.get(i).id;
-            writer.print(indent + "#" + i + ": id=" + driverId);
+            writer.printf("#%d: id=%d", i, driverId);
             List<UserInfo> passengers = getPassengers(driverId);
             int passengersSize = passengers.size();
             writer.print(" NumberPassengers: " + passengersSize);
@@ -398,38 +402,42 @@
             }
             writer.println();
         }
+        writer.decreaseIndent();
         writer.printf("EnablePassengerSupport: %s\n", mEnablePassengerSupport);
         writer.printf("User HAL timeout: %dms\n",  mHalTimeoutMs);
         writer.printf("Initial user: %s\n", mInitialUser);
 
         writer.println("Relevant overlayable properties");
         Resources res = mContext.getResources();
-        writer.printf("%sowner_name=%s\n", indent,
-                res.getString(com.android.internal.R.string.owner_name));
-        writer.printf("%sdefault_guest_name=%s\n", indent,
-                res.getString(R.string.default_guest_name));
+        writer.increaseIndent();
+        writer.printf("owner_name=%s\n", res.getString(com.android.internal.R.string.owner_name));
+        writer.printf("default_guest_name=%s\n", res.getString(R.string.default_guest_name));
+        writer.decreaseIndent();
         writer.printf("User switch in process=%d\n", mUserIdForUserSwitchInProcess);
         writer.printf("Request Id for the user switch in process=%d\n ",
                     mRequestIdForUserSwitchInProcess);
         writer.printf("System UI package name=%s\n", getSystemUiPackageName());
 
         writer.println("Relevant Global settings");
-        dumpGlobalProperty(writer, indent, CarSettings.Global.LAST_ACTIVE_USER_ID);
-        dumpGlobalProperty(writer, indent, CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID);
+        writer.increaseIndent();
+        dumpGlobalProperty(writer, CarSettings.Global.LAST_ACTIVE_USER_ID);
+        dumpGlobalProperty(writer, CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID);
+        writer.decreaseIndent();
 
         mInitialUserSetter.dump(writer);
     }
 
-    private void dumpGlobalProperty(PrintWriter writer, String indent, String property) {
+    private void dumpGlobalProperty(IndentingPrintWriter writer, String property) {
         String value = Settings.Global.getString(mContext.getContentResolver(), property);
-        writer.printf("%s%s=%s\n", indent, property, value);
+        writer.printf("%s=%s\n", property, value);
     }
 
-    private void handleDumpListeners(@NonNull PrintWriter writer, String indent) {
+    private void handleDumpListeners(IndentingPrintWriter writer) {
+        writer.increaseIndent();
         CountDownLatch latch = new CountDownLatch(1);
         mHandler.post(() -> {
             handleDumpServiceLifecycleListeners(writer);
-            handleDumpAppLifecycleListeners(writer, indent);
+            handleDumpAppLifecycleListeners(writer);
             latch.countDown();
         });
         int timeout = 5;
@@ -442,9 +450,10 @@
             Thread.currentThread().interrupt();
             writer.println("Interrupted waiting for handler thread to dump app and user listeners");
         }
+        writer.decreaseIndent();
     }
 
-    private void handleDumpServiceLifecycleListeners(@NonNull PrintWriter writer) {
+    private void handleDumpServiceLifecycleListeners(PrintWriter writer) {
         if (mUserLifecycleListeners.isEmpty()) {
             writer.println("No lifecycle listeners for internal services");
             return;
@@ -452,24 +461,24 @@
         int size = mUserLifecycleListeners.size();
         writer.printf("%d lifecycle listener%s for services\n", size, size == 1 ? "" : "s");
         String indent = "  ";
-        for (UserLifecycleListener listener : mUserLifecycleListeners) {
+        for (int i = 0; i < size; i++) {
+            UserLifecycleListener listener = mUserLifecycleListeners.get(i);
             writer.printf("%s%s\n", indent, FunctionalUtils.getLambdaName(listener));
         }
     }
 
-    private void handleDumpAppLifecycleListeners(@NonNull PrintWriter writer, String indent) {
+    private void handleDumpAppLifecycleListeners(IndentingPrintWriter writer) {
         int size = mAppLifecycleListeners.size();
         if (size == 0) {
             writer.println("No lifecycle listeners for apps");
             return;
         }
-        writer.printf("%d lifecycle listener%s for apps \n", size, size == 1 ? "" : "s");
+        writer.printf("%d lifecycle listener%s for apps\n", size, size == 1 ? "" : "s");
+        writer.increaseIndent();
         for (int i = 0; i < size; i++) {
-            int uid = mAppLifecycleListeners.keyAt(i);
-            IResultReceiver listener = mAppLifecycleListeners.valueAt(i);
-            writer.printf("%suid: %d listener: %s\n", indent, uid,
-                    FunctionalUtils.getLambdaName(listener));
+            mAppLifecycleListeners.valueAt(i).dump(writer);
         }
+        writer.decreaseIndent();
     }
 
     /**
@@ -684,30 +693,45 @@
     }
 
     @Override
-    public void setLifecycleListenerForUid(IResultReceiver listener) {
+    public void setLifecycleListenerForApp(String packageName, IResultReceiver receiver) {
         int uid = Binder.getCallingUid();
-        EventLog.writeEvent(EventLogTags.CAR_USER_SVC_SET_LIFECYCLE_LISTENER, uid);
-        checkInteractAcrossUsersPermission("setLifecycleListenerForUid" + uid);
+        EventLog.writeEvent(EventLogTags.CAR_USER_SVC_SET_LIFECYCLE_LISTENER, uid, packageName);
+        checkInteractAcrossUsersPermission("setLifecycleListenerForApp-" + uid + "-" + packageName);
 
-        try {
-            listener.asBinder().linkToDeath(() -> onListenerDeath(uid), 0);
-        } catch (RemoteException e) {
-            Slog.wtf(TAG, "Cannot listen to death of " + uid);
-        }
-        mHandler.post(() -> mAppLifecycleListeners.append(uid, listener));
+        IBinder receiverBinder = receiver.asBinder();
+        AppLifecycleListener listener = new AppLifecycleListener(uid, packageName, receiver,
+                (l) -> onListenerDeath(l));
+        Slogf.d(TAG, "Adding %s (using binder %s)", listener, receiverBinder);
+        mHandler.post(() -> mAppLifecycleListeners.put(receiverBinder, listener));
     }
 
-    private void onListenerDeath(int uid) {
-        Slog.i(TAG, "Removing listeners for uid " + uid + " on binder death");
-        mHandler.post(() -> mAppLifecycleListeners.remove(uid));
+    private void onListenerDeath(AppLifecycleListener listener) {
+        Slogf.i(TAG, "Removing listener %s on binder death", listener);
+        mHandler.post(() -> mAppLifecycleListeners.remove(listener.receiver.asBinder()));
     }
 
     @Override
-    public void resetLifecycleListenerForUid() {
+    public void resetLifecycleListenerForApp(IResultReceiver receiver) {
         int uid = Binder.getCallingUid();
-        EventLog.writeEvent(EventLogTags.CAR_USER_SVC_RESET_LIFECYCLE_LISTENER, uid);
-        checkInteractAcrossUsersPermission("resetLifecycleListenerForUid-" + uid);
-        mHandler.post(() -> mAppLifecycleListeners.remove(uid));
+        checkInteractAcrossUsersPermission("resetLifecycleListenerForApp-" + uid);
+        IBinder receiverBinder = receiver.asBinder();
+        mHandler.post(() -> {
+            AppLifecycleListener listener = mAppLifecycleListeners.get(receiverBinder);
+            if (listener == null) {
+                Slogf.e(TAG, "resetLifecycleListenerForApp(uid=%d): no listener for receiver", uid);
+                return;
+            }
+            if (listener.uid != uid) {
+                Slogf.e(TAG, "resetLifecycleListenerForApp(): uid mismatch (called by %d) for "
+                        + "listener %s", uid, listener);
+            }
+            EventLog.writeEvent(EventLogTags.CAR_USER_SVC_RESET_LIFECYCLE_LISTENER, uid,
+                    listener.packageName);
+            Slogf.d(TAG, "Removing %s (using binder %s)", listener, receiverBinder);
+            mAppLifecycleListeners.remove(receiverBinder);
+
+            listener.onDestroy();
+        });
     }
 
     /**
@@ -2102,23 +2126,17 @@
     private void handleNotifyAppUserLifecycleListeners(UserLifecycleEvent event) {
         int listenersSize = mAppLifecycleListeners.size();
         if (listenersSize == 0) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Slog.d(TAG, "No app listener to be notified of " + event);
-            }
+            Slogf.d(TAG, "No app listener to be notified of %s", event);
             return;
         }
         // Must use a different TimingsTraceLog because it's another thread
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Slog.d(TAG, "Notifying " + listenersSize + " app listeners of " + event);
-        }
+        Slogf.d(TAG, "Notifying %d app listeners of %s", listenersSize, event);
         int userId = event.getUserId();
         TimingsTraceLog t = new TimingsTraceLog(TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
         int eventType = event.getEventType();
         t.traceBegin("notify-app-listeners-user-" + userId + "-event-" + eventType);
         for (int i = 0; i < listenersSize; i++) {
-            int uid = mAppLifecycleListeners.keyAt(i);
-
-            IResultReceiver listener = mAppLifecycleListeners.valueAt(i);
+            AppLifecycleListener listener = mAppLifecycleListeners.valueAt(i);
             Bundle data = new Bundle();
             data.putInt(CarUserManager.BUNDLE_PARAM_ACTION, eventType);
 
@@ -2126,17 +2144,14 @@
             if (fromUserId != UserHandle.USER_NULL) {
                 data.putInt(CarUserManager.BUNDLE_PARAM_PREVIOUS_USER_ID, fromUserId);
             }
-
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Slog.d(TAG, "Notifying listener for uid " + uid);
-            }
+            Slogf.d(TAG, "Notifying listener %s", listener);
             EventLog.writeEvent(EventLogTags.CAR_USER_SVC_NOTIFY_APP_LIFECYCLE_LISTENER,
-                    uid, eventType, fromUserId, userId);
+                    listener.uid, listener.packageName, eventType, fromUserId, userId);
             try {
-                t.traceBegin("notify-app-listener-uid-" + uid);
-                listener.send(userId, data);
+                t.traceBegin("notify-app-listener-" + listener.toShortString());
+                listener.receiver.send(userId, data);
             } catch (RemoteException e) {
-                Slog.e(TAG, "Error calling lifecycle listener", e);
+                Slogf.e(TAG, e, "Error calling lifecycle listener %s", listener);
             } finally {
                 t.traceEnd();
             }
diff --git a/service/src/com/android/car/watchdog/CarWatchdogService.java b/service/src/com/android/car/watchdog/CarWatchdogService.java
index fccaa8b..93243d4 100644
--- a/service/src/com/android/car/watchdog/CarWatchdogService.java
+++ b/service/src/com/android/car/watchdog/CarWatchdogService.java
@@ -64,6 +64,7 @@
 import com.android.server.utils.Slogf;
 
 import java.lang.ref.WeakReference;
+import java.time.Instant;
 import java.util.List;
 
 /**
@@ -76,10 +77,22 @@
             "com.android.server.jobscheduler.GARAGE_MODE_ON";
     static final String ACTION_GARAGE_MODE_OFF =
             "com.android.server.jobscheduler.GARAGE_MODE_OFF";
+    static final TimeSourceInterface SYSTEM_INSTANCE = new TimeSourceInterface() {
+        @Override
+        public Instant now() {
+            return Instant.now();
+        }
+
+        @Override
+        public String toString() {
+            return "System time instance";
+        }
+    };
 
     private final Context mContext;
     private final ICarWatchdogServiceForSystemImpl mWatchdogServiceForSystem;
     private final PackageInfoHandler mPackageInfoHandler;
+    private final WatchdogStorage mWatchdogStorage;
     private final WatchdogProcessHandler mWatchdogProcessHandler;
     private final WatchdogPerfHandler mWatchdogPerfHandler;
     private final CarWatchdogDaemonHelper mCarWatchdogDaemonHelper;
@@ -99,6 +112,9 @@
             } else if (!action.equals(ACTION_GARAGE_MODE_OFF)) {
                 return;
             }
+            if (isGarageMode) {
+                mWatchdogStorage.shrinkDatabase();
+            }
             try {
                 mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.GARAGE_MODE,
                         isGarageMode ? GarageMode.GARAGE_MODE_ON : GarageMode.GARAGE_MODE_OFF,
@@ -120,14 +136,20 @@
     private boolean mIsConnected;
 
     public CarWatchdogService(Context context) {
+        this(context, new WatchdogStorage(context));
+    }
+
+    @VisibleForTesting
+    CarWatchdogService(Context context, WatchdogStorage watchdogStorage) {
         mContext = context;
+        mWatchdogStorage = watchdogStorage;
         mPackageInfoHandler = new PackageInfoHandler(mContext.getPackageManager());
         mCarWatchdogDaemonHelper = new CarWatchdogDaemonHelper(TAG_WATCHDOG);
         mWatchdogServiceForSystem = new ICarWatchdogServiceForSystemImpl(this);
         mWatchdogProcessHandler = new WatchdogProcessHandler(mWatchdogServiceForSystem,
                 mCarWatchdogDaemonHelper);
         mWatchdogPerfHandler = new WatchdogPerfHandler(mContext, mCarWatchdogDaemonHelper,
-                mPackageInfoHandler);
+                mPackageInfoHandler, mWatchdogStorage);
         mConnectionListener = (isConnected) -> {
             mWatchdogPerfHandler.onDaemonConnectionChange(isConnected);
             synchronized (mLock) {
@@ -140,12 +162,12 @@
     @Override
     public void init() {
         mWatchdogProcessHandler.init();
+        mWatchdogPerfHandler.init();
         subscribePowerCycleChange();
         subscribeUserStateChange();
         subscribeBroadcastReceiver();
         mCarWatchdogDaemonHelper.addOnConnectionChangeListener(mConnectionListener);
         mCarWatchdogDaemonHelper.connect();
-        mWatchdogPerfHandler.init();
         // To make sure the main handler is ready for responding to car watchdog daemon, registering
         // to the daemon is done through the main handler. Once the registration is completed, we
         // can assume that the main handler is not too busy handling other stuffs.
@@ -158,7 +180,7 @@
     @Override
     public void release() {
         mContext.unregisterReceiver(mBroadcastReceiver);
-        mWatchdogPerfHandler.release();
+        mWatchdogStorage.release();
         unregisterFromDaemon();
         mCarWatchdogDaemonHelper.disconnect();
     }
@@ -199,11 +221,6 @@
         mWatchdogProcessHandler.tellClientAlive(client, sessionId);
     }
 
-    @VisibleForTesting
-    int getClientCount(int timeout) {
-        return mWatchdogProcessHandler.getClientCount(timeout);
-    }
-
     /** Returns {@link android.car.watchdog.ResourceOveruseStats} for the calling package. */
     @Override
     @NonNull
@@ -326,6 +343,24 @@
         return mWatchdogPerfHandler.getResourceOveruseConfigurations(resourceOveruseFlag);
     }
 
+    /**
+     * Enables/disables the watchdog daemon client health check process.
+     */
+    public void controlProcessHealthCheck(boolean disable) {
+        ICarImpl.assertPermission(mContext, Car.PERMISSION_USE_CAR_WATCHDOG);
+        mWatchdogProcessHandler.controlProcessHealthCheck(disable);
+    }
+
+    @VisibleForTesting
+    int getClientCount(int timeout) {
+        return mWatchdogProcessHandler.getClientCount(timeout);
+    }
+
+    @VisibleForTesting
+    void setTimeSource(TimeSourceInterface timeSource) {
+        mWatchdogPerfHandler.setTimeSource(timeSource);
+    }
+
     private void postRegisterToDaemonMessage() {
         CarServiceUtils.runOnMain(() -> {
             synchronized (mLock) {
@@ -359,9 +394,9 @@
                 mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.USER_STATE, info.id,
                         userState);
                 if (userState == UserState.USER_STATE_STOPPED) {
-                    mWatchdogProcessHandler.updateUserState(info.id, /*isStopped=*/true);
+                    mWatchdogProcessHandler.updateUserState(info.id, /*isStopped=*/ true);
                 } else {
-                    mWatchdogProcessHandler.updateUserState(info.id, /*isStopped=*/false);
+                    mWatchdogProcessHandler.updateUserState(info.id, /*isStopped=*/ false);
                 }
             }
         } catch (RemoteException | RuntimeException e) {
@@ -399,6 +434,7 @@
                     case CarPowerStateListener.SHUTDOWN_ENTER:
                     case CarPowerStateListener.SUSPEND_ENTER:
                         powerCycle = PowerCycle.POWER_CYCLE_SHUTDOWN_ENTER;
+                        mWatchdogPerfHandler.writeToDatabase();
                     // ON covers resume.
                     case CarPowerStateListener.ON:
                         powerCycle = PowerCycle.POWER_CYCLE_RESUME;
@@ -434,12 +470,12 @@
             String userStateDesc;
             switch (event.getEventType()) {
                 case USER_LIFECYCLE_EVENT_TYPE_STARTING:
-                    mWatchdogProcessHandler.updateUserState(userId, /*isStopped=*/false);
+                    mWatchdogProcessHandler.updateUserState(userId, /*isStopped=*/ false);
                     userState = UserState.USER_STATE_STARTED;
                     userStateDesc = "STARTING";
                     break;
                 case USER_LIFECYCLE_EVENT_TYPE_STOPPED:
-                    mWatchdogProcessHandler.updateUserState(userId, /*isStopped=*/true);
+                    mWatchdogProcessHandler.updateUserState(userId, /*isStopped=*/ true);
                     userState = UserState.USER_STATE_STOPPED;
                     userStateDesc = "STOPPING";
                     break;
@@ -467,6 +503,11 @@
         mContext.registerReceiverForAllUsers(mBroadcastReceiver, filter, null, null);
     }
 
+    @VisibleForTesting
+    void setResourceOveruseKillingDelay(long millis) {
+        mWatchdogPerfHandler.setResourceOveruseKillingDelay(millis);
+    }
+
     private static final class ICarWatchdogServiceForSystemImpl
             extends ICarWatchdogServiceForSystem.Stub {
         private final WeakReference<CarWatchdogService> mService;
diff --git a/service/src/com/android/car/watchdog/PackageInfoHandler.java b/service/src/com/android/car/watchdog/PackageInfoHandler.java
index f04f2b9..5ee811d 100644
--- a/service/src/com/android/car/watchdog/PackageInfoHandler.java
+++ b/service/src/com/android/car/watchdog/PackageInfoHandler.java
@@ -16,6 +16,7 @@
 
 package com.android.car.watchdog;
 
+import android.annotation.Nullable;
 import android.automotive.watchdog.internal.ApplicationCategoryType;
 import android.automotive.watchdog.internal.ComponentType;
 import android.automotive.watchdog.internal.PackageIdentifier;
@@ -25,8 +26,10 @@
 import android.content.pm.PackageManager;
 import android.os.Process;
 import android.os.UserHandle;
+import android.util.ArrayMap;
 import android.util.IntArray;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 
 import com.android.car.CarLog;
 import com.android.internal.annotations.GuardedBy;
@@ -38,13 +41,18 @@
 
 /** Handles package info resolving */
 public final class PackageInfoHandler {
+    public static final String SHARED_PACKAGE_PREFIX = "shared:";
+
     private static final String TAG = CarLog.tagFor(PackageInfoHandler.class);
 
     private final PackageManager mPackageManager;
     private final Object mLock = new Object();
-    /* Cache of uid to package name mapping. */
     @GuardedBy("mLock")
-    private final SparseArray<String> mPackageNamesByUid = new SparseArray<>();
+    private final SparseArray<String> mGenericPackageNameByUid = new SparseArray<>();
+    @GuardedBy("mLock")
+    private final SparseArray<List<String>> mPackagesBySharedUid = new SparseArray<>();
+    @GuardedBy("mLock")
+    private final ArrayMap<String, String> mGenericPackageNameByPackage = new ArrayMap<>();
     @GuardedBy("mLock")
     private List<String> mVendorPackagePrefixes = new ArrayList<>();
 
@@ -53,42 +61,99 @@
     }
 
     /**
-     * Returns package names for the given UIDs.
+     * Returns the generic package names for the given UIDs.
      *
-     * Some UIDs may not have package names. This may occur when a UID is being removed and the
+     * Some UIDs may not have names. This may occur when a UID is being removed and the
      * internal data structures are not up-to-date. The caller should handle it.
      */
-    public SparseArray<String> getPackageNamesForUids(int[] uids) {
+    public SparseArray<String> getNamesForUids(int[] uids) {
         IntArray unmappedUids = new IntArray(uids.length);
-        SparseArray<String> packageNamesByUid = new SparseArray<>();
+        SparseArray<String> genericPackageNameByUid = new SparseArray<>();
         synchronized (mLock) {
             for (int uid : uids) {
-                String packageName = mPackageNamesByUid.get(uid, null);
-                if (packageName != null) {
-                    packageNamesByUid.append(uid, packageName);
+                String genericPackageName = mGenericPackageNameByUid.get(uid, null);
+                if (genericPackageName != null) {
+                    genericPackageNameByUid.append(uid, genericPackageName);
                 } else {
                     unmappedUids.add(uid);
                 }
             }
         }
         if (unmappedUids.size() == 0) {
-            return packageNamesByUid;
+            return genericPackageNameByUid;
         }
-        String[] packageNames = mPackageManager.getNamesForUids(unmappedUids.toArray());
+        String[] genericPackageNames = mPackageManager.getNamesForUids(unmappedUids.toArray());
         synchronized (mLock) {
             for (int i = 0; i < unmappedUids.size(); ++i) {
-                if (packageNames[i] == null || packageNames[i].isEmpty()) {
+                if (genericPackageNames[i] == null || genericPackageNames[i].isEmpty()) {
                     continue;
                 }
-                mPackageNamesByUid.append(unmappedUids.get(i), packageNames[i]);
-                packageNamesByUid.append(unmappedUids.get(i), packageNames[i]);
+                int uid = unmappedUids.get(i);
+                String genericPackageName = genericPackageNames[i];
+                mGenericPackageNameByUid.append(uid, genericPackageName);
+                genericPackageNameByUid.append(uid, genericPackageName);
+                mGenericPackageNameByPackage.put(genericPackageName, genericPackageName);
+                if (!genericPackageName.startsWith(SHARED_PACKAGE_PREFIX)) {
+                    continue;
+                }
+                populateSharedPackagesLocked(uid, genericPackageName);
             }
         }
-        return packageNamesByUid;
+        return genericPackageNameByUid;
     }
 
     /**
-     * Returns package infos for the given UIDs.
+     * Returns the generic package name for the user package.
+     *
+     * Returns null when no generic package name is found.
+     */
+    @Nullable
+    public String getNameForUserPackage(String packageName, int userId) {
+        synchronized (mLock) {
+            String genericPackageName = mGenericPackageNameByPackage.get(packageName);
+            if (genericPackageName != null) {
+                return genericPackageName;
+            }
+        }
+        try {
+            return getNameForPackage(
+                    mPackageManager.getPackageInfoAsUser(packageName, /* flags= */ 0, userId));
+        } catch (PackageManager.NameNotFoundException e) {
+            Slogf.e(TAG, "Package '%s' not found for user %d: %s", packageName, userId, e);
+        }
+        return null;
+    }
+
+    /** Returns the packages owned by the shared UID */
+    public List<String> getPackagesForUid(int uid, String genericPackageName) {
+        synchronized (mLock) {
+            /* When fetching the packages under a shared UID update the internal DS. This will help
+             * capture any recently installed packages.
+             */
+            populateSharedPackagesLocked(uid, genericPackageName);
+            return mPackagesBySharedUid.get(uid);
+        }
+    }
+
+    /** Returns the generic package name for the given package info. */
+    public String getNameForPackage(android.content.pm.PackageInfo packageInfo) {
+        synchronized (mLock) {
+            String genericPackageName = mGenericPackageNameByPackage.get(packageInfo.packageName);
+            if (genericPackageName != null) {
+                return genericPackageName;
+            }
+            if (packageInfo.sharedUserId != null) {
+                populateSharedPackagesLocked(packageInfo.applicationInfo.uid,
+                        SHARED_PACKAGE_PREFIX + packageInfo.sharedUserId);
+                return SHARED_PACKAGE_PREFIX + packageInfo.sharedUserId;
+            }
+            mGenericPackageNameByPackage.put(packageInfo.packageName, packageInfo.packageName);
+            return packageInfo.packageName;
+        }
+    }
+
+    /**
+     * Returns the internal package infos for the given UIDs.
      *
      * Some UIDs may not have package infos. This may occur when a UID is being removed and the
      * internal data structures are not up-to-date. The caller should handle it.
@@ -103,20 +168,29 @@
              */
             mVendorPackagePrefixes = vendorPackagePrefixes;
         }
-        SparseArray<String> packageNamesByUid = getPackageNamesForUids(uids);
-        ArrayList<PackageInfo> packageInfos = new ArrayList<>(packageNamesByUid.size());
-        for (int i = 0; i < packageNamesByUid.size(); ++i) {
-            packageInfos.add(getPackageInfo(packageNamesByUid.keyAt(i),
-                    packageNamesByUid.valueAt(i)));
+        SparseArray<String> genericPackageNameByUid = getNamesForUids(uids);
+        ArrayList<PackageInfo> packageInfos = new ArrayList<>(genericPackageNameByUid.size());
+        for (int i = 0; i < genericPackageNameByUid.size(); ++i) {
+            packageInfos.add(getPackageInfo(genericPackageNameByUid.keyAt(i),
+                    genericPackageNameByUid.valueAt(i)));
         }
         return packageInfos;
     }
 
-    private PackageInfo getPackageInfo(int uid, String packageName) {
+    @GuardedBy("mLock")
+    private void populateSharedPackagesLocked(int uid, String genericPackageName) {
+        String[] packages = mPackageManager.getPackagesForUid(uid);
+        for (String pkg : packages) {
+            mGenericPackageNameByPackage.put(pkg, genericPackageName);
+        }
+        mPackagesBySharedUid.put(uid, Arrays.asList(packages));
+    }
+
+    private PackageInfo getPackageInfo(int uid, String genericPackageName) {
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageIdentifier = new PackageIdentifier();
         packageInfo.packageIdentifier.uid = uid;
-        packageInfo.packageIdentifier.name = packageName;
+        packageInfo.packageIdentifier.name = genericPackageName;
         packageInfo.sharedUidPackages = new ArrayList<>();
         packageInfo.componentType = ComponentType.UNKNOWN;
         /* Application category type mapping is handled on the daemon side. */
@@ -126,76 +200,90 @@
         packageInfo.uidType = appId >= Process.FIRST_APPLICATION_UID ? UidType.APPLICATION :
                 UidType.NATIVE;
 
-        if (packageName.startsWith("shared:")) {
-            String[] sharedUidPackages = mPackageManager.getPackagesForUid(uid);
-            if (sharedUidPackages == null) {
-                return packageInfo;
-            }
-            boolean seenVendor = false;
-            boolean seenSystem = false;
-            boolean seenThirdParty = false;
-            /*
-             * A shared UID has multiple packages associated with it and these packages may be
-             * mapped to different component types. Thus map the shared UID to the most restrictive
-             * component type.
-             */
-            for (int i = 0; i < sharedUidPackages.length; ++i) {
-                int componentType = getPackageComponentType(userId, sharedUidPackages[i]);
-                switch(componentType) {
-                    case ComponentType.VENDOR:
-                        seenVendor = true;
-                        break;
-                    case ComponentType.SYSTEM:
-                        seenSystem = true;
-                        break;
-                    case ComponentType.THIRD_PARTY:
-                        seenThirdParty = true;
-                        break;
-                    default:
-                        Slogf.w(TAG, "Unknown component type %d for package '%s'", componentType,
-                                sharedUidPackages[i]);
+        if (genericPackageName.startsWith(SHARED_PACKAGE_PREFIX)) {
+            List<String> packages = null;
+            synchronized (mLock) {
+                packages = mPackagesBySharedUid.get(uid);
+                if (packages == null) {
+                    return packageInfo;
                 }
             }
-            packageInfo.sharedUidPackages = Arrays.asList(sharedUidPackages);
-            if (seenVendor) {
-                packageInfo.componentType = ComponentType.VENDOR;
-            } else if (seenSystem) {
-                packageInfo.componentType = ComponentType.SYSTEM;
-            } else if (seenThirdParty) {
-                packageInfo.componentType = ComponentType.THIRD_PARTY;
+            List<ApplicationInfo> applicationInfos = new ArrayList<>();
+            for (int i = 0; i < packages.size(); ++i) {
+                try {
+                    applicationInfos.add(mPackageManager.getApplicationInfoAsUser(packages.get(i),
+                            /* flags= */ 0, userId));
+                } catch (PackageManager.NameNotFoundException e) {
+                    Slogf.e(TAG, "Package '%s' not found for user %d: %s", packages.get(i), userId,
+                            e);
+                }
             }
+            packageInfo.componentType = getSharedComponentType(
+                    applicationInfos, genericPackageName);
+            packageInfo.sharedUidPackages = new ArrayList<>(packages);
         } else {
-            packageInfo.componentType = getPackageComponentType(
-                    userId, packageName);
+            packageInfo.componentType = getUserPackageComponentType(
+                    userId, genericPackageName);
         }
         return packageInfo;
     }
 
-    private int getPackageComponentType(int userId, String packageName) {
+    /**
+     * Returns the most restrictive component type shared by the given application infos.
+     *
+     * A shared UID has multiple packages associated with it and these packages may be
+     * mapped to different component types. Thus map the shared UID to the most restrictive
+     * component type.
+     */
+    public int getSharedComponentType(List<ApplicationInfo> applicationInfos,
+            String genericPackageName) {
+        SparseBooleanArray seenComponents = new SparseBooleanArray();
+        for (int i = 0; i < applicationInfos.size(); ++i) {
+            int type = getComponentType(applicationInfos.get(i));
+            seenComponents.put(type, true);
+        }
+        if (seenComponents.get(ComponentType.VENDOR)) {
+            return ComponentType.VENDOR;
+        } else if (seenComponents.get(ComponentType.SYSTEM)) {
+            synchronized (mLock) {
+                for (int i = 0; i < mVendorPackagePrefixes.size(); ++i) {
+                    if (genericPackageName.startsWith(mVendorPackagePrefixes.get(i))) {
+                        return ComponentType.VENDOR;
+                    }
+                }
+            }
+            return ComponentType.SYSTEM;
+        } else if (seenComponents.get(ComponentType.THIRD_PARTY)) {
+            return ComponentType.THIRD_PARTY;
+        }
+        return ComponentType.UNKNOWN;
+    }
+
+    private int getUserPackageComponentType(int userId, String packageName) {
         try {
             ApplicationInfo info = mPackageManager.getApplicationInfoAsUser(packageName,
                     /* flags= */ 0, userId);
-            return getComponentType(packageName, info);
+            return getComponentType(info);
         } catch (PackageManager.NameNotFoundException e) {
             Slogf.e(TAG, "Package '%s' not found for user %d: %s", packageName, userId, e);
         }
         return ComponentType.UNKNOWN;
     }
 
-    /** Returns the component type for the given package and its application info. */
-    public int getComponentType(String packageName, ApplicationInfo info) {
-        if ((info.privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0
-                || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0
-                || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_ODM) != 0) {
+    /** Returns the component type for the given application info. */
+    public int getComponentType(ApplicationInfo applicationInfo) {
+        if ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0
+                || (applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0
+                || (applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_ODM) != 0) {
             return ComponentType.VENDOR;
         }
-        if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0
-                || (info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
-                || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT) != 0
-                || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT) != 0) {
+        if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
+                || (applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
+                || (applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT) != 0
+                || (applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT) != 0) {
             synchronized (mLock) {
-                for (String prefix : mVendorPackagePrefixes) {
-                    if (packageName.startsWith(prefix)) {
+                for (int i = 0; i < mVendorPackagePrefixes.size(); ++i) {
+                    if (applicationInfo.packageName.startsWith(mVendorPackagePrefixes.get(i))) {
                         return ComponentType.VENDOR;
                     }
                 }
diff --git a/service/src/com/android/car/watchdog/TimeSourceInterface.java b/service/src/com/android/car/watchdog/TimeSourceInterface.java
new file mode 100644
index 0000000..1bd6508
--- /dev/null
+++ b/service/src/com/android/car/watchdog/TimeSourceInterface.java
@@ -0,0 +1,27 @@
+/*
+ * 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.watchdog;
+
+import java.time.Instant;
+
+/**
+ * Provider for the current value of "now" for users of {@code java.time}.
+ */
+public interface TimeSourceInterface {
+    /** Returns the current instant from the time source implementation. */
+    Instant now();
+}
diff --git a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
index 8d15e0f..fee73b9 100644
--- a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
+++ b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
@@ -21,6 +21,11 @@
 import static android.automotive.watchdog.internal.ResourceOveruseActionType.NOT_KILLED;
 import static android.automotive.watchdog.internal.ResourceOveruseActionType.NOT_KILLED_USER_OPTED;
 import static android.car.watchdog.CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO;
+import static android.car.watchdog.CarWatchdogManager.STATS_PERIOD_CURRENT_DAY;
+import static android.car.watchdog.CarWatchdogManager.STATS_PERIOD_PAST_15_DAYS;
+import static android.car.watchdog.CarWatchdogManager.STATS_PERIOD_PAST_30_DAYS;
+import static android.car.watchdog.CarWatchdogManager.STATS_PERIOD_PAST_3_DAYS;
+import static android.car.watchdog.CarWatchdogManager.STATS_PERIOD_PAST_7_DAYS;
 import static android.car.watchdog.PackageKillableState.KILLABLE_STATE_NEVER;
 import static android.car.watchdog.PackageKillableState.KILLABLE_STATE_NO;
 import static android.car.watchdog.PackageKillableState.KILLABLE_STATE_YES;
@@ -29,8 +34,11 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
 
 import static com.android.car.watchdog.CarWatchdogService.DEBUG;
+import static com.android.car.watchdog.CarWatchdogService.SYSTEM_INSTANCE;
 import static com.android.car.watchdog.CarWatchdogService.TAG;
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.car.watchdog.PackageInfoHandler.SHARED_PACKAGE_PREFIX;
+import static com.android.car.watchdog.WatchdogStorage.STATS_TEMPORAL_UNIT;
+import static com.android.car.watchdog.WatchdogStorage.ZONE_OFFSET;
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
@@ -56,12 +64,14 @@
 import android.car.watchdog.ResourceOveruseStats;
 import android.car.watchdoglib.CarWatchdogDaemonHelper;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.os.Binder;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
@@ -73,15 +83,19 @@
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 
+import com.android.car.CarServiceUtils;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
+import com.android.internal.util.function.TriConsumer;
 import com.android.server.utils.Slogf;
 
 import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -97,18 +111,19 @@
     public static final String INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA = "MEDIA";
     public static final String INTERNAL_APPLICATION_CATEGORY_TYPE_UNKNOWN = "UNKNOWN";
 
+    static final long RESOURCE_OVERUSE_KILLING_DELAY_MILLS = 10_000;
+
     private static final long MAX_WAIT_TIME_MILLS = 3_000;
 
     private final Context mContext;
     private final CarWatchdogDaemonHelper mCarWatchdogDaemonHelper;
     private final PackageInfoHandler mPackageInfoHandler;
     private final Handler mMainHandler;
+    private final HandlerThread mHandlerThread;
+    private final WatchdogStorage mWatchdogStorage;
     private final Object mLock = new Object();
-    /*
-     * Cache of added resource overuse listeners by uid.
-     */
     @GuardedBy("mLock")
-    private final Map<String, PackageResourceUsage> mUsageByUserPackage = new ArrayMap<>();
+    private final ArrayMap<String, PackageResourceUsage> mUsageByUserPackage = new ArrayMap<>();
     @GuardedBy("mLock")
     private final List<PackageResourceOveruseAction> mOveruseActionsByUserPackage =
             new ArrayList<>();
@@ -120,25 +135,36 @@
             mOveruseSystemListenerInfosByUid = new SparseArray<>();
     /* Set of safe-to-kill system and vendor packages. */
     @GuardedBy("mLock")
-    public final Set<String> mSafeToKillPackages = new ArraySet<>();
+    public final ArraySet<String> mSafeToKillSystemPackages = new ArraySet<>();
+    @GuardedBy("mLock")
+    public final ArraySet<String> mSafeToKillVendorPackages = new ArraySet<>();
     /* Default killable state for packages when not updated by the user. */
     @GuardedBy("mLock")
-    public final Set<String> mDefaultNotKillablePackages = new ArraySet<>();
+    public final ArraySet<String> mDefaultNotKillableGenericPackages = new ArraySet<>();
     @GuardedBy("mLock")
-    private ZonedDateTime mLastStatsReportUTC;
+    private ZonedDateTime mLatestStatsReportDate;
     @GuardedBy("mLock")
     private List<android.automotive.watchdog.internal.ResourceOveruseConfiguration>
             mPendingSetResourceOveruseConfigurationsRequest = null;
     @GuardedBy("mLock")
     boolean mIsConnectedToDaemon;
+    @GuardedBy("mLock")
+    boolean mIsWrittenToDatabase;
+    @GuardedBy("mLock")
+    private TimeSourceInterface mTimeSource;
+    @GuardedBy("mLock")
+    long mResourceOveruseKillingDelayMills;
 
     public WatchdogPerfHandler(Context context, CarWatchdogDaemonHelper daemonHelper,
-            PackageInfoHandler packageInfoHandler) {
+            PackageInfoHandler packageInfoHandler, WatchdogStorage watchdogStorage) {
         mContext = context;
         mCarWatchdogDaemonHelper = daemonHelper;
         mPackageInfoHandler = packageInfoHandler;
         mMainHandler = new Handler(Looper.getMainLooper());
-        mLastStatsReportUTC = ZonedDateTime.now(ZoneOffset.UTC);
+        mHandlerThread = CarServiceUtils.getHandlerThread(CarWatchdogService.class.getSimpleName());
+        mWatchdogStorage = watchdogStorage;
+        mTimeSource = SYSTEM_INSTANCE;
+        mResourceOveruseKillingDelayMills = RESOURCE_OVERUSE_KILLING_DELAY_MILLS;
     }
 
     /** Initializes the handler. */
@@ -146,28 +172,19 @@
         /*
          * TODO(b/183947162): Opt-in to receive package change broadcast and handle package enabled
          *  state changes.
-         *
-         * TODO(b/185287136): Persist in-memory data:
-         *  1. Read the current day's I/O overuse stats from database and push them
-         *  to the daemon.
-         *  2. Fetch the safe-to-kill from daemon on initialization and update mSafeToKillPackages.
          */
-        synchronized (mLock) {
-            checkAndHandleDateChangeLocked();
-        }
+        /* First database read is expensive, so post it on a separate handler thread. */
+        mHandlerThread.getThreadHandler().post(() -> {
+            readFromDatabase();
+            synchronized (mLock) {
+                checkAndHandleDateChangeLocked();
+                mIsWrittenToDatabase = false;
+            }});
         if (DEBUG) {
             Slogf.d(TAG, "WatchdogPerfHandler is initialized");
         }
     }
 
-    /** Releases the handler */
-    public void release() {
-        /* TODO(b/185287136): Write daily usage to SQLite DB storage. */
-        if (DEBUG) {
-            Slogf.d(TAG, "WatchdogPerfHandler is released");
-        }
-    }
-
     /** Dumps its state. */
     public void dump(IndentingPrintWriter writer) {
         /*
@@ -177,15 +194,26 @@
 
     /** Retries any pending requests on re-connecting to the daemon */
     public void onDaemonConnectionChange(boolean isConnected) {
+        boolean hasPendingRequest;
         synchronized (mLock) {
             mIsConnectedToDaemon = isConnected;
+            hasPendingRequest = mPendingSetResourceOveruseConfigurationsRequest != null;
         }
         if (isConnected) {
-            /*
-             * Retry pending set resource overuse configuration request before processing any new
-             * set/get requests. Thus notify the waiting requests only after the retry completes.
-             */
-            retryPendingSetResourceOveruseConfigurations();
+            if (hasPendingRequest) {
+                /*
+                 * Retry pending set resource overuse configuration request before processing any
+                 * new set/get requests. Thus notify the waiting requests only after the retry
+                 * completes.
+                 */
+                retryPendingSetResourceOveruseConfigurations();
+            } else {
+                /* Start fetch/sync configs only when there are no pending set requests because the
+                 * above retry starts fetch/sync configs on success. If the retry fails, the daemon
+                 * has crashed and shouldn't start fetchAndSyncResourceOveruseConfigurations.
+                 */
+                mMainHandler.post(this::fetchAndSyncResourceOveruseConfigurations);
+            }
         }
         synchronized (mLock) {
             mLock.notifyAll();
@@ -207,20 +235,20 @@
         int callingUid = Binder.getCallingUid();
         int callingUserId = UserHandle.getUserId(callingUid);
         UserHandle callingUserHandle = UserHandle.of(callingUserId);
-        String callingPackageName =
-                mPackageInfoHandler.getPackageNamesForUids(new int[]{callingUid})
+        String genericPackageName =
+                mPackageInfoHandler.getNamesForUids(new int[]{callingUid})
                         .get(callingUid, null);
-        if (callingPackageName == null) {
+        if (genericPackageName == null) {
             Slogf.w(TAG, "Failed to fetch package info for uid %d", callingUid);
             return new ResourceOveruseStats.Builder("", callingUserHandle).build();
         }
         ResourceOveruseStats.Builder statsBuilder =
-                new ResourceOveruseStats.Builder(callingPackageName, callingUserHandle);
-        statsBuilder.setIoOveruseStats(getIoOveruseStats(callingUserId, callingPackageName,
-                /* minimumBytesWritten= */ 0, maxStatsPeriod));
+                new ResourceOveruseStats.Builder(genericPackageName, callingUserHandle);
+        statsBuilder.setIoOveruseStats(
+                getIoOveruseStatsForPeriod(callingUserId, genericPackageName, maxStatsPeriod));
         if (DEBUG) {
             Slogf.d(TAG, "Returning all resource overuse stats for calling uid %d [user %d and "
-                            + "package '%s']", callingUid, callingUserId, callingPackageName);
+                            + "package '%s']", callingUid, callingUserId, genericPackageName);
         }
         return statsBuilder.build();
     }
@@ -240,14 +268,17 @@
                 "Must provide resource I/O overuse flag");
         long minimumBytesWritten = getMinimumBytesWritten(minimumStatsFlag);
         List<ResourceOveruseStats> allStats = new ArrayList<>();
-        for (PackageResourceUsage usage : mUsageByUserPackage.values()) {
-            ResourceOveruseStats.Builder statsBuilder = usage.getResourceOveruseStatsBuilder();
-            IoOveruseStats ioOveruseStats = getIoOveruseStats(usage.userId, usage.packageName,
-                    minimumBytesWritten, maxStatsPeriod);
-            if (ioOveruseStats == null) {
-                continue;
+        synchronized (mLock) {
+            for (int i = 0; i < mUsageByUserPackage.size(); ++i) {
+                PackageResourceUsage usage = mUsageByUserPackage.valueAt(i);
+                ResourceOveruseStats.Builder statsBuilder = usage.getResourceOveruseStatsBuilder();
+                IoOveruseStats ioOveruseStats =
+                        getIoOveruseStatsLocked(usage, minimumBytesWritten, maxStatsPeriod);
+                if (ioOveruseStats == null) {
+                    continue;
+                }
+                allStats.add(statsBuilder.setIoOveruseStats(ioOveruseStats).build());
             }
-            allStats.add(statsBuilder.setIoOveruseStats(ioOveruseStats).build());
         }
         if (DEBUG) {
             Slogf.d(TAG, "Returning all resource overuse stats");
@@ -263,7 +294,7 @@
             @CarWatchdogManager.StatsPeriod int maxStatsPeriod) {
         Objects.requireNonNull(packageName, "Package name must be non-null");
         Objects.requireNonNull(userHandle, "User handle must be non-null");
-        Preconditions.checkArgument((userHandle != UserHandle.ALL),
+        Preconditions.checkArgument(!userHandle.equals(UserHandle.ALL),
                 "Must provide the user handle for a specific user");
         Preconditions.checkArgument((resourceOveruseFlag > 0),
                 "Must provide valid resource overuse flag");
@@ -272,13 +303,19 @@
         // When more resource types are added, make this as optional.
         Preconditions.checkArgument((resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) != 0,
                 "Must provide resource I/O overuse flag");
+        String genericPackageName =
+                mPackageInfoHandler.getNameForUserPackage(packageName, userHandle.getIdentifier());
+        if (genericPackageName == null) {
+            throw new IllegalArgumentException("Package '" + packageName + "' not found");
+        }
         ResourceOveruseStats.Builder statsBuilder =
-                new ResourceOveruseStats.Builder(packageName, userHandle);
-        statsBuilder.setIoOveruseStats(getIoOveruseStats(userHandle.getIdentifier(), packageName,
-                /* minimumBytesWritten= */ 0, maxStatsPeriod));
+                new ResourceOveruseStats.Builder(genericPackageName, userHandle);
+        statsBuilder.setIoOveruseStats(getIoOveruseStatsForPeriod(userHandle.getIdentifier(),
+                genericPackageName, maxStatsPeriod));
         if (DEBUG) {
-            Slogf.d(TAG, "Returning resource overuse stats for user %d, package '%s'",
-                    userHandle.getIdentifier(), packageName);
+            Slogf.d(TAG, "Returning resource overuse stats for user %d, package '%s', "
+                    + "generic package '%s'", userHandle.getIdentifier(), packageName,
+                    genericPackageName);
         }
         return statsBuilder.build();
     }
@@ -330,31 +367,17 @@
             boolean isKillable) {
         Objects.requireNonNull(packageName, "Package name must be non-null");
         Objects.requireNonNull(userHandle, "User handle must be non-null");
-        if (userHandle == UserHandle.ALL) {
-            synchronized (mLock) {
-                for (PackageResourceUsage usage : mUsageByUserPackage.values()) {
-                    if (!usage.packageName.equals(packageName)) {
-                        continue;
-                    }
-                    if (!usage.setKillableState(isKillable)) {
-                        Slogf.e(TAG, "Cannot set killable state for package '%s'", packageName);
-                        throw new IllegalArgumentException(
-                                "Package killable state is not updatable");
-                    }
-                }
-                if (!isKillable) {
-                    mDefaultNotKillablePackages.add(packageName);
-                } else {
-                    mDefaultNotKillablePackages.remove(packageName);
-                }
-            }
-            if (DEBUG) {
-                Slogf.d(TAG, "Successfully set killable package state for all users");
-            }
+
+        if (userHandle.equals(UserHandle.ALL)) {
+            setPackageKillableStateForAllUsers(packageName, isKillable);
             return;
         }
         int userId = userHandle.getIdentifier();
-        String key = getUserPackageUniqueId(userId, packageName);
+        String genericPackageName = mPackageInfoHandler.getNameForUserPackage(packageName, userId);
+        if (genericPackageName == null) {
+            throw new IllegalArgumentException("Package '" + packageName + "' not found");
+        }
+        String key = getUserPackageUniqueId(userId, genericPackageName);
         synchronized (mLock) {
             /*
              * When the queried package is not cached in {@link mUsageByUserPackage}, the set API
@@ -366,11 +389,13 @@
              * state when pushing the latest stats. Ergo, the invalid killable state doesn't have
              * any effect.
              */
-            PackageResourceUsage usage = mUsageByUserPackage.getOrDefault(key,
-                    new PackageResourceUsage(userId, packageName));
-            if (!usage.setKillableState(isKillable)) {
+            PackageResourceUsage usage = mUsageByUserPackage.get(key);
+            if (usage == null) {
+                usage = new PackageResourceUsage(userId, genericPackageName);
+            }
+            if (!usage.verifyAndSetKillableStateLocked(isKillable)) {
                 Slogf.e(TAG, "User %d cannot set killable state for package '%s'",
-                        userHandle.getIdentifier(), packageName);
+                        userHandle.getIdentifier(), genericPackageName);
                 throw new IllegalArgumentException("Package killable state is not updatable");
             }
             mUsageByUserPackage.put(key, usage);
@@ -380,12 +405,48 @@
         }
     }
 
+    private void setPackageKillableStateForAllUsers(String packageName, boolean isKillable) {
+        UserManager userManager = UserManager.get(mContext);
+        List<UserInfo> userInfos = userManager.getAliveUsers();
+        String genericPackageName = null;
+        synchronized (mLock) {
+            for (int i = 0; i < userInfos.size(); ++i) {
+                int userId = userInfos.get(i).id;
+                String name = mPackageInfoHandler.getNameForUserPackage(packageName, userId);
+                if (name == null) {
+                    continue;
+                }
+                genericPackageName = name;
+                String key = getUserPackageUniqueId(userId, genericPackageName);
+                PackageResourceUsage usage = mUsageByUserPackage.get(key);
+                if (usage == null) {
+                    continue;
+                }
+                if (!usage.verifyAndSetKillableStateLocked(isKillable)) {
+                    Slogf.e(TAG, "Cannot set killable state for package '%s'", packageName);
+                    throw new IllegalArgumentException(
+                            "Package killable state is not updatable");
+                }
+            }
+            if (genericPackageName != null) {
+                if (!isKillable) {
+                    mDefaultNotKillableGenericPackages.add(genericPackageName);
+                } else {
+                    mDefaultNotKillableGenericPackages.remove(genericPackageName);
+                }
+            }
+        }
+        if (DEBUG) {
+            Slogf.d(TAG, "Successfully set killable package state for all users");
+        }
+    }
+
     /** Returns the list of package killable states on resource overuse for the user. */
     @NonNull
     public List<PackageKillableState> getPackageKillableStatesAsUser(UserHandle userHandle) {
         Objects.requireNonNull(userHandle, "User handle must be non-null");
         PackageManager pm = mContext.getPackageManager();
-        if (userHandle != UserHandle.ALL) {
+        if (!userHandle.equals(UserHandle.ALL)) {
             if (DEBUG) {
                 Slogf.d(TAG, "Returning all package killable states for user %d",
                         userHandle.getIdentifier());
@@ -395,8 +456,9 @@
         List<PackageKillableState> packageKillableStates = new ArrayList<>();
         UserManager userManager = UserManager.get(mContext);
         List<UserInfo> userInfos = userManager.getAliveUsers();
-        for (UserInfo userInfo : userInfos) {
-            packageKillableStates.addAll(getPackageKillableStatesForUserId(userInfo.id, pm));
+        for (int i = 0; i < userInfos.size(); ++i) {
+            packageKillableStates.addAll(
+                    getPackageKillableStatesForUserId(userInfos.get(i).id, pm));
         }
         if (DEBUG) {
             Slogf.d(TAG, "Returning all package killable states for all users");
@@ -406,24 +468,53 @@
 
     private List<PackageKillableState> getPackageKillableStatesForUserId(int userId,
             PackageManager pm) {
-        List<PackageInfo> packageInfos = pm.getInstalledPackagesAsUser(/* flags= */0, userId);
+        List<PackageInfo> packageInfos = pm.getInstalledPackagesAsUser(/* flags= */ 0, userId);
         List<PackageKillableState> states = new ArrayList<>();
         synchronized (mLock) {
+            ArrayMap<String, List<ApplicationInfo>> applicationInfosBySharedPackage =
+                    new ArrayMap<>();
             for (int i = 0; i < packageInfos.size(); ++i) {
                 PackageInfo packageInfo = packageInfos.get(i);
-                String key = getUserPackageUniqueId(userId, packageInfo.packageName);
-                PackageResourceUsage usage = mUsageByUserPackage.getOrDefault(key,
-                        new PackageResourceUsage(userId, packageInfo.packageName));
-                int killableState = usage.syncAndFetchKillableStateLocked(
-                        mPackageInfoHandler.getComponentType(packageInfo.packageName,
-                                packageInfo.applicationInfo));
-                mUsageByUserPackage.put(key, usage);
-                states.add(
-                        new PackageKillableState(packageInfo.packageName, userId, killableState));
+                String genericPackageName = mPackageInfoHandler.getNameForPackage(packageInfo);
+                if (packageInfo.sharedUserId == null) {
+                    int componentType = mPackageInfoHandler.getComponentType(
+                            packageInfo.applicationInfo);
+                    int killableState = getPackageKillableStateForUserPackageLocked(
+                            userId, genericPackageName, componentType,
+                            isSafeToKillLocked(genericPackageName, componentType, null));
+                    states.add(new PackageKillableState(packageInfo.packageName, userId,
+                            killableState));
+                    continue;
+                }
+                List<ApplicationInfo> applicationInfos =
+                        applicationInfosBySharedPackage.get(genericPackageName);
+                if (applicationInfos == null) {
+                    applicationInfos = new ArrayList<>();
+                }
+                applicationInfos.add(packageInfo.applicationInfo);
+                applicationInfosBySharedPackage.put(genericPackageName, applicationInfos);
+            }
+            for (Map.Entry<String, List<ApplicationInfo>> entry :
+                    applicationInfosBySharedPackage.entrySet()) {
+                String genericPackageName = entry.getKey();
+                List<ApplicationInfo> applicationInfos = entry.getValue();
+                int componentType = mPackageInfoHandler.getSharedComponentType(
+                        applicationInfos, genericPackageName);
+                List<String> packageNames = new ArrayList<>(applicationInfos.size());
+                for (int i = 0; i < applicationInfos.size(); ++i) {
+                    packageNames.add(applicationInfos.get(i).packageName);
+                }
+                int killableState = getPackageKillableStateForUserPackageLocked(
+                        userId, genericPackageName, componentType,
+                        isSafeToKillLocked(genericPackageName, componentType, packageNames));
+                for (int i = 0; i < applicationInfos.size(); ++i) {
+                    states.add(new PackageKillableState(
+                            applicationInfos.get(i).packageName, userId, killableState));
+                }
             }
         }
         if (DEBUG) {
-            Slogf.d(TAG, "Returning the package killable states for a user package");
+            Slogf.d(TAG, "Returning the package killable states for user packages");
         }
         return states;
     }
@@ -439,28 +530,11 @@
                 "Must provide at least one configuration");
         Preconditions.checkArgument((resourceOveruseFlag > 0),
                 "Must provide valid resource overuse flag");
-        Set<Integer> seenComponentTypes = new ArraySet<>();
+        checkResourceOveruseConfigs(configurations, resourceOveruseFlag);
         List<android.automotive.watchdog.internal.ResourceOveruseConfiguration> internalConfigs =
                 new ArrayList<>();
-        for (ResourceOveruseConfiguration config : configurations) {
-            /*
-             * TODO(b/185287136): Make sure the validation done here matches the validation done in
-             *  the daemon so set requests retried at a later time will complete successfully.
-             */
-            int componentType = config.getComponentType();
-            if (toComponentTypeStr(componentType).equals("UNKNOWN")) {
-                throw new IllegalArgumentException("Invalid component type in the configuration");
-            }
-            if (seenComponentTypes.contains(componentType)) {
-                throw new IllegalArgumentException(
-                        "Cannot provide duplicate configurations for the same component type");
-            }
-            if ((resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) != 0
-                    && config.getIoOveruseConfiguration() == null) {
-                throw new IllegalArgumentException("Must provide I/O overuse configuration");
-            }
-            seenComponentTypes.add(config.getComponentType());
-            internalConfigs.add(toInternalResourceOveruseConfiguration(config,
+        for (int i = 0; i < configurations.size(); ++i) {
+            internalConfigs.add(toInternalResourceOveruseConfiguration(configurations.get(i),
                     resourceOveruseFlag));
         }
         synchronized (mLock) {
@@ -497,9 +571,9 @@
             throw new IllegalStateException(e);
         }
         List<ResourceOveruseConfiguration> configs = new ArrayList<>();
-        for (android.automotive.watchdog.internal.ResourceOveruseConfiguration internalConfig
-                : internalConfigs) {
-            configs.add(toResourceOveruseConfiguration(internalConfig, resourceOveruseFlag));
+        for (int i = 0; i < internalConfigs.size(); ++i) {
+            configs.add(
+                    toResourceOveruseConfiguration(internalConfigs.get(i), resourceOveruseFlag));
         }
         if (DEBUG) {
             Slogf.d(TAG, "Returning the resource overuse configuration");
@@ -509,20 +583,22 @@
 
     /** Processes the latest I/O overuse stats */
     public void latestIoOveruseStats(List<PackageIoOveruseStats> packageIoOveruseStats) {
+        SparseBooleanArray recurringIoOverusesByUid = new SparseBooleanArray();
         int[] uids = new int[packageIoOveruseStats.size()];
         for (int i = 0; i < packageIoOveruseStats.size(); ++i) {
             uids[i] = packageIoOveruseStats.get(i).uid;
         }
-        SparseArray<String> packageNamesByUid = mPackageInfoHandler.getPackageNamesForUids(uids);
+        SparseArray<String> genericPackageNamesByUid = mPackageInfoHandler.getNamesForUids(uids);
         synchronized (mLock) {
             checkAndHandleDateChangeLocked();
-            for (PackageIoOveruseStats stats : packageIoOveruseStats) {
-                String packageName = packageNamesByUid.get(stats.uid);
-                if (packageName == null) {
+            for (int i = 0; i < packageIoOveruseStats.size(); ++i) {
+                PackageIoOveruseStats stats = packageIoOveruseStats.get(i);
+                String genericPackageName = genericPackageNamesByUid.get(stats.uid);
+                if (genericPackageName == null) {
                     continue;
                 }
                 int userId = UserHandle.getUserId(stats.uid);
-                PackageResourceUsage usage = cacheAndFetchUsageLocked(userId, packageName,
+                PackageResourceUsage usage = cacheAndFetchUsageLocked(userId, genericPackageName,
                         stats.ioOveruseStats);
                 if (stats.shouldNotify) {
                     /*
@@ -532,7 +608,7 @@
                      */
                     ResourceOveruseStats resourceOveruseStats =
                             usage.getResourceOveruseStatsBuilder().setIoOveruseStats(
-                                    usage.getIoOveruseStats()).build();
+                                    usage.getIoOveruseStatsLocked()).build();
                     notifyResourceOveruseStatsLocked(stats.uid, resourceOveruseStats);
                 }
 
@@ -541,7 +617,7 @@
                 }
                 PackageResourceOveruseAction overuseAction = new PackageResourceOveruseAction();
                 overuseAction.packageIdentifier = new PackageIdentifier();
-                overuseAction.packageIdentifier.name = packageName;
+                overuseAction.packageIdentifier.name = genericPackageName;
                 overuseAction.packageIdentifier.uid = stats.uid;
                 overuseAction.resourceTypes = new int[]{ ResourceType.IO };
                 overuseAction.resourceOveruseActionType = NOT_KILLED;
@@ -551,7 +627,7 @@
                  * #2 The package has no recurring overuse behavior and the user opted to not
                  *    kill the package so honor the user's decision.
                  */
-                int killableState = usage.getKillableState();
+                int killableState = usage.getKillableStateLocked();
                 if (killableState == KILLABLE_STATE_NEVER) {
                     mOveruseActionsByUserPackage.add(overuseAction);
                     continue;
@@ -562,39 +638,16 @@
                     mOveruseActionsByUserPackage.add(overuseAction);
                     continue;
                 }
-                try {
-                    int oldEnabledState = -1;
-                    IPackageManager packageManager = ActivityThread.getPackageManager();
-                    if (!hasRecurringOveruse) {
-                        oldEnabledState = packageManager.getApplicationEnabledSetting(packageName,
-                                userId);
-
-                        if (oldEnabledState == COMPONENT_ENABLED_STATE_DISABLED
-                                || oldEnabledState == COMPONENT_ENABLED_STATE_DISABLED_USER
-                                || oldEnabledState == COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
-                            mOveruseActionsByUserPackage.add(overuseAction);
-                            continue;
-                        }
-                    }
-
-                    packageManager.setApplicationEnabledSetting(packageName,
-                            COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, /* flags= */ 0, userId,
-                            mContext.getPackageName());
-
-                    overuseAction.resourceOveruseActionType = hasRecurringOveruse
-                            ? KILLED_RECURRING_OVERUSE : KILLED;
-                    if (!hasRecurringOveruse) {
-                        usage.oldEnabledState = oldEnabledState;
-                    }
-                } catch (RemoteException e) {
-                    Slogf.e(TAG, "Failed to disable application enabled setting for user %d, "
-                            + "package '%s'", userId, packageName);
-                }
-                mOveruseActionsByUserPackage.add(overuseAction);
+                recurringIoOverusesByUid.put(stats.uid, hasRecurringOveruse);
             }
             if (!mOveruseActionsByUserPackage.isEmpty()) {
-                mMainHandler.sendMessage(obtainMessage(
-                        WatchdogPerfHandler::notifyActionsTakenOnOveruse, this));
+                mMainHandler.post(this::notifyActionsTakenOnOveruse);
+            }
+            if (recurringIoOverusesByUid.size() > 0) {
+                mMainHandler.postDelayed(
+                        () -> handleIoOveruseKilling(
+                                recurringIoOverusesByUid, genericPackageNamesByUid),
+                        mResourceOveruseKillingDelayMills);
             }
         }
         if (DEBUG) {
@@ -623,14 +676,83 @@
         }
     }
 
-    /** Resets the resource overuse stats for the given package. */
-    public void resetResourceOveruseStats(Set<String> packageNames) {
+    /** Handle packages that exceed resource overuse thresholds */
+    private void handleIoOveruseKilling(SparseBooleanArray recurringIoOverusesByUid,
+            SparseArray<String> genericPackageNamesByUid) {
+        IPackageManager packageManager = ActivityThread.getPackageManager();
         synchronized (mLock) {
-            for (PackageResourceUsage usage : mUsageByUserPackage.values()) {
-                if (packageNames.contains(usage.packageName)) {
-                    usage.resetStats();
+            for (int i = 0; i < recurringIoOverusesByUid.size(); i++) {
+                int uid = recurringIoOverusesByUid.keyAt(i);
+                boolean hasRecurringOveruse = recurringIoOverusesByUid.valueAt(i);
+                String genericPackageName = genericPackageNamesByUid.get(uid);
+                int userId = UserHandle.getUserId(uid);
+
+                PackageResourceOveruseAction overuseAction = new PackageResourceOveruseAction();
+                overuseAction.packageIdentifier = new PackageIdentifier();
+                overuseAction.packageIdentifier.name = genericPackageName;
+                overuseAction.packageIdentifier.uid = uid;
+                overuseAction.resourceTypes = new int[]{ ResourceType.IO };
+                overuseAction.resourceOveruseActionType = NOT_KILLED;
+
+                String key = getUserPackageUniqueId(userId, genericPackageName);
+                PackageResourceUsage usage = mUsageByUserPackage.get(key);
+                if (usage == null) {
+                    /* This case shouldn't happen but placed here as a fail safe. */
+                    mOveruseActionsByUserPackage.add(overuseAction);
+                    continue;
+                }
+                List<String> packages = Collections.singletonList(genericPackageName);
+                if (usage.isSharedPackage()) {
+                    packages = mPackageInfoHandler.getPackagesForUid(uid, genericPackageName);
+                }
+                for (int pkgIdx = 0; pkgIdx < packages.size(); pkgIdx++) {
+                    String packageName = packages.get(pkgIdx);
+                    try {
+                        int oldEnabledState = -1;
+                        if (!hasRecurringOveruse) {
+                            oldEnabledState = packageManager.getApplicationEnabledSetting(
+                                    packageName, userId);
+                            if (oldEnabledState == COMPONENT_ENABLED_STATE_DISABLED
+                                    || oldEnabledState == COMPONENT_ENABLED_STATE_DISABLED_USER
+                                    || oldEnabledState
+                                    == COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+                                continue;
+                            }
+                        }
+                        packageManager.setApplicationEnabledSetting(packageName,
+                                COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, /* flags= */ 0, userId,
+                                mContext.getPackageName());
+                        overuseAction.resourceOveruseActionType = hasRecurringOveruse
+                                ? KILLED_RECURRING_OVERUSE : KILLED;
+                        if (oldEnabledState != -1) {
+                            usage.oldEnabledStateByPackage.put(packageName, oldEnabledState);
+                        }
+                    } catch (RemoteException e) {
+                        Slogf.e(TAG, "Failed to disable application for user %d, package '%s'",
+                                userId, packageName);
+                    }
+                }
+                if (overuseAction.resourceOveruseActionType == KILLED
+                        || overuseAction.resourceOveruseActionType == KILLED_RECURRING_OVERUSE) {
+                    usage.ioUsage.killed();
+                }
+                mOveruseActionsByUserPackage.add(overuseAction);
+            }
+            if (!mOveruseActionsByUserPackage.isEmpty()) {
+                notifyActionsTakenOnOveruse();
+            }
+        }
+    }
+
+    /** Resets the resource overuse stats for the given generic package names. */
+    public void resetResourceOveruseStats(Set<String> genericPackageNames) {
+        synchronized (mLock) {
+            for (int i = 0; i < mUsageByUserPackage.size(); ++i) {
+                PackageResourceUsage usage = mUsageByUserPackage.valueAt(i);
+                if (genericPackageNames.contains(usage.genericPackageName)) {
+                    usage.resetStatsLocked();
                     /*
-                     * TODO(b/185287136): When the stats are persisted in local DB, reset the stats
+                     * TODO(b/192294393): When the stats are persisted in local DB, reset the stats
                      *  for this package from local DB.
                      */
                 }
@@ -638,22 +760,182 @@
         }
     }
 
+    /** Sets the time source. */
+    public void setTimeSource(TimeSourceInterface timeSource) {
+        synchronized (mLock) {
+            mTimeSource = timeSource;
+        }
+    }
+
+    /** Sets the delay to kill a package after the package is notified of resource overuse. */
+    public void setResourceOveruseKillingDelay(long millis) {
+        synchronized (mLock) {
+            mResourceOveruseKillingDelayMills = millis;
+        }
+    }
+
+    /** Fetches and syncs the resource overuse configurations from watchdog daemon. */
+    private void fetchAndSyncResourceOveruseConfigurations() {
+        synchronized (mLock) {
+            List<android.automotive.watchdog.internal.ResourceOveruseConfiguration> internalConfigs;
+            try {
+                internalConfigs = mCarWatchdogDaemonHelper.getResourceOveruseConfigurations();
+            } catch (RemoteException | RuntimeException e) {
+                Slogf.w(TAG, e, "Failed to fetch resource overuse configurations");
+                return;
+            }
+            if (internalConfigs.isEmpty()) {
+                Slogf.e(TAG, "Fetched resource overuse configurations are empty");
+                return;
+            }
+            mSafeToKillSystemPackages.clear();
+            mSafeToKillVendorPackages.clear();
+            for (int i = 0; i < internalConfigs.size(); i++) {
+                switch (internalConfigs.get(i).componentType) {
+                    case ComponentType.SYSTEM:
+                        mSafeToKillSystemPackages.addAll(internalConfigs.get(i).safeToKillPackages);
+                        break;
+                    case ComponentType.VENDOR:
+                        mSafeToKillVendorPackages.addAll(internalConfigs.get(i).safeToKillPackages);
+                        break;
+                    default:
+                        // All third-party apps are killable.
+                        break;
+                }
+            }
+            if (DEBUG) {
+                Slogf.d(TAG, "Fetched and synced safe to kill packages.");
+            }
+        }
+    }
+
+    private void readFromDatabase() {
+        List<WatchdogStorage.UserPackageSettingsEntry> settingsEntries =
+                mWatchdogStorage.getUserPackageSettings();
+        Slogf.i(TAG, "Read %d user package settings from database", settingsEntries.size());
+        List<WatchdogStorage.IoUsageStatsEntry> ioStatsEntries =
+                mWatchdogStorage.getTodayIoUsageStats();
+        Slogf.i(TAG, "Read %d I/O usage stats from database", ioStatsEntries.size());
+        synchronized (mLock) {
+            for (int i = 0; i < settingsEntries.size(); ++i) {
+                WatchdogStorage.UserPackageSettingsEntry entry = settingsEntries.get(i);
+                if (entry.userId == UserHandle.USER_ALL) {
+                    if (entry.killableState != KILLABLE_STATE_YES) {
+                        mDefaultNotKillableGenericPackages.add(entry.packageName);
+                    }
+                    continue;
+                }
+                String key = getUserPackageUniqueId(entry.userId, entry.packageName);
+                PackageResourceUsage usage = mUsageByUserPackage.get(key);
+                if (usage == null) {
+                    usage = new PackageResourceUsage(entry.userId, entry.packageName);
+                }
+                usage.setKillableStateLocked(entry.killableState);
+                mUsageByUserPackage.put(key, usage);
+            }
+            ZonedDateTime curReportDate =
+                    mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT);
+            for (int i = 0; i < ioStatsEntries.size(); ++i) {
+                WatchdogStorage.IoUsageStatsEntry entry = ioStatsEntries.get(i);
+                String key = getUserPackageUniqueId(entry.userId, entry.packageName);
+                PackageResourceUsage usage = mUsageByUserPackage.get(key);
+                if (usage == null) {
+                    usage = new PackageResourceUsage(entry.userId, entry.packageName);
+                }
+                /* Overwrite in memory cache as the stats will be merged on the daemon side and
+                 * pushed on the next latestIoOveruseStats call. This is tolerable because the next
+                 * push should happen soon.
+                 */
+                usage.ioUsage.overwrite(entry.ioUsage);
+                mUsageByUserPackage.put(key, usage);
+            }
+            if (!ioStatsEntries.isEmpty()) {
+                /* When mLatestStatsReportDate is null, the latest stats push from daemon hasn't
+                 * happened yet. Thus the cached stats contains only the stats read from database.
+                 */
+                mIsWrittenToDatabase = mLatestStatsReportDate == null;
+                mLatestStatsReportDate = curReportDate;
+            }
+        }
+    }
+
+    /** Writes user package settings and stats to database. */
+    public void writeToDatabase() {
+        synchronized (mLock) {
+            if (mIsWrittenToDatabase) {
+                return;
+            }
+            List<WatchdogStorage.UserPackageSettingsEntry>  entries =
+                    new ArrayList<>(mUsageByUserPackage.size());
+            for (int i = 0; i < mUsageByUserPackage.size(); ++i) {
+                PackageResourceUsage usage = mUsageByUserPackage.valueAt(i);
+                entries.add(new WatchdogStorage.UserPackageSettingsEntry(
+                        usage.userId, usage.genericPackageName, usage.getKillableStateLocked()));
+            }
+            for (String packageName : mDefaultNotKillableGenericPackages) {
+                entries.add(new WatchdogStorage.UserPackageSettingsEntry(
+                        UserHandle.USER_ALL, packageName, KILLABLE_STATE_NO));
+            }
+            if (!mWatchdogStorage.saveUserPackageSettings(entries)) {
+                Slogf.e(TAG, "Failed to write user package settings to database");
+            } else {
+                Slogf.i(TAG, "Successfully saved %d user package settings to database",
+                        entries.size());
+            }
+            writeStatsLocked();
+            mIsWrittenToDatabase = true;
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void writeStatsLocked() {
+        List<WatchdogStorage.IoUsageStatsEntry> entries =
+                new ArrayList<>(mUsageByUserPackage.size());
+        for (int i = 0; i < mUsageByUserPackage.size(); ++i) {
+            PackageResourceUsage usage = mUsageByUserPackage.valueAt(i);
+            if (!usage.ioUsage.hasUsage()) {
+                continue;
+            }
+            entries.add(new WatchdogStorage.IoUsageStatsEntry(
+                    usage.userId, usage.genericPackageName, usage.ioUsage));
+        }
+        if (!mWatchdogStorage.saveIoUsageStats(entries)) {
+            Slogf.e(TAG, "Failed to write %d I/O overuse stats to database", entries.size());
+        } else {
+            Slogf.i(TAG, "Successfully saved %d I/O overuse stats to database", entries.size());
+        }
+    }
+
+    @GuardedBy("mLock")
+    private int getPackageKillableStateForUserPackageLocked(
+            int userId, String genericPackageName, int componentType, boolean isSafeToKill) {
+        String key = getUserPackageUniqueId(userId, genericPackageName);
+        PackageResourceUsage usage = mUsageByUserPackage.get(key);
+        if (usage == null) {
+            usage = new PackageResourceUsage(userId, genericPackageName);
+        }
+        int killableState = usage.syncAndFetchKillableStateLocked(componentType, isSafeToKill);
+        mUsageByUserPackage.put(key, usage);
+        return killableState;
+    }
+
+    @GuardedBy("mLock")
     private void notifyResourceOveruseStatsLocked(int uid,
             ResourceOveruseStats resourceOveruseStats) {
-        String packageName = resourceOveruseStats.getPackageName();
+        String genericPackageName = resourceOveruseStats.getPackageName();
         ArrayList<ResourceOveruseListenerInfo> listenerInfos = mOveruseListenerInfosByUid.get(uid);
         if (listenerInfos != null) {
-            for (ResourceOveruseListenerInfo listenerInfo : listenerInfos) {
-                listenerInfo.notifyListener(FLAG_RESOURCE_OVERUSE_IO, uid, packageName,
-                        resourceOveruseStats);
+            for (int i = 0; i < listenerInfos.size(); ++i) {
+                listenerInfos.get(i).notifyListener(
+                        FLAG_RESOURCE_OVERUSE_IO, uid, genericPackageName, resourceOveruseStats);
             }
         }
         for (int i = 0; i < mOveruseSystemListenerInfosByUid.size(); ++i) {
             ArrayList<ResourceOveruseListenerInfo> systemListenerInfos =
                     mOveruseSystemListenerInfosByUid.valueAt(i);
-            for (ResourceOveruseListenerInfo listenerInfo : systemListenerInfos) {
-                listenerInfo.notifyListener(FLAG_RESOURCE_OVERUSE_IO, uid, packageName,
-                        resourceOveruseStats);
+            for (int j = 0; j < systemListenerInfos.size(); ++j) {
+                systemListenerInfos.get(j).notifyListener(
+                        FLAG_RESOURCE_OVERUSE_IO, uid, genericPackageName, resourceOveruseStats);
             }
         }
         if (DEBUG) {
@@ -663,43 +945,55 @@
 
     @GuardedBy("mLock")
     private void checkAndHandleDateChangeLocked() {
-        ZonedDateTime previousUTC = mLastStatsReportUTC;
-        mLastStatsReportUTC = ZonedDateTime.now(ZoneOffset.UTC);
-        if (mLastStatsReportUTC.getDayOfYear() == previousUTC.getDayOfYear()
-                && mLastStatsReportUTC.getYear() == previousUTC.getYear()) {
+        ZonedDateTime currentDate = mTimeSource.now().atZone(ZoneOffset.UTC)
+                .truncatedTo(STATS_TEMPORAL_UNIT);
+        if (currentDate.equals(mLatestStatsReportDate)) {
             return;
         }
-        for (PackageResourceUsage usage : mUsageByUserPackage.values()) {
-            if (usage.oldEnabledState > 0) {
-                // Forgive the daily disabled package on date change.
+        /* After the first database read or on the first stats sync from the daemon, whichever
+         * happens first, the cached stats would either be empty or initialized from the database.
+         * In either case, don't write to database.
+         */
+        if (mLatestStatsReportDate != null && !mIsWrittenToDatabase) {
+            writeStatsLocked();
+        }
+        for (int i = 0; i < mUsageByUserPackage.size(); ++i) {
+            PackageResourceUsage usage = mUsageByUserPackage.valueAt(i);
+            // Forgive the daily disabled package on date change.
+            for (Map.Entry<String, Integer> entry : usage.oldEnabledStateByPackage.entrySet()) {
                 try {
                     IPackageManager packageManager = ActivityThread.getPackageManager();
-                    if (packageManager.getApplicationEnabledSetting(usage.packageName,
+                    if (packageManager.getApplicationEnabledSetting(entry.getKey(),
                             usage.userId)
                             != COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
                         continue;
                     }
-                    packageManager.setApplicationEnabledSetting(usage.packageName,
-                            usage.oldEnabledState,
+                    packageManager.setApplicationEnabledSetting(entry.getKey(),
+                            entry.getValue(),
                             /* flags= */ 0, usage.userId, mContext.getPackageName());
                 } catch (RemoteException e) {
-                    Slogf.e(TAG, "Failed to reset enabled setting for disabled package '%s', user "
-                            + "%d", usage.packageName, usage.userId);
+                    Slogf.e(TAG,
+                            "Failed to reset enabled setting for disabled package '%s', user '%d'",
+                                    usage.genericPackageName, usage.userId);
                 }
             }
-            /* TODO(b/170741935): Stash the old usage into SQLite DB storage. */
-            usage.resetStats();
+            usage.resetStatsLocked();
         }
+        mLatestStatsReportDate = currentDate;
         if (DEBUG) {
             Slogf.d(TAG, "Handled date change successfully");
         }
     }
 
-    private PackageResourceUsage cacheAndFetchUsageLocked(@UserIdInt int userId, String packageName,
+    @GuardedBy("mLock")
+    private PackageResourceUsage cacheAndFetchUsageLocked(
+            @UserIdInt int userId, String genericPackageName,
             android.automotive.watchdog.IoOveruseStats internalStats) {
-        String key = getUserPackageUniqueId(userId, packageName);
-        PackageResourceUsage usage = mUsageByUserPackage.getOrDefault(key,
-                new PackageResourceUsage(userId, packageName));
+        String key = getUserPackageUniqueId(userId, genericPackageName);
+        PackageResourceUsage usage = mUsageByUserPackage.get(key);
+        if (usage == null) {
+            usage = new PackageResourceUsage(userId, genericPackageName);
+        }
         usage.updateLocked(internalStats);
         mUsageByUserPackage.put(key, usage);
         return usage;
@@ -708,32 +1002,64 @@
     @GuardedBy("mLock")
     private boolean isRecurringOveruseLocked(PackageResourceUsage ioUsage) {
         /*
-         * TODO(b/185287136): Look up I/O overuse history and determine whether or not the package
+         * TODO(b/192294393): Look up I/O overuse history and determine whether or not the package
          *  has recurring I/O overuse behavior.
          */
         return false;
     }
 
-    private IoOveruseStats getIoOveruseStats(int userId, String packageName,
+    private IoOveruseStats getIoOveruseStatsForPeriod(int userId, String genericPackageName,
+            @CarWatchdogManager.StatsPeriod int maxStatsPeriod) {
+        synchronized (mLock) {
+            String key = getUserPackageUniqueId(userId, genericPackageName);
+            PackageResourceUsage usage = mUsageByUserPackage.get(key);
+            if (usage == null) {
+                return null;
+            }
+            return getIoOveruseStatsLocked(usage, /* minimumBytesWritten= */ 0, maxStatsPeriod);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private IoOveruseStats getIoOveruseStatsLocked(PackageResourceUsage usage,
             long minimumBytesWritten, @CarWatchdogManager.StatsPeriod int maxStatsPeriod) {
-        String key = getUserPackageUniqueId(userId, packageName);
-        PackageResourceUsage usage = mUsageByUserPackage.get(key);
-        if (usage == null) {
+        if (!usage.ioUsage.hasUsage()) {
+            /* Return I/O overuse stats only when the package has usage for the current day.
+             * Without the current day usage, the returned stats will contain zero remaining
+             * bytes, which is incorrect.
+             */
             return null;
         }
-        IoOveruseStats stats = usage.getIoOveruseStats();
-        long totalBytesWritten = stats != null ? stats.getTotalBytesWritten() : 0;
-        /*
-         * TODO(b/185431129): When maxStatsPeriod > current day, populate the historical stats
-         *  from the local database. Also handle the case where the package doesn't have current
-         *  day stats but has historical stats.
-         */
+        IoOveruseStats currentStats = usage.getIoOveruseStatsLocked();
+        long totalBytesWritten = currentStats.getTotalBytesWritten();
+        int numDays = toNumDays(maxStatsPeriod);
+        IoOveruseStats historyStats = null;
+        if (numDays > 0) {
+            historyStats = mWatchdogStorage.getHistoricalIoOveruseStats(
+                    usage.userId, usage.genericPackageName, numDays - 1);
+            totalBytesWritten += historyStats != null ? historyStats.getTotalBytesWritten() : 0;
+        }
         if (totalBytesWritten < minimumBytesWritten) {
             return null;
         }
-        return stats;
+        if (historyStats == null) {
+            return currentStats;
+        }
+        IoOveruseStats.Builder statsBuilder = new IoOveruseStats.Builder(
+                historyStats.getStartTime(),
+                historyStats.getDurationInSeconds() + currentStats.getDurationInSeconds());
+        statsBuilder.setTotalTimesKilled(
+                historyStats.getTotalTimesKilled() + currentStats.getTotalTimesKilled());
+        statsBuilder.setTotalOveruses(
+                historyStats.getTotalOveruses() + currentStats.getTotalOveruses());
+        statsBuilder.setTotalBytesWritten(
+                historyStats.getTotalBytesWritten() + currentStats.getTotalBytesWritten());
+        statsBuilder.setKillableOnOveruse(currentStats.isKillableOnOveruse());
+        statsBuilder.setRemainingWriteBytes(currentStats.getRemainingWriteBytes());
+        return statsBuilder.build();
     }
 
+    @GuardedBy("mLock")
     private void addResourceOveruseListenerLocked(
             @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag,
             @NonNull IResourceOveruseListener listener,
@@ -750,8 +1076,8 @@
             listenerInfos = new ArrayList<>();
             listenerInfosByUid.put(callingUid, listenerInfos);
         }
-        for (ResourceOveruseListenerInfo listenerInfo : listenerInfos) {
-            if (listenerInfo.listener.asBinder() == binder) {
+        for (int i = 0; i < listenerInfos.size(); ++i) {
+            if (listenerInfos.get(i).listener.asBinder() == binder) {
                 throw new IllegalStateException(
                         "Cannot add " + listenerType + " as it is already added");
             }
@@ -772,6 +1098,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void removeResourceOveruseListenerLocked(@NonNull IResourceOveruseListener listener,
             SparseArray<ArrayList<ResourceOveruseListenerInfo>> listenerInfosByUid) {
         int callingUid = Binder.getCallingUid();
@@ -784,9 +1111,9 @@
         }
         IBinder binder = listener.asBinder();
         ResourceOveruseListenerInfo cachedListenerInfo = null;
-        for (ResourceOveruseListenerInfo listenerInfo : listenerInfos) {
-            if (listenerInfo.listener.asBinder() == binder) {
-                cachedListenerInfo = listenerInfo;
+        for (int i = 0; i < listenerInfos.size(); ++i) {
+            if (listenerInfos.get(i).listener.asBinder() == binder) {
+                cachedListenerInfo = listenerInfos.get(i);
                 break;
             }
         }
@@ -844,6 +1171,7 @@
         boolean doClearPendingRequest = isPendingRequest;
         try {
             mCarWatchdogDaemonHelper.updateResourceOveruseConfigurations(configs);
+            mMainHandler.post(this::fetchAndSyncResourceOveruseConfigurations);
         } catch (RemoteException e) {
             if (e instanceof TransactionTooLargeException) {
                 throw e;
@@ -861,7 +1189,6 @@
                 }
             }
         }
-        /* TODO(b/185287136): Fetch safe-to-kill list from daemon and update mSafeToKillPackages. */
         if (DEBUG) {
             Slogf.d(TAG, "Set the resource overuse configuration successfully");
         }
@@ -887,20 +1214,59 @@
         }
     }
 
-    private static String getUserPackageUniqueId(int userId, String packageName) {
-        return String.valueOf(userId) + ":" + packageName;
+    @GuardedBy("mLock")
+    private boolean isSafeToKillLocked(String genericPackageName, int componentType,
+            List<String> sharedPackages) {
+        BiFunction<List<String>, Set<String>, Boolean> isSafeToKillAnyPackage =
+                (packages, safeToKillPackages) -> {
+                    if (packages == null) {
+                        return false;
+                    }
+                    for (int i = 0; i < packages.size(); i++) {
+                        if (safeToKillPackages.contains(packages.get(i))) {
+                            return true;
+                        }
+                    }
+                    return false;
+                };
+
+        switch (componentType) {
+            case ComponentType.SYSTEM:
+                if (mSafeToKillSystemPackages.contains(genericPackageName)) {
+                    return true;
+                }
+                return isSafeToKillAnyPackage.apply(sharedPackages, mSafeToKillSystemPackages);
+            case ComponentType.VENDOR:
+                if (mSafeToKillVendorPackages.contains(genericPackageName)) {
+                    return true;
+                }
+                /*
+                 * Packages under the vendor shared UID may contain system packages because when
+                 * CarWatchdogService derives the shared component type it attributes system
+                 * packages as vendor packages when there is at least one vendor package.
+                 */
+                return isSafeToKillAnyPackage.apply(sharedPackages, mSafeToKillSystemPackages)
+                        || isSafeToKillAnyPackage.apply(sharedPackages, mSafeToKillVendorPackages);
+            default:
+                // Third-party apps are always killable
+                return true;
+        }
+    }
+
+    private static String getUserPackageUniqueId(int userId, String genericPackageName) {
+        return String.valueOf(userId) + ":" + genericPackageName;
     }
 
     @VisibleForTesting
     static IoOveruseStats.Builder toIoOveruseStatsBuilder(
-            android.automotive.watchdog.IoOveruseStats internalStats) {
-        IoOveruseStats.Builder statsBuilder = new IoOveruseStats.Builder(
-                internalStats.startTime, internalStats.durationInSeconds);
-        statsBuilder.setRemainingWriteBytes(
-                toPerStateBytes(internalStats.remainingWriteBytes));
-        statsBuilder.setTotalBytesWritten(totalPerStateBytes(internalStats.writtenBytes));
-        statsBuilder.setTotalOveruses(internalStats.totalOveruses);
-        return statsBuilder;
+            android.automotive.watchdog.IoOveruseStats internalStats,
+            int totalTimesKilled, boolean isKillableOnOveruses) {
+        return new IoOveruseStats.Builder(internalStats.startTime, internalStats.durationInSeconds)
+                .setTotalOveruses(internalStats.totalOveruses)
+                .setTotalTimesKilled(totalTimesKilled)
+                .setTotalBytesWritten(totalPerStateBytes(internalStats.writtenBytes))
+                .setKillableOnOveruse(isKillableOnOveruses)
+                .setRemainingWriteBytes(toPerStateBytes(internalStats.remainingWriteBytes));
     }
 
     private static PerStateBytes toPerStateBytes(
@@ -958,6 +1324,8 @@
                     metadata.appCategoryType = ApplicationCategoryType.MEDIA;
                     break;
                 default:
+                    Slogf.i(TAG, "Invalid application category type: %s skipping package: %s",
+                            entry.getValue(), metadata.packageName);
                     continue;
             }
             internalConfig.packageMetadata.add(metadata);
@@ -982,7 +1350,8 @@
                 config.getPackageSpecificThresholds());
         internalConfig.categorySpecificThresholds = toPerStateIoOveruseThresholds(
                 config.getAppCategorySpecificThresholds());
-        for (PerStateIoOveruseThreshold threshold : internalConfig.categorySpecificThresholds) {
+        for (int i = 0; i < internalConfig.categorySpecificThresholds.size(); ++i) {
+            PerStateIoOveruseThreshold threshold = internalConfig.categorySpecificThresholds.get(i);
             switch(threshold.name) {
                 case ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MAPS:
                     threshold.name = INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS;
@@ -1020,7 +1389,7 @@
             Map<String, PerStateBytes> thresholds) {
         List<PerStateIoOveruseThreshold> internalThresholds = new ArrayList<>();
         for (Map.Entry<String, PerStateBytes> entry : thresholds.entrySet()) {
-            if (!entry.getKey().isEmpty()) {
+            if (!thresholds.isEmpty()) {
                 internalThresholds.add(toPerStateIoOveruseThreshold(entry.getKey(),
                         entry.getValue()));
             }
@@ -1043,15 +1412,15 @@
             toInternalIoOveruseAlertThresholds(List<IoOveruseAlertThreshold> thresholds) {
         List<android.automotive.watchdog.internal.IoOveruseAlertThreshold> internalThresholds =
                 new ArrayList<>();
-        for (IoOveruseAlertThreshold threshold : thresholds) {
-            if (threshold.getDurationInSeconds() == 0
-                    || threshold.getWrittenBytesPerSecond() == 0) {
+        for (int i = 0; i < thresholds.size(); ++i) {
+            if (thresholds.get(i).getDurationInSeconds() == 0
+                    || thresholds.get(i).getWrittenBytesPerSecond() == 0) {
                 continue;
             }
             android.automotive.watchdog.internal.IoOveruseAlertThreshold internalThreshold =
                     new android.automotive.watchdog.internal.IoOveruseAlertThreshold();
-            internalThreshold.durationInSeconds = threshold.getDurationInSeconds();
-            internalThreshold.writtenBytesPerSecond = threshold.getWrittenBytesPerSecond();
+            internalThreshold.durationInSeconds = thresholds.get(i).getDurationInSeconds();
+            internalThreshold.writtenBytesPerSecond = thresholds.get(i).getWrittenBytesPerSecond();
             internalThresholds.add(internalThreshold);
         }
         return internalThresholds;
@@ -1060,10 +1429,10 @@
     private static ResourceOveruseConfiguration toResourceOveruseConfiguration(
             android.automotive.watchdog.internal.ResourceOveruseConfiguration internalConfig,
             @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag) {
-        Map<String, String> packagesToAppCategoryTypes = new ArrayMap<>();
-        for (PackageMetadata metadata : internalConfig.packageMetadata) {
+        ArrayMap<String, String> packagesToAppCategoryTypes = new ArrayMap<>();
+        for (int i = 0; i < internalConfig.packageMetadata.size(); ++i) {
             String categoryTypeStr;
-            switch (metadata.appCategoryType) {
+            switch (internalConfig.packageMetadata.get(i).appCategoryType) {
                 case ApplicationCategoryType.MAPS:
                     categoryTypeStr = ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MAPS;
                     break;
@@ -1073,7 +1442,8 @@
                 default:
                     continue;
             }
-            packagesToAppCategoryTypes.put(metadata.packageName, categoryTypeStr);
+            packagesToAppCategoryTypes.put(
+                    internalConfig.packageMetadata.get(i).packageName, categoryTypeStr);
         }
         ResourceOveruseConfiguration.Builder configBuilder =
                 new ResourceOveruseConfiguration.Builder(
@@ -1095,15 +1465,23 @@
 
     private static IoOveruseConfiguration toIoOveruseConfiguration(
             android.automotive.watchdog.internal.IoOveruseConfiguration internalConfig) {
+        TriConsumer<Map<String, PerStateBytes>, String, String> replaceKey =
+                (map, oldKey, newKey) -> {
+                    PerStateBytes perStateBytes = map.get(oldKey);
+                    if (perStateBytes != null) {
+                        map.put(newKey, perStateBytes);
+                        map.remove(oldKey);
+                    }
+                };
         PerStateBytes componentLevelThresholds =
                 toPerStateBytes(internalConfig.componentLevelThresholds.perStateWriteBytes);
-        Map<String, PerStateBytes> packageSpecificThresholds =
+        ArrayMap<String, PerStateBytes> packageSpecificThresholds =
                 toPerStateBytesMap(internalConfig.packageSpecificThresholds);
-        Map<String, PerStateBytes> appCategorySpecificThresholds =
+        ArrayMap<String, PerStateBytes> appCategorySpecificThresholds =
                 toPerStateBytesMap(internalConfig.categorySpecificThresholds);
-        replaceKey(appCategorySpecificThresholds, INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS,
+        replaceKey.accept(appCategorySpecificThresholds, INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS,
                 ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MAPS);
-        replaceKey(appCategorySpecificThresholds, INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA,
+        replaceKey.accept(appCategorySpecificThresholds, INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA,
                 ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MEDIA);
         List<IoOveruseAlertThreshold> systemWideThresholds =
                 toIoOveruseAlertThresholds(internalConfig.systemWideThresholds);
@@ -1114,11 +1492,12 @@
         return configBuilder.build();
     }
 
-    private static Map<String, PerStateBytes> toPerStateBytesMap(
+    private static ArrayMap<String, PerStateBytes> toPerStateBytesMap(
             List<PerStateIoOveruseThreshold> thresholds) {
-        Map<String, PerStateBytes> thresholdsMap = new ArrayMap<>();
-        for (PerStateIoOveruseThreshold threshold : thresholds) {
-            thresholdsMap.put(threshold.name, toPerStateBytes(threshold.perStateWriteBytes));
+        ArrayMap<String, PerStateBytes> thresholdsMap = new ArrayMap<>();
+        for (int i = 0; i < thresholds.size(); ++i) {
+            thresholdsMap.put(
+                    thresholds.get(i).name, toPerStateBytes(thresholds.get(i).perStateWriteBytes));
         }
         return thresholdsMap;
     }
@@ -1126,14 +1505,79 @@
     private static List<IoOveruseAlertThreshold> toIoOveruseAlertThresholds(
             List<android.automotive.watchdog.internal.IoOveruseAlertThreshold> internalThresholds) {
         List<IoOveruseAlertThreshold> thresholds = new ArrayList<>();
-        for (android.automotive.watchdog.internal.IoOveruseAlertThreshold internalThreshold
-                : internalThresholds) {
-            thresholds.add(new IoOveruseAlertThreshold(internalThreshold.durationInSeconds,
-                    internalThreshold.writtenBytesPerSecond));
+        for (int i = 0; i < internalThresholds.size(); ++i) {
+            thresholds.add(new IoOveruseAlertThreshold(internalThresholds.get(i).durationInSeconds,
+                    internalThresholds.get(i).writtenBytesPerSecond));
         }
         return thresholds;
     }
 
+    private static void checkResourceOveruseConfigs(
+            List<ResourceOveruseConfiguration> configurations,
+            @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag) {
+        ArraySet<Integer> seenComponentTypes = new ArraySet<>();
+        for (int i = 0; i < configurations.size(); ++i) {
+            ResourceOveruseConfiguration config = configurations.get(i);
+            if (seenComponentTypes.contains(config.getComponentType())) {
+                throw new IllegalArgumentException(
+                        "Cannot provide duplicate configurations for the same component type");
+            }
+            checkResourceOveruseConfig(config, resourceOveruseFlag);
+            seenComponentTypes.add(config.getComponentType());
+        }
+    }
+
+    private static void checkResourceOveruseConfig(ResourceOveruseConfiguration config,
+            @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag) {
+        int componentType = config.getComponentType();
+        if (toComponentTypeStr(componentType).equals("UNKNOWN")) {
+            throw new IllegalArgumentException(
+                    "Invalid component type in the configuration: " + componentType);
+        }
+        if ((resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) != 0
+                && config.getIoOveruseConfiguration() == null) {
+            throw new IllegalArgumentException("Must provide I/O overuse configuration");
+        }
+        checkIoOveruseConfig(config.getIoOveruseConfiguration(), componentType);
+    }
+
+    private static void checkIoOveruseConfig(IoOveruseConfiguration config, int componentType) {
+        if (config.getComponentLevelThresholds().getBackgroundModeBytes() <= 0
+                || config.getComponentLevelThresholds().getForegroundModeBytes() <= 0
+                || config.getComponentLevelThresholds().getGarageModeBytes() <= 0) {
+            throw new IllegalArgumentException(
+                    "For component: " + toComponentTypeStr(componentType)
+                            + " some thresholds are zero for: "
+                            + config.getComponentLevelThresholds().toString());
+        }
+        if (componentType == ComponentType.SYSTEM) {
+            List<IoOveruseAlertThreshold> systemThresholds = config.getSystemWideThresholds();
+            if (systemThresholds.isEmpty()) {
+                throw new IllegalArgumentException(
+                        "Empty system-wide alert thresholds provided in "
+                                + toComponentTypeStr(componentType)
+                                + " config.");
+            }
+            for (int i = 0; i < systemThresholds.size(); i++) {
+                checkIoOveruseAlertThreshold(systemThresholds.get(i));
+            }
+        }
+    }
+
+    private static void checkIoOveruseAlertThreshold(
+            IoOveruseAlertThreshold ioOveruseAlertThreshold) {
+        if (ioOveruseAlertThreshold.getDurationInSeconds() <= 0) {
+            throw new IllegalArgumentException(
+                    "System wide threshold duration must be greater than zero for: "
+                            + ioOveruseAlertThreshold);
+        }
+        if (ioOveruseAlertThreshold.getWrittenBytesPerSecond() <= 0) {
+            throw new IllegalArgumentException(
+                    "System wide threshold written bytes per second must be greater than zero for: "
+                            + ioOveruseAlertThreshold);
+        }
+    }
+
     private static void replaceKey(Map<String, PerStateBytes> map, String oldKey, String newKey) {
         PerStateBytes perStateBytes = map.get(oldKey);
         if (perStateBytes != null) {
@@ -1142,24 +1586,47 @@
         }
     }
 
-    private final class PackageResourceUsage {
-        public final String packageName;
-        public @UserIdInt final int userId;
-        public final PackageIoUsage ioUsage;
-        public int oldEnabledState;
+    private static int toNumDays(@CarWatchdogManager.StatsPeriod int maxStatsPeriod) {
+        switch(maxStatsPeriod) {
+            case STATS_PERIOD_CURRENT_DAY:
+                return 0;
+            case STATS_PERIOD_PAST_3_DAYS:
+                return 3;
+            case STATS_PERIOD_PAST_7_DAYS:
+                return 7;
+            case STATS_PERIOD_PAST_15_DAYS:
+                return 15;
+            case STATS_PERIOD_PAST_30_DAYS:
+                return 30;
+            default:
+                throw new IllegalArgumentException(
+                        "Invalid max stats period provided: " + maxStatsPeriod);
+        }
+    }
 
+    private final class PackageResourceUsage {
+        public final String genericPackageName;
+        public @UserIdInt final int userId;
+        @GuardedBy("mLock")
+        public final PackageIoUsage ioUsage = new PackageIoUsage();
+        @GuardedBy("mLock")
+        public final ArrayMap<String, Integer> oldEnabledStateByPackage = new ArrayMap<>();
+        @GuardedBy("mLock")
         private @KillableState int mKillableState;
 
         /** Must be called only after acquiring {@link mLock} */
-        PackageResourceUsage(@UserIdInt int userId, String packageName) {
-            this.packageName = packageName;
+        PackageResourceUsage(@UserIdInt int userId, String genericPackageName) {
+            this.genericPackageName = genericPackageName;
             this.userId = userId;
-            this.ioUsage = new PackageIoUsage();
-            this.oldEnabledState = -1;
-            this.mKillableState = mDefaultNotKillablePackages.contains(packageName)
+            this.mKillableState = mDefaultNotKillableGenericPackages.contains(genericPackageName)
                     ? KILLABLE_STATE_NO : KILLABLE_STATE_YES;
         }
 
+        public boolean isSharedPackage() {
+            return this.genericPackageName.startsWith(SHARED_PACKAGE_PREFIX);
+        }
+
+        @GuardedBy("mLock")
         public void updateLocked(android.automotive.watchdog.IoOveruseStats internalStats) {
             if (!internalStats.killableOnOveruse) {
                 /*
@@ -1175,29 +1642,36 @@
                  * This case happens when a previously unsafe to kill system/vendor package was
                  * recently marked as safe-to-kill so update the old state to the default value.
                  */
-                mKillableState = mDefaultNotKillablePackages.contains(packageName)
+                mKillableState = mDefaultNotKillableGenericPackages.contains(genericPackageName)
                         ? KILLABLE_STATE_NO : KILLABLE_STATE_YES;
             }
             ioUsage.update(internalStats);
         }
 
         public ResourceOveruseStats.Builder getResourceOveruseStatsBuilder() {
-            return new ResourceOveruseStats.Builder(packageName, UserHandle.of(userId));
+            return new ResourceOveruseStats.Builder(genericPackageName, UserHandle.of(userId));
         }
 
-        public IoOveruseStats getIoOveruseStats() {
+        @GuardedBy("mLock")
+        public IoOveruseStats getIoOveruseStatsLocked() {
             if (!ioUsage.hasUsage()) {
                 return null;
             }
-            return ioUsage.getStatsBuilder().setKillableOnOveruse(
-                        mKillableState != KILLABLE_STATE_NEVER).build();
+            return ioUsage.getIoOveruseStats(mKillableState != KILLABLE_STATE_NEVER);
         }
 
-        public @KillableState int getKillableState() {
+        @GuardedBy("mLock")
+        public @KillableState int getKillableStateLocked() {
             return mKillableState;
         }
 
-        public boolean setKillableState(boolean isKillable) {
+        @GuardedBy("mLock")
+        public void setKillableStateLocked(@KillableState int killableState) {
+            mKillableState = killableState;
+        }
+
+        @GuardedBy("mLock")
+        public boolean verifyAndSetKillableStateLocked(boolean isKillable) {
             if (mKillableState == KILLABLE_STATE_NEVER) {
                 return false;
             }
@@ -1205,43 +1679,72 @@
             return true;
         }
 
-        public int syncAndFetchKillableStateLocked(int myComponentType) {
+        @GuardedBy("mLock")
+        public int syncAndFetchKillableStateLocked(int myComponentType, boolean isSafeToKill) {
             /*
              * The killable state goes out-of-sync:
-             * 1. When the on-device safe-to-kill list is recently updated and the user package
+             * 1. When the on-device safe-to-kill list was recently updated and the user package
              * didn't have any resource usage so the native daemon didn't update the killable state.
              * 2. When a package has no resource usage and is initialized outside of processing the
              * latest resource usage stats.
              */
-            if (myComponentType != ComponentType.THIRD_PARTY
-                    && !mSafeToKillPackages.contains(packageName)) {
+            if (myComponentType != ComponentType.THIRD_PARTY && !isSafeToKill) {
                 mKillableState = KILLABLE_STATE_NEVER;
             } else if (mKillableState == KILLABLE_STATE_NEVER) {
-                mKillableState = mDefaultNotKillablePackages.contains(packageName)
+                mKillableState = mDefaultNotKillableGenericPackages.contains(genericPackageName)
                         ? KILLABLE_STATE_NO : KILLABLE_STATE_YES;
             }
             return mKillableState;
         }
 
-        public void resetStats() {
+        @GuardedBy("mLock")
+        public void resetStatsLocked() {
+            oldEnabledStateByPackage.clear();
             ioUsage.resetStats();
         }
     }
-
-    private static final class PackageIoUsage {
+    /** Defines I/O usage fields for a package. */
+    public static final class PackageIoUsage {
         private android.automotive.watchdog.IoOveruseStats mIoOveruseStats;
         private android.automotive.watchdog.PerStateBytes mForgivenWriteBytes;
-        private long mTotalTimesKilled;
+        private int mTotalTimesKilled;
 
-        PackageIoUsage() {
+        private PackageIoUsage() {
+            mForgivenWriteBytes = new android.automotive.watchdog.PerStateBytes();
             mTotalTimesKilled = 0;
         }
 
-        public boolean hasUsage() {
+        public PackageIoUsage(android.automotive.watchdog.IoOveruseStats ioOveruseStats,
+                android.automotive.watchdog.PerStateBytes forgivenWriteBytes,
+                int totalTimesKilled) {
+            mIoOveruseStats = ioOveruseStats;
+            mForgivenWriteBytes = forgivenWriteBytes;
+            mTotalTimesKilled = totalTimesKilled;
+        }
+
+        public android.automotive.watchdog.IoOveruseStats getInternalIoOveruseStats() {
+            return mIoOveruseStats;
+        }
+
+        public android.automotive.watchdog.PerStateBytes getForgivenWriteBytes() {
+            return mForgivenWriteBytes;
+        }
+
+        public int getTotalTimesKilled() {
+            return mTotalTimesKilled;
+        }
+
+        boolean hasUsage() {
             return mIoOveruseStats != null;
         }
 
-        public void update(android.automotive.watchdog.IoOveruseStats internalStats) {
+        void overwrite(PackageIoUsage ioUsage) {
+            mIoOveruseStats = ioUsage.mIoOveruseStats;
+            mForgivenWriteBytes = ioUsage.mForgivenWriteBytes;
+            mTotalTimesKilled = ioUsage.mTotalTimesKilled;
+        }
+
+        void update(android.automotive.watchdog.IoOveruseStats internalStats) {
             mIoOveruseStats = internalStats;
             if (exceedsThreshold()) {
                 /*
@@ -1254,13 +1757,11 @@
             }
         }
 
-        public IoOveruseStats.Builder getStatsBuilder() {
-            IoOveruseStats.Builder statsBuilder = toIoOveruseStatsBuilder(mIoOveruseStats);
-            statsBuilder.setTotalTimesKilled(mTotalTimesKilled);
-            return statsBuilder;
+        IoOveruseStats getIoOveruseStats(boolean isKillable) {
+            return toIoOveruseStatsBuilder(mIoOveruseStats, mTotalTimesKilled, isKillable).build();
         }
 
-        public boolean exceedsThreshold() {
+        boolean exceedsThreshold() {
             if (!hasUsage()) {
                 return false;
             }
@@ -1270,7 +1771,11 @@
                     || remaining.garageModeBytes == 0;
         }
 
-        public void resetStats() {
+        void killed() {
+            ++mTotalTimesKilled;
+        }
+
+        void resetStats() {
             mIoOveruseStats = null;
             mForgivenWriteBytes = null;
             mTotalTimesKilled = 0;
@@ -1319,7 +1824,7 @@
         }
 
         public void notifyListener(@CarWatchdogManager.ResourceOveruseFlag int resourceType,
-                int overusingUid, String overusingPackage,
+                int overusingUid, String overusingGenericPackageName,
                 ResourceOveruseStats resourceOveruseStats) {
             if ((flag & resourceType) == 0) {
                 return;
@@ -1328,9 +1833,9 @@
                 listener.onOveruse(resourceOveruseStats);
             } catch (RemoteException e) {
                 Slogf.e(TAG, "Failed to notify %s (uid %d, pid: %d) of resource overuse by "
-                                + "package(uid %d, package '%s'): %s",
+                                + "package(uid %d, generic package name '%s'): %s",
                         (isListenerForSystem ? "system listener" : "listener"), uid, pid,
-                        overusingUid, overusingPackage, e);
+                        overusingUid, overusingGenericPackageName, e);
             }
         }
 
diff --git a/service/src/com/android/car/watchdog/WatchdogProcessHandler.java b/service/src/com/android/car/watchdog/WatchdogProcessHandler.java
index cce3d15..63aa135 100644
--- a/service/src/com/android/car/watchdog/WatchdogProcessHandler.java
+++ b/service/src/com/android/car/watchdog/WatchdogProcessHandler.java
@@ -20,8 +20,6 @@
 import static android.car.watchdog.CarWatchdogManager.TIMEOUT_MODERATE;
 import static android.car.watchdog.CarWatchdogManager.TIMEOUT_NORMAL;
 
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.automotive.watchdog.internal.ICarWatchdogServiceForSystem;
@@ -218,8 +216,7 @@
 
     /** Posts health check message */
     public void postHealthCheckMessage(int sessionId) {
-        mMainHandler.sendMessage(obtainMessage(
-                WatchdogProcessHandler::doHealthCheck, this, sessionId));
+        mMainHandler.post(() -> doHealthCheck(sessionId));
     }
 
     /** Returns the registered and alive client count. */
@@ -240,6 +237,16 @@
         }
     }
 
+    /** Enables/disables the watchdog daemon client health check process. */
+    void controlProcessHealthCheck(boolean disable) {
+        try {
+            mCarWatchdogDaemonHelper.controlProcessHealthCheck(disable);
+        } catch (RemoteException e) {
+            Slogf.w(CarWatchdogService.TAG,
+                    "Cannot enable/disable the car watchdog daemon health check process: %s", e);
+        }
+    }
+
     private void onClientDeath(ICarWatchdogServiceCallback client, int timeout) {
         synchronized (mLock) {
             removeClientLocked(client.asBinder(), timeout);
@@ -312,8 +319,8 @@
             }
         }
         sendPingToClients(timeout);
-        mMainHandler.sendMessageDelayed(obtainMessage(WatchdogProcessHandler::analyzeClientResponse,
-                this, timeout), timeoutToDurationMs(timeout));
+        mMainHandler.postDelayed(
+                () -> analyzeClientResponse(timeout), timeoutToDurationMs(timeout));
     }
 
     private int getNewSessionId() {
diff --git a/service/src/com/android/car/watchdog/WatchdogStorage.java b/service/src/com/android/car/watchdog/WatchdogStorage.java
new file mode 100644
index 0000000..46445a5
--- /dev/null
+++ b/service/src/com/android/car/watchdog/WatchdogStorage.java
@@ -0,0 +1,657 @@
+/*
+ * 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.watchdog;
+
+import static com.android.car.watchdog.CarWatchdogService.SYSTEM_INSTANCE;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.automotive.watchdog.PerStateBytes;
+import android.car.watchdog.IoOveruseStats;
+import android.car.watchdog.PackageKillableState.KillableState;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.os.Process;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.car.CarLog;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.utils.Slogf;
+
+import java.time.Instant;
+import java.time.Period;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalUnit;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Defines the database to store/retrieve system resource stats history from local storage.
+ */
+public final class WatchdogStorage {
+    private static final String TAG = CarLog.tagFor(WatchdogStorage.class);
+    private static final int RETENTION_PERIOD_IN_DAYS = 30;
+    /* Stats are stored on a daily basis. */
+    public static final TemporalUnit STATS_TEMPORAL_UNIT = ChronoUnit.DAYS;
+    /* Number of days to retain the stats in local storage. */
+    public static final Period RETENTION_PERIOD =
+            Period.ofDays(RETENTION_PERIOD_IN_DAYS).normalized();
+    /* Zone offset for all date based table entries. */
+    public static final ZoneOffset ZONE_OFFSET = ZoneOffset.UTC;
+
+    private final WatchdogDbHelper mDbHelper;
+    private final ArrayMap<String, UserPackage> mUserPackagesByKey = new ArrayMap<>();
+    private final ArrayMap<String, UserPackage> mUserPackagesById = new ArrayMap<>();
+    private TimeSourceInterface mTimeSource = SYSTEM_INSTANCE;
+
+    public WatchdogStorage(Context context) {
+        this(context, /* useDataSystemCarDir= */ true);
+    }
+
+    @VisibleForTesting
+    WatchdogStorage(Context context, boolean useDataSystemCarDir) {
+        mDbHelper = new WatchdogDbHelper(context, useDataSystemCarDir);
+    }
+
+    /** Releases resources. */
+    public void release() {
+        mDbHelper.close();
+    }
+
+    /** Handles database shrink. */
+    public void shrinkDatabase() {
+        try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) {
+            mDbHelper.onShrink(db);
+        }
+    }
+
+    /** Saves the given user package settings entries and returns whether the change succeeded. */
+    public boolean saveUserPackageSettings(List<UserPackageSettingsEntry> entries) {
+        List<ContentValues> rows = new ArrayList<>(entries.size());
+        /* Capture only unique user ids. */
+        ArraySet<Integer> usersWithMissingIds = new ArraySet<>();
+        for (int i = 0; i < entries.size(); ++i) {
+            UserPackageSettingsEntry entry = entries.get(i);
+            if (mUserPackagesByKey.get(UserPackage.getKey(entry.userId, entry.packageName))
+                    == null) {
+                usersWithMissingIds.add(entry.userId);
+            }
+            rows.add(UserPackageSettingsTable.getContentValues(entry));
+        }
+        try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) {
+            if (!atomicReplaceEntries(db, UserPackageSettingsTable.TABLE_NAME, rows)) {
+                return false;
+            }
+            populateUserPackages(db, usersWithMissingIds);
+        }
+        return true;
+    }
+
+    /** Returns the user package setting entries. */
+    public List<UserPackageSettingsEntry> getUserPackageSettings() {
+        ArrayMap<String, UserPackageSettingsEntry> entriesById;
+        try (SQLiteDatabase db = mDbHelper.getReadableDatabase()) {
+            entriesById = UserPackageSettingsTable.querySettings(db);
+        }
+        List<UserPackageSettingsEntry> entries = new ArrayList<>(entriesById.size());
+        for (int i = 0; i < entriesById.size(); ++i) {
+            String rowId = entriesById.keyAt(i);
+            UserPackageSettingsEntry entry = entriesById.valueAt(i);
+            UserPackage userPackage = new UserPackage(rowId, entry.userId, entry.packageName);
+            mUserPackagesByKey.put(userPackage.getKey(), userPackage);
+            mUserPackagesById.put(userPackage.getUniqueId(), userPackage);
+            entries.add(entry);
+        }
+        return entries;
+    }
+
+    /** Saves the given I/O usage stats. Returns true only on success. */
+    public boolean saveIoUsageStats(List<IoUsageStatsEntry> entries) {
+        return saveIoUsageStats(entries, /* shouldCheckRetention= */ true);
+    }
+
+    /** Returns the saved I/O usage stats for the current day. */
+    public List<IoUsageStatsEntry> getTodayIoUsageStats() {
+        ZonedDateTime statsDate =
+                mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT);
+        long startEpochSeconds = statsDate.toEpochSecond();
+        long endEpochSeconds = mTimeSource.now().atZone(ZONE_OFFSET).toEpochSecond();
+        ArrayMap<String, WatchdogPerfHandler.PackageIoUsage> ioUsagesById;
+        try (SQLiteDatabase db = mDbHelper.getReadableDatabase()) {
+            ioUsagesById = IoUsageStatsTable.queryStats(db, startEpochSeconds, endEpochSeconds);
+        }
+        List<IoUsageStatsEntry> entries = new ArrayList<>();
+        for (int i = 0; i < ioUsagesById.size(); ++i) {
+            String id = ioUsagesById.keyAt(i);
+            UserPackage userPackage = mUserPackagesById.get(id);
+            if (userPackage == null) {
+                Slogf.i(TAG, "Failed to find user id and package name for unique database id: '%s'",
+                        id);
+                continue;
+            }
+            entries.add(new IoUsageStatsEntry(userPackage.getUserId(), userPackage.getPackageName(),
+                    ioUsagesById.valueAt(i)));
+        }
+        return entries;
+    }
+
+    /**
+     * Returns the aggregated historical I/O overuse stats for the given user package or
+     * {@code null} when stats are not available.
+     */
+    @Nullable
+    public IoOveruseStats getHistoricalIoOveruseStats(
+            @UserIdInt int userId, String packageName, int numDaysAgo) {
+        ZonedDateTime currentDate =
+                mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT);
+        long startEpochSeconds = currentDate.minusDays(numDaysAgo).toEpochSecond();
+        long endEpochSeconds = currentDate.toEpochSecond();
+        try (SQLiteDatabase db = mDbHelper.getReadableDatabase()) {
+            UserPackage userPackage = mUserPackagesByKey.get(
+                    UserPackage.getKey(userId, packageName));
+            if (userPackage == null) {
+                /* Packages without historical stats don't have userPackage entry. */
+                return null;
+            }
+            return IoUsageStatsTable.queryHistoricalStats(
+                    db, userPackage.getUniqueId(), startEpochSeconds, endEpochSeconds);
+        }
+    }
+
+    @VisibleForTesting
+    boolean saveIoUsageStats(List<IoUsageStatsEntry> entries, boolean shouldCheckRetention) {
+        ZonedDateTime currentDate =
+                mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT);
+        List<ContentValues> rows = new ArrayList<>(entries.size());
+        for (int i = 0; i < entries.size(); ++i) {
+            IoUsageStatsEntry entry = entries.get(i);
+            UserPackage userPackage = mUserPackagesByKey.get(
+                    UserPackage.getKey(entry.userId, entry.packageName));
+            if (userPackage == null) {
+                Slogf.i(TAG, "Failed to find unique database id for user id '%d' and package '%s",
+                        entry.userId, entry.packageName);
+                continue;
+            }
+            android.automotive.watchdog.IoOveruseStats ioOveruseStats =
+                    entry.ioUsage.getInternalIoOveruseStats();
+            ZonedDateTime statsDate = Instant.ofEpochSecond(ioOveruseStats.startTime)
+                    .atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT);
+            if (shouldCheckRetention && STATS_TEMPORAL_UNIT.between(statsDate, currentDate)
+                    >= RETENTION_PERIOD.get(STATS_TEMPORAL_UNIT)) {
+                continue;
+            }
+            long statsDateEpochSeconds = statsDate.toEpochSecond();
+            rows.add(IoUsageStatsTable.getContentValues(
+                    userPackage.getUniqueId(), entry, statsDateEpochSeconds));
+        }
+        try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) {
+            return atomicReplaceEntries(db, IoUsageStatsTable.TABLE_NAME, rows);
+        }
+    }
+
+    @VisibleForTesting
+    void setTimeSource(TimeSourceInterface timeSource) {
+        mTimeSource = timeSource;
+        mDbHelper.setTimeSource(timeSource);
+    }
+
+    private void populateUserPackages(SQLiteDatabase db, ArraySet<Integer> users) {
+        List<UserPackage> userPackages = UserPackageSettingsTable.queryUserPackages(db, users);
+        for (int i = 0; i < userPackages.size(); ++i) {
+            UserPackage userPackage = userPackages.get(i);
+            mUserPackagesByKey.put(userPackage.getKey(), userPackage);
+            mUserPackagesById.put(userPackage.getUniqueId(), userPackage);
+        }
+    }
+
+    private static boolean atomicReplaceEntries(
+            SQLiteDatabase db, String tableName, List<ContentValues> rows) {
+        if (rows.isEmpty()) {
+            return true;
+        }
+        try {
+            db.beginTransaction();
+            for (int i = 0; i < rows.size(); ++i) {
+                try {
+                    if (db.replaceOrThrow(tableName, null, rows.get(i)) == -1) {
+                        Slogf.e(TAG, "Failed to insert %s entry [%s]", tableName, rows.get(i));
+                        return false;
+                    }
+                } catch (SQLException e) {
+                    Slog.e(TAG, "Failed to insert " + tableName + " entry [" + rows.get(i) + "]",
+                            e);
+                    return false;
+                }
+            }
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+        return true;
+    }
+
+    /** Defines the user package settings entry stored in the UserPackageSettingsTable. */
+    static final class UserPackageSettingsEntry {
+        public final String packageName;
+        public final @UserIdInt int userId;
+        public final @KillableState int killableState;
+
+        UserPackageSettingsEntry(
+                @UserIdInt int userId, String packageName, @KillableState int killableState) {
+            this.userId = userId;
+            this.packageName = packageName;
+            this.killableState = killableState;
+        }
+    }
+
+    /**
+     * Defines the contents and queries for the user package settings table.
+     */
+    static final class UserPackageSettingsTable {
+        public static final String TABLE_NAME = "user_package_settings";
+        public static final String COLUMN_ROWID = "rowid";
+        public static final String COLUMN_PACKAGE_NAME = "package_name";
+        public static final String COLUMN_USER_ID = "user_id";
+        public static final String COLUMN_KILLABLE_STATE = "killable_state";
+
+        public static void createTable(SQLiteDatabase db) {
+            StringBuilder createCommand = new StringBuilder();
+            createCommand.append("CREATE TABLE ").append(TABLE_NAME).append(" (")
+                    .append(COLUMN_PACKAGE_NAME).append(" TEXT NOT NULL, ")
+                    .append(COLUMN_USER_ID).append(" INTEGER NOT NULL, ")
+                    .append(COLUMN_KILLABLE_STATE).append(" INTEGER NOT NULL, ")
+                    .append("PRIMARY KEY (").append(COLUMN_PACKAGE_NAME)
+                    .append(", ").append(COLUMN_USER_ID).append("))");
+            db.execSQL(createCommand.toString());
+            Slogf.i(TAG, "Successfully created the %s table in the %s database version %d",
+                    TABLE_NAME, WatchdogDbHelper.DATABASE_NAME, WatchdogDbHelper.DATABASE_VERSION);
+        }
+
+        public static ContentValues getContentValues(UserPackageSettingsEntry entry) {
+            ContentValues values = new ContentValues();
+            values.put(COLUMN_USER_ID, entry.userId);
+            values.put(COLUMN_PACKAGE_NAME, entry.packageName);
+            values.put(COLUMN_KILLABLE_STATE, entry.killableState);
+            return values;
+        }
+
+        public static ArrayMap<String, UserPackageSettingsEntry> querySettings(SQLiteDatabase db) {
+            StringBuilder queryBuilder = new StringBuilder();
+            queryBuilder.append("SELECT ")
+                    .append(COLUMN_ROWID).append(", ")
+                    .append(COLUMN_USER_ID).append(", ")
+                    .append(COLUMN_PACKAGE_NAME).append(", ")
+                    .append(COLUMN_KILLABLE_STATE)
+                    .append(" FROM ").append(TABLE_NAME);
+
+            try (Cursor cursor = db.rawQuery(queryBuilder.toString(), new String[]{})) {
+                ArrayMap<String, UserPackageSettingsEntry> entriesById = new ArrayMap<>(
+                        cursor.getCount());
+                while (cursor.moveToNext()) {
+                    entriesById.put(cursor.getString(0), new UserPackageSettingsEntry(
+                            cursor.getInt(1), cursor.getString(2), cursor.getInt(3)));
+                }
+                return entriesById;
+            }
+        }
+
+        public static List<UserPackage> queryUserPackages(
+                SQLiteDatabase db, ArraySet<Integer> users) {
+            StringBuilder queryBuilder = new StringBuilder();
+            queryBuilder.append("SELECT ")
+                    .append(COLUMN_ROWID).append(", ")
+                    .append(COLUMN_USER_ID).append(", ")
+                    .append(COLUMN_PACKAGE_NAME)
+                    .append(" FROM ").append(TABLE_NAME);
+            for (int i = 0; i < users.size(); ++i) {
+                if (i == 0) {
+                    queryBuilder.append(" WHERE ").append(COLUMN_USER_ID).append(" IN (");
+                } else {
+                    queryBuilder.append(", ");
+                }
+                queryBuilder.append(users.valueAt(i));
+                if (i == users.size() - 1) {
+                    queryBuilder.append(")");
+                }
+            }
+
+            try (Cursor cursor = db.rawQuery(queryBuilder.toString(), new String[]{})) {
+                List<UserPackage> userPackages = new ArrayList<>(cursor.getCount());
+                while (cursor.moveToNext()) {
+                    userPackages.add(new UserPackage(
+                            cursor.getString(0), cursor.getInt(1), cursor.getString(2)));
+                }
+                return userPackages;
+            }
+        }
+    }
+
+    /** Defines the I/O usage entry stored in the IoUsageStatsTable. */
+    static final class IoUsageStatsEntry {
+        public final @UserIdInt int userId;
+        public final String packageName;
+        public final WatchdogPerfHandler.PackageIoUsage ioUsage;
+
+        IoUsageStatsEntry(@UserIdInt int userId,
+                String packageName, WatchdogPerfHandler.PackageIoUsage ioUsage) {
+            this.userId = userId;
+            this.packageName = packageName;
+            this.ioUsage = ioUsage;
+        }
+    }
+
+    /**
+     * Defines the contents and queries for the I/O usage stats table.
+     */
+    static final class IoUsageStatsTable {
+        public static final String TABLE_NAME = "io_usage_stats";
+        public static final String COLUMN_USER_PACKAGE_ID = "user_package_id";
+        public static final String COLUMN_DATE_EPOCH = "date_epoch";
+        public static final String COLUMN_NUM_OVERUSES = "num_overuses";
+        public static final String COLUMN_NUM_FORGIVEN_OVERUSES =  "num_forgiven_overuses";
+        public static final String COLUMN_NUM_TIMES_KILLED = "num_times_killed";
+        public static final String COLUMN_WRITTEN_FOREGROUND_BYTES = "written_foreground_bytes";
+        public static final String COLUMN_WRITTEN_BACKGROUND_BYTES = "written_background_bytes";
+        public static final String COLUMN_WRITTEN_GARAGE_MODE_BYTES = "written_garage_mode_bytes";
+        /* Below columns will be null for historical stats i.e., when the date != current date. */
+        public static final String COLUMN_REMAINING_FOREGROUND_WRITE_BYTES =
+                "remaining_foreground_write_bytes";
+        public static final String COLUMN_REMAINING_BACKGROUND_WRITE_BYTES =
+                "remaining_background_write_bytes";
+        public static final String COLUMN_REMAINING_GARAGE_MODE_WRITE_BYTES =
+                "remaining_garage_mode_write_bytes";
+        public static final String COLUMN_FORGIVEN_FOREGROUND_WRITE_BYTES =
+                "forgiven_foreground_write_bytes";
+        public static final String COLUMN_FORGIVEN_BACKGROUND_WRITE_BYTES =
+                "forgiven_background_write_bytes";
+        public static final String COLUMN_FORGIVEN_GARAGE_MODE_WRITE_BYTES =
+                "forgiven_garage_mode_write_bytes";
+
+        public static void createTable(SQLiteDatabase db) {
+            StringBuilder createCommand = new StringBuilder();
+            createCommand.append("CREATE TABLE ").append(TABLE_NAME).append(" (")
+                    .append(COLUMN_USER_PACKAGE_ID).append(" INTEGER NOT NULL, ")
+                    .append(COLUMN_DATE_EPOCH).append(" INTEGER NOT NULL, ")
+                    .append(COLUMN_NUM_OVERUSES).append(" INTEGER NOT NULL, ")
+                    .append(COLUMN_NUM_FORGIVEN_OVERUSES).append(" INTEGER NOT NULL, ")
+                    .append(COLUMN_NUM_TIMES_KILLED).append(" INTEGER NOT NULL, ")
+                    .append(COLUMN_WRITTEN_FOREGROUND_BYTES).append(" INTEGER, ")
+                    .append(COLUMN_WRITTEN_BACKGROUND_BYTES).append(" INTEGER, ")
+                    .append(COLUMN_WRITTEN_GARAGE_MODE_BYTES).append(" INTEGER, ")
+                    .append(COLUMN_REMAINING_FOREGROUND_WRITE_BYTES).append(" INTEGER, ")
+                    .append(COLUMN_REMAINING_BACKGROUND_WRITE_BYTES).append(" INTEGER, ")
+                    .append(COLUMN_REMAINING_GARAGE_MODE_WRITE_BYTES).append(" INTEGER, ")
+                    .append(COLUMN_FORGIVEN_FOREGROUND_WRITE_BYTES).append(" INTEGER, ")
+                    .append(COLUMN_FORGIVEN_BACKGROUND_WRITE_BYTES).append(" INTEGER, ")
+                    .append(COLUMN_FORGIVEN_GARAGE_MODE_WRITE_BYTES).append(" INTEGER, ")
+                    .append("PRIMARY KEY (").append(COLUMN_USER_PACKAGE_ID).append(", ")
+                    .append(COLUMN_DATE_EPOCH).append("), FOREIGN KEY (")
+                    .append(COLUMN_USER_PACKAGE_ID).append(") REFERENCES ")
+                    .append(UserPackageSettingsTable.TABLE_NAME).append(" (")
+                    .append(UserPackageSettingsTable.COLUMN_ROWID).append(") ON DELETE CASCADE )");
+            db.execSQL(createCommand.toString());
+            Slogf.i(TAG, "Successfully created the %s table in the %s database version %d",
+                    TABLE_NAME, WatchdogDbHelper.DATABASE_NAME, WatchdogDbHelper.DATABASE_VERSION);
+        }
+
+        public static ContentValues getContentValues(
+                String userPackageId, IoUsageStatsEntry entry, long statsDateEpochSeconds) {
+            android.automotive.watchdog.IoOveruseStats ioOveruseStats =
+                    entry.ioUsage.getInternalIoOveruseStats();
+            ContentValues values = new ContentValues();
+            values.put(COLUMN_USER_PACKAGE_ID, userPackageId);
+            values.put(COLUMN_DATE_EPOCH, statsDateEpochSeconds);
+            values.put(COLUMN_NUM_OVERUSES, ioOveruseStats.totalOveruses);
+            /* TODO(b/195425666): Put total forgiven overuses for the day. */
+            values.put(COLUMN_NUM_FORGIVEN_OVERUSES, 0);
+            values.put(COLUMN_NUM_TIMES_KILLED, entry.ioUsage.getTotalTimesKilled());
+            values.put(
+                    COLUMN_WRITTEN_FOREGROUND_BYTES, ioOveruseStats.writtenBytes.foregroundBytes);
+            values.put(
+                    COLUMN_WRITTEN_BACKGROUND_BYTES, ioOveruseStats.writtenBytes.backgroundBytes);
+            values.put(
+                    COLUMN_WRITTEN_GARAGE_MODE_BYTES, ioOveruseStats.writtenBytes.garageModeBytes);
+            values.put(COLUMN_REMAINING_FOREGROUND_WRITE_BYTES,
+                    ioOveruseStats.remainingWriteBytes.foregroundBytes);
+            values.put(COLUMN_REMAINING_BACKGROUND_WRITE_BYTES,
+                    ioOveruseStats.remainingWriteBytes.backgroundBytes);
+            values.put(COLUMN_REMAINING_GARAGE_MODE_WRITE_BYTES,
+                    ioOveruseStats.remainingWriteBytes.garageModeBytes);
+            android.automotive.watchdog.PerStateBytes forgivenWriteBytes =
+                    entry.ioUsage.getForgivenWriteBytes();
+            values.put(COLUMN_FORGIVEN_FOREGROUND_WRITE_BYTES, forgivenWriteBytes.foregroundBytes);
+            values.put(COLUMN_FORGIVEN_BACKGROUND_WRITE_BYTES, forgivenWriteBytes.backgroundBytes);
+            values.put(COLUMN_FORGIVEN_GARAGE_MODE_WRITE_BYTES, forgivenWriteBytes.garageModeBytes);
+            return values;
+        }
+
+        public static ArrayMap<String, WatchdogPerfHandler.PackageIoUsage> queryStats(
+                SQLiteDatabase db, long startEpochSeconds, long endEpochSeconds) {
+            StringBuilder queryBuilder = new StringBuilder();
+            queryBuilder.append("SELECT ")
+                    .append(COLUMN_USER_PACKAGE_ID).append(", ")
+                    .append("MIN(").append(COLUMN_DATE_EPOCH).append("), ")
+                    .append("SUM(").append(COLUMN_NUM_OVERUSES).append("), ")
+                    .append("SUM(").append(COLUMN_NUM_TIMES_KILLED).append("), ")
+                    .append("SUM(").append(COLUMN_WRITTEN_FOREGROUND_BYTES).append("), ")
+                    .append("SUM(").append(COLUMN_WRITTEN_BACKGROUND_BYTES).append("), ")
+                    .append("SUM(").append(COLUMN_WRITTEN_GARAGE_MODE_BYTES).append("), ")
+                    .append("SUM(").append(COLUMN_REMAINING_FOREGROUND_WRITE_BYTES).append("), ")
+                    .append("SUM(").append(COLUMN_REMAINING_BACKGROUND_WRITE_BYTES).append("), ")
+                    .append("SUM(").append(COLUMN_REMAINING_GARAGE_MODE_WRITE_BYTES).append("), ")
+                    .append("SUM(").append(COLUMN_FORGIVEN_FOREGROUND_WRITE_BYTES).append("), ")
+                    .append("SUM(").append(COLUMN_FORGIVEN_BACKGROUND_WRITE_BYTES).append("), ")
+                    .append("SUM(").append(COLUMN_FORGIVEN_GARAGE_MODE_WRITE_BYTES).append(") ")
+                    .append("FROM ").append(TABLE_NAME).append(" WHERE ")
+                    .append(COLUMN_DATE_EPOCH).append(">= ? and ")
+                    .append(COLUMN_DATE_EPOCH).append("< ? GROUP BY ")
+                    .append(COLUMN_USER_PACKAGE_ID);
+            String[] selectionArgs = new String[]{
+                    String.valueOf(startEpochSeconds), String.valueOf(endEpochSeconds)};
+
+            ArrayMap<String, WatchdogPerfHandler.PackageIoUsage> ioUsageById = new ArrayMap<>();
+            try (Cursor cursor = db.rawQuery(queryBuilder.toString(), selectionArgs)) {
+                while (cursor.moveToNext()) {
+                    android.automotive.watchdog.IoOveruseStats ioOveruseStats =
+                            new android.automotive.watchdog.IoOveruseStats();
+                    ioOveruseStats.startTime = cursor.getLong(1);
+                    ioOveruseStats.durationInSeconds = endEpochSeconds - startEpochSeconds;
+                    ioOveruseStats.totalOveruses = cursor.getInt(2);
+                    ioOveruseStats.writtenBytes = new PerStateBytes();
+                    ioOveruseStats.writtenBytes.foregroundBytes = cursor.getLong(4);
+                    ioOveruseStats.writtenBytes.backgroundBytes = cursor.getLong(5);
+                    ioOveruseStats.writtenBytes.garageModeBytes = cursor.getLong(6);
+                    ioOveruseStats.remainingWriteBytes = new PerStateBytes();
+                    ioOveruseStats.remainingWriteBytes.foregroundBytes = cursor.getLong(7);
+                    ioOveruseStats.remainingWriteBytes.backgroundBytes = cursor.getLong(8);
+                    ioOveruseStats.remainingWriteBytes.garageModeBytes = cursor.getLong(9);
+                    PerStateBytes forgivenWriteBytes = new PerStateBytes();
+                    forgivenWriteBytes.foregroundBytes = cursor.getLong(10);
+                    forgivenWriteBytes.backgroundBytes = cursor.getLong(11);
+                    forgivenWriteBytes.garageModeBytes = cursor.getLong(12);
+
+                    ioUsageById.put(cursor.getString(0), new WatchdogPerfHandler.PackageIoUsage(
+                            ioOveruseStats, forgivenWriteBytes, cursor.getInt(3)));
+                }
+            }
+            return ioUsageById;
+        }
+
+        public static IoOveruseStats queryHistoricalStats(SQLiteDatabase db, String uniqueId,
+                long startEpochSeconds, long endEpochSeconds) {
+            StringBuilder queryBuilder = new StringBuilder();
+            queryBuilder.append("SELECT SUM(").append(COLUMN_NUM_OVERUSES).append("), ")
+                    .append("SUM(").append(COLUMN_NUM_TIMES_KILLED).append("), ")
+                    .append("SUM(").append(COLUMN_WRITTEN_FOREGROUND_BYTES).append("), ")
+                    .append("SUM(").append(COLUMN_WRITTEN_BACKGROUND_BYTES).append("), ")
+                    .append("SUM(").append(COLUMN_WRITTEN_GARAGE_MODE_BYTES).append("), ")
+                    .append("MIN(").append(COLUMN_DATE_EPOCH).append(") ")
+                    .append("FROM ").append(TABLE_NAME).append(" WHERE ")
+                    .append(COLUMN_USER_PACKAGE_ID).append("=? and ")
+                    .append(COLUMN_DATE_EPOCH).append(" >= ? and ")
+                    .append(COLUMN_DATE_EPOCH).append("< ?");
+            String[] selectionArgs = new String[]{uniqueId,
+                    String.valueOf(startEpochSeconds), String.valueOf(endEpochSeconds)};
+            long totalOveruses = 0;
+            long totalTimesKilled = 0;
+            long totalBytesWritten = 0;
+            long earliestEpochSecond = endEpochSeconds;
+            try (Cursor cursor = db.rawQuery(queryBuilder.toString(), selectionArgs)) {
+                if (cursor.getCount() == 0) {
+                    return null;
+                }
+                while (cursor.moveToNext()) {
+                    totalOveruses += cursor.getLong(0);
+                    totalTimesKilled += cursor.getLong(1);
+                    totalBytesWritten += cursor.getLong(2) + cursor.getLong(3) + cursor.getLong(4);
+                    earliestEpochSecond = Math.min(cursor.getLong(5), earliestEpochSecond);
+                }
+            }
+            if (totalBytesWritten == 0) {
+                return null;
+            }
+            long durationInSeconds = endEpochSeconds - earliestEpochSecond;
+            IoOveruseStats.Builder statsBuilder = new IoOveruseStats.Builder(
+                    earliestEpochSecond, durationInSeconds);
+            statsBuilder.setTotalOveruses(totalOveruses);
+            statsBuilder.setTotalTimesKilled(totalTimesKilled);
+            statsBuilder.setTotalBytesWritten(totalBytesWritten);
+            return statsBuilder.build();
+        }
+
+        public static void truncateToDate(SQLiteDatabase db, ZonedDateTime latestTruncateDate) {
+            String selection = COLUMN_DATE_EPOCH + " <= ?";
+            String[] selectionArgs = { String.valueOf(latestTruncateDate.toEpochSecond()) };
+
+            int rows = db.delete(TABLE_NAME, selection, selectionArgs);
+            Slogf.i(TAG, "Truncated %d I/O usage stats entries on pid %d", rows, Process.myPid());
+        }
+
+        public static void trimHistoricalStats(SQLiteDatabase db, ZonedDateTime currentDate) {
+            ContentValues values = new ContentValues();
+            values.putNull(COLUMN_REMAINING_FOREGROUND_WRITE_BYTES);
+            values.putNull(COLUMN_REMAINING_BACKGROUND_WRITE_BYTES);
+            values.putNull(COLUMN_REMAINING_GARAGE_MODE_WRITE_BYTES);
+            values.putNull(COLUMN_FORGIVEN_FOREGROUND_WRITE_BYTES);
+            values.putNull(COLUMN_FORGIVEN_BACKGROUND_WRITE_BYTES);
+            values.putNull(COLUMN_FORGIVEN_GARAGE_MODE_WRITE_BYTES);
+
+            String selection = COLUMN_DATE_EPOCH + " < ?";
+            String[] selectionArgs = { String.valueOf(currentDate.toEpochSecond()) };
+
+            int rows = db.update(TABLE_NAME, values, selection, selectionArgs);
+            Slogf.i(TAG, "Trimmed %d I/O usage stats entries on pid %d", rows, Process.myPid());
+        }
+    }
+
+    /**
+     * Defines the Watchdog database and database level operations.
+     */
+    static final class WatchdogDbHelper extends SQLiteOpenHelper {
+        public static final String DATABASE_DIR = "/data/system/car/watchdog/";
+        public static final String DATABASE_NAME = "car_watchdog.db";
+
+        private static final int DATABASE_VERSION = 1;
+
+        private ZonedDateTime mLatestShrinkDate;
+        private TimeSourceInterface mTimeSource = SYSTEM_INSTANCE;
+
+        WatchdogDbHelper(Context context, boolean useDataSystemCarDir) {
+            /* Use device protected storage because CarService may need to access the database
+             * before the user has authenticated.
+             */
+            super(context.createDeviceProtectedStorageContext(),
+                    useDataSystemCarDir ? DATABASE_DIR + DATABASE_NAME : DATABASE_NAME,
+                    /* name= */ null, DATABASE_VERSION);
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            UserPackageSettingsTable.createTable(db);
+            IoUsageStatsTable.createTable(db);
+        }
+
+        public synchronized void close() {
+            super.close();
+
+            mLatestShrinkDate = null;
+        }
+
+        public void onShrink(SQLiteDatabase db) {
+            ZonedDateTime currentDate =
+                    mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(ChronoUnit.DAYS);
+            if (currentDate.equals(mLatestShrinkDate)) {
+                return;
+            }
+            IoUsageStatsTable.truncateToDate(db, currentDate.minus(RETENTION_PERIOD));
+            IoUsageStatsTable.trimHistoricalStats(db, currentDate);
+            mLatestShrinkDate = currentDate;
+            Slogf.i(TAG, "Shrunk watchdog database for the date '%s'", mLatestShrinkDate);
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
+            /* Still on the 1st version so no upgrade required. */
+        }
+
+        void setTimeSource(TimeSourceInterface timeSource) {
+            mTimeSource = timeSource;
+        }
+    }
+
+    private static final class UserPackage {
+        private final String mUniqueId;
+        private final int mUserId;
+        private final String mPackageName;
+
+        UserPackage(String uniqueId, int userId, String packageName) {
+            mUniqueId = uniqueId;
+            mUserId = userId;
+            mPackageName = packageName;
+        }
+
+        String getKey() {
+            return getKey(mUserId, mPackageName);
+        }
+
+        static String getKey(int userId, String packageName) {
+            return String.format(Locale.ENGLISH, "%d:%s", userId, packageName);
+        }
+
+        public String getUniqueId() {
+            return mUniqueId;
+        }
+
+        public int getUserId() {
+            return mUserId;
+        }
+
+        public String getPackageName() {
+            return mPackageName;
+        }
+
+    }
+}
diff --git a/tests/AdasLocationTestApp/Android.bp b/tests/AdasLocationTestApp/Android.bp
new file mode 100644
index 0000000..cc52ec7
--- /dev/null
+++ b/tests/AdasLocationTestApp/Android.bp
@@ -0,0 +1,51 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+    name: "AdasLocationTestApp",
+
+    srcs: ["src/**/*.java"],
+
+    resource_dirs: ["res"],
+
+    platform_apis: true,
+
+    optimize: {
+        enabled: false,
+    },
+
+    enforce_uses_libs: false,
+    dex_preopt: {
+        enabled: false,
+    },
+
+    required: ["allowed_privapp_com.google.android.car.adaslocation"],
+
+    privileged: true,
+
+    certificate: "platform",
+
+    static_libs: [
+            "com.google.android.material_material",
+            "androidx.appcompat_appcompat",
+    ],
+
+    libs: ["android.car"],
+}
\ No newline at end of file
diff --git a/tests/AdasLocationTestApp/AndroidManifest.xml b/tests/AdasLocationTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..6dcd183
--- /dev/null
+++ b/tests/AdasLocationTestApp/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.google.android.car.adaslocation"
+          android:sharedUserId="android.uid.system">
+
+    <!-- The app needs to access device location to verify ADAS and main location switch work as
+    expected. -->
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+
+    <application android:label="AdasLocationTestApp">
+        <activity android:name=".AdasLocationActivity"
+                  android:theme="@style/Theme.AppCompat"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/AdasLocationTestApp/res/layout/main_activity.xml b/tests/AdasLocationTestApp/res/layout/main_activity.xml
new file mode 100644
index 0000000..0173cd6
--- /dev/null
+++ b/tests/AdasLocationTestApp/res/layout/main_activity.xml
@@ -0,0 +1,102 @@
+<?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.
+  -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                xmlns:app="http://schemas.android.com/apk/res-auto"
+                android:layout_width="fill_parent"
+                android:layout_height="match_parent"
+                android:orientation="vertical">
+    <TextView
+        android:id="@+id/main_location_enabled"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="100dp"
+        android:textStyle="bold"
+        android:text="main location enabled: "/>
+    <TextView
+        android:id="@+id/main_location_status"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_toRightOf="@id/main_location_enabled"
+        android:layout_marginTop="100dp"
+        android:textStyle="bold"/>
+    <TextView
+        android:id="@+id/adas_location_enabled"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerHorizontal="true"
+        android:layout_below="@id/main_location_enabled"
+        android:textStyle="bold"
+        android:text="adas location enabled: "/>
+    <TextView
+        android:id="@+id/adas_location_status"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/main_location_status"
+        android:layout_toRightOf="@id/adas_location_enabled"
+        android:textStyle="bold"/>
+    <TextView
+        android:id="@+id/current_location"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/current_location"
+        android:layout_alignParentLeft="true"
+        android:layout_marginLeft="350dp"
+        android:layout_marginTop= "200dp"
+        android:textStyle="bold"/>
+    <TextView
+        android:id="@+id/current_location_result"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/current_location"
+        android:layout_alignParentLeft="true"
+        android:layout_marginLeft="350dp"
+        android:textStyle="bold"/>
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/observe_fab"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/current_location_result"
+        android:layout_alignParentLeft="true"
+        app:useCompatPadding="true"
+        android:layout_marginLeft="350dp"/>
+    <TextView
+        android:id="@+id/last_location"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/last_location"
+        android:layout_alignParentRight="true"
+        android:layout_marginRight="350dp"
+        android:layout_marginTop= "200dp"
+        android:textStyle="bold"/>
+    <TextView
+        android:id="@+id/last_location_result"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/last_location"
+        android:layout_alignParentRight="true"
+        android:layout_marginRight="350dp"
+        android:textStyle="bold"/>
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/query_fab"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/last_location_result"
+        android:layout_alignParentRight="true"
+        app:useCompatPadding="true"
+        android:layout_marginRight="350dp"/>
+</RelativeLayout>
diff --git a/tests/AdasLocationTestApp/res/values/strings.xml b/tests/AdasLocationTestApp/res/values/strings.xml
new file mode 100644
index 0000000..3b97648
--- /dev/null
+++ b/tests/AdasLocationTestApp/res/values/strings.xml
@@ -0,0 +1,21 @@
+<!--
+  ~ 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:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="last_location" translatable="false">Last Location</string>
+    <string name="current_location" translatable="false">Current Location</string>
+    <string name="no_last_location" translatable="false">no last location</string>
+    <string name="waiting_for_location" translatable="false">waiting for location</string>
+</resources>
\ No newline at end of file
diff --git a/tests/AdasLocationTestApp/src/com/google/android/car/adaslocation/AdasLocationActivity.java b/tests/AdasLocationTestApp/src/com/google/android/car/adaslocation/AdasLocationActivity.java
new file mode 100644
index 0000000..bcb29f1
--- /dev/null
+++ b/tests/AdasLocationTestApp/src/com/google/android/car/adaslocation/AdasLocationActivity.java
@@ -0,0 +1,195 @@
+/*
+ * 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.google.android.car.adaslocation;
+
+import android.Manifest;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.widget.TextView;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.app.ActivityCompat;
+
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public final class AdasLocationActivity extends AppCompatActivity {
+    private static final int KS_PERMISSIONS_REQUEST = 1;
+
+    private static final String[] REQUIRED_PERMISSIONS = new String[]{
+            Manifest.permission.ACCESS_FINE_LOCATION,
+            Manifest.permission.ACCESS_COARSE_LOCATION,
+    };
+
+    private boolean mIsRegister;
+    private LocationManager mLocationManager;
+    private FloatingActionButton mObserveFab;
+    private TextView mObserveLocationResult;
+    private LocationListener mLocationListener;
+    private FloatingActionButton mQueryFab;
+    private TextView mLastLocationResult;
+    private TextView mMainLocationEnabled;
+    private TextView mAdasLocationEnabled;
+    private BroadcastReceiver mReceiver;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main_activity);
+
+        mLocationManager = getApplicationContext().getSystemService(LocationManager.class);
+        mObserveFab = findViewById(R.id.observe_fab);
+        mObserveLocationResult = findViewById(R.id.current_location_result);
+        mLocationListener = new LocationListener() {
+            @Override
+            public void onLocationChanged(Location location) {
+                mObserveLocationResult.setText(locationToFormattedString(location));
+            }
+
+            @Override
+            public void onProviderEnabled(String provider) {
+            }
+
+            @Override
+            public void onProviderDisabled(String provider) {
+            }
+        };
+        mObserveFab.setOnClickListener(
+                v -> {
+                    if (!mIsRegister) {
+                        startListening();
+                    } else {
+                        stopListening();
+                    }
+                }
+        );
+        mQueryFab = findViewById(R.id.query_fab);
+        mLastLocationResult = findViewById(R.id.last_location_result);
+        mQueryFab.setOnClickListener(
+                v -> {
+                    Location location = mLocationManager
+                            .getLastKnownLocation(LocationManager.GPS_PROVIDER);
+                    if (location != null) {
+                        mLastLocationResult.setText(locationToFormattedString(location));
+                    } else {
+                        mLastLocationResult.setText(R.string.no_last_location);
+                    }
+                }
+        );
+
+        mReceiver =
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        if (LocationManager.MODE_CHANGED_ACTION == intent.getAction()) {
+                            mMainLocationEnabled.setText(Boolean.toString(mLocationManager
+                                    .isLocationEnabled()));
+                            return;
+                        }
+                        if (LocationManager.ACTION_ADAS_GNSS_ENABLED_CHANGED
+                                == intent.getAction()) {
+                            mAdasLocationEnabled
+                                    .setText(Boolean.toString(mLocationManager
+                                            .isAdasGnssLocationEnabled()));
+                            return;
+                        }
+                    }
+                };
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        initPermissions();
+
+        mMainLocationEnabled = findViewById(R.id.main_location_status);
+        mMainLocationEnabled.setText(Boolean.toString(mLocationManager.isLocationEnabled()));
+        mAdasLocationEnabled = findViewById(R.id.adas_location_status);
+        mAdasLocationEnabled.setText(Boolean.toString(mLocationManager
+                .isAdasGnssLocationEnabled()));
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(LocationManager.MODE_CHANGED_ACTION);
+        intentFilter.addAction(LocationManager.ACTION_ADAS_GNSS_ENABLED_CHANGED);
+        registerReceiver(mReceiver, intentFilter);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        Set<String> missingPermissions = checkExistingPermissions();
+        if (!missingPermissions.isEmpty()) {
+            return;
+        }
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        if (mIsRegister) {
+            stopListening();
+        }
+        mLastLocationResult.setText("");
+    }
+
+    private static String locationToFormattedString(Location location) {
+        return String.format("Location: lat=%10.6f, lon=%10.6f ",
+                location.getLatitude(),
+                location.getLongitude());
+    }
+
+    private void initPermissions() {
+        Set<String> missingPermissions = checkExistingPermissions();
+        if (!missingPermissions.isEmpty()) {
+            requestPermissions(missingPermissions);
+        }
+    }
+
+    private Set<String> checkExistingPermissions() {
+        return Arrays.stream(REQUIRED_PERMISSIONS).filter(permission -> ActivityCompat
+                .checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED)
+                .collect(Collectors.toSet());
+    }
+
+    private void requestPermissions(Set<String> permissions) {
+        requestPermissions(permissions.toArray(new String[permissions.size()]),
+                KS_PERMISSIONS_REQUEST);
+    }
+
+    private void startListening() {
+        mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
+                0, 0, mLocationListener);
+        mObserveLocationResult.setText(R.string.waiting_for_location);
+        mIsRegister = true;
+    }
+
+    private void stopListening() {
+        mLocationManager.removeUpdates(mLocationListener);
+        mObserveLocationResult.setText("");
+        mIsRegister = false;
+    }
+}
diff --git a/tests/CarEvsCameraPreviewApp/res/values/config.xml b/tests/CarEvsCameraPreviewApp/res/values/config.xml
new file mode 100644
index 0000000..935c096
--- /dev/null
+++ b/tests/CarEvsCameraPreviewApp/res/values/config.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ 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>
+    <!-- Shade of the background behind the camera window. 1.0 for fully opaque, 0.0 for fully
+         transparent. -->
+    <item name="config_cameraBackgroundScrim" format="float" type="dimen">0.7</item>
+</resources>
\ No newline at end of file
diff --git a/tests/CarEvsCameraPreviewApp/res/values/overlayable.xml b/tests/CarEvsCameraPreviewApp/res/values/overlayable.xml
new file mode 100644
index 0000000..0f77eab
--- /dev/null
+++ b/tests/CarEvsCameraPreviewApp/res/values/overlayable.xml
@@ -0,0 +1,33 @@
+<?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.-->
+<!--
+THIS FILE WAS AUTO GENERATED, DO NOT EDIT MANUALLY.
+REGENERATE USING packages/apps/Car/tests/tools/rro/generate-overlayable.py
+-->
+<resources>
+  <overlayable name="CarEvsCameraPreviewApp">
+    <policy type="system|product|signature">
+      <item type="color" name="button_background"/>
+      <item type="color" name="button_text"/>
+      <item type="dimen" name="camera_preview_height"/>
+      <item type="dimen" name="camera_preview_width"/>
+      <item type="dimen" name="close_button_text_size"/>
+      <item type="dimen" name="config_cameraBackgroundScrim"/>
+      <item type="id" name="close_button"/>
+      <item type="id" name="evs_preview_container"/>
+      <item type="layout" name="evs_preview_activity"/>
+      <item type="string" name="app_name"/>
+      <item type="string" name="close_button_text"/>
+      <item type="style" name="Theme.Transparent"/>
+    </policy>
+  </overlayable>
+</resources>
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 737d918..6ff7940 100644
--- a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraPreviewActivity.java
+++ b/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraPreviewActivity.java
@@ -34,8 +34,8 @@
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.WindowManager;
-import android.widget.Button;
 import android.widget.LinearLayout;
 
 import java.util.ArrayList;
@@ -55,6 +55,7 @@
 
     /** GL backed surface view to render the camera preview */
     private CarEvsCameraGLSurfaceView mEvsView;
+    private ViewGroup mRootView;
     private LinearLayout mPreviewContainer;
 
     /** Display manager to monitor the display's state */
@@ -164,8 +165,9 @@
                 Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, mCarServiceLifecycleListener);
 
         mEvsView = new CarEvsCameraGLSurfaceView(getApplication(), this);
-        mPreviewContainer = (LinearLayout) LayoutInflater.from(this).inflate(
+        mRootView = (ViewGroup) LayoutInflater.from(this).inflate(
                 R.layout.evs_preview_activity, /* root= */ null);
+        mPreviewContainer = mRootView.findViewById(R.id.evs_preview_container);
         LinearLayout.LayoutParams viewParam = new LinearLayout.LayoutParams(
                 LinearLayout.LayoutParams.MATCH_PARENT,
                 LinearLayout.LayoutParams.MATCH_PARENT,
@@ -173,31 +175,31 @@
         );
         mEvsView.setLayoutParams(viewParam);
         mPreviewContainer.addView(mEvsView, 0);
-        Button closeButton = mPreviewContainer.findViewById(R.id.close_button);
-        closeButton.setOnClickListener((v) -> finish());
+        View closeButton = mRootView.findViewById(R.id.close_button);
+        if (closeButton != null) {
+            closeButton.setOnClickListener(v -> finish());
+        }
 
         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,
+                width, height,
                 2020 /* WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY */,
-                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                WindowManager.LayoutParams.FLAG_DIM_BEHIND
                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
                 PixelFormat.TRANSLUCENT);
-        params.gravity = Gravity.LEFT | Gravity.TOP;
+        params.gravity = Gravity.CENTER;
+        params.dimAmount = getResources().getFloat(R.dimen.config_cameraBackgroundScrim);
+
         if (mUseSystemWindow) {
             WindowManager wm = getSystemService(WindowManager.class);
-            wm.addView(mPreviewContainer, params);
+            wm.addView(mRootView, params);
         } else {
-            setContentView(mPreviewContainer, params);
+            setContentView(mRootView, params);
         }
     }
 
@@ -252,7 +254,7 @@
         mDisplayManager.unregisterDisplayListener(mDisplayListener);
         if (mUseSystemWindow) {
             WindowManager wm = getSystemService(WindowManager.class);
-            wm.removeView(mPreviewContainer);
+            wm.removeView(mRootView);
         }
     }
 
diff --git a/tests/CarLibTests/src/android/car/CarTelemetryManagerTest.java b/tests/CarLibTests/src/android/car/CarTelemetryManagerTest.java
deleted file mode 100644
index 906662d..0000000
--- a/tests/CarLibTests/src/android/car/CarTelemetryManagerTest.java
+++ /dev/null
@@ -1,175 +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 android.car;
-
-import static android.car.telemetry.CarTelemetryManager.ERROR_NONE;
-import static android.car.telemetry.CarTelemetryManager.ERROR_SAME_MANIFEST_EXISTS;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.verify;
-
-import android.app.Application;
-import android.car.telemetry.CarTelemetryManager;
-import android.car.telemetry.ManifestKey;
-import android.car.testapi.CarTelemetryController;
-import android.car.testapi.FakeCar;
-
-import androidx.test.core.app.ApplicationProvider;
-
-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.MockitoRule;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.internal.DoNotInstrument;
-
-import java.util.concurrent.Executor;
-
-@RunWith(RobolectricTestRunner.class)
-@DoNotInstrument
-public class CarTelemetryManagerTest {
-    @Rule
-    public MockitoRule rule = MockitoJUnit.rule();
-
-    private static final byte[] ERROR_BYTES = "ERROR".getBytes();
-    private static final byte[] MANIFEST_BYTES = "MANIFEST".getBytes();
-    private static final byte[] SCRIPT_RESULT_BYTES = "SCRIPT RESULT".getBytes();
-    private static final ManifestKey DEFAULT_MANIFEST_KEY =
-            new ManifestKey("NAME", 1);
-    private static final Executor DIRECT_EXECUTOR = Runnable::run;
-
-    private CarTelemetryController mCarTelemetryController;
-    private CarTelemetryManager mCarTelemetryManager;
-
-    @Mock
-    private CarTelemetryManager.CarTelemetryResultsListener mListener;
-
-
-
-    @Before
-    public void setUp() {
-        Application context = ApplicationProvider.getApplicationContext();
-        FakeCar fakeCar = FakeCar.createFakeCar(context);
-        Car carApi = fakeCar.getCar();
-
-        mCarTelemetryManager =
-                (CarTelemetryManager) carApi.getCarManager(Car.CAR_TELEMETRY_SERVICE);
-        mCarTelemetryController = fakeCar.getCarTelemetryController();
-    }
-
-    @Test
-    public void setListener_shouldSucceed() {
-        mCarTelemetryManager.setListener(DIRECT_EXECUTOR, mListener);
-
-        assertThat(mCarTelemetryController.isListenerSet()).isTrue();
-    }
-
-    @Test
-    public void clearListener_shouldSucceed() {
-        mCarTelemetryManager.setListener(DIRECT_EXECUTOR, mListener);
-        mCarTelemetryManager.clearListener();
-
-        assertThat(mCarTelemetryController.isListenerSet()).isFalse();
-    }
-
-    @Test
-    public void addManifest_whenNew_shouldSucceed() {
-        int result = mCarTelemetryManager.addManifest(DEFAULT_MANIFEST_KEY, MANIFEST_BYTES);
-
-        assertThat(result).isEqualTo(ERROR_NONE);
-        assertThat(mCarTelemetryController.getValidManifestsCount()).isEqualTo(1);
-    }
-
-    @Test
-    public void addManifest_whenDuplicate_shouldIgnore() {
-        int firstResult =
-                mCarTelemetryManager.addManifest(DEFAULT_MANIFEST_KEY, MANIFEST_BYTES);
-        int secondResult =
-                mCarTelemetryManager.addManifest(DEFAULT_MANIFEST_KEY, MANIFEST_BYTES);
-
-        assertThat(firstResult).isEqualTo(ERROR_NONE);
-        assertThat(secondResult).isEqualTo(ERROR_SAME_MANIFEST_EXISTS);
-        assertThat(mCarTelemetryController.getValidManifestsCount()).isEqualTo(1);
-    }
-
-    @Test
-    public void removeManifest_whenValid_shouldSucceed() {
-        mCarTelemetryManager.addManifest(DEFAULT_MANIFEST_KEY, MANIFEST_BYTES);
-
-        boolean result = mCarTelemetryManager.removeManifest(DEFAULT_MANIFEST_KEY);
-
-        assertThat(result).isTrue();
-        assertThat(mCarTelemetryController.getValidManifestsCount()).isEqualTo(0);
-    }
-
-    @Test
-    public void removeManifest_whenInvalid_shouldIgnore() {
-        mCarTelemetryManager.addManifest(DEFAULT_MANIFEST_KEY, MANIFEST_BYTES);
-
-        boolean result = mCarTelemetryManager.removeManifest(new ManifestKey("NAME", 100));
-
-        assertThat(result).isFalse();
-        assertThat(mCarTelemetryController.getValidManifestsCount()).isEqualTo(1);
-    }
-
-    @Test
-    public void removeAllManifests_shouldSucceed() {
-        mCarTelemetryManager.addManifest(DEFAULT_MANIFEST_KEY, MANIFEST_BYTES);
-        mCarTelemetryManager.addManifest(new ManifestKey("NAME", 100), MANIFEST_BYTES);
-
-        mCarTelemetryManager.removeAllManifests();
-
-        assertThat(mCarTelemetryController.getValidManifestsCount()).isEqualTo(0);
-    }
-
-    @Test
-    public void sendFinishedReports_shouldSucceed() {
-        mCarTelemetryManager.setListener(DIRECT_EXECUTOR, mListener);
-        mCarTelemetryController.addDataForKey(DEFAULT_MANIFEST_KEY, SCRIPT_RESULT_BYTES);
-
-        mCarTelemetryManager.sendFinishedReports(DEFAULT_MANIFEST_KEY);
-
-        verify(mListener).onResult(DEFAULT_MANIFEST_KEY, SCRIPT_RESULT_BYTES);
-    }
-
-    @Test
-    public void sendAllFinishedReports_shouldSucceed() {
-        mCarTelemetryManager.setListener(DIRECT_EXECUTOR, mListener);
-        mCarTelemetryController.addDataForKey(DEFAULT_MANIFEST_KEY, SCRIPT_RESULT_BYTES);
-        ManifestKey key2 = new ManifestKey("key name", 1);
-        mCarTelemetryController.addDataForKey(key2, SCRIPT_RESULT_BYTES);
-
-        mCarTelemetryManager.sendAllFinishedReports();
-
-        verify(mListener).onResult(DEFAULT_MANIFEST_KEY, SCRIPT_RESULT_BYTES);
-        verify(mListener).onResult(key2, SCRIPT_RESULT_BYTES);
-    }
-
-    @Test
-    public void sendScriptExecutionErrors_shouldSucceed() {
-        mCarTelemetryManager.setListener(DIRECT_EXECUTOR, mListener);
-        mCarTelemetryController.setErrorData(ERROR_BYTES);
-
-        mCarTelemetryManager.sendScriptExecutionErrors();
-
-        verify(mListener).onError(ERROR_BYTES);
-    }
-}
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerPermisisonTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerPermisisonTest.java
index 01cff52..466333b 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerPermisisonTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerPermisisonTest.java
@@ -17,6 +17,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.mock;
 import static org.testng.Assert.assertThrows;
 import static org.testng.Assert.expectThrows;
 
@@ -35,6 +36,8 @@
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import java.util.concurrent.Executor;
+
 /**
  * This class contains security permission tests for the {@link CarInputManager}'s system APIs.
  */
@@ -62,14 +65,22 @@
     }
 
     @Test
-    public void testEnableFeaturePermission() {
+    public void testRequestInputEventCapturePermission() {
         assertThrows(SecurityException.class, () -> mCarInputManager.requestInputEventCapture(
                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
                 new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 0, mMockedCallback));
     }
 
     @Test
-    public void testInjectKeyEvent() {
+    public void testRequestInputEventCaptureWithExecutorPermission() {
+        assertThrows(SecurityException.class, () -> mCarInputManager.requestInputEventCapture(
+                CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
+                new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 0,
+                mock(Executor.class), mMockedCallback));
+    }
+
+    @Test
+    public void testInjectKeyEventPermission() {
         long currentTime = SystemClock.uptimeMillis();
         KeyEvent anyKeyEvent = new KeyEvent(/* downTime= */ currentTime,
                 /* eventTime= */ currentTime, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HOME,
@@ -82,4 +93,3 @@
                 "Injecting KeyEvent requires INJECT_EVENTS permission");
     }
 }
-
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/telemetry/CarTelemetryManagerPermissionTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/telemetry/CarTelemetryManagerPermissionTest.java
index 509c931..84409cf 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/telemetry/CarTelemetryManagerPermissionTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/telemetry/CarTelemetryManagerPermissionTest.java
@@ -22,7 +22,7 @@
 
 import android.car.Car;
 import android.car.telemetry.CarTelemetryManager;
-import android.car.telemetry.ManifestKey;
+import android.car.telemetry.MetricsConfigKey;
 import android.content.Context;
 
 import androidx.annotation.NonNull;
@@ -45,8 +45,8 @@
 public class CarTelemetryManagerPermissionTest {
     private final Context mContext =
             InstrumentationRegistry.getInstrumentation().getTargetContext();
-    private final ManifestKey mManifestKey = new ManifestKey("name", 1);
-    private final byte[] mManifestBytes = "manifest".getBytes();
+    private final MetricsConfigKey mMetricsConfigKey = new MetricsConfigKey("name", 1);
+    private final byte[] mMetricsConfigBytes = "manifest".getBytes();
 
     private Car mCar;
     private CarTelemetryManager mCarTelemetryManager;
@@ -83,25 +83,26 @@
     }
 
     @Test
-    public void testAddManifest() throws Exception {
+    public void testAddMetricsConfig() throws Exception {
         Exception e = expectThrows(SecurityException.class,
-                () -> mCarTelemetryManager.addManifest(mManifestKey, mManifestBytes));
+                () -> mCarTelemetryManager.addMetricsConfig(mMetricsConfigKey,
+                        mMetricsConfigBytes));
 
         assertThat(e.getMessage()).contains(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE);
     }
 
     @Test
-    public void testRemoveManifest() throws Exception {
+    public void testRemoveMetricsConfig() throws Exception {
         Exception e = expectThrows(SecurityException.class,
-                () -> mCarTelemetryManager.removeManifest(mManifestKey));
+                () -> mCarTelemetryManager.removeMetricsConfig(mMetricsConfigKey));
 
         assertThat(e.getMessage()).contains(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE);
     }
 
     @Test
-    public void testRemoveAllManifests() throws Exception {
+    public void testRemoveAllMetricsConfigs() throws Exception {
         Exception e = expectThrows(SecurityException.class,
-                () -> mCarTelemetryManager.removeAllManifests());
+                () -> mCarTelemetryManager.removeAllMetricsConfigs());
 
         assertThat(e.getMessage()).contains(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE);
     }
@@ -109,7 +110,7 @@
     @Test
     public void testSendFinishedReports() throws Exception {
         Exception e = expectThrows(SecurityException.class,
-                () -> mCarTelemetryManager.sendFinishedReports(mManifestKey));
+                () -> mCarTelemetryManager.sendFinishedReports(mMetricsConfigKey));
 
         assertThat(e.getMessage()).contains(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE);
     }
@@ -122,23 +123,22 @@
         assertThat(e.getMessage()).contains(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE);
     }
 
-    @Test
-    public void testSendScriptExecutionErrors() throws Exception {
-        Exception e = expectThrows(SecurityException.class,
-                () -> mCarTelemetryManager.sendScriptExecutionErrors());
-
-        assertThat(e.getMessage()).contains(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE);
-    }
-
     private class FakeCarTelemetryResultsListener implements
             CarTelemetryManager.CarTelemetryResultsListener {
         @Override
-        public void onResult(@NonNull ManifestKey key, @NonNull byte[] result) {
+        public void onResult(@NonNull MetricsConfigKey key, @NonNull byte[] result) {
         }
 
         @Override
-        public void onError(@NonNull byte[] error) {
+        public void onError(@NonNull MetricsConfigKey key, @NonNull byte[] error) {
+        }
+
+        @Override
+        public void onAddMetricsConfigStatus(@NonNull MetricsConfigKey key, int statusCode) {
+        }
+
+        @Override
+        public void onRemoveMetricsConfigStatus(@NonNull MetricsConfigKey key, boolean success) {
         }
     }
-
 }
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
index af573f7..ea527fa 100644
--- a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -50,6 +50,8 @@
     <uses-permission android:name="android.car.permission.READ_CAR_STEERING"/>
     <uses-permission android:name="android.car.permission.STORAGE_MONITORING"/>
     <uses-permission android:name="android.car.permission.CAR_DYNAMICS_STATE"/>
+    <!-- use for CarServiceTest -->
+    <uses-permission android:name="android.car.permission.USE_CAR_TELEMETRY_SERVICE"/>
     <!-- Allow querying and writing to any property -->
     <uses-permission android:name="android.car.permission.CAR_ENERGY_PORTS" />
     <uses-permission android:name="android.car.permission.PERMISSION_CONTROL_ENERGY_PORTS" />
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml b/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml
index a29296c..82c2c30 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml
@@ -48,5 +48,40 @@
                 android:padding="20dp"
                 android:text="@string/cluster_stop"
                 android:id="@+id/cluster_stop_button"/>
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="10dp"
+                android:padding="20dp">
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:gravity="center"
+                        android:layout_margin="5dp"
+                        android:text="@string/cluster_activity_state"/>
+                    <RadioGroup
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="horizontal">
+                        <RadioButton
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_margin="5dp"
+                            android:id="@+id/cluster_activity_state_default"
+                            android:text="@string/cluster_activity_state_default"/>
+                        <RadioButton
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_margin="5dp"
+                            android:id="@+id/cluster_activity_state_enabled"
+                            android:text="@string/cluster_activity_state_enabled"/>
+                        <RadioButton
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_margin="5dp"
+                            android:id="@+id/cluster_activity_state_disabled"
+                            android:text="@string/cluster_activity_state_disabled"/>
+                    </RadioGroup>
+            </LinearLayout>
     </LinearLayout>
 </LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml b/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml
index f1d7d81..ed3f100 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml
@@ -44,6 +44,58 @@
             android:background="#334666"
             android:orientation="vertical">
 
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_margin="10dp"
+                android:orientation="horizontal">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="10dp"
+                    android:layout_marginEnd="10dp"
+                    android:layout_gravity="center_vertical"
+                    android:text="Number of messages:"/>
+
+                <NumberPicker
+                    android:id="@+id/number_messages"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:background="#1da9ff"
+                    android:foreground="?android:attr/selectableItemBackground"
+                    android:textSize="30sp"/>
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="10dp"
+                    android:layout_marginEnd="10dp"
+                    android:layout_gravity="center_vertical"
+                    android:text="Number of people:"/>
+
+                <NumberPicker
+                    android:id="@+id/number_people"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:background="#1da9ff"
+                    android:foreground="?android:attr/selectableItemBackground"
+                    android:textSize="30sp"/>
+            </LinearLayout>
+
+            <Button
+                android:id="@+id/customizable_message_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="10dp"
+                android:layout_marginBottom="50dp"
+                android:layout_marginStart="10dp"
+                android:layout_marginEnd="10dp"
+                android:background="#1da9ff"
+                android:foreground="?android:attr/selectableItemBackground"
+                android:text="Customizable message notification builder"
+                android:textSize="30sp"/>
+
             <Button
                 android:id="@+id/category_call_button"
                 android:layout_width="wrap_content"
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
index 0613b45..2fb2de5 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
@@ -167,6 +167,10 @@
     <string name="cluster_start_activity" translatable="false">Start Nav Activity</string>
     <string name="cluster_start_activity_failed" translatable="false">Failed to start activity in cluster</string>
     <string name="cluster_not_started" translatable="false">Missing navigation focus</string>
+    <string name="cluster_activity_state" translatable="false">Cluster activity state</string>
+    <string name="cluster_activity_state_default" translatable="false">Default</string>
+    <string name="cluster_activity_state_enabled" translatable="false">On</string>
+    <string name="cluster_activity_state_disabled" translatable="false">Off</string>
 
     <!--  input test -->
     <string name="volume_up" translatable="false">Volume +</string>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java
index ab4e2d9..5599465 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java
@@ -34,6 +34,7 @@
 import android.car.cluster.navigation.NavigationState.Step;
 import android.car.cluster.navigation.NavigationState.Timestamp;
 import android.car.navigation.CarNavigationStatusManager;
+import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.util.Log;
@@ -41,6 +42,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.Button;
+import android.widget.RadioButton;
 import android.widget.Toast;
 
 import androidx.annotation.IdRes;
@@ -124,6 +126,7 @@
         NavigationStateProto[] navigationStateArray = new NavigationStateProto[1];
 
         navigationStateArray[0] = NavigationStateProto.newBuilder()
+                .setServiceStatus(NavigationStateProto.ServiceStatus.NORMAL)
                 .addSteps(Step.newBuilder()
                         .setManeuver(Maneuver.newBuilder()
                                 .setType(Maneuver.Type.DEPART)
@@ -205,6 +208,13 @@
 
         view.findViewById(R.id.cluster_start_button).setOnClickListener(v -> initCluster());
         view.findViewById(R.id.cluster_stop_button).setOnClickListener(v -> stopCluster());
+        view.findViewById(R.id.cluster_activity_state_default).setOnClickListener(v ->
+                changeClusterActivityState(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT));
+        view.findViewById(R.id.cluster_activity_state_enabled).setOnClickListener(v ->
+                changeClusterActivityState(PackageManager.COMPONENT_ENABLED_STATE_ENABLED));
+        view.findViewById(R.id.cluster_activity_state_disabled).setOnClickListener(v ->
+                changeClusterActivityState(PackageManager.COMPONENT_ENABLED_STATE_DISABLED));
+        updateInitialClusterActivityState(view);
 
         mTurnByTurnButton = view.findViewById(R.id.cluster_turn_left_button);
         mTurnByTurnButton.setOnClickListener(v -> toggleSendTurn());
@@ -212,6 +222,36 @@
         return view;
     }
 
+    private void updateInitialClusterActivityState(View view) {
+        PackageManager pm = getContext().getPackageManager();
+        ComponentName clusterActivity =
+                new ComponentName(getContext(), FakeClusterNavigationActivity.class);
+        int currentComponentState = pm.getComponentEnabledSetting(clusterActivity);
+        RadioButton button = view.findViewById(
+                convertClusterActivityStateToViewId(currentComponentState));
+        button.setChecked(true);
+    }
+
+    private int convertClusterActivityStateToViewId(int componentState) {
+        switch (componentState) {
+            case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT:
+                return R.id.cluster_activity_state_default;
+            case PackageManager.COMPONENT_ENABLED_STATE_ENABLED:
+                return R.id.cluster_activity_state_enabled;
+            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
+                return R.id.cluster_activity_state_disabled;
+        }
+        throw new IllegalStateException("Unknown component state: " + componentState);
+    }
+
+    private void changeClusterActivityState(int newComponentState) {
+        PackageManager pm = getContext().getPackageManager();
+        ComponentName clusterActivity =
+                new ComponentName(getContext(), FakeClusterNavigationActivity.class);
+        pm.setComponentEnabledSetting(clusterActivity, newComponentState,
+                PackageManager.DONT_KILL_APP);
+    }
+
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
         initCarApi();
@@ -271,6 +311,7 @@
             mTimer.cancel();
             mTimer = null;
         }
+        sendTurn(NavigationStateProto.newBuilder().build());
         mTurnByTurnButton.setText(R.string.cluster_start_guidance);
     }
 
@@ -278,13 +319,11 @@
      * Sends one update of the navigation state through the {@link CarNavigationStatusManager}
      */
     private void sendTurn(@NonNull NavigationStateProto state) {
-        try {
+        if (hasFocus()) {
             Bundle bundle = new Bundle();
             bundle.putByteArray("navstate2", state.toByteArray());
-            mCarNavigationStatusManager.sendEvent(1, bundle);
+            mCarNavigationStatusManager.sendNavigationStateChange(bundle);
             Log.i(TAG, "Sending nav state: " + state);
-        } catch (CarNotConnectedException e) {
-            Log.e(TAG, "Failed to send turn information.", e);
         }
     }
 
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java
index 3d6d76d..9dbef65 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java
@@ -14,6 +14,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.NumberPicker;
 
 import androidx.core.app.NotificationCompat;
 import androidx.core.app.NotificationCompat.Action;
@@ -26,7 +27,9 @@
 import com.google.android.car.kitchensink.KitchenSinkActivity;
 import com.google.android.car.kitchensink.R;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 
 /**
  * Test fragment that can send all sorts of notifications.
@@ -100,6 +103,7 @@
         initCallButton(view);
         initCustomGroupSummaryButton(view);
         initGroupWithoutSummaryButton(view);
+        initCustomizableMessageButton(view);
 
         return view;
     }
@@ -244,6 +248,83 @@
         });
     }
 
+    private void initCustomizableMessageButton(View view) {
+        NumberPicker messagesPicker = view.findViewById(R.id.number_messages);
+        messagesPicker.setMinValue(1);
+        messagesPicker.setMaxValue(25);
+        messagesPicker.setWrapSelectorWheel(true);
+        NumberPicker peoplePicker = view.findViewById(R.id.number_people);
+        peoplePicker.setMinValue(1);
+        peoplePicker.setMaxValue(25);
+        peoplePicker.setWrapSelectorWheel(true);
+
+        view.findViewById(R.id.customizable_message_button).setOnClickListener(v -> {
+            int id = mCurrentNotificationId++;
+
+            int numPeople = peoplePicker.getValue();
+            int numMessages = messagesPicker.getValue();
+
+            PendingIntent replyIntent = createServiceIntent(id, "reply");
+            PendingIntent markAsReadIntent = createServiceIntent(id, "read");
+
+            List<Person> personList = new ArrayList<>();
+
+            for (int i = 1; i <= numPeople; i++) {
+                personList.add(new Person.Builder()
+                        .setName("Person " + i)
+                        .setIcon(IconCompat.createWithResource(v.getContext(),
+                                i % 2 == 1 ? R.drawable.avatar1 : R.drawable.avatar2))
+                        .build());
+            }
+
+            MessagingStyle messagingStyle =
+                    new MessagingStyle(personList.get(0))
+                            .setConversationTitle("Customizable Group chat");
+            if (personList.size() > 1) {
+                messagingStyle.setGroupConversation(true);
+            }
+
+            int messageNumber = 1;
+            for (int i = 0; i < numMessages; i++) {
+                int personNum = i % numPeople;
+                if (personNum == numPeople - 1) {
+                    messageNumber++;
+                }
+                Person person = personList.get(personNum);
+                String messageText = person.getName() + "'s " + messageNumber + " message";
+                messagingStyle.addMessage(
+                        new MessagingStyle.Message(
+                                messageText,
+                                System.currentTimeMillis(),
+                                person));
+            }
+
+            NotificationCompat.Builder notification = new NotificationCompat
+                    .Builder(mContext, IMPORTANCE_HIGH_ID)
+                    .setContentTitle("Customizable Group chat (Title)")
+                    .setContentText("Customizable Group chat (Text)")
+                    .setShowWhen(true)
+                    .setCategory(Notification.CATEGORY_MESSAGE)
+                    .setSmallIcon(R.drawable.car_ic_mode)
+                    .setStyle(messagingStyle)
+                    .setAutoCancel(true)
+                    .setColor(mContext.getColor(android.R.color.holo_green_light))
+                    .addAction(
+                            new Action.Builder(R.drawable.ic_check_box, "read", markAsReadIntent)
+                                    .setSemanticAction(Action.SEMANTIC_ACTION_MARK_AS_READ)
+                                    .setShowsUserInterface(false)
+                                    .build())
+                    .addAction(
+                            new Action.Builder(R.drawable.ic_check_box, "reply", replyIntent)
+                                    .setSemanticAction(Action.SEMANTIC_ACTION_REPLY)
+                                    .setShowsUserInterface(false)
+                                    .addRemoteInput(new RemoteInput.Builder("input").build())
+                                    .build());
+
+            mManager.notify(id, notification.build());
+        });
+    }
+
     private void initMessagingStyleButtonForDiffPerson(View view) {
         view.findViewById(R.id.category_message_diff_person_button).setOnClickListener(v -> {
             int id = mCurrentNotificationId++;
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/CarAudioZoneVolumeAdapter.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/CarAudioZoneVolumeAdapter.java
index 4493806..f8d19d6 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/CarAudioZoneVolumeAdapter.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/CarAudioZoneVolumeAdapter.java
@@ -66,25 +66,26 @@
         }
         if (mVolumeList[position] != null) {
             vh.id.setText(mVolumeList[position].id);
-            vh.maxVolume.setText(String.valueOf(mVolumeList[position].maxGain));
             vh.currentVolume.setText(String.valueOf(mVolumeList[position].currentGain));
             int color = mVolumeList[position].hasAudioFocus ? Color.GREEN : Color.GRAY;
             vh.requestButton.setBackgroundColor(color);
             if (position == 0) {
+                vh.maxVolume.setText("Max");
                 vh.upButton.setVisibility(View.INVISIBLE);
                 vh.downButton.setVisibility(View.INVISIBLE);
                 vh.requestButton.setVisibility(View.INVISIBLE);
                 vh.muteButton.setVisibility(View.INVISIBLE);
             } else {
+                vh.maxVolume.setText(String.valueOf(mVolumeList[position].maxGain));
                 vh.upButton.setVisibility(View.VISIBLE);
                 vh.downButton.setVisibility(View.VISIBLE);
                 vh.requestButton.setVisibility(View.VISIBLE);
                 vh.muteButton.setVisibility(mGroupMuteEnabled ? View.VISIBLE : View.INVISIBLE);
                 vh.upButton.setOnClickListener((view) -> {
-                    mFragment.adjustVolumeByOne(mVolumeList[position].groupId, true);
+                    mFragment.adjustVolumeUp(mVolumeList[position].groupId);
                 });
                 vh.downButton.setOnClickListener((view) -> {
-                    mFragment.adjustVolumeByOne(mVolumeList[position].groupId, false);
+                    mFragment.adjustVolumeDown(mVolumeList[position].groupId);
                 });
                 vh.muteButton.setChecked(mVolumeList[position].isMuted);
                 vh.muteButton.setOnClickListener((view) -> {
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/CarAudioZoneVolumeFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/CarAudioZoneVolumeFragment.java
index 675f495..2419511 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/CarAudioZoneVolumeFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/CarAudioZoneVolumeFragment.java
@@ -52,7 +52,10 @@
     private static final int MSG_REQUEST_FOCUS = 1;
     private static final int MSG_FOCUS_CHANGED = 2;
     private static final int MSG_STOP_RINGTONE = 3;
+    private static final int MSG_ADJUST_VOLUME = 4;
     private static final long RINGTONE_STOP_TIME_MS = 3_000;
+    private static final int ADJUST_VOLUME_UP = 0;
+    private static final int ADJUST_VOLUME_DOWN = 1;
 
     private final int mZoneId;
     private final Object mLock = new Object();
@@ -68,10 +71,19 @@
     @GuardedBy("mLock")
     private Ringtone mRingtone;
 
-    public void sendVolumeChangedMessage(int groupId, int flags) {
+    void sendVolumeChangedMessage(int groupId, int flags) {
         mHandler.sendMessage(mHandler.obtainMessage(MSG_VOLUME_CHANGED, groupId, flags));
     }
 
+    void adjustVolumeUp(int groupId) {
+        mHandler.sendMessage(mHandler.obtainMessage(MSG_ADJUST_VOLUME, groupId, ADJUST_VOLUME_UP));
+    }
+
+    void adjustVolumeDown(int groupId) {
+        mHandler.sendMessage(mHandler
+                .obtainMessage(MSG_ADJUST_VOLUME, groupId, ADJUST_VOLUME_DOWN));
+    }
+
     private class VolumeHandler extends Handler {
         private AudioFocusListener mFocusListener;
 
@@ -105,10 +117,12 @@
                     mVolumeInfos[mGroupIdIndexMap.get(focusGroupId)].hasAudioFocus = true;
                     mCarAudioZoneVolumeAdapter.refreshVolumes(mVolumeInfos);
                     break;
-                default :
-                    Log.wtf(TAG,"VolumeHandler handleMessage called with unknown message"
+                case MSG_ADJUST_VOLUME:
+                    adjustVolumeByOne(msg.arg1, msg.arg2 == ADJUST_VOLUME_UP);
+                    break;
+                default:
+                    Log.wtf(TAG, "VolumeHandler handleMessage called with unknown message"
                             + msg.what);
-
             }
         }
     }
@@ -144,7 +158,6 @@
         CarAudioZoneVolumeInfo titlesInfo = new CarAudioZoneVolumeInfo();
         titlesInfo.id = "Group id";
         titlesInfo.currentGain = "Current";
-        titlesInfo.maxGain = "Max";
         mVolumeInfos[0] = titlesInfo;
 
         int i = 1;
@@ -155,13 +168,14 @@
             volumeInfo.id = String.valueOf(groupId);
             int current = mCarAudioManager.getGroupVolume(mZoneId, groupId);
             int max = mCarAudioManager.getGroupMaxVolume(mZoneId, groupId);
+            int min = mCarAudioManager.getGroupMinVolume(mZoneId, groupId);
             volumeInfo.currentGain = String.valueOf(current);
-            volumeInfo.maxGain = String.valueOf(max);
+            volumeInfo.maxGain = max;
+            volumeInfo.minGain = min;
             volumeInfo.isMuted = mCarAudioManager.isVolumeGroupMuted(mZoneId, groupId);
 
             mVolumeInfos[i] = volumeInfo;
-            if (DEBUG)
-            {
+            if (DEBUG) {
                 Log.d(TAG, groupId + " max: " + volumeInfo.maxGain + " current: "
                         + volumeInfo.currentGain + " is muted " + volumeInfo.isMuted);
             }
@@ -170,18 +184,38 @@
         mCarAudioZoneVolumeAdapter.refreshVolumes(mVolumeInfos);
     }
 
-    public void adjustVolumeByOne(int groupId, boolean up) {
+    private void adjustVolumeByOne(int groupId, boolean up) {
         if (mCarAudioManager == null) {
             Log.e(TAG, "CarAudioManager is null");
             return;
         }
         int current = mCarAudioManager.getGroupVolume(mZoneId, groupId);
-        int volume = current + (up ? 1 : -1);
-        mCarAudioManager.setGroupVolume(mZoneId, groupId, volume, AudioManager.FLAG_SHOW_UI);
-        if (DEBUG) {
-            Log.d(TAG, "Set group " + groupId + " volume " + volume + " in audio zone "
-                    + mZoneId);
+        CarAudioZoneVolumeInfo info = getVolumeInfo(groupId);
+        int volume = up ? current + 1 : current - 1;
+        if (volume > info.maxGain) {
+            if (DEBUG) {
+                Log.d(TAG, "Reached " + groupId + " max volume "
+                        + " limit " + volume);
+            }
+            return;
         }
+        if (volume < info.minGain) {
+            if (DEBUG) {
+                Log.d(TAG, "Reached " + groupId + " min volume "
+                        + " limit " + volume);
+            }
+            return;
+        }
+        mCarAudioManager.setGroupVolume(mZoneId, groupId, volume, /* flags= */ 0);
+        if (DEBUG) {
+            Log.d(TAG, "Set group " + groupId + " volume "
+                    + mCarAudioManager.getGroupVolume(mZoneId, groupId)
+                    + " in audio zone " + mZoneId);
+        }
+    }
+
+    private CarAudioZoneVolumeInfo getVolumeInfo(int groupId) {
+        return mVolumeInfos[mGroupIdIndexMap.get(groupId)];
     }
 
     public void toggleMute(int groupId) {
@@ -197,7 +231,7 @@
         }
     }
 
-    public void requestFocus(int groupId) {
+    void requestFocus(int groupId) {
         // Automatic volume change only works for primary audio zone.
         if (mZoneId == CarAudioManager.PRIMARY_AUDIO_ZONE) {
             mHandler.sendMessage(mHandler
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeTestFragment.java
index 2a2c38e..821d362 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeTestFragment.java
@@ -62,7 +62,8 @@
     public static final class CarAudioZoneVolumeInfo {
         public int groupId;
         public String id;
-        public String maxGain;
+        public int maxGain;
+        public int minGain;
         public String currentGain;
         public boolean hasAudioFocus;
         public boolean isMuted;
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/watchdog/CarWatchdogTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/watchdog/CarWatchdogTestFragment.java
index 33bc54a..36a300e 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/watchdog/CarWatchdogTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/watchdog/CarWatchdogTestFragment.java
@@ -73,6 +73,7 @@
     private static final long TEN_MEGABYTES = 1024 * 1024 * 10;
     private static final int DISK_DELAY_MS = 3000;
     private static final String TAG = "CarWatchdogTestFragment";
+    private static final double WARN_THRESHOLD_PERCENT = 0.8;
     private static final double EXCEED_WARN_THRESHOLD_PERCENT = 0.9;
 
     private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
@@ -133,13 +134,23 @@
                                         return;
                                     }
 
+                                    /*
+                                     * CarService notifies applications on exceeding 80% of the
+                                     * threshold. The app maybe notified before completing the
+                                     * following write. Ergo, the minimum expected written bytes
+                                     * should be the warn threshold rather than the actual amount
+                                     * of bytes written by the app.
+                                     */
+                                    long bytesToWarnThreshold = (long) Math.ceil(
+                                            (remainingBytes + TEN_MEGABYTES)
+                                                    * WARN_THRESHOLD_PERCENT);
+
+                                    listener.setExpectedMinWrittenBytes(bytesToWarnThreshold);
+
                                     long bytesToExceedWarnThreshold =
                                             (long) Math.ceil(remainingBytes
                                                     * EXCEED_WARN_THRESHOLD_PERCENT);
 
-                                    listener.setExpectedMinWrittenBytes(
-                                            TEN_MEGABYTES + bytesToExceedWarnThreshold);
-
                                     if (!writeToDisk(bytesToExceedWarnThreshold)) {
                                         mCarWatchdogManager.removeResourceOveruseListener(listener);
                                         return;
diff --git a/tests/NetworkPreferenceApp/res/layout/manager.xml b/tests/NetworkPreferenceApp/res/layout/manager.xml
index 0f826ee..a36bc2e 100644
--- a/tests/NetworkPreferenceApp/res/layout/manager.xml
+++ b/tests/NetworkPreferenceApp/res/layout/manager.xml
@@ -24,7 +24,7 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_weight="1"
-        android:weightSum="2">
+        android:weightSum="3">
         <LinearLayout
             style="@style/SectionContainer"
             android:layout_width="match_parent"
@@ -47,34 +47,29 @@
             android:layout_height="wrap_content"
             android:layout_weight="1"
             android:weightSum="2">
-            <LinearLayout
-                android:layout_width="match_parent"
+            <TextView
+                android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_weight="1"
-                android:weightSum="2">
-                <TextView
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:text="@string/label_apply_latest_policy_on_boot"/>
-                <Switch
-                    android:id="@+id/reapplyPANSOnBootSwitch"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"/>
-            </LinearLayout>
-            <LinearLayout
-                android:layout_width="match_parent"
+                android:text="@string/label_apply_latest_policy_on_boot"/>
+            <Switch
+                android:id="@+id/reapplyPANSOnBootSwitch"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"/>
+        </LinearLayout>
+        <LinearLayout
+            style="@style/SectionContainer"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:weightSum="2">
+            <TextView
+                android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_weight="1"
-                android:weightSum="2">
-                <TextView
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:text="@string/label_apply_wifi_policy_on_boot"/>
-                <Switch
-                    android:id="@+id/reapplyWifiSuggestionsOnBootSwitch"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"/>
-            </LinearLayout>
+                android:text="@string/label_apply_wifi_policy_on_boot"/>
+            <Switch
+                android:id="@+id/reapplyWifiSuggestionsOnBootSwitch"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"/>
         </LinearLayout>
     </LinearLayout>
     <LinearLayout
diff --git a/tests/ThemePlayground/AndroidManifest.xml b/tests/ThemePlayground/AndroidManifest.xml
index cebff5e..8070a15 100644
--- a/tests/ThemePlayground/AndroidManifest.xml
+++ b/tests/ThemePlayground/AndroidManifest.xml
@@ -48,6 +48,12 @@
              android:resizeableActivity="true"
              android:allowEmbedded="true">
         </activity>
+        <activity android:name=".ColorPalette"
+             android:label="@string/palette_elements"
+             android:windowSoftInputMode="stateUnchanged"
+             android:resizeableActivity="true"
+             android:allowEmbedded="true">
+        </activity>
         <activity android:name=".ProgressBarSamples"
                   android:label="@string/progress_bar_elements"
                   android:windowSoftInputMode="stateUnchanged"
diff --git a/tests/ThemePlayground/res/layout/color_palette.xml b/tests/ThemePlayground/res/layout/color_palette.xml
new file mode 100644
index 0000000..e335b30
--- /dev/null
+++ b/tests/ThemePlayground/res/layout/color_palette.xml
@@ -0,0 +1,436 @@
+<?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
+  -->
+
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <ScrollView
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginBottom="8dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="8dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent1_0">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent1_0"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent1_10">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent1_10"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent1_50">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent1_50"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent1_100">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent1_100"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent1_200">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent1_200"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent1_300">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent1_300"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent1_400">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent1_400"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent1_500">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent1_500"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent1_600">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent1_600"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent1_700">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent1_700"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent1_800">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent1_800"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent1_900">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent1_900"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent1_1000">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent1_1000"/>
+            </FrameLayout>
+
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent2_0">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent2_0"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent2_10">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent2_10"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent2_50">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent2_50"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent2_100">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent2_100"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent2_200">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent2_200"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent2_300">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent2_300"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent2_400">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent2_400"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent2_500">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent2_500"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent2_600">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent2_600"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent2_700">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent2_700"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent2_800">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent2_800"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent2_900">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent2_900"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent2_1000">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent2_1000"/>
+            </FrameLayout>
+
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent3_0">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent3_0"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent3_10">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent3_10"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent3_50">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent3_50"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent3_100">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent3_100"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent3_200">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent3_200"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent3_300">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent3_300"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent3_400">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent3_400"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent3_500">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent3_500"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent3_600">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent3_600"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent3_700">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent3_700"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent3_800">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent3_800"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent3_900">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent3_900"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="100dp"
+                android:background="@drawable/system_accent3_1000">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="*android:color/system_accent3_1000"/>
+            </FrameLayout>
+
+        </LinearLayout>
+    </ScrollView>
+    <include layout="@layout/menu_button"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/tests/ThemePlayground/res/menu/menu_main.xml b/tests/ThemePlayground/res/menu/menu_main.xml
index 8e4fd8c..647b02d 100644
--- a/tests/ThemePlayground/res/menu/menu_main.xml
+++ b/tests/ThemePlayground/res/menu/menu_main.xml
@@ -30,6 +30,10 @@
         android:orderInCategory="100"
         android:title="@string/panel_elements"/>
     <item
+        android:id="@+id/palette_elements"
+        android:orderInCategory="100"
+        android:title="@string/palette_elements"/>
+    <item
         android:id="@+id/progress_bar_elements"
         android:orderInCategory="100"
         android:title="@string/progress_bar_elements"/>
diff --git a/tests/ThemePlayground/res/values/drawables.xml b/tests/ThemePlayground/res/values/drawables.xml
new file mode 100644
index 0000000..aeb9c88
--- /dev/null
+++ b/tests/ThemePlayground/res/values/drawables.xml
@@ -0,0 +1,61 @@
+<?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">
+
+    <drawable name="system_accent1_0">@*android:color/system_accent1_0</drawable>
+    <drawable name="system_accent1_10">@*android:color/system_accent1_10</drawable>
+    <drawable name="system_accent1_50">@*android:color/system_accent1_50</drawable>
+    <drawable name="system_accent1_100">@*android:color/system_accent1_100</drawable>
+    <drawable name="system_accent1_200">@*android:color/system_accent1_200</drawable>
+    <drawable name="system_accent1_300">@*android:color/system_accent1_300</drawable>
+    <drawable name="system_accent1_400">@*android:color/system_accent1_400</drawable>
+    <drawable name="system_accent1_500">@*android:color/system_accent1_500</drawable>
+    <drawable name="system_accent1_600">@*android:color/system_accent1_600</drawable>
+    <drawable name="system_accent1_700">@*android:color/system_accent1_700</drawable>
+    <drawable name="system_accent1_800">@*android:color/system_accent1_800</drawable>
+    <drawable name="system_accent1_900">@*android:color/system_accent1_900</drawable>
+    <drawable name="system_accent1_1000">@*android:color/system_accent1_1000</drawable>
+
+    <drawable name="system_accent2_0">@*android:color/system_accent2_0</drawable>
+    <drawable name="system_accent2_10">@*android:color/system_accent2_10</drawable>
+    <drawable name="system_accent2_50">@*android:color/system_accent2_50</drawable>
+    <drawable name="system_accent2_100">@*android:color/system_accent2_100</drawable>
+    <drawable name="system_accent2_200">@*android:color/system_accent2_200</drawable>
+    <drawable name="system_accent2_300">@*android:color/system_accent2_300</drawable>
+    <drawable name="system_accent2_400">@*android:color/system_accent2_400</drawable>
+    <drawable name="system_accent2_500">@*android:color/system_accent2_500</drawable>
+    <drawable name="system_accent2_600">@*android:color/system_accent2_600</drawable>
+    <drawable name="system_accent2_700">@*android:color/system_accent2_700</drawable>
+    <drawable name="system_accent2_800">@*android:color/system_accent2_800</drawable>
+    <drawable name="system_accent2_900">@*android:color/system_accent2_900</drawable>
+    <drawable name="system_accent2_1000">@*android:color/system_accent2_1000</drawable>
+
+    <drawable name="system_accent3_0">@*android:color/system_accent3_0</drawable>
+    <drawable name="system_accent3_10">@*android:color/system_accent3_10</drawable>
+    <drawable name="system_accent3_50">@*android:color/system_accent3_50</drawable>
+    <drawable name="system_accent3_100">@*android:color/system_accent3_100</drawable>
+    <drawable name="system_accent3_200">@*android:color/system_accent3_200</drawable>
+    <drawable name="system_accent3_300">@*android:color/system_accent3_300</drawable>
+    <drawable name="system_accent3_400">@*android:color/system_accent3_400</drawable>
+    <drawable name="system_accent3_500">@*android:color/system_accent3_500</drawable>
+    <drawable name="system_accent3_600">@*android:color/system_accent3_600</drawable>
+    <drawable name="system_accent3_700">@*android:color/system_accent3_700</drawable>
+    <drawable name="system_accent3_800">@*android:color/system_accent3_800</drawable>
+    <drawable name="system_accent3_900">@*android:color/system_accent3_900</drawable>
+    <drawable name="system_accent3_1000">@*android:color/system_accent3_1000</drawable>
+
+</resources>
diff --git a/tests/ThemePlayground/res/values/strings.xml b/tests/ThemePlayground/res/values/strings.xml
index c355777..bd4b79a 100644
--- a/tests/ThemePlayground/res/values/strings.xml
+++ b/tests/ThemePlayground/res/values/strings.xml
@@ -20,6 +20,7 @@
     <string name="button_elements" translatable="false">Button Elements</string>
     <string name="progress_bar_elements" translatable="false">Progress Bar Elements</string>
     <string name="panel_elements" translatable="false">Color Panels</string>
+    <string name="palette_elements" translatable="false">Color Palette</string>
     <string name="dialog_elements" translatable="false">Dialogs</string>
     <string name="toggle_theme" translatable="false">Change configuration(Day/Night)</string>
     <string name="apply" translatable="false">Apply</string>
diff --git a/tests/ThemePlayground/src/com/android/car/themeplayground/AbstractSampleActivity.java b/tests/ThemePlayground/src/com/android/car/themeplayground/AbstractSampleActivity.java
index c197925..0f7cdfa 100644
--- a/tests/ThemePlayground/src/com/android/car/themeplayground/AbstractSampleActivity.java
+++ b/tests/ThemePlayground/src/com/android/car/themeplayground/AbstractSampleActivity.java
@@ -58,6 +58,8 @@
                 return startSampleActivity(ProgressBarSamples.class);
             case R.id.panel_elements:
                 return startSampleActivity(ColorSamples.class);
+            case R.id.palette_elements:
+                return startSampleActivity(ColorPalette.class);
             case R.id.dialog_elements:
                 return startSampleActivity(DialogSamples.class);
             case R.id.toggle_theme:
diff --git a/tests/ThemePlayground/src/com/android/car/themeplayground/ColorPalette.java b/tests/ThemePlayground/src/com/android/car/themeplayground/ColorPalette.java
new file mode 100644
index 0000000..5f9f9ac
--- /dev/null
+++ b/tests/ThemePlayground/src/com/android/car/themeplayground/ColorPalette.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.themeplayground;
+
+import android.os.Bundle;
+
+/**
+ * Activity that renders a bunch of color values from the theme.
+ */
+public class ColorPalette extends AbstractSampleActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Utils.onActivityCreateSetTheme(this);
+        setContentView(R.layout.color_palette);
+    }
+}
diff --git a/tests/UserSwitchMonitorApp/Android.bp b/tests/UserSwitchMonitorApp/Android.bp
index 9aa39a9..b5f29eb 100644
--- a/tests/UserSwitchMonitorApp/Android.bp
+++ b/tests/UserSwitchMonitorApp/Android.bp
@@ -27,3 +27,18 @@
 
     sdk_version: "system_current",
 }
+
+// "Cloned" app used to make sure events are received by apps with shared uid
+android_app {
+    name: "UserSwitchMonitorApp2",
+
+    manifest: "AndroidManifest2.xml",
+
+    libs: [
+        "android.car-system-stubs",
+    ],
+
+    srcs: ["src/**/*.java"],
+
+    sdk_version: "system_current",
+}
diff --git a/tests/UserSwitchMonitorApp/AndroidManifest.xml b/tests/UserSwitchMonitorApp/AndroidManifest.xml
index e78d690..fec167a 100644
--- a/tests/UserSwitchMonitorApp/AndroidManifest.xml
+++ b/tests/UserSwitchMonitorApp/AndroidManifest.xml
@@ -16,15 +16,17 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.google.android.car.userswitchmonitor">
+    package="com.google.android.car.userswitchmonitor"
+    android:sharedUserId="com.google.android.car.userswitchmonitor">
 
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
 
-    <application android:label="User Switch Monitor">
-        <service android:name=".UserSwitchMonitorService"/>
-        <receiver android:name=".BootCompletedReceiver" android:exported="true">
+    <application android:icon="@drawable/ic_launcher" android:label="User Switch Monitor">
+        <service android:name="com.google.android.car.userswitchmonitor.UserSwitchMonitorService"/>
+        <receiver android:name="com.google.android.car.userswitchmonitor.BootCompletedReceiver"
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.BOOT_COMPLETED"/>
             </intent-filter>
diff --git a/tests/UserSwitchMonitorApp/AndroidManifest2.xml b/tests/UserSwitchMonitorApp/AndroidManifest2.xml
new file mode 100644
index 0000000..bb38752
--- /dev/null
+++ b/tests/UserSwitchMonitorApp/AndroidManifest2.xml
@@ -0,0 +1,36 @@
+<?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
+  -->
+
+<!-- "Cloned" app used to make sure events are received by apps with shared uid -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.google.android.car.userswitchmonitor2"
+    android:sharedUserId="com.google.android.car.userswitchmonitor">
+
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+
+    <application android:icon="@drawable/ic_launcher" android:label="User Switch Monitor">
+        <service android:name="com.google.android.car.userswitchmonitor.UserSwitchMonitorService"/>
+        <receiver android:name="com.google.android.car.userswitchmonitor.BootCompletedReceiver"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED"/>
+            </intent-filter>
+        </receiver>
+    </application>
+</manifest>
diff --git a/tests/UserSwitchMonitorApp/src/com/google/android/car/userswitchmonitor/UserSwitchMonitorService.java b/tests/UserSwitchMonitorApp/src/com/google/android/car/userswitchmonitor/UserSwitchMonitorService.java
index f76b0d6..73c5cc4 100644
--- a/tests/UserSwitchMonitorApp/src/com/google/android/car/userswitchmonitor/UserSwitchMonitorService.java
+++ b/tests/UserSwitchMonitorApp/src/com/google/android/car/userswitchmonitor/UserSwitchMonitorService.java
@@ -41,6 +41,10 @@
 
     static final String TAG = "UserSwitchMonitor";
 
+    private static final String CMD_HELP = "help";
+    private static final String CMD_REGISTER = "register";
+    private static final String CMD_UNREGISTER = "unregister";
+
     private final Object mLock = new Object();
 
     private final int mUserId = android.os.Process.myUserHandle().getIdentifier();
@@ -64,11 +68,16 @@
         mContext = getApplicationContext();
         mCar = Car.createCar(mContext);
         mCarUserManager = (CarUserManager) mCar.getCarManager(Car.CAR_USER_SERVICE);
-        mCarUserManager.addListener((r)-> r.run(), mListener);
+        registerListener();
 
         mNotificationManager = mContext.getSystemService(NotificationManager.class);
     }
 
+    private void registerListener() {
+        Log.d(TAG, "registerListener(): " + mListener);
+        mCarUserManager.addListener((r)-> r.run(), mListener);
+    }
+
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
         Log.d(TAG, "onStartCommand(" + mUserId + "): " + intent);
@@ -79,11 +88,13 @@
                 NotificationManager.IMPORTANCE_MIN);
         mNotificationManager.createNotificationChannel(channel);
 
+        // Cannot use R.drawable because package name is different on app2
+        int iconResId = mContext.getApplicationInfo().icon;
         startForeground(startId,
                 new Notification.Builder(mContext, channelId)
                         .setContentText(name)
                         .setContentTitle(name)
-                        .setSmallIcon(R.drawable.ic_launcher)
+                        .setSmallIcon(iconResId)
                         .build());
 
         return super.onStartCommand(intent, flags, startId);
@@ -93,19 +104,29 @@
     public void onDestroy() {
         Log.d(TAG, "onDestroy(" + mUserId + ")");
 
-        if (mCarUserManager != null) {
-            mCarUserManager.removeListener(mListener);
-        } else {
-            Log.w(TAG, "Cannot remove listener because manager is null");
-        }
+        unregisterListener();
         if (mCar != null && mCar.isConnected()) {
             mCar.disconnect();
         }
         super.onDestroy();
     }
 
+    private void unregisterListener() {
+        Log.d(TAG, "unregisterListener(): " + mListener);
+        if (mCarUserManager != null) {
+            mCarUserManager.removeListener(mListener);
+        } else {
+            Log.w(TAG, "Cannot remove listener because manager is null");
+        }
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (args != null && args.length > 0) {
+            executeCommand(pw, args);
+            return;
+        }
+
         pw.printf("User id: %d\n", mUserId);
         synchronized (mLock) {
             if (mEvents.isEmpty()) {
@@ -127,4 +148,48 @@
         return null;
     }
 
+    private void executeCommand(PrintWriter pw, String[] args) {
+        String cmd = args[0];
+        switch (cmd) {
+            case CMD_HELP:
+                cmdHelp(pw);
+                break;
+            case CMD_REGISTER:
+                cmdRegister(pw);
+                break;
+            case CMD_UNREGISTER:
+                cmdUnregister(pw);
+                break;
+            default:
+                pw.printf("invalid command: %s\n\n",  cmd);
+                cmdHelp(pw);
+        }
+    }
+
+    private void cmdHelp(PrintWriter pw) {
+        pw.printf("Options:\n");
+        pw.printf("  help: show this help\n");
+        pw.printf("  register: register the service to receive events\n");
+        pw.printf("  unregister: unregister the service from receiving events\n");
+    }
+
+
+    private void cmdRegister(PrintWriter pw) {
+        pw.printf("registering listener %s\n", mListener);
+        runCmd(pw, () -> registerListener());
+    }
+
+    private void cmdUnregister(PrintWriter pw) {
+        pw.printf("unregistering listener %s\n", mListener);
+        runCmd(pw, () -> unregisterListener());
+    }
+
+    private void runCmd(PrintWriter pw, Runnable r) {
+        try {
+            r.run();
+        } catch (Exception e) {
+            Log.e(TAG, "error running command", e);
+            pw.printf("failed: %s\n", e);
+        }
+    }
 }
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java
index af81c61..276e829 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java
@@ -36,6 +36,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
@@ -246,6 +248,24 @@
         APP_FOCUS_TYPE_NAVIGATION, false)).isTrue();
     }
 
+    @Test
+    public void testGetAppTypeOwner() throws Exception {
+        CarAppFocusManager manager = createManager(getContext(), mEventThread);
+
+        assertThat(manager.getAppTypeOwner(APP_FOCUS_TYPE_NAVIGATION)).isNull();
+
+        FocusOwnershipCallback owner = new FocusOwnershipCallback();
+        assertThat(manager.requestAppFocus(APP_FOCUS_TYPE_NAVIGATION, owner))
+                .isEqualTo(APP_FOCUS_REQUEST_SUCCEEDED);
+
+        assertThat(manager.getAppTypeOwner(APP_FOCUS_TYPE_NAVIGATION))
+                .containsExactly("android.car.apitest", "com.google.android.car.kitchensink");
+
+        manager.abandonAppFocus(owner, APP_FOCUS_TYPE_NAVIGATION);
+
+        assertThat(manager.getAppTypeOwner(APP_FOCUS_TYPE_NAVIGATION)).isNull();
+    }
+
     private CarAppFocusManager createManager() throws InterruptedException {
         return createManager(getContext(), mEventThread);
     }
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarBugreportManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarBugreportManagerTest.java
index 6a7ccdb..21aef7b 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarBugreportManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarBugreportManagerTest.java
@@ -26,6 +26,7 @@
 import android.car.CarBugreportManager;
 import android.car.CarBugreportManager.CarBugreportManagerCallback;
 import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.FlakyTest;
 import android.test.suitebuilder.annotation.LargeTest;
 
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -71,7 +72,7 @@
     }
 
     @Test
-    public void test_requestBugreport_failsWhenNoPermission() throws Exception {
+    public void test_requestBugreport_failsWhenNoPermission() {
         dropPermissions();
 
         SecurityException expected =
@@ -83,6 +84,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 197652182)
     public void test_requestBugreport_works() throws Exception {
         getPermissions();
 
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 62f2b0a..2662eaf 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
@@ -18,15 +18,22 @@
 import static android.car.test.util.UserTestingHelper.clearUserLockCredentials;
 import static android.car.test.util.UserTestingHelper.setMaxSupportedUsers;
 import static android.car.test.util.UserTestingHelper.setUserLockCredentials;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING;
 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.car.Car;
 import android.car.testapi.BlockingUserLifecycleListener;
+import android.car.user.CarUserManager;
 import android.car.user.CarUserManager.UserLifecycleEvent;
 import android.content.pm.UserInfo;
+import android.os.Process;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
 
@@ -34,12 +41,15 @@
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import java.util.List;
+
 public final class CarUserManagerTest extends CarMultiUserTestBase {
 
     private static final String TAG = CarUserManagerTest.class.getSimpleName();
 
     private static final int PIN = 2345;
 
+    private static final int START_TIMEOUT_MS = 20_000;
     private static final int SWITCH_TIMEOUT_MS = 70_000;
 
     private static final int sMaxNumberUsersBefore = UserManager.getMaxSupportedUsers();
@@ -92,6 +102,75 @@
         assertUserInfo(newGuest, loadedGuest);
     }
 
+    @Test
+    public void testLifecycleMultipleListeners() throws Exception {
+        int newUserId = createUser("Test").id;
+        Car car2 = Car.createCar(getContext().getApplicationContext());
+        CarUserManager mgr2 = (CarUserManager) car2.getCarManager(Car.CAR_USER_SERVICE);
+        CarUserManager mgr1 = mCarUserManager;
+        Log.d(TAG, "myUid=" + Process.myUid() + ",mgr1=" + mgr1 + ", mgr2=" + mgr2);
+        assertWithMessage("mgrs").that(mgr1).isNotSameInstanceAs(mgr2);
+
+        BlockingUserLifecycleListener listener1 = BlockingUserLifecycleListener
+                .forSpecificEvents()
+                .forUser(newUserId)
+                .setTimeout(START_TIMEOUT_MS)
+                .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING)
+                .build();
+        BlockingUserLifecycleListener listener2 = BlockingUserLifecycleListener
+                .forSpecificEvents()
+                .forUser(newUserId)
+                .setTimeout(START_TIMEOUT_MS)
+                .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING)
+                .build();
+
+        Log.d(TAG, "registering listener1: " + listener1);
+        mgr1.addListener(Runnable::run, listener1);
+        Log.v(TAG, "ok");
+        try {
+            Log.d(TAG, "registering listener2: " + listener2);
+            mgr2.addListener(Runnable::run, listener2);
+            Log.v(TAG, "ok");
+            try {
+                IActivityManager am = ActivityManager.getService();
+                Log.d(TAG, "Starting user " + newUserId);
+                am.startUserInBackground(newUserId);
+                Log.v(TAG, "ok");
+
+                Log.d(TAG, "Waiting for events");
+                List<UserLifecycleEvent> events1 = listener1.waitForEvents();
+                Log.d(TAG, "events1: " + events1);
+                List<UserLifecycleEvent> events2 = listener2.waitForEvents();
+                Log.d(TAG, "events2: " + events2);
+                assertStartUserEvent(events1, newUserId);
+                assertStartUserEvent(events2, newUserId);
+            } finally {
+                Log.d(TAG, "unregistering listener2: " + listener2);
+                mgr2.removeListener(listener2);
+                Log.v(TAG, "ok");
+            }
+        } finally {
+            Log.d(TAG, "unregistering listener1: " + listener1);
+            mgr1.removeListener(listener1);
+            Log.v(TAG, "ok");
+        }
+    }
+
+    private void assertStartUserEvent(List<UserLifecycleEvent> events, @UserIdInt int userId) {
+        assertWithMessage("events").that(events).hasSize(1);
+
+        UserLifecycleEvent event = events.get(0);
+        assertWithMessage("type").that(event.getEventType())
+                .isEqualTo(USER_LIFECYCLE_EVENT_TYPE_STARTING);
+        assertWithMessage("user id on %s", event).that(event.getUserId()).isEqualTo(userId);
+        assertWithMessage("user handle on %s", event).that(event.getUserHandle().getIdentifier())
+                .isEqualTo(userId);
+        assertWithMessage("previous user id on %s", event).that(event.getPreviousUserId())
+                .isEqualTo(UserHandle.USER_NULL);
+        assertWithMessage("previous user handle on %s", event).that(event.getPreviousUserHandle())
+                .isNull();
+    }
+
     /**
      * 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
index 9fb2781..cab66f9 100644
--- 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
@@ -16,14 +16,18 @@
 
 package android.car.apitest.media;
 
+import static android.car.Car.AUDIO_SERVICE;
+import static android.car.media.CarAudioManager.AUDIO_FEATURE_DYNAMIC_ROUTING;
+import static android.car.media.CarAudioManager.AUDIO_FEATURE_VOLUME_GROUP_MUTING;
+import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE;
+import static android.media.AudioAttributes.USAGE_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;
 
@@ -38,11 +42,13 @@
 @RunWith(AndroidJUnit4.class)
 public class CarAudioManagerTest extends CarApiTestBase {
 
+    private static final int TEST_FLAGS = 0;
+
     private CarAudioManager mCarAudioManager;
 
     @Before
     public void setUp() throws Exception {
-        mCarAudioManager = (CarAudioManager) getCar().getCarManager(Car.AUDIO_SERVICE);
+        mCarAudioManager = (CarAudioManager) getCar().getCarManager(AUDIO_SERVICE);
         assertThat(mCarAudioManager).isNotNull();
     }
 
@@ -52,21 +58,21 @@
 
         List<Integer> zoneIds = mCarAudioManager.getAudioZoneIds();
         assertThat(zoneIds).isNotEmpty();
-        assertThat(zoneIds).contains(CarAudioManager.PRIMARY_AUDIO_ZONE);
+        assertThat(zoneIds).contains(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);
+        mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_DYNAMIC_ROUTING);
+        mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_VOLUME_GROUP_MUTING);
     }
 
     @Test
     public void test_getVolumeGroupCount() throws Exception {
         int primaryZoneCount = mCarAudioManager.getVolumeGroupCount();
         assertThat(
-                mCarAudioManager.getVolumeGroupCount(CarAudioManager.PRIMARY_AUDIO_ZONE)).isEqualTo(
+                mCarAudioManager.getVolumeGroupCount(PRIMARY_AUDIO_ZONE)).isEqualTo(
                 primaryZoneCount);
     }
 
@@ -74,14 +80,14 @@
     public void test_getGroupVolume() throws Exception {
         int groudId = 0;
         int volume = mCarAudioManager.getGroupVolume(groudId);
-        assertThat(mCarAudioManager.getGroupVolume(CarAudioManager.PRIMARY_AUDIO_ZONE,
-                groudId)).isEqualTo(volume);
+        assertThat(mCarAudioManager.getGroupVolume(PRIMARY_AUDIO_ZONE, groudId))
+                .isEqualTo(volume);
         int maxVolume = mCarAudioManager.getGroupMaxVolume(groudId);
-        assertThat(mCarAudioManager.getGroupMaxVolume(CarAudioManager.PRIMARY_AUDIO_ZONE,
-                groudId)).isEqualTo(maxVolume);
+        assertThat(mCarAudioManager.getGroupMaxVolume(PRIMARY_AUDIO_ZONE, groudId))
+                .isEqualTo(maxVolume);
         int minVolume = mCarAudioManager.getGroupMinVolume(groudId);
-        assertThat(mCarAudioManager.getGroupMinVolume(CarAudioManager.PRIMARY_AUDIO_ZONE,
-                groudId)).isEqualTo(minVolume);
+        assertThat(mCarAudioManager.getGroupMinVolume(PRIMARY_AUDIO_ZONE, groudId))
+                .isEqualTo(minVolume);
         assertThat(volume).isAtLeast(minVolume);
         assertThat(volume).isAtMost(maxVolume);
     }
@@ -90,8 +96,8 @@
     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);
+        mCarAudioManager.setGroupVolume(groudId, volume, TEST_FLAGS);
+        mCarAudioManager.setGroupVolume(PRIMARY_AUDIO_ZONE, groudId, volume, TEST_FLAGS);
         assertThat(mCarAudioManager.getGroupVolume(groudId)).isEqualTo(volume);
     }
 
@@ -99,8 +105,7 @@
     public void test_getInputDevicesForZoneId() throws Exception {
         assumeDynamicRoutingIsEnabled();
 
-        List<AudioDeviceInfo> info = mCarAudioManager.getInputDevicesForZoneId(
-                CarAudioManager.PRIMARY_AUDIO_ZONE);
+        List<AudioDeviceInfo> info = mCarAudioManager.getInputDevicesForZoneId(PRIMARY_AUDIO_ZONE);
         assertThat(info).isNotEmpty();
     }
 
@@ -109,15 +114,15 @@
         assumeDynamicRoutingIsEnabled();
 
         AudioDeviceInfo device = mCarAudioManager.getOutputDeviceForUsage(
-                CarAudioManager.PRIMARY_AUDIO_ZONE, AudioAttributes.USAGE_MEDIA);
+                PRIMARY_AUDIO_ZONE, 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 groupId = mCarAudioManager.getVolumeGroupIdForUsage(USAGE_MEDIA);
+        assertThat(mCarAudioManager.getVolumeGroupIdForUsage(PRIMARY_AUDIO_ZONE, USAGE_MEDIA))
+                .isEqualTo(groupId);
         int primaryZoneCount = mCarAudioManager.getVolumeGroupCount();
         assertThat(groupId).isLessThan(primaryZoneCount);
     }
@@ -126,8 +131,99 @@
     public void test_getZoneIdForUid() throws Exception {
         assumeDynamicRoutingIsEnabled();
 
-        assertThat(mCarAudioManager.getZoneIdForUid(Process.myUid())).isEqualTo(
-                CarAudioManager.PRIMARY_AUDIO_ZONE);
+        assertThat(mCarAudioManager.getZoneIdForUid(Process.myUid())).isEqualTo(PRIMARY_AUDIO_ZONE);
+    }
+
+    @Test
+    public void setVolumeGroupMute_toMute_mutesVolumeGroup() throws Exception {
+        assumeVolumeGroupMutingIsEnabled();
+        int groupId = 0;
+        boolean  muteState = mCarAudioManager.isVolumeGroupMuted(PRIMARY_AUDIO_ZONE, groupId);
+
+        try {
+            mCarAudioManager.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, groupId, true, TEST_FLAGS);
+            assertThat(mCarAudioManager.isVolumeGroupMuted(PRIMARY_AUDIO_ZONE, groupId))
+                    .isEqualTo(true);
+        } finally {
+            mCarAudioManager.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, groupId, muteState, TEST_FLAGS);
+        }
+    }
+
+    @Test
+    public void setVolumeGroupMute_toUnMute_unMutesVolumeGroup() throws Exception {
+        assumeVolumeGroupMutingIsEnabled();
+        int groupId = 0;
+        boolean  muteState = mCarAudioManager.isVolumeGroupMuted(PRIMARY_AUDIO_ZONE, groupId);
+
+        try {
+            mCarAudioManager.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, groupId, false, TEST_FLAGS);
+            assertThat(mCarAudioManager.isVolumeGroupMuted(PRIMARY_AUDIO_ZONE, groupId))
+                    .isEqualTo(false);
+        } finally {
+            mCarAudioManager.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, groupId, muteState, TEST_FLAGS);
+        }
+    }
+
+    @Test
+    public void setGroupVolume_whileMuted_unMutesVolumeGroup() throws Exception {
+        assumeVolumeGroupMutingIsEnabled();
+        int groupId = 0;
+        boolean  muteState = mCarAudioManager.isVolumeGroupMuted(PRIMARY_AUDIO_ZONE, groupId);
+        int volume = mCarAudioManager.getGroupVolume(PRIMARY_AUDIO_ZONE, groupId);
+        int minVolume = mCarAudioManager.getGroupMinVolume(PRIMARY_AUDIO_ZONE, groupId);
+
+        try {
+            mCarAudioManager.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, groupId, true, TEST_FLAGS);
+
+            mCarAudioManager.setGroupVolume(PRIMARY_AUDIO_ZONE, groupId, minVolume, TEST_FLAGS);
+            assertThat(mCarAudioManager.isVolumeGroupMuted(PRIMARY_AUDIO_ZONE, groupId))
+                    .isEqualTo(false);
+        } finally {
+            mCarAudioManager.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, groupId, muteState, TEST_FLAGS);
+            mCarAudioManager.setGroupVolume(PRIMARY_AUDIO_ZONE, groupId, volume, TEST_FLAGS);
+        }
+    }
+
+    @Test
+    public void getGroupVolume_whileMuted_returnsMinVolume() throws Exception {
+        assumeVolumeGroupMutingIsEnabled();
+        int groupId = 0;
+        boolean  muteState = mCarAudioManager.isVolumeGroupMuted(PRIMARY_AUDIO_ZONE, groupId);
+        int minVolume = mCarAudioManager.getGroupMinVolume(PRIMARY_AUDIO_ZONE, groupId);
+
+        try {
+            mCarAudioManager.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, groupId, true, TEST_FLAGS);
+
+            assertThat(mCarAudioManager.getGroupVolume(PRIMARY_AUDIO_ZONE, groupId))
+                    .isEqualTo(minVolume);
+        } finally {
+            mCarAudioManager.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, groupId, muteState, TEST_FLAGS);
+        }
+    }
+
+    @Test
+    public void getGroupVolume_whileUnMuted_returnLastSetValue() throws Exception {
+        assumeVolumeGroupMutingIsEnabled();
+        int groupId = 0;
+        boolean  muteState = mCarAudioManager.isVolumeGroupMuted(PRIMARY_AUDIO_ZONE, groupId);
+        int volume = mCarAudioManager.getGroupVolume(PRIMARY_AUDIO_ZONE, groupId);
+        int minVolume = mCarAudioManager.getGroupMinVolume(PRIMARY_AUDIO_ZONE, groupId);
+        int maxVolume = mCarAudioManager.getGroupMaxVolume(PRIMARY_AUDIO_ZONE, groupId);
+        int testVolume = (minVolume + maxVolume) / 2;
+
+        try {
+            mCarAudioManager.setGroupVolume(PRIMARY_AUDIO_ZONE, groupId, testVolume, TEST_FLAGS);
+
+            mCarAudioManager.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, groupId, true, TEST_FLAGS);
+
+            mCarAudioManager.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, groupId, false, TEST_FLAGS);
+
+            assertThat(mCarAudioManager.getGroupVolume(PRIMARY_AUDIO_ZONE, groupId))
+                    .isEqualTo(testVolume);
+        } finally {
+            mCarAudioManager.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, groupId, muteState, TEST_FLAGS);
+            mCarAudioManager.setGroupVolume(PRIMARY_AUDIO_ZONE, groupId, volume, TEST_FLAGS);
+        }
     }
 
     @Test
@@ -136,12 +232,14 @@
 
         // TODO(b/191660867): Better to change this to play something and asert true.
         assertThat(
-                mCarAudioManager.isPlaybackOnVolumeGroupActive(CarAudioManager.PRIMARY_AUDIO_ZONE,
+                mCarAudioManager.isPlaybackOnVolumeGroupActive(PRIMARY_AUDIO_ZONE,
                         0)).isFalse();
     }
 
     private void assumeDynamicRoutingIsEnabled() {
-        assumeTrue(mCarAudioManager.isAudioFeatureEnabled(
-                CarAudioManager.AUDIO_FEATURE_DYNAMIC_ROUTING));
+        assumeTrue(mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_DYNAMIC_ROUTING));
+    }
+    private void assumeVolumeGroupMutingIsEnabled() {
+        assumeTrue(mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_VOLUME_GROUP_MUTING));
     }
 }
diff --git a/tests/carservice_test/AndroidManifest.xml b/tests/carservice_test/AndroidManifest.xml
index 7bf749e..47dbb26 100644
--- a/tests/carservice_test/AndroidManifest.xml
+++ b/tests/carservice_test/AndroidManifest.xml
@@ -59,6 +59,11 @@
             <meta-data android:name="distractionOptimized"
                  android:value="true"/>
         </activity>
+        <activity android:name="androidx.car.app.activity.CarAppActivity"
+            android:label="CarAppActivity">
+            <meta-data android:name="distractionOptimized"
+                android:value="true"/>
+        </activity>
 
         <receiver android:name="com.android.car.CarStorageMonitoringBroadcastReceiver"
              android:exported="true"
diff --git a/tests/carservice_test/src/androidx/car/app/activity/CarAppActivity.java b/tests/carservice_test/src/androidx/car/app/activity/CarAppActivity.java
new file mode 100644
index 0000000..b42b7cc
--- /dev/null
+++ b/tests/carservice_test/src/androidx/car/app/activity/CarAppActivity.java
@@ -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.
+ */
+
+package androidx.car.app.activity;
+
+
+import static com.android.car.pm.ActivityBlockingActivityTest.DoActivity.DIALOG_TITLE;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+
+/**
+ * An activity to represent a template activity in tests.
+ */
+public class CarAppActivity extends Activity {
+    public static final String ACTION_SHOW_DIALOG = "SHOW_DIALOG";
+    public static final String ACTION_START_SECOND_INSTANCE = "START_SECOND_INSTANCE";
+    public static final String SECOND_INSTANCE_TITLE = "Second Instance";
+    private static final String BUNDLE_KEY_IS_SECOND_INSTANCE = "is_second_instance";
+
+    private final ShowDialogReceiver mShowDialogReceiver = new ShowDialogReceiver();
+    private final StartSecondInstanceReceiver
+            mStartSecondInstanceReceiver = new StartSecondInstanceReceiver();
+
+    private class ShowDialogReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            showDialog();
+        }
+    }
+
+    private class StartSecondInstanceReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            startSecondInstance();
+        }
+    }
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (getIntent().getBooleanExtra(BUNDLE_KEY_IS_SECOND_INSTANCE, false)) {
+            getActionBar().setTitle(SECOND_INSTANCE_TITLE);
+        }
+        this.registerReceiver(mShowDialogReceiver, new IntentFilter(ACTION_SHOW_DIALOG));
+        this.registerReceiver(mStartSecondInstanceReceiver,
+                new IntentFilter(ACTION_START_SECOND_INSTANCE));
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        this.unregisterReceiver(mShowDialogReceiver);
+        this.unregisterReceiver(mStartSecondInstanceReceiver);
+    }
+
+    private void startSecondInstance() {
+        Intent intent = new Intent(CarAppActivity.this, CarAppActivity.class);
+        intent.putExtra(BUNDLE_KEY_IS_SECOND_INSTANCE, true);
+        startActivity(intent);
+    }
+
+    private void showDialog() {
+        AlertDialog dialog = new AlertDialog.Builder(this)
+                .setTitle(DIALOG_TITLE)
+                .setMessage("Message")
+                .create();
+        dialog.show();
+    }
+}
diff --git a/tests/carservice_test/src/com/android/car/AppFocusTest.java b/tests/carservice_test/src/com/android/car/AppFocusTest.java
index 390acf2..1afb52f 100644
--- a/tests/carservice_test/src/com/android/car/AppFocusTest.java
+++ b/tests/carservice_test/src/com/android/car/AppFocusTest.java
@@ -16,6 +16,9 @@
 package com.android.car;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import static com.google.common.truth.Truth.assertThat;
 
 import android.car.Car;
 import android.car.CarAppFocusManager;
@@ -46,10 +49,13 @@
         manager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, ownershipListener);
         listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
                 CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true);
+        assertThat(manager.getAppTypeOwner(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION))
+                .containsExactly("com.android.car.test", "com.google.android.car.kitchensink");
         listener.resetWait();
         manager.abandonAppFocus(ownershipListener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
         listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
                 CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, false);
+        assertNull(manager.getAppTypeOwner(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION));
         manager.removeFocusListener(listener);
     }
 
diff --git a/tests/carservice_test/src/com/android/car/CarTelemetryManagerTest.java b/tests/carservice_test/src/com/android/car/CarTelemetryManagerTest.java
new file mode 100644
index 0000000..88302de
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/CarTelemetryManagerTest.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2017 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.car.telemetry.CarTelemetryManager.ERROR_METRICS_CONFIG_ALREADY_EXISTS;
+import static android.car.telemetry.CarTelemetryManager.ERROR_METRICS_CONFIG_NONE;
+import static android.car.telemetry.CarTelemetryManager.ERROR_METRICS_CONFIG_PARSE_FAILED;
+import static android.car.telemetry.CarTelemetryManager.ERROR_METRICS_CONFIG_VERSION_TOO_OLD;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import android.annotation.NonNull;
+import android.car.Car;
+import android.car.telemetry.CarTelemetryManager;
+import android.car.telemetry.MetricsConfigKey;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+
+import com.android.car.telemetry.CarTelemetryService;
+import com.android.car.telemetry.TelemetryProto;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+/** Test the public entry points for the CarTelemetryManager. */
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class CarTelemetryManagerTest extends MockedCarTestBase {
+    private static final long TIMEOUT_MS = 5_000L;
+    private static final String TAG = CarTelemetryManagerTest.class.getSimpleName();
+    private static final byte[] INVALID_METRICS_CONFIG = "bad config".getBytes();
+    private static final Executor DIRECT_EXECUTOR = Runnable::run;
+    private static final MetricsConfigKey KEY_V1 = new MetricsConfigKey("my_metrics_config", 1);
+    private static final MetricsConfigKey KEY_V2 = new MetricsConfigKey("my_metrics_config", 2);
+    private static final TelemetryProto.MetricsConfig METRICS_CONFIG_V1 =
+            TelemetryProto.MetricsConfig.newBuilder()
+                    .setName("my_metrics_config").setVersion(1).setScript("no-op").build();
+    private static final TelemetryProto.MetricsConfig METRICS_CONFIG_V2 =
+            METRICS_CONFIG_V1.toBuilder().setVersion(2).build();
+
+    private final FakeCarTelemetryResultsListener mListener = new FakeCarTelemetryResultsListener();
+    private final HandlerThread mTelemetryThread =
+            CarServiceUtils.getHandlerThread(CarTelemetryService.class.getSimpleName());
+    private final Handler mHandler = new Handler(mTelemetryThread.getLooper());
+
+    private CarTelemetryManager mCarTelemetryManager;
+    private CountDownLatch mIdleHandlerLatch = new CountDownLatch(1);
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        assumeTrue(getCar().isFeatureEnabled(Car.CAR_TELEMETRY_SERVICE));
+
+        mTelemetryThread.getLooper().getQueue().addIdleHandler(() -> {
+            mIdleHandlerLatch.countDown();
+            return true;
+        });
+
+        Log.i(TAG, "attempting to get CAR_TELEMETRY_SERVICE");
+        mCarTelemetryManager = (CarTelemetryManager) getCar().getCarManager(
+                Car.CAR_TELEMETRY_SERVICE);
+        mCarTelemetryManager.setListener(DIRECT_EXECUTOR, mListener);
+    }
+
+    @Test
+    public void testSetClearListener() {
+        mCarTelemetryManager.clearListener();
+        mCarTelemetryManager.setListener(DIRECT_EXECUTOR, mListener);
+
+        // setListener multiple times should fail
+        assertThrows(IllegalStateException.class,
+                () -> mCarTelemetryManager.setListener(DIRECT_EXECUTOR, mListener));
+    }
+
+    @Test
+    public void testAddMetricsConfig() throws Exception {
+        // invalid config, should fail
+        mCarTelemetryManager.addMetricsConfig(KEY_V1, INVALID_METRICS_CONFIG);
+        waitForHandlerThreadToFinish();
+        assertThat(mListener.getAddConfigStatus(KEY_V1)).isEqualTo(
+                ERROR_METRICS_CONFIG_PARSE_FAILED);
+
+        // new valid config, should succeed
+        mCarTelemetryManager.addMetricsConfig(KEY_V1, METRICS_CONFIG_V1.toByteArray());
+        waitForHandlerThreadToFinish();
+        assertThat(mListener.getAddConfigStatus(KEY_V1)).isEqualTo(ERROR_METRICS_CONFIG_NONE);
+
+        // duplicate config, should fail
+        mCarTelemetryManager.addMetricsConfig(KEY_V1, METRICS_CONFIG_V1.toByteArray());
+        waitForHandlerThreadToFinish();
+        assertThat(mListener.getAddConfigStatus(KEY_V1)).isEqualTo(
+                ERROR_METRICS_CONFIG_ALREADY_EXISTS);
+
+        // newer version of the config should replace older version
+        mCarTelemetryManager.addMetricsConfig(KEY_V2, METRICS_CONFIG_V2.toByteArray());
+        waitForHandlerThreadToFinish();
+        assertThat(mListener.getAddConfigStatus(KEY_V2)).isEqualTo(ERROR_METRICS_CONFIG_NONE);
+
+        // older version of the config should not be accepted
+        mCarTelemetryManager.addMetricsConfig(KEY_V1, METRICS_CONFIG_V1.toByteArray());
+        waitForHandlerThreadToFinish();
+        assertThat(mListener.getAddConfigStatus(KEY_V1)).isEqualTo(
+                ERROR_METRICS_CONFIG_VERSION_TOO_OLD);
+    }
+
+    @Test
+    public void testRemoveMetricsConfig() throws Exception {
+        mCarTelemetryManager.removeMetricsConfig(KEY_V1);
+        waitForHandlerThreadToFinish();
+        assertThat(mListener.getRemoveConfigStatus(KEY_V1)).isFalse();
+
+        mCarTelemetryManager.addMetricsConfig(KEY_V1, METRICS_CONFIG_V1.toByteArray());
+        waitForHandlerThreadToFinish();
+        mCarTelemetryManager.removeMetricsConfig(KEY_V1);
+        waitForHandlerThreadToFinish();
+        assertThat(mListener.getRemoveConfigStatus(KEY_V1)).isTrue();
+    }
+
+    private void waitForHandlerThreadToFinish() throws Exception {
+        assertWithMessage("handler not idle in %sms", TIMEOUT_MS)
+                .that(mIdleHandlerLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+        mIdleHandlerLatch = new CountDownLatch(1); // reset idle handler condition
+        mHandler.runWithScissors(() -> {
+        }, TIMEOUT_MS);
+    }
+
+
+    private static final class FakeCarTelemetryResultsListener
+            implements CarTelemetryManager.CarTelemetryResultsListener {
+
+        private Map<MetricsConfigKey, Boolean> mRemoveConfigStatusMap = new ArrayMap<>();
+        private Map<MetricsConfigKey, Integer> mAddConfigStatusMap = new ArrayMap<>();
+
+        @Override
+        public void onResult(@NonNull MetricsConfigKey key, @NonNull byte[] result) {
+        }
+
+        @Override
+        public void onError(@NonNull MetricsConfigKey key, @NonNull byte[] error) {
+        }
+
+        @Override
+        public void onAddMetricsConfigStatus(@NonNull MetricsConfigKey key, int statusCode) {
+            mAddConfigStatusMap.put(key, statusCode);
+        }
+
+        @Override
+        public void onRemoveMetricsConfigStatus(@NonNull MetricsConfigKey key, boolean success) {
+            mRemoveConfigStatusMap.put(key, success);
+        }
+
+        public int getAddConfigStatus(MetricsConfigKey key) {
+            return mAddConfigStatusMap.getOrDefault(key, -100);
+        }
+
+        public boolean getRemoveConfigStatus(MetricsConfigKey key) {
+            return mRemoveConfigStatusMap.getOrDefault(key, false);
+        }
+    }
+}
diff --git a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperLegacyTest.java b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperLegacyTest.java
index e006387..49ca55a 100644
--- a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperLegacyTest.java
+++ b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperLegacyTest.java
@@ -15,6 +15,10 @@
  */
 package com.android.car.audio;
 
+import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE;
+import static android.media.AudioDeviceInfo.TYPE_BUILTIN_MIC;
+import static android.media.AudioDeviceInfo.TYPE_FM_TUNER;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -23,6 +27,8 @@
 
 import android.annotation.XmlRes;
 import android.content.Context;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
 import android.util.SparseArray;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -65,9 +71,10 @@
 
         RuntimeException exception = expectThrows(RuntimeException.class,
                 () -> new CarAudioZonesHelperLegacy(mContext, mCarVolumeGroups,
-                        carAudioDeviceInfos, mMockAudioControlWrapper, mMockCarAudioSettings));
+                        carAudioDeviceInfos, mMockAudioControlWrapper, mMockCarAudioSettings,
+                        getInputDevices()));
 
-        assertThat(exception.getMessage()).contains("Two addresses map to same bus number:");
+        assertThat(exception).hasMessageThat().contains("Two addresses map to same bus number:");
     }
 
     @Test
@@ -78,9 +85,79 @@
 
         RuntimeException exception = expectThrows(RuntimeException.class,
                 () -> new CarAudioZonesHelperLegacy(mContext, mCarVolumeGroups,
-                carAudioDeviceInfos, mMockAudioControlWrapper, mMockCarAudioSettings));
+                        carAudioDeviceInfos, mMockAudioControlWrapper,
+                        mMockCarAudioSettings, getInputDevices()));
 
-        assertThat(exception.getMessage()).contains("Invalid bus -1 was associated with context");
+        assertThat(exception).hasMessageThat()
+                .contains("Invalid bus -1 was associated with context");
+    }
+
+    @Test
+    public void constructor_throwsIfNullInputDevices() throws Exception {
+        List<CarAudioDeviceInfo> carAudioDeviceInfos = getValidCarAudioDeviceInfos();
+
+        when(mMockAudioControlWrapper.getBusForContext(anyInt())).thenReturn(INVALID_BUS);
+
+        NullPointerException exception = expectThrows(NullPointerException.class,
+                () -> new CarAudioZonesHelperLegacy(mContext, mCarVolumeGroups,
+                        carAudioDeviceInfos, mMockAudioControlWrapper,
+                        mMockCarAudioSettings, null));
+
+        assertThat(exception).hasMessageThat().contains("Input Devices");
+    }
+
+    @Test
+    public void constructor_throwsIfNullContext() throws Exception {
+        List<CarAudioDeviceInfo> carAudioDeviceInfos = getValidCarAudioDeviceInfos();
+
+        when(mMockAudioControlWrapper.getBusForContext(anyInt())).thenReturn(INVALID_BUS);
+
+        NullPointerException exception = expectThrows(NullPointerException.class,
+                () -> new CarAudioZonesHelperLegacy(null, mCarVolumeGroups,
+                        carAudioDeviceInfos, mMockAudioControlWrapper,
+                        mMockCarAudioSettings, getInputDevices()));
+
+        assertThat(exception).hasMessageThat().contains("Context");
+    }
+
+    @Test
+    public void constructor_throwsIfNullCarAudioDeviceInfo() throws Exception {
+        when(mMockAudioControlWrapper.getBusForContext(anyInt())).thenReturn(INVALID_BUS);
+
+        NullPointerException exception = expectThrows(NullPointerException.class,
+                () -> new CarAudioZonesHelperLegacy(mContext, mCarVolumeGroups,
+                        null, mMockAudioControlWrapper,
+                        mMockCarAudioSettings, getInputDevices()));
+
+        assertThat(exception).hasMessageThat().contains("Car Audio Device Info");
+    }
+
+    @Test
+    public void constructor_throwsIfNullCarAudioControl() throws Exception {
+        List<CarAudioDeviceInfo> carAudioDeviceInfos = getValidCarAudioDeviceInfos();
+
+        when(mMockAudioControlWrapper.getBusForContext(anyInt())).thenReturn(INVALID_BUS);
+
+        NullPointerException exception = expectThrows(NullPointerException.class,
+                () -> new CarAudioZonesHelperLegacy(mContext, mCarVolumeGroups,
+                        carAudioDeviceInfos, null,
+                        mMockCarAudioSettings, getInputDevices()));
+
+        assertThat(exception).hasMessageThat().contains("Car Audio Control");
+    }
+
+    @Test
+    public void constructor_throwsIfNullCarAudioSettings() throws Exception {
+        List<CarAudioDeviceInfo> carAudioDeviceInfos = getValidCarAudioDeviceInfos();
+
+        when(mMockAudioControlWrapper.getBusForContext(anyInt())).thenReturn(INVALID_BUS);
+
+        NullPointerException exception = expectThrows(NullPointerException.class,
+                () -> new CarAudioZonesHelperLegacy(mContext, mCarVolumeGroups,
+                        carAudioDeviceInfos, mMockAudioControlWrapper,
+                        null, getInputDevices()));
+
+        assertThat(exception).hasMessageThat().contains("Car Audio Settings");
     }
 
     @Test
@@ -89,7 +166,8 @@
         when(mMockAudioControlWrapper.getBusForContext(anyInt())).thenReturn(1);
 
         CarAudioZonesHelperLegacy helper = new CarAudioZonesHelperLegacy(mContext, mCarVolumeGroups,
-                carAudioDeviceInfos, mMockAudioControlWrapper, mMockCarAudioSettings);
+                carAudioDeviceInfos, mMockAudioControlWrapper,
+                mMockCarAudioSettings, getInputDevices());
 
         SparseArray<CarAudioZone> zones = helper.loadAudioZones();
 
@@ -103,7 +181,8 @@
         when(mMockAudioControlWrapper.getBusForContext(anyInt())).thenReturn(1);
 
         CarAudioZonesHelperLegacy helper = new CarAudioZonesHelperLegacy(mContext, mCarVolumeGroups,
-                carAudioDeviceInfos, mMockAudioControlWrapper, mMockCarAudioSettings);
+                carAudioDeviceInfos, mMockAudioControlWrapper,
+                mMockCarAudioSettings, getInputDevices());
 
         SparseArray<CarAudioZone> zones = helper.loadAudioZones();
         CarVolumeGroup[] volumeGroups = zones.get(0).getVolumeGroups();
@@ -111,6 +190,38 @@
     }
 
     @Test
+    public void loadAudioZones_primaryZoneHasInputDevice() throws Exception {
+        List<CarAudioDeviceInfo> carAudioDeviceInfos = getValidCarAudioDeviceInfos();
+
+        when(mMockAudioControlWrapper.getBusForContext(anyInt())).thenReturn(1);
+
+        CarAudioZonesHelperLegacy helper = new CarAudioZonesHelperLegacy(mContext, mCarVolumeGroups,
+                carAudioDeviceInfos, mMockAudioControlWrapper,
+                mMockCarAudioSettings, getInputDevices());
+
+        SparseArray<CarAudioZone> zones = helper.loadAudioZones();
+        CarAudioZone primaryZone = zones.get(PRIMARY_AUDIO_ZONE);
+        assertThat(primaryZone.getInputAudioDevices()).hasSize(1);
+    }
+
+    @Test
+    public void loadAudioZones_primaryZoneHasMicrophoneInputDevice() throws Exception {
+        List<CarAudioDeviceInfo> carAudioDeviceInfos = getValidCarAudioDeviceInfos();
+
+        when(mMockAudioControlWrapper.getBusForContext(anyInt())).thenReturn(1);
+
+        CarAudioZonesHelperLegacy helper = new CarAudioZonesHelperLegacy(mContext, mCarVolumeGroups,
+                carAudioDeviceInfos, mMockAudioControlWrapper,
+                mMockCarAudioSettings, getInputDevices());
+
+        SparseArray<CarAudioZone> zones = helper.loadAudioZones();
+        CarAudioZone primaryZone = zones.get(PRIMARY_AUDIO_ZONE);
+        List<AudioDeviceAttributes> audioDeviceInfos =
+                primaryZone.getInputAudioDevices();
+        assertThat(audioDeviceInfos.get(0).getType()).isEqualTo(TYPE_BUILTIN_MIC);
+    }
+
+    @Test
     public void loadAudioZones_associatesLegacyContextsWithCorrectBuses() throws Exception {
         List<CarAudioDeviceInfo> carAudioDeviceInfos = getValidCarAudioDeviceInfos();
 
@@ -118,7 +229,8 @@
         when(mMockAudioControlWrapper.getBusForContext(CarAudioContext.MUSIC)).thenReturn(1);
 
         CarAudioZonesHelperLegacy helper = new CarAudioZonesHelperLegacy(mContext, mCarVolumeGroups,
-                carAudioDeviceInfos, mMockAudioControlWrapper, mMockCarAudioSettings);
+                carAudioDeviceInfos, mMockAudioControlWrapper,
+                mMockCarAudioSettings, getInputDevices());
 
         SparseArray<CarAudioZone> zones = helper.loadAudioZones();
 
@@ -145,7 +257,8 @@
                 .thenReturn(1);
 
         CarAudioZonesHelperLegacy helper = new CarAudioZonesHelperLegacy(mContext, mCarVolumeGroups,
-                carAudioDeviceInfos, mMockAudioControlWrapper, mMockCarAudioSettings);
+                carAudioDeviceInfos, mMockAudioControlWrapper,
+                mMockCarAudioSettings, getInputDevices());
 
         SparseArray<CarAudioZone> zones = helper.loadAudioZones();
 
@@ -166,6 +279,18 @@
         return Lists.newArrayList(deviceInfo1, deviceInfo2);
     }
 
+    private AudioDeviceInfo[] getInputDevices() {
+        AudioDeviceInfo deviceInfo1 = Mockito.mock(AudioDeviceInfo.class);
+        when(deviceInfo1.getType()).thenReturn(TYPE_BUILTIN_MIC);
+        when(deviceInfo1.getAddress()).thenReturn("mic");
+        when(deviceInfo1.isSink()).thenReturn(false);
+        AudioDeviceInfo deviceInfo2 = Mockito.mock(AudioDeviceInfo.class);
+        when(deviceInfo2.getAddress()).thenReturn("tuner");
+        when(deviceInfo2.getType()).thenReturn(TYPE_FM_TUNER);
+        when(deviceInfo2.isSink()).thenReturn(false);
+        return new AudioDeviceInfo[]{deviceInfo1, deviceInfo2};
+    }
+
     private List<CarAudioDeviceInfo> getValidCarAudioDeviceInfos() {
         CarAudioDeviceInfo deviceInfo1 = Mockito.mock(CarAudioDeviceInfo.class);
         when(deviceInfo1.getAddress()).thenReturn("bus001_media");
diff --git a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java
index 74b00c7..14d3ba8 100644
--- a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java
+++ b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java
@@ -15,6 +15,11 @@
  */
 package com.android.car.audio;
 
+import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE;
+import static android.media.AudioDeviceInfo.TYPE_BUILTIN_MIC;
+import static android.media.AudioDeviceInfo.TYPE_BUS;
+import static android.media.AudioDeviceInfo.TYPE_FM_TUNER;
+
 import static com.android.car.audio.CarAudioService.DEFAULT_AUDIO_CONTEXT;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -23,7 +28,6 @@
 import static org.mockito.Mockito.when;
 import static org.testng.Assert.expectThrows;
 
-import android.car.media.CarAudioManager;
 import android.content.Context;
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
@@ -104,14 +108,11 @@
 
     private AudioDeviceInfo[] generateInputDeviceInfos() {
         return new AudioDeviceInfo[]{
-                generateInputAudioDeviceInfo(PRIMARY_ZONE_MICROPHONE_ADDRESS,
-                        AudioDeviceInfo.TYPE_BUILTIN_MIC),
-                generateInputAudioDeviceInfo(PRIMARY_ZONE_FM_TUNER_ADDRESS,
-                        AudioDeviceInfo.TYPE_FM_TUNER),
-                generateInputAudioDeviceInfo(SECONDARY_ZONE_BACK_MICROPHONE_ADDRESS,
-                        AudioDeviceInfo.TYPE_BUS),
+                generateInputAudioDeviceInfo(PRIMARY_ZONE_MICROPHONE_ADDRESS, TYPE_BUILTIN_MIC),
+                generateInputAudioDeviceInfo(PRIMARY_ZONE_FM_TUNER_ADDRESS, TYPE_FM_TUNER),
+                generateInputAudioDeviceInfo(SECONDARY_ZONE_BACK_MICROPHONE_ADDRESS, TYPE_BUS),
                 generateInputAudioDeviceInfo(SECONDARY_ZONE_BUS_1000_INPUT_ADDRESS,
-                        AudioDeviceInfo.TYPE_BUILTIN_MIC)
+                        TYPE_BUILTIN_MIC)
         };
     }
 
@@ -169,7 +170,7 @@
 
         List<Integer> zoneIds = getListOfZoneIds(zones);
         assertThat(zones.size()).isEqualTo(2);
-        assertThat(zones.contains(CarAudioManager.PRIMARY_AUDIO_ZONE)).isTrue();
+        assertThat(zones.contains(PRIMARY_AUDIO_ZONE)).isTrue();
         assertThat(zones.contains(SECONDARY_ZONE_ID)).isTrue();
     }
 
@@ -184,7 +185,7 @@
 
         SparseIntArray audioZoneIdToOccupantZoneIdMapping =
                 cazh.getCarAudioZoneIdToOccupantZoneIdMapping();
-        assertThat(audioZoneIdToOccupantZoneIdMapping.get(CarAudioManager.PRIMARY_AUDIO_ZONE))
+        assertThat(audioZoneIdToOccupantZoneIdMapping.get(PRIMARY_AUDIO_ZONE))
                 .isEqualTo(PRIMARY_OCCUPANT_ID);
         assertThat(audioZoneIdToOccupantZoneIdMapping.get(SECONDARY_ZONE_ID, -1))
                 .isEqualTo(-1);
@@ -305,7 +306,7 @@
             SparseArray<CarAudioZone> zones = cazh.loadAudioZones();
 
             assertThat(zones.size()).isEqualTo(2);
-            assertThat(zones.contains(CarAudioManager.PRIMARY_AUDIO_ZONE)).isTrue();
+            assertThat(zones.contains(PRIMARY_AUDIO_ZONE)).isTrue();
             assertThat(zones.contains(SECONDARY_ZONE_ID)).isTrue();
         }
     }
@@ -339,6 +340,30 @@
     }
 
     @Test
+    public void loadAudioZones_primaryZoneHasInputDevices() throws Exception {
+        CarAudioZonesHelper cazh = new CarAudioZonesHelper(mCarAudioSettings, mInputStream,
+                mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos, false);
+
+        SparseArray<CarAudioZone> zones = cazh.loadAudioZones();
+
+        CarAudioZone primaryZone = zones.get(PRIMARY_AUDIO_ZONE);
+        assertThat(primaryZone.getInputAudioDevices()).hasSize(2);
+    }
+
+    @Test
+    public void loadAudioZones_primaryZoneHasMicrophoneDevice() throws Exception {
+        CarAudioZonesHelper cazh = new CarAudioZonesHelper(mCarAudioSettings, mInputStream,
+                mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos, false);
+
+        SparseArray<CarAudioZone> zones = cazh.loadAudioZones();
+
+        CarAudioZone primaryZone = zones.get(PRIMARY_AUDIO_ZONE);
+        for (AudioDeviceAttributes info : primaryZone.getInputAudioDevices()) {
+            assertThat(info.getType()).isEqualTo(TYPE_BUILTIN_MIC);
+        }
+    }
+
+    @Test
     public void loadAudioZones_parsesInputDevices() throws Exception {
         try (InputStream inputDevicesStream = mContext.getResources().openRawResource(
                 R.raw.car_audio_configuration_with_input_devices)) {
diff --git a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesValidatorTest.java b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesValidatorTest.java
index 9c32893..552eedb 100644
--- a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesValidatorTest.java
+++ b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesValidatorTest.java
@@ -15,9 +15,18 @@
  */
 package com.android.car.audio;
 
-import static org.mockito.Mockito.when;
+import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE;
+import static android.media.AudioDeviceInfo.TYPE_BUILTIN_MIC;
+import static android.media.AudioDeviceInfo.TYPE_BUS;
+import static android.media.AudioDeviceInfo.TYPE_FM_TUNER;
 
-import android.car.media.CarAudioManager;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.expectThrows;
+
+import android.media.AudioDeviceAttributes;
 import android.util.SparseArray;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -28,6 +37,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 
+import java.util.ArrayList;
 import java.util.List;
 
 @RunWith(AndroidJUnit4.class)
@@ -37,10 +47,49 @@
 
     @Test
     public void validate_thereIsAtLeastOneZone() {
-        thrown.expect(RuntimeException.class);
-        thrown.expectMessage("At least one zone should be defined");
+        RuntimeException exception = expectThrows(RuntimeException.class,
+                () -> CarAudioZonesValidator.validate(new SparseArray<CarAudioZone>()));
 
-        CarAudioZonesValidator.validate(new SparseArray<CarAudioZone>());
+        assertThat(exception).hasMessageThat().contains("At least one zone should be defined");
+
+    }
+
+    @Test
+    public void validate_failsOnEmptyInputDevices() {
+        CarAudioZone zone = new MockBuilder().withInputDevices(new ArrayList<>()).build();
+        SparseArray<CarAudioZone> zones = new SparseArray<>();
+        zones.put(zone.getId(), zone);
+
+        IllegalArgumentException exception = expectThrows(IllegalArgumentException.class,
+                () -> CarAudioZonesValidator.validate(zones));
+
+        assertThat(exception).hasMessageThat().contains("Primary Zone Input Devices");
+    }
+
+    @Test
+    public void validate_failsOnNullInputDevices() {
+        CarAudioZone zone = new MockBuilder().withInputDevices(null).build();
+        SparseArray<CarAudioZone> zones = new SparseArray<>();
+        zones.put(zone.getId(), zone);
+
+        NullPointerException exception = expectThrows(NullPointerException.class,
+                () -> CarAudioZonesValidator.validate(zones));
+
+        assertThat(exception).hasMessageThat().contains("Primary Zone Input Devices");
+    }
+
+    @Test
+    public void validate_failsOnMissingMicrophoneInputDevices() {
+        CarAudioZone zone = new MockBuilder().withInputDevices(
+                List.of(generateInputAudioDeviceAttributeInfo("tuner", TYPE_FM_TUNER)))
+                .build();
+        SparseArray<CarAudioZone> zones = new SparseArray<>();
+        zones.put(zone.getId(), zone);
+
+        RuntimeException exception = expectThrows(RuntimeException.class,
+                () -> CarAudioZonesValidator.validate(zones));
+
+        assertThat(exception).hasMessageThat().contains("Primary Zone must have");
     }
 
     @Test
@@ -52,10 +101,11 @@
                 .build();
         zones.put(zoneOne.getId(), zoneOne);
 
-        thrown.expect(RuntimeException.class);
-        thrown.expectMessage("Invalid volume groups configuration for zone " + 1);
+        RuntimeException exception = expectThrows(RuntimeException.class,
+                () -> CarAudioZonesValidator.validate(zones));
 
-        CarAudioZonesValidator.validate(zones);
+        assertThat(exception).hasMessageThat()
+                .contains("Invalid volume groups configuration for zone " + 1);
     }
 
     @Test
@@ -77,12 +127,11 @@
         zones.put(primaryZone.getId(), primaryZone);
         zones.put(secondaryZone.getId(), secondaryZone);
 
+        RuntimeException exception = expectThrows(RuntimeException.class,
+                () -> CarAudioZonesValidator.validate(zones));
 
-        thrown.expect(RuntimeException.class);
-        thrown.expectMessage(
+        assertThat(exception).hasMessageThat().contains(
                 "Device with address three appears in multiple volume groups or audio zones");
-
-        CarAudioZonesValidator.validate(zones);
     }
 
     @Test
@@ -93,7 +142,7 @@
     }
 
     private SparseArray<CarAudioZone> generateAudioZonesWithPrimary() {
-        CarAudioZone zone = new MockBuilder().build();
+        CarAudioZone zone = new MockBuilder().withInputDevices(getValidInputDevices()).build();
         SparseArray<CarAudioZone> zones = new SparseArray<>();
         zones.put(zone.getId(), zone);
         return zones;
@@ -105,21 +154,23 @@
         return mockVolumeGroup;
     }
 
-    private CarAudioZone getMockPrimaryZone() {
-        CarAudioZone zoneMock = Mockito.mock(CarAudioZone.class);
-        when(zoneMock.getId()).thenReturn(CarAudioManager.PRIMARY_AUDIO_ZONE);
-        return zoneMock;
+    private List<AudioDeviceAttributes> getValidInputDevices() {
+        return List.of(generateInputAudioDeviceAttributeInfo("mic", TYPE_BUILTIN_MIC),
+                generateInputAudioDeviceAttributeInfo("tuner", TYPE_FM_TUNER),
+                generateInputAudioDeviceAttributeInfo("bus", TYPE_BUS));
     }
     private static class MockBuilder {
         private boolean mHasValidVolumeGroups = true;
-        private int mZoneId = 0;
+        private int mZoneId = PRIMARY_AUDIO_ZONE;
         private CarVolumeGroup[] mVolumeGroups = new CarVolumeGroup[0];
+        private List<AudioDeviceAttributes> mInputDevices = new ArrayList<>();
 
         CarAudioZone build() {
             CarAudioZone zoneMock = Mockito.mock(CarAudioZone.class);
             when(zoneMock.getId()).thenReturn(mZoneId);
             when(zoneMock.validateVolumeGroups()).thenReturn(mHasValidVolumeGroups);
             when(zoneMock.getVolumeGroups()).thenReturn(mVolumeGroups);
+            when(zoneMock.getInputAudioDevices()).thenReturn(mInputDevices);
             return zoneMock;
         }
 
@@ -137,5 +188,17 @@
             mVolumeGroups = volumeGroups;
             return this;
         }
+
+        MockBuilder withInputDevices(List<AudioDeviceAttributes> inputDevices) {
+            mInputDevices = inputDevices;
+            return this;
+        }
+    }
+
+    private AudioDeviceAttributes generateInputAudioDeviceAttributeInfo(String address, int type) {
+        AudioDeviceAttributes inputMock = mock(AudioDeviceAttributes.class);
+        when(inputMock.getAddress()).thenReturn(address);
+        when(inputMock.getType()).thenReturn(type);
+        return inputMock;
     }
 }
\ No newline at end of file
diff --git a/tests/carservice_test/src/com/android/car/garagemode/ControllerTest.java b/tests/carservice_test/src/com/android/car/garagemode/ControllerTest.java
index ca3598a..64c68fc 100644
--- a/tests/carservice_test/src/com/android/car/garagemode/ControllerTest.java
+++ b/tests/carservice_test/src/com/android/car/garagemode/ControllerTest.java
@@ -24,16 +24,21 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+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.when;
 
 import android.car.Car;
 import android.car.hardware.power.CarPowerManager;
 import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Resources;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
@@ -42,6 +47,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.car.CarLocalServices;
+import com.android.car.R;
+import com.android.car.power.CarPowerManagementService;
 import com.android.car.systeminterface.SystemInterface;
 import com.android.car.user.CarUserService;
 
@@ -74,8 +81,10 @@
     @Mock private CarPowerManager mCarPowerManagerMock;
     @Mock private CarUserService mCarUserServiceMock;
     @Mock private SystemInterface mSystemInterfaceMock;
+    @Mock private CarPowerManagementService mCarPowerManagementServiceMock;
     private CarUserService mCarUserServiceOriginal;
     private SystemInterface mSystemInterfaceOriginal;
+    private CarPowerManagementService mCarPowerManagementServiceOriginal;
     @Captor private ArgumentCaptor<Intent> mIntentCaptor;
     @Captor private ArgumentCaptor<Integer> mIntegerCaptor;
 
@@ -106,10 +115,15 @@
         mController.setCarPowerManager(mCarPowerManagerMock);
         mFuture = new CompletableFuture<>();
         mCarUserServiceOriginal = CarLocalServices.getService(CarUserService.class);
+        mCarPowerManagementServiceOriginal = CarLocalServices.getService(
+                CarPowerManagementService.class);
         CarLocalServices.removeServiceForTest(CarUserService.class);
         CarLocalServices.addService(CarUserService.class, mCarUserServiceMock);
         CarLocalServices.removeServiceForTest(SystemInterface.class);
         CarLocalServices.addService(SystemInterface.class, mSystemInterfaceMock);
+        CarLocalServices.removeServiceForTest(CarPowerManagementService.class);
+        CarLocalServices.addService(CarPowerManagementService.class,
+                mCarPowerManagementServiceMock);
         doReturn(new ArrayList<Integer>()).when(mCarUserServiceMock)
                 .startAllBackgroundUsersInGarageMode();
         doNothing().when(mSystemInterfaceMock)
@@ -122,6 +136,9 @@
         CarLocalServices.addService(CarUserService.class, mCarUserServiceOriginal);
         CarLocalServices.removeServiceForTest(SystemInterface.class);
         CarLocalServices.addService(SystemInterface.class, mSystemInterfaceOriginal);
+        CarLocalServices.removeServiceForTest(CarPowerManagementService.class);
+        CarLocalServices.addService(CarPowerManagementService.class,
+                mCarPowerManagementServiceOriginal);
     }
 
     @Test
@@ -219,4 +236,83 @@
         // Verify that worker that polls running jobs from JobScheduler is scheduled.
         verify(mHandlerMock).postDelayed(any(), eq(JOB_SNAPSHOT_INITIAL_UPDATE_MS));
     }
+
+    @Test
+    public void testInitAndRelease() {
+
+        GarageMode garageMode = mock(GarageMode.class);
+        Controller controller = new Controller(mContextMock, mLooperMock, mWakeupPolicy,
+                mHandlerMock, garageMode);
+
+        controller.init();
+        controller.release();
+
+        verify(garageMode).init();
+        verify(garageMode).release();
+    }
+
+    @Test
+    public void testConstructor() {
+        Resources resourcesMock = mock(Resources.class);
+        when(mContextMock.getResources()).thenReturn(resourcesMock);
+        when(resourcesMock.getStringArray(R.array.config_garageModeCadence))
+                .thenReturn(sTemplateWakeupSchedule);
+        Controller controller = new Controller(mContextMock, mLooperMock);
+
+        assertThat(controller).isNotNull();
+    }
+
+    @Test
+    public void testScheduleNextWakeup() {
+        GarageMode garageMode = mock(GarageMode.class);
+
+        // Enter GarageMode only 1 time, no wake up after that
+        WakeupPolicy wakeUpPolicy = new WakeupPolicy(new String[] { "15m,1" });
+
+        Controller controller = new Controller(mContextMock, mLooperMock, wakeUpPolicy,
+                mHandlerMock, garageMode);
+        controller.setCarPowerManager(mCarPowerManagerMock);
+
+        // Imitate entering and leavimg GarageMode
+        controller.initiateGarageMode(/* future= */ null);
+
+        controller.scheduleNextWakeup();
+
+        verify(mCarPowerManagerMock).scheduleNextWakeupTime(900);
+
+        // Imitate entering Garage mode after sleep
+        controller.initiateGarageMode(/* future= */ null);
+
+        // Should be no more calls to scheduleNextWakeupTime
+        controller.scheduleNextWakeup();
+
+        Mockito.verifyNoMoreInteractions(mCarPowerManagerMock);
+    }
+
+    @Test
+    public void testOnStateChanged() {
+        GarageMode garageMode = mock(GarageMode.class);
+
+        Controller controller = Mockito.spy(new Controller(mContextMock, mLooperMock, mWakeupPolicy,
+                mHandlerMock, garageMode));
+
+        controller.onStateChanged(CarPowerStateListener.SHUTDOWN_CANCELLED, null);
+        verify(controller).resetGarageMode();
+
+        clearInvocations(controller);
+        controller.onStateChanged(CarPowerStateListener.SHUTDOWN_ENTER, null);
+        verify(controller).resetGarageMode();
+
+        clearInvocations(controller);
+        controller.onStateChanged(CarPowerStateListener.SUSPEND_ENTER, null);
+        verify(controller).resetGarageMode();
+
+        clearInvocations(controller);
+        controller.onStateChanged(CarPowerStateListener.SUSPEND_EXIT, null);
+        verify(controller).resetGarageMode();
+
+        clearInvocations(controller);
+        controller.onStateChanged(CarPowerStateListener.INVALID , null);
+        verify(controller, never()).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 6133baf..6183549 100644
--- a/tests/carservice_test/src/com/android/car/garagemode/GarageModeTest.java
+++ b/tests/carservice_test/src/com/android/car/garagemode/GarageModeTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -36,6 +37,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.car.CarLocalServices;
+import com.android.car.power.CarPowerManagementService;
 import com.android.car.user.CarUserService;
 
 import org.junit.After;
@@ -50,6 +52,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -131,6 +134,42 @@
                 105);
     }
 
+    @Test
+    public void garageModeTestExitImmediately() throws Exception {
+        CarPowerManagementService mockCarPowerManagementService =
+                mock(CarPowerManagementService.class);
+
+        // Mock CPMS to force Garage Mode early exit
+        CarLocalServices.removeServiceForTest(CarPowerManagementService.class);
+        CarLocalServices.addService(CarPowerManagementService.class, mockCarPowerManagementService);
+        when(mockCarPowerManagementService.garageModeShouldExitImmediately()).thenReturn(true);
+
+        // Check exit immediately without future
+        GarageMode garageMode = new GarageMode(mController);
+        garageMode.init();
+        garageMode.enterGarageMode(/* future= */ null);
+        assertThat(garageMode.isGarageModeActive()).isFalse();
+
+        // Create new instance of GarageMode
+        garageMode = new GarageMode(mController);
+        garageMode.init();
+        // Check exit immediately with future
+        CompletableFuture<Void> future = new CompletableFuture<>();
+        garageMode.enterGarageMode(future);
+        assertThat(garageMode.isGarageModeActive()).isFalse();
+        assertThat(future.isDone()).isTrue();
+
+        // Create new instance of GarageMode
+        garageMode = new GarageMode(mController);
+        garageMode.init();
+        // Check exit immediately with completed future
+        garageMode.enterGarageMode(future);
+        assertThat(garageMode.isGarageModeActive()).isFalse();
+        assertThat(future.isDone()).isTrue();
+
+        CarLocalServices.removeServiceForTest(CarPowerManagementService.class);
+    }
+
     private void waitForHandlerThreadToFinish(CountDownLatch latch) throws Exception {
         assertWithMessage("Latch has timed out.")
                 .that(latch.await(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
diff --git a/tests/carservice_test/src/com/android/car/pm/ActivityBlockingActivityTest.java b/tests/carservice_test/src/com/android/car/pm/ActivityBlockingActivityTest.java
index 69632f0..b8c4eab 100644
--- a/tests/carservice_test/src/com/android/car/pm/ActivityBlockingActivityTest.java
+++ b/tests/carservice_test/src/com/android/car/pm/ActivityBlockingActivityTest.java
@@ -16,12 +16,18 @@
 
 package com.android.car.pm;
 
+import static androidx.car.app.activity.CarAppActivity.ACTION_SHOW_DIALOG;
+import static androidx.car.app.activity.CarAppActivity.ACTION_START_SECOND_INSTANCE;
+import static androidx.car.app.activity.CarAppActivity.SECOND_INSTANCE_TITLE;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertNotNull;
 
 import android.app.Activity;
 import android.app.ActivityOptions;
+import android.app.AlertDialog;
+import android.app.UiAutomation;
 import android.car.Car;
 import android.car.drivingstate.CarDrivingStateEvent;
 import android.car.drivingstate.CarDrivingStateManager;
@@ -30,10 +36,12 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.Configurator;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.Until;
 import android.view.Display;
 
+import androidx.car.app.activity.CarAppActivity;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
@@ -72,6 +80,8 @@
                 car.getCarManager(Car.CAR_DRIVING_STATE_SERVICE);
         assertNotNull(mCarDrivingStateManager);
 
+        Configurator.getInstance()
+                .setUiAutomationFlags(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
         mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
 
         setDrivingStateMoving();
@@ -98,6 +108,58 @@
     }
 
     @Test
+    public void testBlockingActivity_doActivity_showingDialog_isNotBlocked() throws Exception {
+        Intent intent = new Intent();
+        intent.putExtra(DoActivity.INTENT_EXTRA_SHOW_DIALOG, true);
+        intent.setComponent(toComponentName(getTestContext(), DoActivity.class));
+        startActivity(intent);
+
+        assertThat(mDevice.wait(Until.findObject(By.text(
+                DoActivity.DIALOG_TITLE)),
+                UI_TIMEOUT_MS)).isNotNull();
+        assertBlockingActivityNotFound();
+    }
+
+    @Test
+    public void testBlockingActivity_doTemplateActivity_isNotBlocked() throws Exception {
+        startActivity(toComponentName(getTestContext(), CarAppActivity.class));
+
+        assertThat(mDevice.wait(Until.findObject(By.text(
+                CarAppActivity.class.getSimpleName())),
+                UI_TIMEOUT_MS)).isNotNull();
+        assertBlockingActivityNotFound();
+    }
+
+    @Test
+    public void testBlockingActivity_multipleDoTemplateActivity_notBlocked() throws Exception {
+        startActivity(toComponentName(getTestContext(), CarAppActivity.class));
+        assertThat(mDevice.wait(Until.findObject(By.text(
+                CarAppActivity.class.getSimpleName())),
+                UI_TIMEOUT_MS)).isNotNull();
+        getContext().sendBroadcast(new Intent().setAction(ACTION_START_SECOND_INSTANCE));
+        assertThat(mDevice.wait(Until.findObject(By.text(
+                SECOND_INSTANCE_TITLE)),
+                UI_TIMEOUT_MS)).isNotNull();
+        assertBlockingActivityNotFound();
+    }
+
+    @Test
+    public void testBlockingActivity_doTemplateActivity_showingDialog_isBlocked() throws Exception {
+        startActivity(toComponentName(getTestContext(), CarAppActivity.class));
+        assertThat(mDevice.wait(Until.findObject(By.text(
+                CarAppActivity.class.getSimpleName())),
+                UI_TIMEOUT_MS)).isNotNull();
+        assertBlockingActivityNotFound();
+
+        getContext().sendBroadcast(new Intent().setAction(ACTION_SHOW_DIALOG));
+        assertThat(mDevice.wait(Until.findObject(By.text(DoActivity.DIALOG_TITLE)),
+                UI_TIMEOUT_MS)).isNotNull();
+
+        assertThat(mDevice.wait(Until.findObject(By.res(ACTIVITY_BLOCKING_ACTIVITY_TEXTVIEW_ID)),
+                UI_TIMEOUT_MS)).isNotNull();
+    }
+
+    @Test
     public void testBlockingActivity_nonDoActivity_isBlocked() throws Exception {
         startNonDoActivity(NonDoActivity.EXTRA_DO_NOTHING);
 
@@ -153,11 +215,13 @@
     private void startActivity(ComponentName name) {
         Intent intent = new Intent();
         intent.setComponent(name);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        startActivity(intent);
+    }
 
+    private void startActivity(Intent intent) {
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         ActivityOptions options = ActivityOptions.makeBasic();
         options.setLaunchDisplayId(Display.DEFAULT_DISPLAY);
-
         getContext().startActivity(intent, options.toBundle());
     }
 
@@ -240,6 +304,20 @@
     }
 
     public static class DoActivity extends TempActivity {
+        public static final String INTENT_EXTRA_SHOW_DIALOG = "SHOW_DIALOG";
+        public static final String DIALOG_TITLE = "Title";
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            if (getIntent().getBooleanExtra(INTENT_EXTRA_SHOW_DIALOG, false)) {
+                AlertDialog dialog = new AlertDialog.Builder(DoActivity.this)
+                        .setTitle(DIALOG_TITLE)
+                        .setMessage("Message")
+                        .create();
+                dialog.show();
+            }
+        }
     }
 
     /** Activity that closes itself after some timeout to clean up the screen. */
diff --git a/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java b/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java
index aaca795..ba01d41 100644
--- a/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java
@@ -87,13 +87,14 @@
     @Mock private IBinder mDaemonBinder;
     @Mock private IBinder mServiceBinder;
     @Mock private ICarWatchdog mCarWatchdogDaemon;
+    @Mock private WatchdogStorage mMockWatchdogStorage;
 
     private CarWatchdogService mCarWatchdogService;
     private ICarWatchdogServiceForSystem mWatchdogServiceForSystemImpl;
 
     @Before
     public void setUpMocks() throws Exception {
-        mCarWatchdogService = new CarWatchdogService(mMockContext);
+        mCarWatchdogService = new CarWatchdogService(mMockContext, mMockWatchdogStorage);
 
         mockQueryService(CAR_WATCHDOG_DAEMON_INTERFACE, mDaemonBinder, mCarWatchdogDaemon);
         when(mCar.getEventHandler()).thenReturn(mMainHandler);
diff --git a/tests/carservice_unit_test/Android.bp b/tests/carservice_unit_test/Android.bp
index 1f22098..57e14d9 100644
--- a/tests/carservice_unit_test/Android.bp
+++ b/tests/carservice_unit_test/Android.bp
@@ -69,8 +69,6 @@
     // mockito-target-inline dependency
     jni_libs: [
         "libdexmakerjvmtiagent",
-	"libscriptexecutorjni",
-        "libscriptexecutorjniutils-test",
         "libstaticjvmtiagent",
     ],
 }
diff --git a/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java b/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
index c67d3d8..99bbc4c 100644
--- a/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
+++ b/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
@@ -193,6 +193,13 @@
         verify(mFakeCarWatchdog).actionTakenOnResourceOveruse(eq(actions));
     }
 
+    @Test
+    public void testIndirectCall_controlProcessHealthCheck() throws Exception {
+        mCarWatchdogDaemonHelper.controlProcessHealthCheck(true);
+
+        verify(mFakeCarWatchdog).controlProcessHealthCheck(eq(true));
+    }
+
     /*
      * Test that the {@link CarWatchdogDaemonHelper} throws {@code IllegalArgumentException} when
      * trying to register already-registered service again.
diff --git a/tests/carservice_unit_test/src/com/android/car/CarInputRotaryServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarInputRotaryServiceTest.java
index b832462..acfa606 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarInputRotaryServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarInputRotaryServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.car;
 
+import static com.android.car.CarInputService.ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -48,6 +50,7 @@
 import com.android.car.hal.InputHalService;
 import com.android.car.hal.UserHalService;
 import com.android.car.internal.common.CommonConstants.UserLifecycleEventType;
+import com.android.car.pm.CarSafetyAccessibilityService;
 import com.android.car.user.CarUserService;
 import com.android.internal.app.AssistUtils;
 import com.android.internal.util.test.BroadcastInterceptingContext;
@@ -140,12 +143,21 @@
     }
 
     @Test
-    public void rotaryServiceSettingsUpdated_whenRotaryServiceIsNotEmpty() throws Exception {
+    public void accessibilitySettingsUpdated_whenRotaryServiceIsNotEmpty() throws Exception {
+        final String existingService = "com.android.temp/com.android.car.TempService";
         final String rotaryService = "com.android.car.rotary/com.android.car.rotary.RotaryService";
+        final String carSafetyAccessibilityService = mContext.getPackageName()
+                + "/"
+                + CarSafetyAccessibilityService.class.getName();
         init(rotaryService);
         assertThat(mMockContext.getString(R.string.rotaryService)).isEqualTo(rotaryService);
-
         final int userId = 11;
+        Settings.Secure.putStringForUser(
+                mMockContext.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                existingService,
+                userId);
+
 
         // By default RotaryService is not enabled.
         String enabledServices = Settings.Secure.getStringForUser(
@@ -167,7 +179,12 @@
                 mMockContext.getContentResolver(),
                 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                 userId);
-        assertThat(enabledServices).contains(rotaryService);
+        assertThat(enabledServices).isEqualTo(
+                existingService
+                        + ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR
+                        + carSafetyAccessibilityService
+                        + ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR
+                        + rotaryService);
 
         enabled = Settings.Secure.getStringForUser(
                 mMockContext.getContentResolver(),
@@ -177,7 +194,11 @@
     }
 
     @Test
-    public void rotaryServiceSettingsNotUpdated_whenRotaryServiceIsEmpty() throws Exception {
+    public void accessibilitySettingsUpdated_withoutRotaryService_whenRotaryServiceIsEmpty()
+            throws Exception {
+        final String carSafetyAccessibilityService = mContext.getPackageName()
+                + "/"
+                + CarSafetyAccessibilityService.class.getName();
         final String rotaryService = "";
         init(rotaryService);
         assertThat(mMockContext.getString(R.string.rotaryService)).isEqualTo(rotaryService);
@@ -199,7 +220,55 @@
                 mMockContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_ENABLED,
                 userId);
+        assertThat(enabled).isEqualTo("1");
+        String enabledServices = Settings.Secure.getStringForUser(
+                mMockContext.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                userId);
+        assertThat(enabledServices).isEqualTo(carSafetyAccessibilityService);
+    }
+
+    @Test
+    public void accessibilitySettingsUpdated_accessibilityServicesAlreadyEnabled()
+            throws Exception {
+        final String rotaryService = "com.android.car.rotary/com.android.car.rotary.RotaryService";
+        final String carSafetyAccessibilityService = mContext.getPackageName()
+                + "/"
+                + CarSafetyAccessibilityService.class.getName();
+        init(rotaryService);
+        assertThat(mMockContext.getString(R.string.rotaryService)).isEqualTo(rotaryService);
+        final int userId = 11;
+        Settings.Secure.putStringForUser(
+                mMockContext.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                carSafetyAccessibilityService
+                        + ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR
+                        + rotaryService,
+                userId);
+
+        String enabled = Settings.Secure.getStringForUser(
+                mMockContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_ENABLED,
+                userId);
         assertThat(enabled).isNull();
+
+        // Enable RotaryService by sending user switch event.
+        sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING, userId);
+
+        String enabledServices = Settings.Secure.getStringForUser(
+                mMockContext.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                userId);
+        assertThat(enabledServices).isEqualTo(
+                carSafetyAccessibilityService
+                        + ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR
+                        + rotaryService);
+
+        enabled = Settings.Secure.getStringForUser(
+                mMockContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_ENABLED,
+                userId);
+        assertThat(enabled).isEqualTo("1");
     }
 
     @After
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPolicyVolumeCallbackTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPolicyVolumeCallbackTest.java
index fc78159..b070bd8 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPolicyVolumeCallbackTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPolicyVolumeCallbackTest.java
@@ -153,6 +153,35 @@
     }
 
     @Test
+    public void onVolumeAdjustment_withAdjustRaise_whileMuted_setsGroupVolumeToMin() {
+        setGroupVolume(TEST_MAX_VOLUME);
+        setGroupVolumeMute(true);
+
+        CarAudioPolicyVolumeCallback callback =
+                new CarAudioPolicyVolumeCallback(mMockCarAudioService, mMockAudioManager, true);
+
+
+        callback.onVolumeAdjustment(ADJUST_RAISE);
+
+        verify(mMockCarAudioService).setGroupVolume(PRIMARY_AUDIO_ZONE,
+                TEST_VOLUME_GROUP, TEST_MIN_VOLUME, TEST_EXPECTED_FLAGS);
+    }
+
+    @Test
+    public void onVolumeAdjustment_withAdjustLower_whileMuted_setsGroupVolumeToMin() {
+        setGroupVolume(TEST_MAX_VOLUME);
+        setGroupVolumeMute(true);
+
+        CarAudioPolicyVolumeCallback callback =
+                new CarAudioPolicyVolumeCallback(mMockCarAudioService, mMockAudioManager, true);
+
+        callback.onVolumeAdjustment(ADJUST_LOWER);
+
+        verify(mMockCarAudioService).setGroupVolume(PRIMARY_AUDIO_ZONE,
+                TEST_VOLUME_GROUP, TEST_MIN_VOLUME, TEST_EXPECTED_FLAGS);
+    }
+
+    @Test
     public void onVolumeAdjustment_withAdjustSame_doesNothing() {
         setGroupVolume(TEST_VOLUME);
 
@@ -207,8 +236,8 @@
 
     @Test
     public void onVolumeAdjustment_forGroupMute_withAdjustToggleMute_togglesMutesVolumeGroup() {
-        when(mMockCarAudioService.isVolumeGroupMuted(anyInt(), anyInt()))
-                .thenReturn(true);
+        setGroupVolumeMute(true);
+
         CarAudioPolicyVolumeCallback callback =
                 new CarAudioPolicyVolumeCallback(mMockCarAudioService, mMockAudioManager, true);
 
@@ -220,7 +249,8 @@
 
     @Test
     public void onVolumeAdjustment_forGroupMute_withAdjustUnMute_UnMutesVolumeGroup() {
-        when(mMockCarAudioService.isVolumeGroupMuted(anyInt(), anyInt())).thenReturn(false);
+        setGroupVolumeMute(false);
+
         CarAudioPolicyVolumeCallback callback =
                 new CarAudioPolicyVolumeCallback(mMockCarAudioService, mMockAudioManager, true);
 
@@ -234,4 +264,9 @@
         when(mMockCarAudioService.getGroupVolume(anyInt(), anyInt()))
                 .thenReturn(groupVolume);
     }
+
+    private void setGroupVolumeMute(boolean mute) {
+        when(mMockCarAudioService.isVolumeGroupMuted(anyInt(), anyInt()))
+                .thenReturn(mute);
+    }
 }
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioUtilsTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioUtilsTest.java
index e913b53..b3d02be 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioUtilsTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioUtilsTest.java
@@ -16,14 +16,23 @@
 
 package com.android.car.audio;
 
+import static android.media.AudioDeviceInfo.TYPE_BUILTIN_MIC;
+import static android.media.AudioDeviceInfo.TYPE_FM_TUNER;
+
 import static com.android.car.audio.CarAudioUtils.hasExpired;
+import static com.android.car.audio.CarAudioUtils.isMicrophoneInputDevice;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.when;
+
+import android.media.AudioDeviceInfo;
+
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 
 @RunWith(AndroidJUnit4.class)
 public class CarAudioUtilsTest {
@@ -37,4 +46,18 @@
     public void hasExpired_forCurrentTimeAfterTimeout_returnsFalse() {
         assertThat(hasExpired(0, 300, 200)).isTrue();
     }
+
+    @Test
+    public void isMicrophoneInputDevice_forMicrophoneDevice_returnsTrue() {
+        AudioDeviceInfo deviceInfo = Mockito.mock(AudioDeviceInfo.class);
+        when(deviceInfo.getType()).thenReturn(TYPE_BUILTIN_MIC);
+        assertThat(isMicrophoneInputDevice(deviceInfo)).isTrue();
+    }
+
+    @Test
+    public void isMicrophoneInputDevice_forNonMicrophoneDevice_returnsFalse() {
+        AudioDeviceInfo deviceInfo = Mockito.mock(AudioDeviceInfo.class);
+        when(deviceInfo.getType()).thenReturn(TYPE_FM_TUNER);
+        assertThat(isMicrophoneInputDevice(deviceInfo)).isFalse();
+    }
 }
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarDuckingUtilsTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarDuckingUtilsTest.java
index de39471..ece0f00 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarDuckingUtilsTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarDuckingUtilsTest.java
@@ -23,8 +23,15 @@
 import static android.media.AudioAttributes.USAGE_MEDIA;
 import static android.media.AudioAttributes.USAGE_NOTIFICATION;
 import static android.media.AudioAttributes.USAGE_SAFETY;
+import static android.media.AudioAttributes.USAGE_VIRTUAL_SOURCE;
 import static android.media.AudioManager.AUDIOFOCUS_GAIN_TRANSIENT;
 
+import static com.android.car.audio.CarAudioContext.CALL;
+import static com.android.car.audio.CarAudioContext.EMERGENCY;
+import static com.android.car.audio.CarAudioContext.INVALID;
+import static com.android.car.audio.CarAudioContext.MUSIC;
+import static com.android.car.audio.CarAudioContext.NAVIGATION;
+
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -161,6 +168,16 @@
     }
 
     @Test
+    public void getAddressesToDuck_doesNotConsidersInvalidUsage() {
+        CarAudioZone mockZone = generateAudioZoneMock();
+        int[] usages = new int[]{USAGE_VIRTUAL_SOURCE};
+
+        List<String> addresses = CarDuckingUtils.getAddressesToDuck(usages, mockZone);
+
+        assertThat(addresses).isEmpty();
+    }
+
+    @Test
     public void getAddressesToDuck_withDuckedAndUnduckedContextsSharingDevice_excludesThatDevice() {
         CarAudioZone mockZone = generateAudioZoneMock();
         when(mockZone.getAddressForContext(CarAudioContext.SAFETY)).thenReturn(NAVIGATION_ADDRESS);
@@ -220,12 +237,11 @@
 
     private static CarAudioZone generateAudioZoneMock() {
         CarAudioZone mockZone = mock(CarAudioZone.class);
-        when(mockZone.getAddressForContext(CarAudioContext.MUSIC)).thenReturn(MEDIA_ADDRESS);
-        when(mockZone.getAddressForContext(CarAudioContext.EMERGENCY)).thenReturn(
-                EMERGENCY_ADDRESS);
-        when(mockZone.getAddressForContext(CarAudioContext.CALL)).thenReturn(CALL_ADDRESS);
-        when(mockZone.getAddressForContext(CarAudioContext.NAVIGATION)).thenReturn(
-                NAVIGATION_ADDRESS);
+        when(mockZone.getAddressForContext(MUSIC)).thenReturn(MEDIA_ADDRESS);
+        when(mockZone.getAddressForContext(EMERGENCY)).thenReturn(EMERGENCY_ADDRESS);
+        when(mockZone.getAddressForContext(CALL)).thenReturn(CALL_ADDRESS);
+        when(mockZone.getAddressForContext(NAVIGATION)).thenReturn(NAVIGATION_ADDRESS);
+        when(mockZone.getAddressForContext(INVALID)).thenThrow(new IllegalArgumentException());
 
         return mockZone;
     }
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupUnitTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupUnitTest.java
index 9e38583..d71a0d5 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupUnitTest.java
@@ -16,7 +16,15 @@
 
 package com.android.car.audio;
 
-import static com.google.common.truth.Truth.assertThat;
+import static com.android.car.audio.CarAudioContext.ALARM;
+import static com.android.car.audio.CarAudioContext.CALL;
+import static com.android.car.audio.CarAudioContext.CALL_RING;
+import static com.android.car.audio.CarAudioContext.EMERGENCY;
+import static com.android.car.audio.CarAudioContext.MUSIC;
+import static com.android.car.audio.CarAudioContext.NAVIGATION;
+import static com.android.car.audio.CarAudioContext.NOTIFICATION;
+
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -70,11 +78,12 @@
     public void setDeviceInfoForContext_associatesDeviceAddresses() {
         CarVolumeGroup.Builder builder = getBuilder();
 
-        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
-        builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, mNavigationDeviceInfo);
+        builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
+        builder.setDeviceInfoForContext(NAVIGATION, mNavigationDeviceInfo);
         CarVolumeGroup carVolumeGroup = builder.build();
 
-        assertThat(carVolumeGroup.getAddresses()).containsExactly(MEDIA_DEVICE_ADDRESS,
+        assertWithMessage("%s and %s", MEDIA_DEVICE_ADDRESS, NAVIGATION_DEVICE_ADDRESS)
+                .that(carVolumeGroup.getAddresses()).containsExactly(MEDIA_DEVICE_ADDRESS,
                 NAVIGATION_DEVICE_ADDRESS);
     }
 
@@ -82,139 +91,149 @@
     public void setDeviceInfoForContext_associatesContexts() {
         CarVolumeGroup.Builder builder = getBuilder();
 
-        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
-        builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, mNavigationDeviceInfo);
+        builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
+        builder.setDeviceInfoForContext(NAVIGATION, mNavigationDeviceInfo);
         CarVolumeGroup carVolumeGroup = builder.build();
 
-        assertThat(carVolumeGroup.getContexts()).asList().containsExactly(CarAudioContext.MUSIC,
-                CarAudioContext.NAVIGATION);
+        assertWithMessage("Music[%s] and Navigation[%s] Context", MUSIC, NAVIGATION)
+                .that(carVolumeGroup.getContexts()).asList().containsExactly(MUSIC, NAVIGATION);
     }
 
     @Test
     public void setDeviceInfoForContext_withDifferentStepSize_throws() {
         CarVolumeGroup.Builder builder = getBuilder();
-        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+        builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
         CarAudioDeviceInfo differentStepValueDevice = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
                 .setStepValue(mMediaDeviceInfo.getStepValue() + 1).build();
 
         IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
-                () -> builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION,
+                () -> builder.setDeviceInfoForContext(NAVIGATION,
                         differentStepValueDevice));
 
-        assertThat(thrown).hasMessageThat()
+        assertWithMessage("setDeviceInfoForContext failure for different step size")
+                .that(thrown).hasMessageThat()
                 .contains("Gain controls within one group must have same step value");
     }
 
     @Test
     public void setDeviceInfoForContext_withSameContext_throws() {
         CarVolumeGroup.Builder builder = getBuilder();
-        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+        builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
 
         IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
-                () -> builder.setDeviceInfoForContext(CarAudioContext.MUSIC,
+                () -> builder.setDeviceInfoForContext(MUSIC,
                         mNavigationDeviceInfo));
 
-        assertThat(thrown).hasMessageThat()
-                .contains("has already been set to");
+        assertWithMessage("setDeviceInfoForSameContext failure for repeated context")
+                .that(thrown).hasMessageThat().contains("has already been set to");
     }
 
     @Test
     public void setDeviceInfoForContext_withFirstCall_setsMinGain() {
         CarVolumeGroup.Builder builder = getBuilder();
 
-        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+        builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
 
-        assertThat(builder.mMinGain).isEqualTo(mMediaDeviceInfo.getMinGain());
+        assertWithMessage("Min Gain from builder")
+                .that(builder.mMinGain).isEqualTo(mMediaDeviceInfo.getMinGain());
     }
 
     @Test
     public void setDeviceInfoForContext_withFirstCall_setsMaxGain() {
         CarVolumeGroup.Builder builder = getBuilder();
 
-        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+        builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
 
-        assertThat(builder.mMaxGain).isEqualTo(mMediaDeviceInfo.getMaxGain());
+        assertWithMessage("Max Gain from builder")
+                .that(builder.mMaxGain).isEqualTo(mMediaDeviceInfo.getMaxGain());
     }
 
     @Test
     public void setDeviceInfoForContext_withFirstCall_setsDefaultGain() {
         CarVolumeGroup.Builder builder = getBuilder();
 
-        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+        builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
 
-        assertThat(builder.mDefaultGain).isEqualTo(mMediaDeviceInfo.getDefaultGain());
+        assertWithMessage("Default Gain from builder")
+                .that(builder.mDefaultGain).isEqualTo(mMediaDeviceInfo.getDefaultGain());
     }
 
     @Test
     public void setDeviceInfoForContext_SecondCallWithSmallerMinGain_updatesMinGain() {
         CarVolumeGroup.Builder builder = getBuilder();
-        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+        builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
         CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
                 .setMinGain(mMediaDeviceInfo.getMinGain() - 1).build();
 
-        builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+        builder.setDeviceInfoForContext(NAVIGATION, secondInfo);
 
-        assertThat(builder.mMinGain).isEqualTo(secondInfo.getMinGain());
+        assertWithMessage("Second, smaller min gain from builder")
+                .that(builder.mMinGain).isEqualTo(secondInfo.getMinGain());
     }
 
     @Test
     public void setDeviceInfoForContext_SecondCallWithLargerMinGain_keepsFirstMinGain() {
         CarVolumeGroup.Builder builder = getBuilder();
-        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+        builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
         CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
                 .setMinGain(mMediaDeviceInfo.getMinGain() + 1).build();
 
-        builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+        builder.setDeviceInfoForContext(NAVIGATION, secondInfo);
 
-        assertThat(builder.mMinGain).isEqualTo(mMediaDeviceInfo.getMinGain());
+        assertWithMessage("First, smaller min gain from builder")
+                .that(builder.mMinGain).isEqualTo(mMediaDeviceInfo.getMinGain());
     }
 
     @Test
     public void setDeviceInfoForContext_SecondCallWithLargerMaxGain_updatesMaxGain() {
         CarVolumeGroup.Builder builder = getBuilder();
-        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+        builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
         CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
                 .setMaxGain(mMediaDeviceInfo.getMaxGain() + 1).build();
 
-        builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+        builder.setDeviceInfoForContext(NAVIGATION, secondInfo);
 
-        assertThat(builder.mMaxGain).isEqualTo(secondInfo.getMaxGain());
+        assertWithMessage("Second, larger max gain from builder")
+                .that(builder.mMaxGain).isEqualTo(secondInfo.getMaxGain());
     }
 
     @Test
     public void setDeviceInfoForContext_SecondCallWithSmallerMaxGain_keepsFirstMaxGain() {
         CarVolumeGroup.Builder builder = getBuilder();
-        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+        builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
         CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
                 .setMaxGain(mMediaDeviceInfo.getMaxGain() - 1).build();
 
-        builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+        builder.setDeviceInfoForContext(NAVIGATION, secondInfo);
 
-        assertThat(builder.mMaxGain).isEqualTo(mMediaDeviceInfo.getMaxGain());
+        assertWithMessage("First, larger max gain from builder")
+                .that(builder.mMaxGain).isEqualTo(mMediaDeviceInfo.getMaxGain());
     }
 
     @Test
     public void setDeviceInfoForContext_SecondCallWithLargerDefaultGain_updatesDefaultGain() {
         CarVolumeGroup.Builder builder = getBuilder();
-        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+        builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
         CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
                 .setDefaultGain(mMediaDeviceInfo.getDefaultGain() + 1).build();
 
-        builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+        builder.setDeviceInfoForContext(NAVIGATION, secondInfo);
 
-        assertThat(builder.mDefaultGain).isEqualTo(secondInfo.getDefaultGain());
+        assertWithMessage("Second, larger default gain from builder")
+                .that(builder.mDefaultGain).isEqualTo(secondInfo.getDefaultGain());
     }
 
     @Test
     public void setDeviceInfoForContext_SecondCallWithSmallerDefaultGain_keepsFirstDefaultGain() {
         CarVolumeGroup.Builder builder = getBuilder();
-        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+        builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
         CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
                 .setDefaultGain(mMediaDeviceInfo.getDefaultGain() - 1).build();
 
-        builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+        builder.setDeviceInfoForContext(NAVIGATION, secondInfo);
 
-        assertThat(builder.mDefaultGain).isEqualTo(mMediaDeviceInfo.getDefaultGain());
+        assertWithMessage("Second, smaller default gain from builder")
+                .that(builder.mDefaultGain).isEqualTo(mMediaDeviceInfo.getDefaultGain());
     }
 
     @Test
@@ -223,13 +242,14 @@
 
         Exception e = expectThrows(IllegalArgumentException.class, builder::build);
 
-        assertThat(e).hasMessageThat().isEqualTo(
-                "setDeviceInfoForContext has to be called at least once before building");
+        assertWithMessage("Builder build failure").that(e).hasMessageThat()
+                .isEqualTo(
+                        "setDeviceInfoForContext has to be called at least once before building");
     }
 
     @Test
     public void builderBuild_withNoStoredGain_usesDefaultGain() {
-        CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(CarAudioContext.MUSIC,
+        CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(MUSIC,
                 mMediaDeviceInfo);
         when(mSettingsMock.getStoredVolumeGainIndexForUser(UserHandle.USER_CURRENT, ZONE_ID,
                 GROUP_ID)).thenReturn(-1);
@@ -237,50 +257,55 @@
 
         CarVolumeGroup carVolumeGroup = builder.build();
 
-        assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(DEFAULT_GAIN_INDEX);
+        assertWithMessage("Current gain index")
+                .that(carVolumeGroup.getCurrentGainIndex()).isEqualTo(DEFAULT_GAIN_INDEX);
     }
 
     @Test
     public void builderBuild_withTooLargeStoredGain_usesDefaultGain() {
-        CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(CarAudioContext.MUSIC,
+        CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(MUSIC,
                 mMediaDeviceInfo);
         when(mSettingsMock.getStoredVolumeGainIndexForUser(UserHandle.USER_CURRENT, ZONE_ID,
                 GROUP_ID)).thenReturn(MAX_GAIN_INDEX + 1);
 
         CarVolumeGroup carVolumeGroup = builder.build();
 
-        assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(DEFAULT_GAIN_INDEX);
+        assertWithMessage("Current gain index")
+                .that(carVolumeGroup.getCurrentGainIndex()).isEqualTo(DEFAULT_GAIN_INDEX);
     }
 
     @Test
     public void builderBuild_withTooSmallStoredGain_usesDefaultGain() {
-        CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(CarAudioContext.MUSIC,
+        CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(MUSIC,
                 mMediaDeviceInfo);
         when(mSettingsMock.getStoredVolumeGainIndexForUser(UserHandle.USER_CURRENT, ZONE_ID,
                 GROUP_ID)).thenReturn(MIN_GAIN_INDEX - 1);
 
         CarVolumeGroup carVolumeGroup = builder.build();
 
-        assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(DEFAULT_GAIN_INDEX);
+        assertWithMessage("Current gain index")
+                .that(carVolumeGroup.getCurrentGainIndex()).isEqualTo(DEFAULT_GAIN_INDEX);
     }
 
     @Test
     public void builderBuild_withValidStoredGain_usesStoredGain() {
-        CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(CarAudioContext.MUSIC,
+        CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(MUSIC,
                 mMediaDeviceInfo);
         when(mSettingsMock.getStoredVolumeGainIndexForUser(UserHandle.USER_CURRENT, ZONE_ID,
                 GROUP_ID)).thenReturn(MAX_GAIN_INDEX - 1);
 
         CarVolumeGroup carVolumeGroup = builder.build();
 
-        assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(MAX_GAIN_INDEX - 1);
+        assertWithMessage("Current gain index")
+                .that(carVolumeGroup.getCurrentGainIndex()).isEqualTo(MAX_GAIN_INDEX - 1);
     }
 
     @Test
     public void getAddressForContext_withSupportedContext_returnsAddress() {
         CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
 
-        assertThat(carVolumeGroup.getAddressForContext(CarAudioContext.MUSIC))
+        assertWithMessage("Supported context's address")
+                .that(carVolumeGroup.getAddressForContext(MUSIC))
                 .isEqualTo(mMediaDeviceInfo.getAddress());
     }
 
@@ -288,14 +313,16 @@
     public void getAddressForContext_withUnsupportedContext_returnsNull() {
         CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
 
-        assertThat(carVolumeGroup.getAddressForContext(CarAudioContext.NAVIGATION)).isNull();
+        assertWithMessage("Unsupported context's address")
+                .that(carVolumeGroup.getAddressForContext(NAVIGATION)).isNull();
     }
 
     @Test
     public void isMuted_whenDefault_returnsFalse() {
         CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
 
-        assertThat(carVolumeGroup.isMuted()).isFalse();
+        assertWithMessage("Default mute state")
+                .that(carVolumeGroup.isMuted()).isFalse();
     }
 
     @Test
@@ -304,7 +331,8 @@
 
         carVolumeGroup.setMute(true);
 
-        assertThat(carVolumeGroup.isMuted()).isTrue();
+        assertWithMessage("Set mute state")
+                .that(carVolumeGroup.isMuted()).isTrue();
     }
 
     @Test
@@ -313,7 +341,8 @@
 
         carVolumeGroup.setMute(false);
 
-        assertThat(carVolumeGroup.isMuted()).isFalse();
+        assertWithMessage("Set mute state")
+                .that(carVolumeGroup.isMuted()).isFalse();
     }
 
     @Test
@@ -352,8 +381,9 @@
 
         List<Integer> contextsList = carVolumeGroup.getContextsForAddress(MEDIA_DEVICE_ADDRESS);
 
-        assertThat(contextsList).containsExactly(CarAudioContext.MUSIC,
-                CarAudioContext.CALL, CarAudioContext.CALL_RING);
+        assertWithMessage("Contexts for bounded address %s", MEDIA_DEVICE_ADDRESS)
+                .that(contextsList).containsExactly(MUSIC,
+                CALL, CALL_RING);
     }
 
     @Test
@@ -362,7 +392,8 @@
 
         List<Integer> contextsList = carVolumeGroup.getContextsForAddress(OTHER_ADDRESS);
 
-        assertThat(contextsList).isEmpty();
+        assertWithMessage("Contexts for non-bounded address %s", OTHER_ADDRESS)
+                .that(contextsList).isEmpty();
     }
 
     @Test
@@ -372,7 +403,8 @@
         CarAudioDeviceInfo actualDevice = carVolumeGroup.getCarAudioDeviceInfoForAddress(
                 MEDIA_DEVICE_ADDRESS);
 
-        assertThat(actualDevice).isEqualTo(mMediaDeviceInfo);
+        assertWithMessage("Device information for bounded address %s", MEDIA_DEVICE_ADDRESS)
+                .that(actualDevice).isEqualTo(mMediaDeviceInfo);
     }
 
     @Test
@@ -382,7 +414,8 @@
         CarAudioDeviceInfo actualDevice = carVolumeGroup.getCarAudioDeviceInfoForAddress(
                 OTHER_ADDRESS);
 
-        assertThat(actualDevice).isNull();
+        assertWithMessage("Device information for non-bounded address %s", OTHER_ADDRESS)
+                .that(actualDevice).isNull();
     }
 
     @Test
@@ -401,7 +434,8 @@
 
         carVolumeGroup.setCurrentGainIndex(TEST_GAIN_INDEX);
 
-        assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(TEST_GAIN_INDEX);
+        assertWithMessage("Updated current gain index")
+                .that(carVolumeGroup.getCurrentGainIndex()).isEqualTo(TEST_GAIN_INDEX);
     }
 
     @Test
@@ -410,7 +444,8 @@
 
         IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
                 () -> carVolumeGroup.setCurrentGainIndex(MIN_GAIN_INDEX - 1));
-        assertThat(thrown).hasMessageThat()
+        assertWithMessage("Set out of bound gain index failure")
+                .that(thrown).hasMessageThat()
                 .contains("Gain out of range (" + MIN_GAIN + ":" + MAX_GAIN + ")");
     }
 
@@ -420,7 +455,8 @@
 
         IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
                 () -> carVolumeGroup.setCurrentGainIndex(MAX_GAIN_INDEX + 1));
-        assertThat(thrown).hasMessageThat()
+        assertWithMessage("Set out of bound gain index failure")
+                .that(thrown).hasMessageThat()
                 .contains("Gain out of range (" + MIN_GAIN + ":" + MAX_GAIN + ")");
     }
 
@@ -456,7 +492,8 @@
 
         carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
 
-        assertThat(carVolumeGroup.isMuted()).isTrue();
+        assertWithMessage("Saved mute state from settings")
+                .that(carVolumeGroup.isMuted()).isTrue();
     }
 
     @Test
@@ -465,7 +502,8 @@
 
         carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
 
-        assertThat(carVolumeGroup.isMuted()).isFalse();
+        assertWithMessage("Default mute state")
+                .that(carVolumeGroup.isMuted()).isFalse();
     }
 
     @Test
@@ -474,7 +512,8 @@
 
         carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
 
-        assertThat(carVolumeGroup.isMuted()).isFalse();
+        assertWithMessage("Saved mute state from settings")
+                .that(carVolumeGroup.isMuted()).isFalse();
     }
 
     @Test
@@ -483,35 +522,70 @@
 
         carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
 
-        assertThat(carVolumeGroup.isMuted()).isFalse();
+        assertWithMessage("Default mute state")
+                .that(carVolumeGroup.isMuted()).isFalse();
     }
 
     @Test
     public void hasCriticalAudioContexts_withoutCriticalContexts_returnsFalse() {
         CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
 
-        assertThat(carVolumeGroup.hasCriticalAudioContexts()).isFalse();
+        assertWithMessage("Group without critical audio context")
+                .that(carVolumeGroup.hasCriticalAudioContexts()).isFalse();
     }
 
     @Test
     public void hasCriticalAudioContexts_withCriticalContexts_returnsTrue() {
         CarVolumeGroup carVolumeGroup = getBuilder()
-                .setDeviceInfoForContext(CarAudioContext.EMERGENCY, mMediaDeviceInfo)
+                .setDeviceInfoForContext(EMERGENCY, mMediaDeviceInfo)
                 .build();
 
-        assertThat(carVolumeGroup.hasCriticalAudioContexts()).isTrue();
+        assertWithMessage("Group with critical audio context")
+                .that(carVolumeGroup.hasCriticalAudioContexts()).isTrue();
+    }
+
+    @Test
+    public void getCurrentGainIndex_whileMuted_returnsMinGain() {
+        CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+        carVolumeGroup.setCurrentGainIndex(TEST_GAIN_INDEX);
+
+        carVolumeGroup.setMute(true);
+
+        assertWithMessage("Muted current gain index")
+                .that(carVolumeGroup.getCurrentGainIndex()).isEqualTo(MIN_GAIN_INDEX);
+    }
+
+    @Test
+    public void getCurrentGainIndex_whileUnMuted_returnsLastSetGain() {
+        CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+        carVolumeGroup.setCurrentGainIndex(TEST_GAIN_INDEX);
+
+        carVolumeGroup.setMute(false);
+
+        assertWithMessage("Un-muted current gain index")
+                .that(carVolumeGroup.getCurrentGainIndex()).isEqualTo(TEST_GAIN_INDEX);
+    }
+
+    @Test
+    public void setCurrentGainIndex_whileMuted_unMutesVolumeGroup() {
+        CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+        carVolumeGroup.setMute(true);
+        carVolumeGroup.setCurrentGainIndex(TEST_GAIN_INDEX);
+
+        assertWithMessage("Mute state after volume change")
+                .that(carVolumeGroup.isMuted()).isEqualTo(false);
     }
 
     private CarVolumeGroup getCarVolumeGroupWithMusicBound() {
         return getBuilder()
-                .setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo)
+                .setDeviceInfoForContext(MUSIC, mMediaDeviceInfo)
                 .build();
     }
 
     private CarVolumeGroup getCarVolumeGroupWithNavigationBound(CarAudioSettings settings,
             boolean useCarVolumeGroupMute) {
         return new CarVolumeGroup.Builder(0, 0, settings, useCarVolumeGroupMute)
-                .setDeviceInfoForContext(CarAudioContext.NAVIGATION, mNavigationDeviceInfo)
+                .setDeviceInfoForContext(NAVIGATION, mNavigationDeviceInfo)
                 .build();
     }
 
@@ -527,13 +601,13 @@
     private CarVolumeGroup testVolumeGroupSetup() {
         CarVolumeGroup.Builder builder = getBuilder();
 
-        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
-        builder.setDeviceInfoForContext(CarAudioContext.CALL, mMediaDeviceInfo);
-        builder.setDeviceInfoForContext(CarAudioContext.CALL_RING, mMediaDeviceInfo);
+        builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
+        builder.setDeviceInfoForContext(CALL, mMediaDeviceInfo);
+        builder.setDeviceInfoForContext(CALL_RING, mMediaDeviceInfo);
 
-        builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, mNavigationDeviceInfo);
-        builder.setDeviceInfoForContext(CarAudioContext.ALARM, mNavigationDeviceInfo);
-        builder.setDeviceInfoForContext(CarAudioContext.NOTIFICATION, mNavigationDeviceInfo);
+        builder.setDeviceInfoForContext(NAVIGATION, mNavigationDeviceInfo);
+        builder.setDeviceInfoForContext(ALARM, mNavigationDeviceInfo);
+        builder.setDeviceInfoForContext(NOTIFICATION, mNavigationDeviceInfo);
 
         return builder.build();
     }
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/MockedPowerHalService.java b/tests/carservice_unit_test/src/com/android/car/hal/MockedPowerHalService.java
index ff50677..2ab14dc 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/MockedPowerHalService.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/MockedPowerHalService.java
@@ -20,6 +20,8 @@
 import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateReq;
 import android.util.Log;
 
+import com.android.car.CarServiceUtils;
+
 import java.util.LinkedList;
 
 public class MockedPowerHalService extends PowerHalService {
@@ -47,7 +49,8 @@
                 mock(UserHalService.class),
                 mock(DiagnosticHalService.class),
                 mock(ClusterHalService.class),
-                mock(HalClient.class));
+                mock(HalClient.class),
+                CarServiceUtils.getHandlerThread(VehicleHal.class.getSimpleName()));
         return mockedVehicleHal;
     }
 
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/VehicleHalTest.java b/tests/carservice_unit_test/src/com/android/car/hal/VehicleHalTest.java
index 763b680..4aa9a81 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/VehicleHalTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/VehicleHalTest.java
@@ -35,6 +35,10 @@
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import com.android.car.CarServiceUtils;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -69,6 +73,10 @@
     @Mock private ClusterHalService mClusterHalService;
     @Mock private HalClient mHalClient;
 
+    private final HandlerThread mHandlerThread = CarServiceUtils.getHandlerThread(
+            VehicleHal.class.getSimpleName());
+    private final Handler mHandler = new Handler(mHandlerThread.getLooper());
+
     private VehicleHal mVehicleHal;
 
     /** Hal services configurations */
@@ -78,7 +86,7 @@
     public void setUp() throws Exception {
         mVehicleHal = new VehicleHal(mPowerHalService,
                 mPropertyHalService, mInputHalService, mVmsHalService, mUserHalService,
-                mDiagnosticHalService, mClusterHalService, mHalClient);
+                mDiagnosticHalService, mClusterHalService, mHalClient, mHandlerThread);
 
         mConfigs.clear();
 
@@ -185,7 +193,8 @@
         propValues.add(propValue);
 
         // Act
-        mVehicleHal.onPropertyEvent(propValues);
+        mHandler.post(() -> mVehicleHal.onPropertyEvent(propValues));
+        CarServiceUtils.runOnLooperSync(mHandlerThread.getLooper(), () -> {});
 
         // Assert
         verify(dispatchList).add(propValue);
@@ -201,7 +210,8 @@
         int areaId = VehicleHal.NO_AREA;
 
         // Act
-        mVehicleHal.onPropertySetError(errorCode, propId, areaId);
+        mHandler.post(() -> mVehicleHal.onPropertySetError(errorCode, propId, areaId));
+        CarServiceUtils.runOnLooperSync(mHandlerThread.getLooper(), () -> {});
 
         // Assert
         verify(mPowerHalService).onPropertySetError(propId, areaId, errorCode);
@@ -215,7 +225,8 @@
         int areaId = VehicleHal.NO_AREA;
 
         // Act
-        mVehicleHal.onPropertySetError(errorCode, propId, areaId);
+        mHandler.post(() -> mVehicleHal.onPropertySetError(errorCode, propId, areaId));
+        CarServiceUtils.runOnLooperSync(mHandlerThread.getLooper(), () -> {});
 
         // Assert
         verify(mPowerHalService).onPropertySetError(propId, areaId, errorCode);
diff --git a/tests/carservice_unit_test/src/com/android/car/pm/CarSafetyAccessibilityServiceTest.java b/tests/carservice_unit_test/src/com/android/car/pm/CarSafetyAccessibilityServiceTest.java
new file mode 100644
index 0000000..7494f5d
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/pm/CarSafetyAccessibilityServiceTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.pm;
+
+
+import static org.mockito.Mockito.verify;
+
+import android.car.AbstractExtendedMockitoCarServiceTestCase;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.car.CarLocalServices;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+
+@RunWith(JUnit4.class)
+public class CarSafetyAccessibilityServiceTest extends AbstractExtendedMockitoCarServiceTestCase {
+    @Mock
+    private CarPackageManagerService mMockCarPackageManagerService;
+
+    private CarSafetyAccessibilityService mCarSafetyAccessibilityService;
+
+    @Before
+    public void setup() {
+        mockGetCarLocalService(CarPackageManagerService.class, mMockCarPackageManagerService);
+        mCarSafetyAccessibilityService = new CarSafetyAccessibilityService();
+    }
+
+    @Test
+    public void onAccessibilityEvent_carPackageManagerServiceNotified() {
+        mCarSafetyAccessibilityService.onAccessibilityEvent(new AccessibilityEvent());
+
+        verify(mMockCarPackageManagerService).onWindowChangeEvent();
+    }
+
+    @Override
+    protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
+        session.spyStatic(CarLocalServices.class);
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/pm/WindowDumpParserTest.java b/tests/carservice_unit_test/src/com/android/car/pm/WindowDumpParserTest.java
new file mode 100644
index 0000000..b64c6e5
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/pm/WindowDumpParserTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class WindowDumpParserTest {
+    private static final String WINDOW_DUMP = "WINDOW MANAGER WINDOWS (dumpsys window windows)\n"
+            + "  Window #0 Window{ccae0fb u10 com.app1}:\n"
+            + "    mDisplayId=0 rootTaskId=1000006 mSession=Session{ef1bfd2 2683:u10a10088} "
+            + "mClient=android.os.BinderProxy@46bd28a\n"
+            + "    mOwnerUid=1010088 showForAllUsers=false package=com.app1 "
+            + "appop=SYSTEM_ALERT_WINDOW\n"
+            + "    mAttrs={(0,0)(0x0) gr=TOP RIGHT CENTER sim={adjust=pan} ty=APPLICATION_OVERLAY"
+            + " fmt=TRANSPARENT\n"
+            + "    isOnScreen=true\n"
+            + "    isVisible=true\n"
+
+            + "  Window #1 Window{17aaef4 u0 App 2}:\n"
+            + "    mDisplayId=1 rootTaskId=1000006 mSession=Session{629ba4e 2235:u0a10120} "
+            + "mClient=android.os.Binderproxy@3f3ea06\n"
+            + "    mOwnerUid=10120 showForAllUsers=true package=com.app2 appop=NONE\n"
+            + "    mAttrs={(24,0)(84x419) gr=BOTTOM RIGHT CENTER sim={adjust=pan} "
+            + "ty=DISPLAY_OVERLAY fmt=TRANSLUCENT\n"
+            + "      fl=NOT_FOCUSABLE LAYOUT_NO_LIMITS HARDWARE_ACCELERATED\n"
+            + "      pfl=NO_MOVE_ANIMATION USE_BLAST INSET_PARENT_FRAME_BY_IME\n"
+            + "      bhv=DEFAULT\n"
+            + "      fitTypes=STATUS_BARS NAVIGATION_BARS CAPTION_BAR}\n"
+            + "    Requested w=1080 h=1920 mLayoutSeq=154\n"
+            + "    mBaseLayer=21000 mSubLayer=0    mToken=AppWindowToken{546fe66 token=Token"
+            + "{8b7a6c1 ActivityRecord{2789ba8 u0 com.app2/.MainActivity t5}}}\n"
+            + "    mAppToken=AppWindowToken{546fe66 token=Token{8b7a6c1 ActivityRecord{2789ba8 u0"
+            + " com.app2/.MainActivity t5}}}\n"
+
+            + ".BinderProxy@3f3ea06}\n"
+            + "    Frames: containing=[0,0][1080,600] parent=[0,0][1080,600] display=[-10000,"
+            + "-10000][10000,10000]\n"
+            + "    mFrame=[972,181][1056,600] last=[0,0][0,0]\n"
+            + "     surface=[0,0][0,0]\n"
+            + "    WindowStateAnimator{8edd4a7 HVAC Passenger Temp}:\n"
+            + "      mDrawState=NO_SURFACE       mLastHidden=false\n"
+            + "      mEnterAnimationPending=false      mSystemDecorRect=[0,0][0,0]\n"
+            + "      mShownAlpha=0.0 mAlpha=1.0 mLastAlpha=0.0\n"
+            + "    mForceSeamlesslyRotate=false seamlesslyRotate: pending=null "
+            + "finishedFrameNumber=0\n"
+            + "    isOnScreen=false\n"
+            + "    isVisible=false\n"
+
+            + "  Window #2 Window{1c5571 u0 HVAC Driver Temp}:\n"
+            + "    mDisplayId=1 rootTaskId=1000006 mSession=Session{629ba4e 2235:u0a10120} "
+            + "mClient=android.os.BinderProxy@99ccafb\n"
+            + "    mOwnerUid=10120 showForAllUsers=true package=com.app2 appop=NONE\n"
+            + "    mAttrs={(24,0)(84x419) gr=BOTTOM LEFT CENTER sim={adjust=pan} "
+            + "ty=DISPLAY_OVERLAY fmt=TRANSLUCENT\n"
+            + "      fl=NOT_FOCUSABLE LAYOUT_NO_LIMITS HARDWARE_ACCELERATED\n"
+            + "      pfl=NO_MOVE_ANIMATION USE_BLAST INSET_PARENT_FRAME_BY_IME\n"
+            + "      bhv=DEFAULT\n"
+            + "      fitTypes=STATUS_BARS NAVIGATION_BARS CAPTION_BAR}\n"
+            + "    Requested w=84 h=419 mLayoutSeq=143\n"
+            + "    mBaseLayer=21000 mSubLayer=0    mToken=ActivityRecord{b44066 u10 com.app2/"
+            + "SecondActivity t1000031}\n"
+            + "    mActivityRecord=ActivityRecord{b44066 u10 com.app2/SecondActivity t1000031}\n"
+            + ".BinderProxy@99ccafb}\n"
+            + "    mViewVisibility=0x4 mHaveFrame=true mObscured=false\n"
+            + "    mGivenContentInsets=[0,0][0,0] mGivenVisibleInsets=[0,0][0,0]\n"
+            + "    isOnScreen=false\n"
+            + "    isVisible=false\n"
+
+            + "  Window #3 Window{1c5571 u0 HVAC Driver Temp}:\n"
+            + "    mDisplayId=2 rootTaskId=1000006 mSession=Session{629ba4e 2235:u0a10120} "
+            + "mClient=android.os.BinderProxy@99ccafb\n"
+            + "    mOwnerUid=10120 showForAllUsers=true package=com.app3 appop=NONE\n"
+            + "    mAttrs={(24,0)(84x419) gr=BOTTOM LEFT CENTER sim={adjust=pan} "
+            + "ty=DISPLAY_OVERLAY fmt=TRANSLUCENT\n"
+            + "      fl=NOT_FOCUSABLE LAYOUT_NO_LIMITS HARDWARE_ACCELERATED\n"
+            + "      pfl=NO_MOVE_ANIMATION USE_BLAST INSET_PARENT_FRAME_BY_IME\n"
+            + "      bhv=DEFAULT\n"
+            + "      fitTypes=STATUS_BARS NAVIGATION_BARS CAPTION_BAR}\n"
+            + "    Requested w=84 h=419 mLayoutSeq=143\n"
+            + "    mBaseLayer=291000 mSubLayer=0    mToken=WindowToken{6bd1718 android.os"
+            + "    mActivityRecord=ActivityRecord{a3f066 u10 com.app3/MainActivity t1000031}\n"
+            + ".BinderProxy@99ccafb}\n"
+            + "    mViewVisibility=0x4 mHaveFrame=true mObscured=false\n"
+            + "    mGivenContentInsets=[0,0][0,0] mGivenVisibleInsets=[0,0][0,0]\n"
+            + "    isOnScreen=false\n"
+            + "    isVisible=false\n"
+
+            + "  Window #4 Window{1c5571 u0 HVAC Driver Temp}:\n"
+            + "    mDisplayId=2 rootTaskId=1000006 mSession=Session{629ba4e 2235:u0a10120} "
+            + "mClient=android.os.BinderProxy@99ccafb\n"
+            + "    mOwnerUid=10120 showForAllUsers=true package=com.app3 appop=NONE\n"
+            + "    mAttrs={(24,0)(84x419) gr=BOTTOM LEFT CENTER sim={adjust=pan} "
+            + "ty=APPLICATION_STARTING fmt=TRANSLUCENT\n"
+            + "      fl=NOT_FOCUSABLE LAYOUT_NO_LIMITS HARDWARE_ACCELERATED\n"
+            + "      pfl=NO_MOVE_ANIMATION USE_BLAST INSET_PARENT_FRAME_BY_IME\n"
+            + "      bhv=DEFAULT\n"
+            + "      fitTypes=STATUS_BARS NAVIGATION_BARS CAPTION_BAR}\n"
+            + "    Requested w=84 h=419 mLayoutSeq=143\n"
+            + "    mBaseLayer=291000 mSubLayer=0    mToken=WindowToken{6bd1718 android.os"
+            + ".BinderProxy@99ccafb}\n"
+            + "    mViewVisibility=0x4 mHaveFrame=true mObscured=false\n"
+            + "    mGivenContentInsets=[0,0][0,0] mGivenVisibleInsets=[0,0][0,0]\n"
+            + "    isOnScreen=false\n"
+            + "    isVisible=false\n";
+
+    private static final WindowDumpParser.Window APP_1_WINDOW = new WindowDumpParser.Window(
+            "com.app1", 0, null);
+    private static final WindowDumpParser.Window APP_2_WINDOW = new WindowDumpParser.Window(
+            "com.app2", 1, "2789ba8 u0 com.app2/.MainActivity t5");
+    private static final WindowDumpParser.Window APP_2_WINDOW_2 = new WindowDumpParser.Window(
+            "com.app2", 1, "b44066 u10 com.app2/SecondActivity t1000031");
+    private static final WindowDumpParser.Window APP_3_WINDOW = new WindowDumpParser.Window(
+            "com.app3", 2, "a3f066 u10 com.app3/MainActivity t1000031");
+
+    @Test
+    public void testWindowDumpParsing() {
+        assertThat(WindowDumpParser.getParsedAppWindows(WINDOW_DUMP, "com.app1")).containsExactly(
+                APP_1_WINDOW);
+        assertThat(WindowDumpParser.getParsedAppWindows(WINDOW_DUMP, "com.app2")).containsExactly(
+                APP_2_WINDOW, APP_2_WINDOW_2);
+        assertThat(WindowDumpParser.getParsedAppWindows(WINDOW_DUMP, "com.app3")).containsExactly(
+                APP_3_WINDOW);
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/CarTelemetryServiceTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/CarTelemetryServiceTest.java
index 0e9a168..a17e3fa 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/CarTelemetryServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/CarTelemetryServiceTest.java
@@ -16,108 +16,167 @@
 
 package com.android.car.telemetry;
 
-import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.car.telemetry.CarTelemetryManager;
-import android.car.telemetry.ManifestKey;
+import android.car.telemetry.ICarTelemetryServiceListener;
+import android.car.telemetry.MetricsConfigKey;
 import android.content.Context;
+import android.os.Handler;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.car.CarLocalServices;
+import com.android.car.CarPropertyService;
+import com.android.car.systeminterface.SystemInterface;
+
 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;
 
+import java.io.File;
+import java.nio.file.Files;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(MockitoJUnitRunner.class)
 @SmallTest
 public class CarTelemetryServiceTest {
+    private static final long TIMEOUT_MS = 5_000L;
+    private static final String METRICS_CONFIG_NAME = "my_metrics_config";
+    private static final MetricsConfigKey KEY_V1 = new MetricsConfigKey(METRICS_CONFIG_NAME, 1);
+    private static final MetricsConfigKey KEY_V2 = new MetricsConfigKey(METRICS_CONFIG_NAME, 2);
+    private static final TelemetryProto.MetricsConfig METRICS_CONFIG_V1 =
+            TelemetryProto.MetricsConfig.newBuilder()
+                    .setName(METRICS_CONFIG_NAME).setVersion(1).setScript("no-op").build();
+    private static final TelemetryProto.MetricsConfig METRICS_CONFIG_V2 =
+            TelemetryProto.MetricsConfig.newBuilder()
+                    .setName(METRICS_CONFIG_NAME).setVersion(2).setScript("no-op").build();
+
+    private CountDownLatch mIdleHandlerLatch = new CountDownLatch(1);
+    private CarTelemetryService mService;
+    private File mTempSystemCarDir;
+    private Handler mTelemetryHandler;
+
     @Rule
     public MockitoRule mMockitoRule = MockitoJUnit.rule();
     @Mock
+    private CarPropertyService mMockCarPropertyService;
+    @Mock
     private Context mContext;
-
-    private final ManifestKey mManifestKeyV1 = new ManifestKey("Name", 1);
-    private final ManifestKey mManifestKeyV2 = new ManifestKey("Name", 2);
-    private final TelemetryProto.MetricsConfig mMetricsConfig =
-            TelemetryProto.MetricsConfig.newBuilder().setScript("no-op").build();
-
-    private CarTelemetryService mService;
+    @Mock
+    private ICarTelemetryServiceListener mMockListener;
+    @Mock
+    private SystemInterface mMockSystemInterface;
 
     @Before
-    public void setUp() {
-        mService = new CarTelemetryService(mContext);
+    public void setUp() throws Exception {
+        CarLocalServices.removeServiceForTest(SystemInterface.class);
+        CarLocalServices.addService(SystemInterface.class, mMockSystemInterface);
+
+        mTempSystemCarDir = Files.createTempDirectory("telemetry_test").toFile();
+        when(mMockSystemInterface.getSystemCarDir()).thenReturn(mTempSystemCarDir);
+
+        mService = new CarTelemetryService(mContext, mMockCarPropertyService);
+        mService.init();
+        mService.setListener(mMockListener);
+
+        mTelemetryHandler = mService.getTelemetryHandler();
+        mTelemetryHandler.getLooper().getQueue().addIdleHandler(() -> {
+            mIdleHandlerLatch.countDown();
+            return true;
+        });
+        waitForHandlerThreadToFinish();
     }
 
     @Test
-    public void testAddManifest_newManifest_shouldSucceed() {
-        int result = mService.addManifest(mManifestKeyV1, mMetricsConfig.toByteArray());
+    public void testAddMetricsConfig_newMetricsConfig_shouldSucceed() throws Exception {
+        mService.addMetricsConfig(KEY_V1, METRICS_CONFIG_V1.toByteArray());
 
-        assertThat(result).isEqualTo(CarTelemetryManager.ERROR_NONE);
+        waitForHandlerThreadToFinish();
+        verify(mMockListener).onAddMetricsConfigStatus(
+                eq(KEY_V1), eq(CarTelemetryManager.ERROR_METRICS_CONFIG_NONE));
     }
 
     @Test
-    public void testAddManifest_duplicateManifest_shouldFail() {
-        mService.addManifest(mManifestKeyV1, mMetricsConfig.toByteArray());
+    public void testAddMetricsConfig_duplicateMetricsConfig_shouldFail() throws Exception {
+        mService.addMetricsConfig(KEY_V1, METRICS_CONFIG_V1.toByteArray());
+        waitForHandlerThreadToFinish();
+        verify(mMockListener).onAddMetricsConfigStatus(
+                eq(KEY_V1), eq(CarTelemetryManager.ERROR_METRICS_CONFIG_NONE));
 
-        int result = mService.addManifest(mManifestKeyV1, mMetricsConfig.toByteArray());
+        mService.addMetricsConfig(KEY_V1, METRICS_CONFIG_V1.toByteArray());
 
-        assertThat(result).isEqualTo(CarTelemetryManager.ERROR_SAME_MANIFEST_EXISTS);
+        waitForHandlerThreadToFinish();
+        verify(mMockListener).onAddMetricsConfigStatus(
+                eq(KEY_V1), eq(CarTelemetryManager.ERROR_METRICS_CONFIG_ALREADY_EXISTS));
     }
 
     @Test
-    public void testAddManifest_invalidManifest_shouldFail() {
-        int result = mService.addManifest(mManifestKeyV1, "bad manifest".getBytes());
+    public void testAddMetricsConfig_invalidMetricsConfig_shouldFail() throws Exception {
+        mService.addMetricsConfig(KEY_V1, "bad manifest".getBytes());
 
-        assertThat(result).isEqualTo(CarTelemetryManager.ERROR_PARSE_MANIFEST_FAILED);
+        waitForHandlerThreadToFinish();
+        verify(mMockListener).onAddMetricsConfigStatus(
+                eq(KEY_V1), eq(CarTelemetryManager.ERROR_METRICS_CONFIG_PARSE_FAILED));
     }
 
     @Test
-    public void testAddManifest_olderManifest_shouldFail() {
-        mService.addManifest(mManifestKeyV2, mMetricsConfig.toByteArray());
+    public void testAddMetricsConfig_olderMetricsConfig_shouldFail() throws Exception {
+        mService.addMetricsConfig(KEY_V2, METRICS_CONFIG_V2.toByteArray());
+        waitForHandlerThreadToFinish();
+        verify(mMockListener).onAddMetricsConfigStatus(
+                eq(KEY_V2), eq(CarTelemetryManager.ERROR_METRICS_CONFIG_NONE));
 
-        int result = mService.addManifest(mManifestKeyV1, mMetricsConfig.toByteArray());
+        mService.addMetricsConfig(KEY_V1, METRICS_CONFIG_V1.toByteArray());
 
-        assertThat(result).isEqualTo(CarTelemetryManager.ERROR_NEWER_MANIFEST_EXISTS);
+        waitForHandlerThreadToFinish();
+        verify(mMockListener).onAddMetricsConfigStatus(
+                eq(KEY_V1), eq(CarTelemetryManager.ERROR_METRICS_CONFIG_VERSION_TOO_OLD));
     }
 
     @Test
-    public void testAddManifest_newerManifest_shouldReplace() {
-        mService.addManifest(mManifestKeyV1, mMetricsConfig.toByteArray());
+    public void testAddMetricsConfig_newerMetricsConfig_shouldReplace() throws Exception {
+        mService.addMetricsConfig(KEY_V1, METRICS_CONFIG_V1.toByteArray());
 
-        int result = mService.addManifest(mManifestKeyV2, mMetricsConfig.toByteArray());
+        mService.addMetricsConfig(KEY_V2, METRICS_CONFIG_V2.toByteArray());
 
-        assertThat(result).isEqualTo(CarTelemetryManager.ERROR_NONE);
+        waitForHandlerThreadToFinish();
+        verify(mMockListener).onAddMetricsConfigStatus(
+                eq(KEY_V2), eq(CarTelemetryManager.ERROR_METRICS_CONFIG_NONE));
     }
 
     @Test
-    public void testRemoveManifest_manifestExists_shouldSucceed() {
-        mService.addManifest(mManifestKeyV1, mMetricsConfig.toByteArray());
+    public void testRemoveMetricsConfig_manifestExists_shouldSucceed() throws Exception {
+        mService.addMetricsConfig(KEY_V1, METRICS_CONFIG_V1.toByteArray());
 
-        boolean result = mService.removeManifest(mManifestKeyV1);
+        mService.removeMetricsConfig(KEY_V1);
 
-        assertThat(result).isTrue();
+        waitForHandlerThreadToFinish();
+        verify(mMockListener).onRemoveMetricsConfigStatus(eq(KEY_V1), eq(true));
     }
 
     @Test
-    public void testRemoveManifest_manifestDoesNotExist_shouldFail() {
-        boolean result = mService.removeManifest(mManifestKeyV1);
+    public void testRemoveMetricsConfig_manifestDoesNotExist_shouldFail() throws Exception {
+        mService.removeMetricsConfig(KEY_V1);
 
-        assertThat(result).isFalse();
+        waitForHandlerThreadToFinish();
+        verify(mMockListener).onRemoveMetricsConfigStatus(eq(KEY_V1), eq(false));
     }
 
-    @Test
-    public void testRemoveAllManifests_shouldSucceed() {
-        mService.addManifest(mManifestKeyV1, mMetricsConfig.toByteArray());
-        mService.addManifest(mManifestKeyV2, mMetricsConfig.toByteArray());
-
-        mService.removeAllManifests();
-
-        // verify that the manifests are cleared by adding them again, should succeed
-        int result = mService.addManifest(mManifestKeyV1, mMetricsConfig.toByteArray());
-        assertThat(result).isEqualTo(CarTelemetryManager.ERROR_NONE);
-        result = mService.addManifest(mManifestKeyV2, mMetricsConfig.toByteArray());
-        assertThat(result).isEqualTo(CarTelemetryManager.ERROR_NONE);
+    private void waitForHandlerThreadToFinish() throws Exception {
+        assertWithMessage("handler not idle in %sms", TIMEOUT_MS)
+                .that(mIdleHandlerLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+        mIdleHandlerLatch = new CountDownLatch(1); // reset idle handler condition
+        mTelemetryHandler.runWithScissors(() -> { }, TIMEOUT_MS);
     }
 }
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/JniUtilsTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/JniUtilsTest.java
deleted file mode 100644
index 67a5ac8..0000000
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/JniUtilsTest.java
+++ /dev/null
@@ -1,124 +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 static com.google.common.truth.Truth.assertThat;
-
-import android.os.Bundle;
-import android.util.Log;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public final class JniUtilsTest {
-
-    private static final String TAG = JniUtilsTest.class.getSimpleName();
-
-    private static final String BOOLEAN_KEY = "boolean_key";
-    private static final String INT_KEY = "int_key";
-    private static final String STRING_KEY = "string_key";
-    private static final String NUMBER_KEY = "number_key";
-
-    private static final boolean BOOLEAN_VALUE = true;
-    private static final double NUMBER_VALUE = 0.1;
-    private static final int INT_VALUE = 10;
-    private static final String STRING_VALUE = "test";
-
-    // Pointer to Lua Engine instantiated in native space.
-    private long mLuaEnginePtr = 0;
-
-    static {
-        System.loadLibrary("scriptexecutorjniutils-test");
-    }
-
-    @Before
-    public void setUp() {
-        mLuaEnginePtr = nativeCreateLuaEngine();
-    }
-
-    @After
-    public void tearDown() {
-        nativeDestroyLuaEngine(mLuaEnginePtr);
-    }
-
-    // Simply invokes PushBundleToLuaTable native method under test.
-    private native void nativePushBundleToLuaTableCaller(long luaEnginePtr, Bundle bundle);
-
-    // Creates an instance of LuaEngine on the heap and returns the pointer.
-    private native long nativeCreateLuaEngine();
-
-    // Destroys instance of LuaEngine on the native side at provided memory address.
-    private native void nativeDestroyLuaEngine(long luaEnginePtr);
-
-    // Returns size of a Lua object located at the specified position on the stack.
-    private native int nativeGetObjectSize(long luaEnginePtr, int index);
-
-    /*
-     * Family of methods to check if the table on top of the stack has
-     * the given value under provided key.
-     */
-    private native boolean nativeHasBooleanValue(long luaEnginePtr, String key, boolean value);
-    private native boolean nativeHasStringValue(long luaEnginePtr, String key, String value);
-    private native boolean nativeHasIntValue(long luaEnginePtr, String key, int value);
-    private native boolean nativeHasDoubleValue(long luaEnginePtr, String key, double value);
-
-    @Test
-    public void pushBundleToLuaTable_nullBundleMakesEmptyLuaTable() {
-        Log.d(TAG, "Using Lua Engine with address " + mLuaEnginePtr);
-        nativePushBundleToLuaTableCaller(mLuaEnginePtr, null);
-        // Get the size of the object on top of the stack,
-        // which is where our table is supposed to be.
-        assertThat(nativeGetObjectSize(mLuaEnginePtr, 1)).isEqualTo(0);
-    }
-
-    @Test
-    public void pushBundleToLuaTable_valuesOfDifferentTypes() {
-        Bundle bundle = new Bundle();
-        bundle.putBoolean(BOOLEAN_KEY, BOOLEAN_VALUE);
-        bundle.putInt(INT_KEY, INT_VALUE);
-        bundle.putDouble(NUMBER_KEY, NUMBER_VALUE);
-        bundle.putString(STRING_KEY, STRING_VALUE);
-
-        // Invokes the corresponding helper method to convert the bundle
-        // to Lua table on Lua stack.
-        nativePushBundleToLuaTableCaller(mLuaEnginePtr, bundle);
-
-        // Check contents of Lua table.
-        assertThat(nativeHasBooleanValue(mLuaEnginePtr, BOOLEAN_KEY, BOOLEAN_VALUE)).isTrue();
-        assertThat(nativeHasIntValue(mLuaEnginePtr, INT_KEY, INT_VALUE)).isTrue();
-        assertThat(nativeHasDoubleValue(mLuaEnginePtr, NUMBER_KEY, NUMBER_VALUE)).isTrue();
-        assertThat(nativeHasStringValue(mLuaEnginePtr, STRING_KEY, STRING_VALUE)).isTrue();
-    }
-
-
-    @Test
-    public void pushBundleToLuaTable_wrongKey() {
-        Bundle bundle = new Bundle();
-        bundle.putBoolean(BOOLEAN_KEY, BOOLEAN_VALUE);
-
-        // Invokes the corresponding helper method to convert the bundle
-        // to Lua table on Lua stack.
-        nativePushBundleToLuaTableCaller(mLuaEnginePtr, bundle);
-
-        // Check contents of Lua table.
-        assertThat(nativeHasBooleanValue(mLuaEnginePtr, "wrong key", BOOLEAN_VALUE)).isFalse();
-    }
-}
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/MetricsConfigStoreTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/MetricsConfigStoreTest.java
new file mode 100644
index 0000000..5b326ff
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/MetricsConfigStoreTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import android.car.telemetry.CarTelemetryManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class MetricsConfigStoreTest {
+    private static final String NAME_FOO = "Foo";
+    private static final String NAME_BAR = "Bar";
+    private static final TelemetryProto.MetricsConfig METRICS_CONFIG_FOO =
+            TelemetryProto.MetricsConfig.newBuilder().setName(NAME_FOO).build();
+    private static final TelemetryProto.MetricsConfig METRICS_CONFIG_BAR =
+            TelemetryProto.MetricsConfig.newBuilder().setName(NAME_BAR).build();
+
+    private File mTestRootDir;
+    private File mTestMetricsConfigDir;
+    private MetricsConfigStore mMetricsConfigStore;
+
+    @Before
+    public void setUp() throws Exception {
+        mTestRootDir = Files.createTempDirectory("car_telemetry_test").toFile();
+        mTestMetricsConfigDir = new File(mTestRootDir, MetricsConfigStore.METRICS_CONFIG_DIR);
+
+        mMetricsConfigStore = new MetricsConfigStore(mTestRootDir);
+        assertThat(mTestMetricsConfigDir.exists()).isTrue();
+    }
+
+    @Test
+    public void testRetrieveActiveMetricsConfigs_shouldSendConfigsToListener() throws Exception {
+        writeConfigToDisk(METRICS_CONFIG_FOO);
+        writeConfigToDisk(METRICS_CONFIG_BAR);
+        mMetricsConfigStore = new MetricsConfigStore(mTestRootDir); // reload data
+
+        List<TelemetryProto.MetricsConfig> result = mMetricsConfigStore.getActiveMetricsConfigs();
+
+        assertThat(result).containsExactly(METRICS_CONFIG_FOO, METRICS_CONFIG_BAR);
+    }
+
+    @Test
+    public void testAddMetricsConfig_shouldWriteConfigToDisk() throws Exception {
+        int status = mMetricsConfigStore.addMetricsConfig(METRICS_CONFIG_FOO);
+
+        assertThat(status).isEqualTo(CarTelemetryManager.ERROR_METRICS_CONFIG_NONE);
+        assertThat(readConfigFromFile(NAME_FOO)).isEqualTo(METRICS_CONFIG_FOO);
+    }
+
+    @Test
+    public void testDeleteMetricsConfig_whenNoConfig_shouldReturnFalse() {
+        boolean status = mMetricsConfigStore.deleteMetricsConfig(NAME_BAR);
+
+        assertThat(status).isFalse();
+    }
+
+    @Test
+    public void testDeleteMetricsConfig_shouldDeleteConfigFromDisk() throws Exception {
+        writeConfigToDisk(METRICS_CONFIG_BAR);
+
+        boolean status = mMetricsConfigStore.deleteMetricsConfig(NAME_BAR);
+
+        assertThat(status).isTrue();
+        assertThat(new File(mTestMetricsConfigDir, NAME_BAR).exists()).isFalse();
+    }
+
+    private void writeConfigToDisk(TelemetryProto.MetricsConfig config) throws Exception {
+        File file = new File(mTestMetricsConfigDir, config.getName());
+        Files.write(file.toPath(), config.toByteArray());
+        assertThat(file.exists()).isTrue();
+    }
+
+    private TelemetryProto.MetricsConfig readConfigFromFile(String fileName) throws Exception {
+        byte[] bytes = Files.readAllBytes(new File(mTestMetricsConfigDir, fileName).toPath());
+        return TelemetryProto.MetricsConfig.parseFrom(bytes);
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/ResultStoreTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/ResultStoreTest.java
new file mode 100644
index 0000000..af0281a
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/ResultStoreTest.java
@@ -0,0 +1,268 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+
+import android.os.PersistableBundle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ResultStoreTest {
+    private static final PersistableBundle TEST_INTERIM_BUNDLE = new PersistableBundle();
+    private static final PersistableBundle TEST_FINAL_BUNDLE = new PersistableBundle();
+
+    private File mTestRootDir;
+    private File mTestInterimResultDir;
+    private File mTestFinalResultDir;
+    private ResultStore mResultStore;
+
+    @Mock
+    private ResultStore.FinalResultCallback mMockFinalResultCallback;
+    @Captor
+    private ArgumentCaptor<PersistableBundle> mBundleCaptor;
+
+
+    @Before
+    public void setUp() throws Exception {
+        TEST_INTERIM_BUNDLE.putString("test key", "interim value");
+        TEST_FINAL_BUNDLE.putString("test key", "final value");
+
+        mTestRootDir = Files.createTempDirectory("car_telemetry_test").toFile();
+        mTestInterimResultDir = new File(mTestRootDir, ResultStore.INTERIM_RESULT_DIR);
+        mTestFinalResultDir = new File(mTestRootDir, ResultStore.FINAL_RESULT_DIR);
+
+        mResultStore = new ResultStore(mTestRootDir);
+    }
+
+    @Test
+    public void testConstructor_shouldCreateResultsFolder() {
+        // constructor is called in setUp()
+        assertThat(mTestInterimResultDir.exists()).isTrue();
+        assertThat(mTestFinalResultDir.exists()).isTrue();
+    }
+
+    @Test
+    public void testConstructor_shouldLoadInterimResultsIntoMemory() throws Exception {
+        String testInterimFileName = "test_file_1";
+        writeBundleToFile(mTestInterimResultDir, testInterimFileName, TEST_INTERIM_BUNDLE);
+
+        mResultStore = new ResultStore(mTestRootDir);
+
+        // should compare value instead of reference
+        assertThat(mResultStore.getInterimResult(testInterimFileName).toString())
+                .isEqualTo(TEST_INTERIM_BUNDLE.toString());
+    }
+
+    @Test
+    public void testFlushToDisk_shouldWriteResultsToFileAndCheckContent() throws Exception {
+        String testInterimFileName = "test_file_1";
+        String testFinalFileName = "test_file_2";
+        writeBundleToFile(mTestInterimResultDir, testInterimFileName, TEST_INTERIM_BUNDLE);
+        writeBundleToFile(mTestFinalResultDir, testFinalFileName, TEST_FINAL_BUNDLE);
+
+        mResultStore.flushToDisk();
+
+        assertThat(new File(mTestInterimResultDir, testInterimFileName).exists()).isTrue();
+        assertThat(new File(mTestFinalResultDir, testFinalFileName).exists()).isTrue();
+        // the content check will need to be modified when data encryption is implemented
+        PersistableBundle interimData =
+                readBundleFromFile(mTestInterimResultDir, testInterimFileName);
+        assertThat(interimData.toString()).isEqualTo(TEST_INTERIM_BUNDLE.toString());
+        PersistableBundle finalData = readBundleFromFile(mTestFinalResultDir, testFinalFileName);
+        assertThat(finalData.toString()).isEqualTo(TEST_FINAL_BUNDLE.toString());
+    }
+
+    @Test
+    public void testFlushToDisk_shouldRemoveStaleData() throws Exception {
+        File staleTestFile1 = new File(mTestInterimResultDir, "stale_test_file_1");
+        File staleTestFile2 = new File(mTestFinalResultDir, "stale_test_file_2");
+        File activeTestFile3 = new File(mTestInterimResultDir, "active_test_file_3");
+        writeBundleToFile(staleTestFile1, TEST_INTERIM_BUNDLE);
+        writeBundleToFile(staleTestFile2, TEST_FINAL_BUNDLE);
+        writeBundleToFile(activeTestFile3, TEST_INTERIM_BUNDLE);
+        long currTimeMs = System.currentTimeMillis();
+        staleTestFile1.setLastModified(0L); // stale
+        staleTestFile2.setLastModified(
+                currTimeMs - TimeUnit.MILLISECONDS.convert(31, TimeUnit.DAYS)); // stale
+        activeTestFile3.setLastModified(
+                currTimeMs - TimeUnit.MILLISECONDS.convert(29, TimeUnit.DAYS)); // active
+
+        mResultStore.flushToDisk();
+
+        assertThat(staleTestFile1.exists()).isFalse();
+        assertThat(staleTestFile2.exists()).isFalse();
+        assertThat(activeTestFile3.exists()).isTrue();
+    }
+
+    @Test
+    public void testGetFinalResult_whenNoData_shouldReceiveNull() throws Exception {
+        String metricsConfigName = "my_metrics_config";
+
+        mResultStore.getFinalResult(metricsConfigName, true, mMockFinalResultCallback);
+
+        verify(mMockFinalResultCallback).onFinalResult(eq(metricsConfigName),
+                mBundleCaptor.capture());
+        assertThat(mBundleCaptor.getValue()).isNull();
+    }
+
+    @Test
+    public void testGetFinalResult_whenDataCorrupt_shouldReceiveNull() throws Exception {
+        String metricsConfigName = "my_metrics_config";
+        Files.write(new File(mTestFinalResultDir, metricsConfigName).toPath(),
+                "not a bundle".getBytes(StandardCharsets.UTF_8));
+
+        mResultStore.getFinalResult(metricsConfigName, true, mMockFinalResultCallback);
+
+        verify(mMockFinalResultCallback).onFinalResult(eq(metricsConfigName),
+                mBundleCaptor.capture());
+        assertThat(mBundleCaptor.getValue()).isNull();
+    }
+
+    @Test
+    public void testGetFinalResult_whenDeleteFlagTrue_shouldDeleteData() throws Exception {
+        String testFinalFileName = "my_metrics_config";
+        writeBundleToFile(mTestFinalResultDir, testFinalFileName, TEST_FINAL_BUNDLE);
+
+        mResultStore.getFinalResult(testFinalFileName, true, mMockFinalResultCallback);
+
+        verify(mMockFinalResultCallback).onFinalResult(eq(testFinalFileName),
+                mBundleCaptor.capture());
+        // should compare value instead of reference
+        assertThat(mBundleCaptor.getValue().toString()).isEqualTo(TEST_FINAL_BUNDLE.toString());
+        assertThat(new File(mTestFinalResultDir, testFinalFileName).exists()).isFalse();
+    }
+
+    @Test
+    public void testGetFinalResult_whenDeleteFlagFalse_shouldNotDeleteData() throws Exception {
+        String testFinalFileName = "my_metrics_config";
+        writeBundleToFile(mTestFinalResultDir, testFinalFileName, TEST_FINAL_BUNDLE);
+
+        mResultStore.getFinalResult(testFinalFileName, false, mMockFinalResultCallback);
+
+        verify(mMockFinalResultCallback).onFinalResult(eq(testFinalFileName),
+                mBundleCaptor.capture());
+        // should compare value instead of reference
+        assertThat(mBundleCaptor.getValue().toString()).isEqualTo(TEST_FINAL_BUNDLE.toString());
+        assertThat(new File(mTestFinalResultDir, testFinalFileName).exists()).isTrue();
+    }
+
+    @Test
+    public void testPutFinalResult_shouldRemoveInterimResultFromMemory() throws Exception {
+        String metricsConfigName = "my_metrics_config";
+        mResultStore.putInterimResult(metricsConfigName, TEST_INTERIM_BUNDLE);
+
+        mResultStore.putFinalResult(metricsConfigName, TEST_FINAL_BUNDLE);
+
+        assertThat(mResultStore.getInterimResult(metricsConfigName)).isNull();
+    }
+
+    @Test
+    public void testPutInterimResultAndFlushToDisk_shouldReplaceExistingFile() throws Exception {
+        String newKey = "new key";
+        String newValue = "new value";
+        String metricsConfigName = "my_metrics_config";
+        writeBundleToFile(mTestInterimResultDir, metricsConfigName, TEST_INTERIM_BUNDLE);
+        TEST_INTERIM_BUNDLE.putString(newKey, newValue);
+
+        mResultStore.putInterimResult(metricsConfigName, TEST_INTERIM_BUNDLE);
+        mResultStore.flushToDisk();
+
+        PersistableBundle bundle = readBundleFromFile(mTestInterimResultDir, metricsConfigName);
+        assertThat(bundle.getString(newKey)).isEqualTo(newValue);
+        assertThat(bundle.toString()).isEqualTo(TEST_INTERIM_BUNDLE.toString());
+    }
+
+    @Test
+    public void testPutInterimResultAndFlushToDisk_shouldWriteDirtyResultsOnly() throws Exception {
+        File fileFoo = new File(mTestInterimResultDir, "foo");
+        File fileBar = new File(mTestInterimResultDir, "bar");
+        writeBundleToFile(fileFoo, TEST_INTERIM_BUNDLE);
+        writeBundleToFile(fileBar, TEST_INTERIM_BUNDLE);
+        mResultStore = new ResultStore(mTestRootDir); // re-load data
+        PersistableBundle newData = new PersistableBundle();
+        newData.putDouble("pi", 3.1415926);
+
+        mResultStore.putInterimResult("bar", newData); // make bar dirty
+        fileFoo.delete(); // delete the clean file from the file system
+        mResultStore.flushToDisk(); // write dirty data
+
+        // foo is a clean file that should not be written in shutdown
+        assertThat(fileFoo.exists()).isFalse();
+        assertThat(readBundleFromFile(fileBar).toString()).isEqualTo(newData.toString());
+    }
+
+    @Test
+    public void testPutFinalResultAndFlushToDisk_shouldRemoveInterimResultFile() throws Exception {
+        String metricsConfigName = "my_metrics_config";
+        writeBundleToFile(mTestInterimResultDir, metricsConfigName, TEST_INTERIM_BUNDLE);
+
+        mResultStore.putFinalResult(metricsConfigName, TEST_FINAL_BUNDLE);
+        mResultStore.flushToDisk();
+
+        assertThat(new File(mTestInterimResultDir, metricsConfigName).exists()).isFalse();
+        assertThat(new File(mTestFinalResultDir, metricsConfigName).exists()).isTrue();
+    }
+
+    private void writeBundleToFile(
+            File dir, String fileName, PersistableBundle persistableBundle) throws Exception {
+        writeBundleToFile(new File(dir, fileName), persistableBundle);
+    }
+
+    /**
+     * Writes a persistable bundle to the result directory with the given directory and file name,
+     * and verifies that it was successfully written.
+     */
+    private void writeBundleToFile(
+            File file, PersistableBundle persistableBundle) throws Exception {
+        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
+            persistableBundle.writeToStream(byteArrayOutputStream);
+            Files.write(file.toPath(), byteArrayOutputStream.toByteArray());
+        }
+        assertWithMessage("bundle is not written to the result directory")
+                .that(file.exists()).isTrue();
+    }
+
+    private PersistableBundle readBundleFromFile(File dir, String fileName) throws Exception {
+        return readBundleFromFile(new File(dir, fileName));
+    }
+
+    /** Reads a persistable bundle from the given path. */
+    private PersistableBundle readBundleFromFile(File file) throws Exception {
+        try (FileInputStream fis = new FileInputStream(file)) {
+            return PersistableBundle.readFromStream(fis);
+        }
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/ScriptExecutorTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/ScriptExecutorTest.java
deleted file mode 100644
index 774da25..0000000
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/ScriptExecutorTest.java
+++ /dev/null
@@ -1,123 +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 static org.junit.Assert.fail;
-
-import android.car.telemetry.IScriptExecutor;
-import android.car.telemetry.IScriptExecutorListener;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@RunWith(JUnit4.class)
-public final class ScriptExecutorTest {
-
-    private IScriptExecutor mScriptExecutor;
-    private ScriptExecutor mInstance;
-    private Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
-
-
-    private static final class ScriptExecutorListener extends IScriptExecutorListener.Stub {
-        @Override
-        public void onScriptFinished(byte[] result) {
-        }
-
-        @Override
-        public void onSuccess(Bundle stateToPersist) {
-        }
-
-        @Override
-        public void onError(int errorType, String message, String stackTrace) {
-        }
-    }
-
-    private final IScriptExecutorListener mFakeScriptExecutorListener =
-            new ScriptExecutorListener();
-
-    // TODO(b/189241508). Parsing of publishedData is not implemented yet.
-    // Null is the only accepted input.
-    private final Bundle mPublishedData = null;
-    private final Bundle mSavedState = new Bundle();
-
-    private static final String LUA_SCRIPT =
-            "function hello(data, state)\n"
-            + "    print(\"Hello World\")\n"
-            + "end\n";
-
-    private static final String LUA_METHOD = "hello";
-
-    private final CountDownLatch mBindLatch = new CountDownLatch(1);
-
-    private static final int BIND_SERVICE_TIMEOUT_SEC = 5;
-
-    private final ServiceConnection mScriptExecutorConnection =
-            new ServiceConnection() {
-                @Override
-                public void onServiceConnected(ComponentName className, IBinder service) {
-                    mScriptExecutor = IScriptExecutor.Stub.asInterface(service);
-                    mBindLatch.countDown();
-                }
-
-                @Override
-                public void onServiceDisconnected(ComponentName className) {
-                    fail("Service unexpectedly disconnected");
-                }
-            };
-
-    @Before
-    public void setUp() throws InterruptedException {
-        mContext.bindIsolatedService(new Intent(mContext, ScriptExecutor.class),
-                Context.BIND_AUTO_CREATE, "scriptexecutor", mContext.getMainExecutor(),
-                mScriptExecutorConnection);
-        if (!mBindLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS)) {
-            fail("Failed to bind to ScripExecutor service");
-        }
-    }
-
-    @Test
-    public void invokeScript_helloWorld() throws RemoteException {
-        // Expect to load "hello world" script successfully and push the function.
-        mScriptExecutor.invokeScript(LUA_SCRIPT, LUA_METHOD, mPublishedData, mSavedState,
-                mFakeScriptExecutorListener);
-        // Sleep, otherwise the test case will complete before the script loads
-        // because invokeScript is non-blocking.
-        try {
-            // TODO(b/192285332): Replace sleep logic with waiting for specific callbacks
-            // to be called once they are implemented. Otherwise, this could be a flaky test.
-            TimeUnit.SECONDS.sleep(10);
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-            fail(e.getMessage());
-        }
-    }
-}
-
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerControllerUnitTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerControllerUnitTest.java
index 931a557..89db3f6 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerControllerUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerControllerUnitTest.java
@@ -81,7 +81,7 @@
         // Checks that mMockDataBroker's setOnScriptFinishedCallback is called after it's injected
         // into controller's constructor with @InjectMocks
         verify(mMockDataBroker).setOnScriptFinishedCallback(
-                any(DataBrokerController.ScriptFinishedCallback.class));
+                any(DataBroker.ScriptFinishedCallback.class));
     }
 
     @Test
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerTest.java
new file mode 100644
index 0000000..6c41e4b
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerTest.java
@@ -0,0 +1,431 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.car.hardware.CarPropertyConfig;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.os.SystemClock;
+
+import com.android.car.CarPropertyService;
+import com.android.car.telemetry.ResultStore;
+import com.android.car.telemetry.TelemetryProto;
+import com.android.car.telemetry.publisher.PublisherFactory;
+import com.android.car.telemetry.publisher.StatsManagerProxy;
+import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutor;
+import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutorListener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Collections;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.PriorityBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DataBrokerTest {
+    private static final int PROP_ID = 100;
+    private static final int PROP_AREA = 200;
+    private static final int PRIORITY_HIGH = 1;
+    private static final int PRIORITY_LOW = 100;
+    private static final long TIMEOUT_MS = 5_000L;
+    private static final CarPropertyConfig<Integer> PROP_CONFIG =
+            CarPropertyConfig.newBuilder(Integer.class, PROP_ID, PROP_AREA).setAccess(
+                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ).build();
+    private static final TelemetryProto.VehiclePropertyPublisher
+            VEHICLE_PROPERTY_PUBLISHER_CONFIGURATION =
+            TelemetryProto.VehiclePropertyPublisher.newBuilder().setReadRate(
+                    1).setVehiclePropertyId(PROP_ID).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).setPriority(PRIORITY_HIGH).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).setPriority(PRIORITY_LOW).build();
+    private static final TelemetryProto.MetricsConfig METRICS_CONFIG_BAR =
+            TelemetryProto.MetricsConfig.newBuilder().setName("Bar").setVersion(
+                    1).addSubscribers(SUBSCRIBER_BAR).build();
+
+
+    // when count reaches 0, all handler messages are scheduled to be dispatched after current time
+    private CountDownLatch mIdleHandlerLatch = new CountDownLatch(1);
+    private PersistableBundle mData = new PersistableBundle();
+    private DataBrokerImpl mDataBroker;
+    private FakeScriptExecutor mFakeScriptExecutor;
+    private ScriptExecutionTask mHighPriorityTask;
+    private ScriptExecutionTask mLowPriorityTask;
+
+    @Mock
+    private Context mMockContext;
+    @Mock
+    private CarPropertyService mMockCarPropertyService;
+    @Mock
+    private Handler mMockHandler;
+    @Mock
+    private StatsManagerProxy mMockStatsManager;
+    @Mock
+    private SharedPreferences mMockSharedPreferences;
+    @Mock
+    private IBinder mMockScriptExecutorBinder;
+    @Mock
+    private ResultStore mMockResultStore;
+
+    @Before
+    public void setUp() {
+        when(mMockCarPropertyService.getPropertyList())
+                .thenReturn(Collections.singletonList(PROP_CONFIG));
+        // bind service should return true, otherwise broker is disabled
+        when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true);
+        PublisherFactory factory = new PublisherFactory(
+                mMockCarPropertyService, mMockHandler, mMockStatsManager, mMockSharedPreferences);
+        mDataBroker = new DataBrokerImpl(mMockContext, factory, mMockResultStore);
+        // add IdleHandler to get notified when all messages and posts are handled
+        mDataBroker.getTelemetryHandler().getLooper().getQueue().addIdleHandler(() -> {
+            mIdleHandlerLatch.countDown();
+            return true;
+        });
+
+        mFakeScriptExecutor = new FakeScriptExecutor();
+        when(mMockScriptExecutorBinder.queryLocalInterface(anyString()))
+                .thenReturn(mFakeScriptExecutor);
+        when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(i -> {
+            ServiceConnection conn = i.getArgument(1);
+            conn.onServiceConnected(null, mMockScriptExecutorBinder);
+            return true;
+        });
+
+        mHighPriorityTask = new ScriptExecutionTask(
+                new DataSubscriber(mDataBroker, METRICS_CONFIG_FOO, SUBSCRIBER_FOO),
+                mData,
+                SystemClock.elapsedRealtime());
+        mLowPriorityTask = new ScriptExecutionTask(
+                new DataSubscriber(mDataBroker, METRICS_CONFIG_BAR, SUBSCRIBER_BAR),
+                mData,
+                SystemClock.elapsedRealtime());
+    }
+
+    @Test
+    public void testSetTaskExecutionPriority_whenNoTask_shouldNotInvokeScriptExecutor()
+            throws Exception {
+        mDataBroker.setTaskExecutionPriority(PRIORITY_HIGH);
+
+        waitForHandlerThreadToFinish();
+        assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testSetTaskExecutionPriority_whenNextTaskPriorityLow_shouldNotRunTask()
+            throws Exception {
+        mDataBroker.getTaskQueue().add(mLowPriorityTask);
+
+        mDataBroker.setTaskExecutionPriority(PRIORITY_HIGH);
+
+        waitForHandlerThreadToFinish();
+        // task is not polled
+        assertThat(mDataBroker.getTaskQueue().peek()).isEqualTo(mLowPriorityTask);
+        assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testSetTaskExecutionPriority_whenNextTaskPriorityHigh_shouldInvokeScriptExecutor()
+            throws Exception {
+        mDataBroker.getTaskQueue().add(mHighPriorityTask);
+
+        mDataBroker.setTaskExecutionPriority(PRIORITY_HIGH);
+
+        waitForHandlerThreadToFinish();
+        // task is polled and run
+        assertThat(mDataBroker.getTaskQueue().peek()).isNull();
+        assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void testScheduleNextTask_whenNoTask_shouldNotInvokeScriptExecutor() throws Exception {
+        mDataBroker.scheduleNextTask();
+
+        waitForHandlerThreadToFinish();
+        assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testScheduleNextTask_whenTaskInProgress_shouldNotInvokeScriptExecutorAgain()
+            throws Exception {
+        PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
+        taskQueue.add(mHighPriorityTask);
+        mDataBroker.scheduleNextTask(); // start a task
+        waitForHandlerThreadToFinish();
+        assertThat(taskQueue.peek()).isNull(); // assert that task is polled and running
+        taskQueue.add(mHighPriorityTask); // add another task into the queue
+
+        mDataBroker.scheduleNextTask(); // schedule next task while the last task is in progress
+
+        waitForHandlerThreadToFinish();
+        // verify task is not polled
+        assertThat(taskQueue.peek()).isEqualTo(mHighPriorityTask);
+        // expect one invocation for the task that is running
+        assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void testScheduleNextTask_whenTaskCompletes_shouldAutomaticallyScheduleNextTask()
+            throws Exception {
+        PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
+        // add two tasks into the queue for execution
+        taskQueue.add(mHighPriorityTask);
+        taskQueue.add(mHighPriorityTask);
+
+        mDataBroker.scheduleNextTask(); // start a task
+        waitForHandlerThreadToFinish();
+        // end a task, should automatically schedule the next task
+        mFakeScriptExecutor.notifyScriptSuccess(mData); // posts to telemetry handler
+
+        waitForHandlerThreadToFinish();
+        // verify queue is empty, both tasks are polled and executed
+        assertThat(taskQueue.peek()).isNull();
+        assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(2);
+    }
+
+    @Test
+    public void testScheduleNextTask_onScriptSuccess_shouldStoreInterimResult() throws Exception {
+        mData.putBoolean("script is finished", false);
+        mData.putDouble("value of euler's number", 2.71828);
+        mDataBroker.getTaskQueue().add(mHighPriorityTask);
+
+        mDataBroker.scheduleNextTask();
+        waitForHandlerThreadToFinish();
+        mFakeScriptExecutor.notifyScriptSuccess(mData); // posts to telemetry handler
+
+        waitForHandlerThreadToFinish();
+        assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(1);
+        verify(mMockResultStore).putInterimResult(
+                eq(mHighPriorityTask.getMetricsConfig().getName()), eq(mData));
+    }
+
+    @Test
+    public void testScheduleNextTask_whenScriptFinishes_shouldStoreFinalResult()
+            throws Exception {
+        mData.putBoolean("script is finished", true);
+        mData.putDouble("value of pi", 3.14159265359);
+        mDataBroker.getTaskQueue().add(mHighPriorityTask);
+
+        mDataBroker.scheduleNextTask();
+        waitForHandlerThreadToFinish();
+        mFakeScriptExecutor.notifyScriptFinish(mData); // posts to telemetry handler
+
+        waitForHandlerThreadToFinish();
+        assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(1);
+        verify(mMockResultStore).putFinalResult(
+                eq(mHighPriorityTask.getMetricsConfig().getName()), eq(mData));
+    }
+
+    @Test
+    public void testScheduleNextTask_whenInterimDataExists_shouldPassToScriptExecutor()
+            throws Exception {
+        mData.putDouble("value of golden ratio", 1.618033);
+        mDataBroker.getTaskQueue().add(mHighPriorityTask);
+        when(mMockResultStore.getInterimResult(mHighPriorityTask.getMetricsConfig().getName()))
+                .thenReturn(mData);
+
+        mDataBroker.scheduleNextTask();
+
+        waitForHandlerThreadToFinish();
+        assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(1);
+        assertThat(mFakeScriptExecutor.getSavedState()).isEqualTo(mData);
+    }
+
+    @Test
+    public void testScheduleNextTask_whenBindScriptExecutorFailed_shouldDisableBroker()
+            throws Exception {
+        // fail all future attempts to bind to it
+        Mockito.reset(mMockContext);
+        when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(false);
+        mDataBroker.addMetricsConfiguration(METRICS_CONFIG_FOO);
+        PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
+        taskQueue.add(mHighPriorityTask);
+
+        // will rebind to ScriptExecutor if it is null
+        mDataBroker.scheduleNextTask();
+
+        waitForHandlerThreadToFinish();
+        // all subscribers should have been removed
+        assertThat(mDataBroker.getSubscriptionMap()).hasSize(0);
+        assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testScheduleNextTask_whenScriptExecutorThrowsException_shouldRequeueTask()
+            throws Exception {
+        PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
+        taskQueue.add(mHighPriorityTask);
+        mFakeScriptExecutor.failNextApiCalls(1); // fail the next invokeScript() call
+
+        mDataBroker.scheduleNextTask();
+
+        waitForHandlerThreadToFinish();
+        // expect invokeScript() to be called and failed, causing the same task to be re-queued
+        assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(1);
+        assertThat(taskQueue.peek()).isEqualTo(mHighPriorityTask);
+    }
+
+    @Test
+    public void testAddTaskToQueue_shouldInvokeScriptExecutor() throws Exception {
+        mDataBroker.addTaskToQueue(mHighPriorityTask);
+
+        waitForHandlerThreadToFinish();
+        assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void testAddMetricsConfiguration_newMetricsConfig() {
+        mDataBroker.addMetricsConfiguration(METRICS_CONFIG_BAR);
+
+        assertThat(mDataBroker.getSubscriptionMap()).hasSize(1);
+        assertThat(mDataBroker.getSubscriptionMap()).containsKey(METRICS_CONFIG_BAR.getName());
+        // there should be one data subscriber in the subscription list of METRICS_CONFIG_BAR
+        assertThat(mDataBroker.getSubscriptionMap().get(METRICS_CONFIG_BAR.getName())).hasSize(1);
+    }
+
+
+    @Test
+    public void testAddMetricsConfiguration_duplicateMetricsConfig_shouldDoNothing() {
+        // this metrics config has already been added in setUp()
+        mDataBroker.addMetricsConfiguration(METRICS_CONFIG_FOO);
+
+        assertThat(mDataBroker.getSubscriptionMap()).hasSize(1);
+        assertThat(mDataBroker.getSubscriptionMap()).containsKey(METRICS_CONFIG_FOO.getName());
+        assertThat(mDataBroker.getSubscriptionMap().get(METRICS_CONFIG_FOO.getName())).hasSize(1);
+    }
+
+    @Test
+    public void testRemoveMetricsConfiguration_shouldRemoveAllAssociatedTasks() {
+        mDataBroker.addMetricsConfiguration(METRICS_CONFIG_FOO);
+        mDataBroker.addMetricsConfiguration(METRICS_CONFIG_BAR);
+        ScriptExecutionTask taskWithMetricsConfigFoo = new ScriptExecutionTask(
+                new DataSubscriber(mDataBroker, METRICS_CONFIG_FOO, SUBSCRIBER_FOO),
+                mData,
+                SystemClock.elapsedRealtime());
+        PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
+        taskQueue.add(mHighPriorityTask); // associated with METRICS_CONFIG_FOO
+        taskQueue.add(mLowPriorityTask); // associated with METRICS_CONFIG_BAR
+        taskQueue.add(taskWithMetricsConfigFoo); // associated with METRICS_CONFIG_FOO
+        assertThat(taskQueue).hasSize(3);
+
+        mDataBroker.removeMetricsConfiguration(METRICS_CONFIG_FOO);
+
+        assertThat(taskQueue).hasSize(1);
+        assertThat(taskQueue.poll()).isEqualTo(mLowPriorityTask);
+    }
+
+    @Test
+    public void testRemoveMetricsConfiguration_whenMetricsConfigNonExistent_shouldDoNothing() {
+        mDataBroker.removeMetricsConfiguration(METRICS_CONFIG_BAR);
+
+        assertThat(mDataBroker.getSubscriptionMap()).hasSize(0);
+    }
+
+    private void waitForHandlerThreadToFinish() throws Exception {
+        assertWithMessage("handler not idle in %sms", TIMEOUT_MS)
+                .that(mIdleHandlerLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+        mIdleHandlerLatch = new CountDownLatch(1); // reset idle handler condition
+    }
+
+    private static class FakeScriptExecutor implements IScriptExecutor {
+        private IScriptExecutorListener mListener;
+        private int mApiInvocationCount = 0;
+        private int mFailApi = 0;
+        private PersistableBundle mSavedState = null;
+
+        @Override
+        public void invokeScript(String scriptBody, String functionName,
+                PersistableBundle publishedData, @Nullable PersistableBundle savedState,
+                IScriptExecutorListener listener)
+                throws RemoteException {
+            mApiInvocationCount++;
+            mSavedState = savedState;
+            mListener = listener;
+            if (mFailApi > 0) {
+                mFailApi--;
+                throw new RemoteException("Simulated failure");
+            }
+        }
+
+        @Override
+        public IBinder asBinder() {
+            return null;
+        }
+
+        /** Mocks script temporary completion. */
+        public void notifyScriptSuccess(PersistableBundle bundle) {
+            try {
+                mListener.onSuccess(bundle);
+            } catch (RemoteException e) {
+                // nothing to do
+            }
+        }
+
+        /** Mocks script producing final result. */
+        public void notifyScriptFinish(PersistableBundle bundle) {
+            try {
+                mListener.onScriptFinished(bundle);
+            } catch (RemoteException e) {
+                // nothing to do
+            }
+        }
+
+        /** Fails the next N invokeScript() call. */
+        public void failNextApiCalls(int n) {
+            mFailApi = n;
+        }
+
+        /** Returns number of times the ScriptExecutor API was invoked. */
+        public int getApiInvocationCount() {
+            return mApiInvocationCount;
+        }
+
+        /** Returns the interim data passed in invokeScript(). */
+        public PersistableBundle getSavedState() {
+            return mSavedState;
+        }
+    }
+}
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
deleted file mode 100644
index 45c5a22..0000000
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerUnitTest.java
+++ /dev/null
@@ -1,110 +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.databroker;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.when;
-
-import android.car.hardware.CarPropertyConfig;
-
-import com.android.car.CarPropertyService;
-import com.android.car.telemetry.TelemetryProto;
-import com.android.car.telemetry.publisher.PublisherFactory;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.util.Collections;
-
-@RunWith(MockitoJUnitRunner.class)
-public class DataBrokerUnitTest {
-    private static final int PROP_ID = 100;
-    private static final int PROP_AREA = 200;
-    private static final CarPropertyConfig<Integer> PROP_CONFIG =
-            CarPropertyConfig.newBuilder(Integer.class, PROP_ID, PROP_AREA).setAccess(
-                    CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ).build();
-    private static final TelemetryProto.VehiclePropertyPublisher
-            VEHICLE_PROPERTY_PUBLISHER_CONFIGURATION =
-            TelemetryProto.VehiclePropertyPublisher.newBuilder().setReadRate(
-                    1).setVehiclePropertyId(PROP_ID).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();
-
-    @Mock
-    private CarPropertyService mMockCarPropertyService;
-
-    private DataBrokerImpl mDataBroker;
-
-    @Before
-    public void setUp() {
-        when(mMockCarPropertyService.getPropertyList())
-                .thenReturn(Collections.singletonList(PROP_CONFIG));
-        PublisherFactory factory = new PublisherFactory(mMockCarPropertyService);
-        mDataBroker = new DataBrokerImpl(factory);
-    }
-
-    @Test
-    public void testAddMetricsConfiguration_newMetricsConfig() {
-        mDataBroker.addMetricsConfiguration(METRICS_CONFIG_FOO);
-
-        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.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_removeNonexistentMetricsConfig() {
-        boolean status = mDataBroker.removeMetricsConfiguration(METRICS_CONFIG_FOO);
-
-        assertThat(status).isFalse();
-    }
-}
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/CarTelemetrydPublisherTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/CarTelemetrydPublisherTest.java
new file mode 100644
index 0000000..bc3cd47
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/CarTelemetrydPublisherTest.java
@@ -0,0 +1,220 @@
+/*
+ * 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.publisher;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.automotive.telemetry.internal.ICarDataListener;
+import android.automotive.telemetry.internal.ICarTelemetryInternal;
+import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import com.android.car.telemetry.TelemetryProto;
+import com.android.car.telemetry.databroker.DataSubscriber;
+import com.android.car.test.FakeHandlerWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CarTelemetrydPublisherTest extends AbstractExtendedMockitoTestCase {
+    private static final String SERVICE_NAME = ICarTelemetryInternal.DESCRIPTOR + "/default";
+    private static final int CAR_DATA_ID_1 = 1;
+    private static final TelemetryProto.Publisher PUBLISHER_PARAMS_1 =
+            TelemetryProto.Publisher.newBuilder()
+                    .setCartelemetryd(TelemetryProto.CarTelemetrydPublisher.newBuilder()
+                            .setId(CAR_DATA_ID_1))
+                    .build();
+
+    private final FakeHandlerWrapper mFakeHandlerWrapper =
+            new FakeHandlerWrapper(Looper.getMainLooper(), FakeHandlerWrapper.Mode.IMMEDIATE);
+
+    @Mock private IBinder mMockBinder;
+    @Mock private DataSubscriber mMockDataSubscriber;
+
+    @Captor private ArgumentCaptor<IBinder.DeathRecipient> mLinkToDeathCallbackCaptor;
+
+    @Nullable private Throwable mPublisherFailure;
+    private FakeCarTelemetryInternal mFakeCarTelemetryInternal;
+    private CarTelemetrydPublisher mPublisher;
+
+    @Before
+    public void setUp() throws Exception {
+        mPublisher = new CarTelemetrydPublisher(
+                this::onPublisherFailure, mFakeHandlerWrapper.getMockHandler());
+        mFakeCarTelemetryInternal = new FakeCarTelemetryInternal(mMockBinder);
+        when(mMockDataSubscriber.getPublisherParam()).thenReturn(PUBLISHER_PARAMS_1);
+        when(mMockBinder.queryLocalInterface(any())).thenReturn(mFakeCarTelemetryInternal);
+        doNothing().when(mMockBinder).linkToDeath(mLinkToDeathCallbackCaptor.capture(), anyInt());
+        doReturn(mMockBinder).when(() -> ServiceManager.checkService(SERVICE_NAME));
+    }
+
+    @Override
+    protected void onSessionBuilder(CustomMockitoSessionBuilder builder) {
+        builder.spyStatic(ServiceManager.class);
+    }
+
+    @Test
+    public void testAddDataSubscriber_registersNewListener() {
+        mPublisher.addDataSubscriber(mMockDataSubscriber);
+
+        assertThat(mFakeCarTelemetryInternal.mListener).isNotNull();
+        assertThat(mPublisher.isConnectedToCarTelemetryd()).isTrue();
+        assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isTrue();
+    }
+
+    @Test
+    public void testAddDataSubscriber_withInvalidId_fails() {
+        DataSubscriber invalidDataSubscriber = Mockito.mock(DataSubscriber.class);
+        when(invalidDataSubscriber.getPublisherParam()).thenReturn(
+                TelemetryProto.Publisher.newBuilder()
+                        .setCartelemetryd(TelemetryProto.CarTelemetrydPublisher.newBuilder()
+                                .setId(42000))  // invalid ID
+                        .build());
+
+        Throwable error = assertThrows(IllegalArgumentException.class,
+                () -> mPublisher.addDataSubscriber(invalidDataSubscriber));
+
+        assertThat(error).hasMessageThat().contains("Invalid CarData ID");
+        assertThat(mFakeCarTelemetryInternal.mListener).isNull();
+        assertThat(mPublisher.isConnectedToCarTelemetryd()).isFalse();
+        assertThat(mPublisher.hasDataSubscriber(invalidDataSubscriber)).isFalse();
+    }
+
+    @Test
+    public void testRemoveDataSubscriber_ignoresIfNotFound() {
+        mPublisher.removeDataSubscriber(mMockDataSubscriber);
+    }
+
+    @Test
+    public void testRemoveDataSubscriber_removesOnlySingleSubscriber() {
+        DataSubscriber subscriber2 = Mockito.mock(DataSubscriber.class);
+        when(subscriber2.getPublisherParam()).thenReturn(PUBLISHER_PARAMS_1);
+        mPublisher.addDataSubscriber(mMockDataSubscriber);
+        mPublisher.addDataSubscriber(subscriber2);
+
+        mPublisher.removeDataSubscriber(subscriber2);
+
+        assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isTrue();
+        assertThat(mPublisher.hasDataSubscriber(subscriber2)).isFalse();
+        assertThat(mFakeCarTelemetryInternal.mListener).isNotNull();
+    }
+
+    @Test
+    public void testRemoveDataSubscriber_disconnectsFromICarTelemetry() {
+        mPublisher.addDataSubscriber(mMockDataSubscriber);
+
+        mPublisher.removeDataSubscriber(mMockDataSubscriber);
+
+        assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isFalse();
+        assertThat(mFakeCarTelemetryInternal.mListener).isNull();
+    }
+
+    @Test
+    public void testRemoveAllDataSubscribers_succeeds() {
+        DataSubscriber subscriber2 = Mockito.mock(DataSubscriber.class);
+        when(subscriber2.getPublisherParam()).thenReturn(PUBLISHER_PARAMS_1);
+        mPublisher.addDataSubscriber(mMockDataSubscriber);
+        mPublisher.addDataSubscriber(subscriber2);
+
+        mPublisher.removeAllDataSubscribers();
+
+        assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isFalse();
+        assertThat(mPublisher.hasDataSubscriber(subscriber2)).isFalse();
+        assertThat(mFakeCarTelemetryInternal.mListener).isNull();
+    }
+
+    @Test
+    public void testNotifiesFailureConsumer_whenBinderDies() {
+        mPublisher.addDataSubscriber(mMockDataSubscriber);
+
+        mLinkToDeathCallbackCaptor.getValue().binderDied();
+
+        assertThat(mFakeCarTelemetryInternal.mSetListenerCallCount).isEqualTo(1);
+        assertThat(mPublisherFailure).hasMessageThat()
+                .contains("ICarTelemetryInternal binder died");
+    }
+
+    @Test
+    public void testNotifiesFailureConsumer_whenFailsConnectToService() {
+        mFakeCarTelemetryInternal.setApiFailure(new RemoteException("tough life"));
+
+        mPublisher.addDataSubscriber(mMockDataSubscriber);
+
+        assertThat(mPublisherFailure).hasMessageThat()
+                .contains("Cannot set CarData listener");
+    }
+
+    private void onPublisherFailure(AbstractPublisher publisher, Throwable error) {
+        mPublisherFailure = error;
+    }
+
+    private static class FakeCarTelemetryInternal implements ICarTelemetryInternal {
+        @Nullable ICarDataListener mListener;
+        int mSetListenerCallCount = 0;
+        private final IBinder mBinder;
+        @Nullable private RemoteException mApiFailure = null;
+
+        FakeCarTelemetryInternal(IBinder binder) {
+            mBinder = binder;
+        }
+
+        @Override
+        public IBinder asBinder() {
+            return mBinder;
+        }
+
+        @Override
+        public void setListener(ICarDataListener listener) throws RemoteException {
+            mSetListenerCallCount += 1;
+            if (mApiFailure != null) {
+                throw mApiFailure;
+            }
+            mListener = listener;
+        }
+
+        @Override
+        public void clearListener() throws RemoteException {
+            if (mApiFailure != null) {
+                throw mApiFailure;
+            }
+            mListener = null;
+        }
+
+        void setApiFailure(RemoteException e) {
+            mApiFailure = e;
+        }
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/HashUtilsTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/HashUtilsTest.java
new file mode 100644
index 0000000..1be835d
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/HashUtilsTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.publisher;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class HashUtilsTest {
+    @Test
+    public void testSha256() {
+        assertThat(HashUtils.sha256("")).isEqualTo(1449310910991872227L);
+        assertThat(HashUtils.sha256("a")).isEqualTo(-3837880752741967926L);
+        assertThat(HashUtils.sha256("aa")).isEqualTo(-8157175689457624170L);
+        assertThat(HashUtils.sha256("b")).isEqualTo(5357375904281011006L);
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/StatsPublisherTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/StatsPublisherTest.java
new file mode 100644
index 0000000..a672bd5
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/StatsPublisherTest.java
@@ -0,0 +1,274 @@
+/*
+ * 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.publisher;
+
+import static com.android.car.telemetry.TelemetryProto.StatsPublisher.SystemMetric.APP_START_MEMORY_STATE_CAPTURED;
+import static com.android.car.telemetry.publisher.StatsPublisher.APP_START_MEMORY_STATE_CAPTURED_ATOM_MATCHER_ID;
+import static com.android.car.telemetry.publisher.StatsPublisher.APP_START_MEMORY_STATE_CAPTURED_EVENT_METRIC_ID;
+import static com.android.car.telemetry.publisher.StatsPublisher.ATOM_APP_START_MEMORY_STATE_CAPTURED_ID;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.StatsManager;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.SystemClock;
+
+import com.android.car.telemetry.StatsLogProto;
+import com.android.car.telemetry.StatsdConfigProto;
+import com.android.car.telemetry.TelemetryProto;
+import com.android.car.telemetry.databroker.DataSubscriber;
+import com.android.car.test.FakeHandlerWrapper;
+import com.android.car.test.FakeSharedPreferences;
+
+import com.google.common.collect.Range;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class StatsPublisherTest {
+    private static final TelemetryProto.Publisher STATS_PUBLISHER_PARAMS_1 =
+            TelemetryProto.Publisher.newBuilder()
+                    .setStats(TelemetryProto.StatsPublisher.newBuilder()
+                            .setSystemMetric(APP_START_MEMORY_STATE_CAPTURED))
+                    .build();
+    private static final TelemetryProto.Subscriber SUBSCRIBER_1 =
+            TelemetryProto.Subscriber.newBuilder()
+                    .setHandler("handler_fn_1")
+                    .setPublisher(STATS_PUBLISHER_PARAMS_1)
+                    .build();
+    private static final TelemetryProto.MetricsConfig METRICS_CONFIG =
+            TelemetryProto.MetricsConfig.newBuilder()
+                    .setName("myconfig")
+                    .setVersion(1)
+                    .addSubscribers(SUBSCRIBER_1)
+                    .build();
+
+    private static final long SUBSCRIBER_1_HASH = -8101507323446050791L;  // Used as ID.
+
+    private static final StatsdConfigProto.StatsdConfig STATSD_CONFIG_1 =
+            StatsdConfigProto.StatsdConfig.newBuilder()
+                    .setId(SUBSCRIBER_1_HASH)
+                    .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
+                            .setId(APP_START_MEMORY_STATE_CAPTURED_ATOM_MATCHER_ID)
+                            .setSimpleAtomMatcher(
+                                    StatsdConfigProto.SimpleAtomMatcher.newBuilder()
+                                            .setAtomId(
+                                                    ATOM_APP_START_MEMORY_STATE_CAPTURED_ID)))
+                    .addEventMetric(StatsdConfigProto.EventMetric.newBuilder()
+                            .setId(APP_START_MEMORY_STATE_CAPTURED_EVENT_METRIC_ID)
+                            .setWhat(APP_START_MEMORY_STATE_CAPTURED_ATOM_MATCHER_ID))
+                    .addAllowedLogSource("AID_SYSTEM")
+                    .build();
+
+    private static final StatsLogProto.ConfigMetricsReportList EMPTY_STATS_REPORT =
+            StatsLogProto.ConfigMetricsReportList.newBuilder().build();
+
+    private StatsPublisher mPublisher;  // subject
+    private final FakeSharedPreferences mFakeSharedPref = new FakeSharedPreferences();
+    private final FakeHandlerWrapper mFakeHandlerWrapper =
+            new FakeHandlerWrapper(Looper.getMainLooper(), FakeHandlerWrapper.Mode.QUEUEING);
+    private Throwable mPublisherFailure;
+
+    @Mock private DataSubscriber mMockDataSubscriber;
+    @Mock private StatsManagerProxy mStatsManager;
+
+    @Captor private ArgumentCaptor<PersistableBundle> mBundleCaptor;
+
+    @Before
+    public void setUp() throws Exception {
+        mPublisher = createRestartedPublisher();
+        when(mMockDataSubscriber.getPublisherParam()).thenReturn(STATS_PUBLISHER_PARAMS_1);
+        when(mMockDataSubscriber.getMetricsConfig()).thenReturn(METRICS_CONFIG);
+        when(mMockDataSubscriber.getSubscriber()).thenReturn(SUBSCRIBER_1);
+    }
+
+    /**
+     * Emulates a restart by creating a new StatsPublisher. StatsManager and SharedPreference
+     * stays the same.
+     */
+    private StatsPublisher createRestartedPublisher() {
+        return new StatsPublisher(
+                this::onPublisherFailure,
+                mStatsManager,
+                mFakeSharedPref,
+                mFakeHandlerWrapper.getMockHandler());
+    }
+
+    @Test
+    public void testAddDataSubscriber_registersNewListener() throws Exception {
+        mPublisher.addDataSubscriber(mMockDataSubscriber);
+
+        verify(mStatsManager, times(1))
+                .addConfig(SUBSCRIBER_1_HASH, STATSD_CONFIG_1.toByteArray());
+        assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isTrue();
+    }
+
+    @Test
+    public void testAddDataSubscriber_sameVersion_addsToStatsdOnce() throws Exception {
+        mPublisher.addDataSubscriber(mMockDataSubscriber);
+        mPublisher.addDataSubscriber(mMockDataSubscriber);
+
+        verify(mStatsManager, times(1))
+                .addConfig(SUBSCRIBER_1_HASH, STATSD_CONFIG_1.toByteArray());
+        assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isTrue();
+    }
+
+    @Test
+    public void testAddDataSubscriber_whenRestarted_addsToStatsdOnce() throws Exception {
+        mPublisher.addDataSubscriber(mMockDataSubscriber);
+        StatsPublisher publisher2 = createRestartedPublisher();
+
+        publisher2.addDataSubscriber(mMockDataSubscriber);
+
+        verify(mStatsManager, times(1))
+                .addConfig(SUBSCRIBER_1_HASH, STATSD_CONFIG_1.toByteArray());
+        assertThat(publisher2.hasDataSubscriber(mMockDataSubscriber)).isTrue();
+    }
+
+    @Test
+    public void testRemoveDataSubscriber_removesFromStatsd() throws Exception {
+        mPublisher.addDataSubscriber(mMockDataSubscriber);
+
+        mPublisher.removeDataSubscriber(mMockDataSubscriber);
+
+        verify(mStatsManager, times(1)).removeConfig(SUBSCRIBER_1_HASH);
+        assertThat(mFakeSharedPref.getAll().isEmpty()).isTrue();  // also removes from SharedPref.
+        assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isFalse();
+    }
+
+    @Test
+    public void testRemoveDataSubscriber_ifNotFound_nothingHappensButCallsStatsdRemove()
+            throws Exception {
+        mPublisher.removeDataSubscriber(mMockDataSubscriber);
+
+        // It should try removing StatsdConfig from StatsD, in case it was added there before and
+        // left dangled.
+        verify(mStatsManager, times(1)).removeConfig(SUBSCRIBER_1_HASH);
+        assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isFalse();
+    }
+
+    @Test
+    public void testRemoveAllDataSubscriber_whenRestarted_removesFromStatsdAndClears()
+            throws Exception {
+        mPublisher.addDataSubscriber(mMockDataSubscriber);
+        StatsPublisher publisher2 = createRestartedPublisher();
+
+        publisher2.removeAllDataSubscribers();
+
+        verify(mStatsManager, times(1)).removeConfig(SUBSCRIBER_1_HASH);
+        assertThat(mFakeSharedPref.getAll().isEmpty()).isTrue();  // also removes from SharedPref.
+        assertThat(publisher2.hasDataSubscriber(mMockDataSubscriber)).isFalse();
+    }
+
+    @Test
+    public void testAddDataSubscriber_queuesPeriodicTaskInTheHandler() {
+        mPublisher.addDataSubscriber(mMockDataSubscriber);
+
+        assertThat(mFakeHandlerWrapper.getQueuedMessages()).hasSize(1);
+        Message msg = mFakeHandlerWrapper.getQueuedMessages().get(0);
+        long expectedPullPeriodMillis = 10 * 60 * 1000;  // 10 minutes
+        assertThatMessageIsScheduledWithGivenDelay(msg, expectedPullPeriodMillis);
+    }
+
+    @Test
+    public void testAddDataSubscriber_whenFails_notifiesFailureConsumer() throws Exception {
+        doThrow(new StatsManager.StatsUnavailableException("fail"))
+                .when(mStatsManager).addConfig(anyLong(), any());
+
+        mPublisher.addDataSubscriber(mMockDataSubscriber);
+
+        assertThat(mPublisherFailure).hasMessageThat().contains("Failed to add config");
+    }
+
+    @Test
+    public void testRemoveDataSubscriber_removesPeriodicStatsdReportPull() {
+        mPublisher.addDataSubscriber(mMockDataSubscriber);
+
+        mPublisher.removeDataSubscriber(mMockDataSubscriber);
+
+        assertThat(mFakeHandlerWrapper.getQueuedMessages()).isEmpty();
+    }
+
+    @Test
+    public void testRemoveAllDataSubscriber_removesPeriodicStatsdReportPull() {
+        mPublisher.addDataSubscriber(mMockDataSubscriber);
+
+        mPublisher.removeAllDataSubscribers();
+
+        assertThat(mFakeHandlerWrapper.getQueuedMessages()).isEmpty();
+    }
+
+    @Test
+    public void testAfterDispatchItSchedulesANewPullReportTask() throws Exception {
+        mPublisher.addDataSubscriber(mMockDataSubscriber);
+        Message firstMessage = mFakeHandlerWrapper.getQueuedMessages().get(0);
+        when(mStatsManager.getReports(anyLong())).thenReturn(EMPTY_STATS_REPORT.toByteArray());
+
+        mFakeHandlerWrapper.dispatchQueuedMessages();
+
+        assertThat(mFakeHandlerWrapper.getQueuedMessages()).hasSize(1);
+        Message newMessage = mFakeHandlerWrapper.getQueuedMessages().get(0);
+        assertThat(newMessage).isNotEqualTo(firstMessage);
+        long expectedPullPeriodMillis = 10 * 60 * 1000;  // 10 minutes
+        assertThatMessageIsScheduledWithGivenDelay(newMessage, expectedPullPeriodMillis);
+    }
+
+    @Test
+    public void testPullsStatsdReport() throws Exception {
+        mPublisher.addDataSubscriber(mMockDataSubscriber);
+        when(mStatsManager.getReports(anyLong())).thenReturn(
+                StatsLogProto.ConfigMetricsReportList.newBuilder()
+                        // add 2 empty reports
+                        .addReports(StatsLogProto.ConfigMetricsReport.newBuilder())
+                        .addReports(StatsLogProto.ConfigMetricsReport.newBuilder())
+                        .build().toByteArray());
+
+        mFakeHandlerWrapper.dispatchQueuedMessages();
+
+        verify(mMockDataSubscriber).push(mBundleCaptor.capture());
+        assertThat(mBundleCaptor.getValue().getInt("reportsCount")).isEqualTo(2);
+    }
+
+    // TODO(b/189143813): add test cases when connecting to Statsd fails
+    // TODO(b/189143813): add test cases for handling config version upgrades
+
+    private void onPublisherFailure(AbstractPublisher publisher, Throwable error) {
+        mPublisherFailure = error;
+    }
+
+    private static void assertThatMessageIsScheduledWithGivenDelay(Message msg, long delayMillis) {
+        long expectedTimeMillis = SystemClock.uptimeMillis() + delayMillis;
+        long deltaMillis = 1000;  // +/- 1 seconds is good enough for testing
+        assertThat(msg.getWhen()).isIn(Range
+                .closed(expectedTimeMillis - deltaMillis, expectedTimeMillis + deltaMillis));
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/VehiclePropertyPublisherTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/VehiclePropertyPublisherTest.java
index bcd408a..508120f 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/VehiclePropertyPublisherTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/VehiclePropertyPublisherTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -34,11 +35,13 @@
 import android.car.hardware.CarPropertyValue;
 import android.car.hardware.property.CarPropertyEvent;
 import android.car.hardware.property.ICarPropertyEventListener;
-import android.os.Bundle;
+import android.os.Looper;
+import android.os.PersistableBundle;
 
 import com.android.car.CarPropertyService;
 import com.android.car.telemetry.TelemetryProto;
 import com.android.car.telemetry.databroker.DataSubscriber;
+import com.android.car.test.FakeHandlerWrapper;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -74,7 +77,7 @@
                             .setVehiclePropertyId(-200))
                     .build();
 
-    // mMockCarPropertyService supported car property list.
+    // CarPropertyConfigs for mMockCarPropertyService.
     private static final CarPropertyConfig<Integer> PROP_CONFIG_1 =
             CarPropertyConfig.newBuilder(Integer.class, PROP_ID_1, AREA_ID).setAccess(
                     CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ).build();
@@ -82,16 +85,18 @@
             CarPropertyConfig.newBuilder(Integer.class, PROP_ID_2, AREA_ID).setAccess(
                     CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE).build();
 
+    private final FakeHandlerWrapper mFakeHandlerWrapper =
+            new FakeHandlerWrapper(Looper.getMainLooper(), FakeHandlerWrapper.Mode.IMMEDIATE);
+
     @Mock
     private DataSubscriber mMockDataSubscriber;
-
     @Mock
     private CarPropertyService mMockCarPropertyService;
 
     @Captor
     private ArgumentCaptor<ICarPropertyEventListener> mCarPropertyCallbackCaptor;
     @Captor
-    private ArgumentCaptor<Bundle> mBundleCaptor;
+    private ArgumentCaptor<PersistableBundle> mBundleCaptor;
 
     private VehiclePropertyPublisher mVehiclePropertyPublisher;
 
@@ -100,7 +105,10 @@
         when(mMockDataSubscriber.getPublisherParam()).thenReturn(PUBLISHER_PARAMS_1);
         when(mMockCarPropertyService.getPropertyList())
                 .thenReturn(List.of(PROP_CONFIG_1, PROP_CONFIG_2_WRITE_ONLY));
-        mVehiclePropertyPublisher = new VehiclePropertyPublisher(mMockCarPropertyService);
+        mVehiclePropertyPublisher = new VehiclePropertyPublisher(
+                mMockCarPropertyService,
+                this::onPublisherFailure,
+                mFakeHandlerWrapper.getMockHandler());
     }
 
     @Test
@@ -108,7 +116,21 @@
         mVehiclePropertyPublisher.addDataSubscriber(mMockDataSubscriber);
 
         verify(mMockCarPropertyService).registerListener(eq(PROP_ID_1), eq(PROP_READ_RATE), any());
-        assertThat(mVehiclePropertyPublisher.getDataSubscribers()).hasSize(1);
+        assertThat(mVehiclePropertyPublisher.hasDataSubscriber(mMockDataSubscriber)).isTrue();
+    }
+
+    @Test
+    public void testAddDataSubscriber_withSamePropertyId_registersSingleListener() {
+        DataSubscriber subscriber2 = mock(DataSubscriber.class);
+        when(subscriber2.getPublisherParam()).thenReturn(PUBLISHER_PARAMS_1);
+
+        mVehiclePropertyPublisher.addDataSubscriber(mMockDataSubscriber);
+        mVehiclePropertyPublisher.addDataSubscriber(subscriber2);
+
+        verify(mMockCarPropertyService, times(1))
+                .registerListener(eq(PROP_ID_1), eq(PROP_READ_RATE), any());
+        assertThat(mVehiclePropertyPublisher.hasDataSubscriber(mMockDataSubscriber)).isTrue();
+        assertThat(mVehiclePropertyPublisher.hasDataSubscriber(subscriber2)).isTrue();
     }
 
     @Test
@@ -125,7 +147,7 @@
                 () -> mVehiclePropertyPublisher.addDataSubscriber(invalidDataSubscriber));
 
         assertThat(error).hasMessageThat().contains("No access.");
-        assertThat(mVehiclePropertyPublisher.getDataSubscribers()).isEmpty();
+        assertThat(mVehiclePropertyPublisher.hasDataSubscriber(mMockDataSubscriber)).isFalse();
     }
 
     @Test
@@ -137,7 +159,7 @@
                 () -> mVehiclePropertyPublisher.addDataSubscriber(invalidDataSubscriber));
 
         assertThat(error).hasMessageThat().contains("not found");
-        assertThat(mVehiclePropertyPublisher.getDataSubscribers()).isEmpty();
+        assertThat(mVehiclePropertyPublisher.hasDataSubscriber(mMockDataSubscriber)).isFalse();
     }
 
     @Test
@@ -145,21 +167,28 @@
         mVehiclePropertyPublisher.addDataSubscriber(mMockDataSubscriber);
 
         mVehiclePropertyPublisher.removeDataSubscriber(mMockDataSubscriber);
-        // TODO(b/189143814): add proper verification
+
+        verify(mMockCarPropertyService, times(1)).unregisterListener(eq(PROP_ID_1), any());
+        assertThat(mVehiclePropertyPublisher.hasDataSubscriber(mMockDataSubscriber)).isFalse();
     }
 
     @Test
-    public void testRemoveDataSubscriber_failsIfNotFound() {
-        Throwable error = assertThrows(IllegalArgumentException.class,
-                () -> mVehiclePropertyPublisher.removeDataSubscriber(mMockDataSubscriber));
-
-        assertThat(error).hasMessageThat().contains("subscriber not found");
+    public void testRemoveDataSubscriber_ignoresIfNotFound() {
+        mVehiclePropertyPublisher.removeDataSubscriber(mMockDataSubscriber);
     }
 
     @Test
     public void testRemoveAllDataSubscribers_succeeds() {
+        DataSubscriber subscriber2 = mock(DataSubscriber.class);
+        when(subscriber2.getPublisherParam()).thenReturn(PUBLISHER_PARAMS_1);
+        mVehiclePropertyPublisher.addDataSubscriber(mMockDataSubscriber);
+        mVehiclePropertyPublisher.addDataSubscriber(subscriber2);
+
         mVehiclePropertyPublisher.removeAllDataSubscribers();
-        // TODO(b/189143814): add tests
+
+        assertThat(mVehiclePropertyPublisher.hasDataSubscriber(mMockDataSubscriber)).isFalse();
+        assertThat(mVehiclePropertyPublisher.hasDataSubscriber(subscriber2)).isFalse();
+        verify(mMockCarPropertyService, times(1)).unregisterListener(eq(PROP_ID_1), any());
     }
 
     @Test
@@ -171,8 +200,9 @@
         mCarPropertyCallbackCaptor.getValue().onEvent(Collections.singletonList(PROP_EVENT_1));
 
         verify(mMockDataSubscriber).push(mBundleCaptor.capture());
-        CarPropertyEvent event = mBundleCaptor.getValue().getParcelable(
-                VehiclePropertyPublisher.CAR_PROPERTY_EVENT_KEY);
-        assertThat(event).isEqualTo(PROP_EVENT_1);
+        // TODO(b/197269115): add more assertions on the contents of
+        // PersistableBundle object.
     }
+
+    private void onPublisherFailure(AbstractPublisher publisher, Throwable error) { }
 }
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/systemmonitor/SystemMonitorUnitTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/systemmonitor/SystemMonitorUnitTest.java
new file mode 100644
index 0000000..09591be
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/systemmonitor/SystemMonitorUnitTest.java
@@ -0,0 +1,209 @@
+/*
+ * 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.systemmonitor;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.MemoryInfo;
+import android.content.Context;
+import android.os.Handler;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+@RunWith(MockitoJUnitRunner.class)
+public class SystemMonitorUnitTest {
+
+    @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+    private static final String TEST_LOADAVG = "1.2 3.4 2.2 123/1452 21348";
+    private static final String TEST_LOADAVG_BAD_FORMAT = "1.2 3.4";
+    private static final String TEST_LOADAVG_NOT_FLOAT = "1.2 abc 2.1 12/231 2";
+    private static final long TEST_AVAILMEM = 3_000_000_000L;
+    private static final long TEST_TOTALMEM = 8_000_000_000L;
+
+    @Mock private Context mMockContext;
+    @Mock private Handler mMockHandler; // it promptly executes the runnable in the same thread
+    @Mock private ActivityManager mMockActivityManager;
+    @Mock private SystemMonitor.SystemMonitorCallback mMockCallback;
+
+    @Captor ArgumentCaptor<Runnable> mRunnableCaptor;
+    @Captor ArgumentCaptor<SystemMonitorEvent> mEventCaptor;
+
+    @Before
+    public void setup() {
+        when(mMockContext.getSystemService(anyString())).thenReturn(mMockActivityManager);
+        when(mMockHandler.post(any(Runnable.class))).thenAnswer(i -> {
+            Runnable runnable = i.getArgument(0);
+            runnable.run();
+            return true;
+        });
+        doAnswer(i -> {
+            MemoryInfo mi = i.getArgument(0);
+            mi.availMem = TEST_AVAILMEM;
+            mi.totalMem = TEST_TOTALMEM;
+            return null;
+        }).when(mMockActivityManager).getMemoryInfo(any(MemoryInfo.class));
+    }
+
+    @Test
+    public void testSetEventCpuUsageLevel_setsCorrectUsageLevelForHighUsage() {
+        SystemMonitor systemMonitor = SystemMonitor.create(mMockContext, mMockHandler);
+        SystemMonitorEvent event = new SystemMonitorEvent();
+
+        systemMonitor.setEventCpuUsageLevel(event, /* cpuLoadPerCore= */ 1.5);
+
+        assertThat(event.getCpuUsageLevel())
+            .isEqualTo(SystemMonitorEvent.USAGE_LEVEL_HI);
+    }
+
+    @Test
+    public void testSetEventCpuUsageLevel_setsCorrectUsageLevelForMedUsage() {
+        SystemMonitor systemMonitor = SystemMonitor.create(mMockContext, mMockHandler);
+        SystemMonitorEvent event = new SystemMonitorEvent();
+
+        systemMonitor.setEventCpuUsageLevel(event, /* cpuLoadPerCore= */ 0.6);
+
+        assertThat(event.getCpuUsageLevel())
+            .isEqualTo(SystemMonitorEvent.USAGE_LEVEL_MED);
+    }
+
+    @Test
+    public void testSetEventCpuUsageLevel_setsCorrectUsageLevelForLowUsage() {
+        SystemMonitor systemMonitor = SystemMonitor.create(mMockContext, mMockHandler);
+        SystemMonitorEvent event = new SystemMonitorEvent();
+
+        systemMonitor.setEventCpuUsageLevel(event, /* cpuLoadPerCore= */ 0.5);
+
+        assertThat(event.getCpuUsageLevel())
+            .isEqualTo(SystemMonitorEvent.USAGE_LEVEL_LOW);
+    }
+
+    @Test
+    public void testSetEventMemUsageLevel_setsCorrectUsageLevelForHighUsage() {
+        SystemMonitor systemMonitor = SystemMonitor.create(mMockContext, mMockHandler);
+        SystemMonitorEvent event = new SystemMonitorEvent();
+
+        systemMonitor.setEventMemUsageLevel(event, /* memLoadRatio= */ 0.98);
+
+        assertThat(event.getMemoryUsageLevel())
+            .isEqualTo(SystemMonitorEvent.USAGE_LEVEL_HI);
+    }
+
+    @Test
+    public void testSetEventMemUsageLevel_setsCorrectUsageLevelForMedUsage() {
+        SystemMonitor systemMonitor = SystemMonitor.create(mMockContext, mMockHandler);
+        SystemMonitorEvent event = new SystemMonitorEvent();
+
+        systemMonitor.setEventMemUsageLevel(event, /* memLoadRatio= */ 0.85);
+
+        assertThat(event.getMemoryUsageLevel())
+            .isEqualTo(SystemMonitorEvent.USAGE_LEVEL_MED);
+    }
+
+    @Test
+    public void testSetEventMemUsageLevel_setsCorrectUsageLevelForLowUsage() {
+        SystemMonitor systemMonitor = SystemMonitor.create(mMockContext, mMockHandler);
+        SystemMonitorEvent event = new SystemMonitorEvent();
+
+        systemMonitor.setEventMemUsageLevel(event, /* memLoadRatio= */ 0.80);
+
+        assertThat(event.getMemoryUsageLevel())
+            .isEqualTo(SystemMonitorEvent.USAGE_LEVEL_LOW);
+    }
+
+    @Test
+    public void testAfterSetCallback_callbackCalled() throws IOException {
+        SystemMonitor systemMonitor = new SystemMonitor(
+                mMockContext, mMockHandler, writeTempFile(TEST_LOADAVG));
+
+        systemMonitor.setSystemMonitorCallback(mMockCallback);
+
+        verify(mMockCallback, atLeastOnce()).onSystemMonitorEvent(mEventCaptor.capture());
+        SystemMonitorEvent event = mEventCaptor.getValue();
+        assertThat(event.getCpuUsageLevel()).isAnyOf(
+                SystemMonitorEvent.USAGE_LEVEL_LOW,
+                SystemMonitorEvent.USAGE_LEVEL_MED,
+                SystemMonitorEvent.USAGE_LEVEL_HI);
+        assertThat(event.getMemoryUsageLevel()).isAnyOf(
+                SystemMonitorEvent.USAGE_LEVEL_LOW,
+                SystemMonitorEvent.USAGE_LEVEL_MED,
+                SystemMonitorEvent.USAGE_LEVEL_HI);
+    }
+
+    @Test
+    public void testWhenLoadavgIsBadFormat_getCpuLoadReturnsNull() throws IOException {
+        SystemMonitor systemMonitor = new SystemMonitor(
+                mMockContext, mMockHandler, writeTempFile(TEST_LOADAVG_BAD_FORMAT));
+
+        assertThat(systemMonitor.getCpuLoad()).isNull();
+    }
+
+    @Test
+    public void testWhenLoadavgIsNotFloatParsable_getCpuLoadReturnsNull() throws IOException {
+        SystemMonitor systemMonitor = new SystemMonitor(
+                mMockContext, mMockHandler, writeTempFile(TEST_LOADAVG_NOT_FLOAT));
+
+        assertThat(systemMonitor.getCpuLoad()).isNull();
+    }
+
+    @Test
+    public void testWhenUnsetCallback_sameCallbackFromSetCallbackIsRemoved() throws IOException {
+        SystemMonitor systemMonitor = new SystemMonitor(
+                mMockContext, mMockHandler, writeTempFile(TEST_LOADAVG));
+
+        systemMonitor.setSystemMonitorCallback(mMockCallback);
+        systemMonitor.unsetSystemMonitorCallback();
+
+        verify(mMockHandler, times(1)).post(mRunnableCaptor.capture());
+        Runnable setRunnable = mRunnableCaptor.getValue();
+        verify(mMockHandler, times(1)).removeCallbacks(mRunnableCaptor.capture());
+        Runnable unsetRunnalbe = mRunnableCaptor.getValue();
+        assertThat(setRunnable).isEqualTo(unsetRunnalbe);
+    }
+
+    /**
+     * Creates and writes to the temp file, returns its path.
+     */
+    private String writeTempFile(String content) throws IOException {
+        File tempFile = temporaryFolder.newFile();
+        try (FileWriter fw = new FileWriter(tempFile)) {
+            fw.write(content);
+        }
+        return tempFile.getAbsolutePath();
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
index b12aa27..f97965e 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
@@ -21,16 +21,18 @@
 import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetAliveUsers;
 import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetAllUsers;
 import static android.car.watchdog.CarWatchdogManager.TIMEOUT_CRITICAL;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 
+import static com.android.car.watchdog.WatchdogStorage.ZONE_OFFSET;
 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.doReturn;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyList;
@@ -61,6 +63,8 @@
 import android.automotive.watchdog.internal.ResourceSpecificConfiguration;
 import android.automotive.watchdog.internal.StateType;
 import android.automotive.watchdog.internal.UidType;
+import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
+import android.car.hardware.power.ICarPowerStateListener;
 import android.car.test.mocks.AbstractExtendedMockitoTestCase;
 import android.car.watchdog.CarWatchdogManager;
 import android.car.watchdog.ICarWatchdogServiceCallback;
@@ -80,7 +84,9 @@
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -90,12 +96,13 @@
 import android.util.ArraySet;
 import android.util.SparseArray;
 
-import com.android.car.CarLog;
+import com.android.car.CarLocalServices;
 import com.android.car.CarServiceUtils;
-import com.android.internal.util.function.TriConsumer;
+import com.android.car.power.CarPowerManagementService;
 
 import com.google.common.truth.Correspondence;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -104,6 +111,9 @@
 import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import java.time.Instant;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -112,55 +122,99 @@
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiConsumer;
 
 /**
  * <p>This class contains unit tests for the {@link CarWatchdogService}.
  */
 @RunWith(MockitoJUnitRunner.class)
-public class CarWatchdogServiceUnitTest extends AbstractExtendedMockitoTestCase {
-    static final String TAG = CarLog.tagFor(CarWatchdogService.class);
+public final class CarWatchdogServiceUnitTest extends AbstractExtendedMockitoTestCase {
     private static final String CAR_WATCHDOG_DAEMON_INTERFACE = "carwatchdogd_system";
     private static final int MAX_WAIT_TIME_MS = 3000;
     private static final int INVALID_SESSION_ID = -1;
+    private static final int RESOURCE_OVERUSE_KILLING_DELAY_MILLS = 1000;
+    private static final long STATS_DURATION_SECONDS = 3 * 60 * 60;
 
     @Mock private Context mMockContext;
     @Mock private PackageManager mMockPackageManager;
     @Mock private UserManager mMockUserManager;
+    @Mock private CarPowerManagementService mMockCarPowerManagementService;
     @Mock private IBinder mMockBinder;
     @Mock private ICarWatchdog mMockCarWatchdogDaemon;
+    @Mock private WatchdogStorage mMockWatchdogStorage;
 
     private CarWatchdogService mCarWatchdogService;
     private ICarWatchdogServiceForSystem mWatchdogServiceForSystemImpl;
     private IBinder.DeathRecipient mCarWatchdogDaemonBinderDeathRecipient;
     private BroadcastReceiver mBroadcastReceiver;
-    private final SparseArray<String> mPackageNamesByUids = new SparseArray<>();
-    private final SparseArray<String[]> mSharedPackagesByUids = new SparseArray<>();
-    private final ArrayMap<String, ApplicationInfo> mApplicationInfosByPackages = new ArrayMap<>();
+    private boolean mIsDaemonCrashed;
+    private ICarPowerStateListener mCarPowerStateListener;
+    private TimeSourceInterface mTimeSource;
+
+    private final SparseArray<String> mGenericPackageNameByUid = new SparseArray<>();
+    private final SparseArray<List<String>> mPackagesBySharedUid = new SparseArray<>();
+    private final ArrayMap<String, android.content.pm.PackageInfo> mPmPackageInfoByUserPackage =
+            new ArrayMap<>();
+    private final ArraySet<String> mDisabledUserPackages = new ArraySet<>();
+    private final List<WatchdogStorage.UserPackageSettingsEntry> mUserPackageSettingsEntries =
+            new ArrayList<>();
+    private final List<WatchdogStorage.IoUsageStatsEntry> mIoUsageStatsEntries = new ArrayList<>();
 
     @Override
     protected void onSessionBuilder(CustomMockitoSessionBuilder builder) {
         builder
             .spyStatic(ServiceManager.class)
             .spyStatic(Binder.class)
-            .spyStatic(ActivityThread.class);
+            .spyStatic(ActivityThread.class)
+            .spyStatic(CarLocalServices.class);
     }
 
     /**
      * Initialize all of the objects with the @Mock annotation.
      */
     @Before
-    public void setUpMocks() throws Exception {
+    public void setUp() throws Exception {
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockContext.getPackageName()).thenReturn(
                 CarWatchdogServiceUnitTest.class.getCanonicalName());
-        mCarWatchdogService = new CarWatchdogService(mMockContext);
+        doReturn(mMockCarPowerManagementService)
+                .when(() -> CarLocalServices.getService(CarPowerManagementService.class));
+        mCarWatchdogService = new CarWatchdogService(mMockContext, mMockWatchdogStorage);
+        mCarWatchdogService.setResourceOveruseKillingDelay(RESOURCE_OVERUSE_KILLING_DELAY_MILLS);
+        setDate(/* numDaysAgo= */ 0);
         mockWatchdogDaemon();
+        mockWatchdogStorage();
         setupUsers();
         mCarWatchdogService.init();
-        mWatchdogServiceForSystemImpl = registerCarWatchdogService();
+        captureCarPowerStateListener();
         captureBroadcastReceiver();
+        captureWatchdogServiceForSystem();
         captureDaemonBinderDeathRecipient();
+        verifyDatabaseInit(/* wantedInvocations= */ 1);
         mockPackageManager();
+        verifyResourceOveruseConfigurationsSynced(/* wantedInvocations= */ 1);
+    }
+
+    /**
+     * Releases resources.
+     */
+    @After
+    public void tearDown() throws Exception {
+        if (mIsDaemonCrashed) {
+            /* Note: On daemon crash, CarWatchdogService retries daemon connection on the main
+             * thread. This retry outlives the test and impacts other test runs. Thus always call
+             * restartWatchdogDaemonAndAwait after crashing the daemon and before completing
+             * teardown.
+             */
+            restartWatchdogDaemonAndAwait();
+        }
+        mUserPackageSettingsEntries.clear();
+        mIoUsageStatsEntries.clear();
+        mGenericPackageNameByUid.clear();
+        mPackagesBySharedUid.clear();
+        mPmPackageInfoByUserPackage.clear();
+        mDisabledUserPackages.clear();
     }
 
     @Test
@@ -215,6 +269,7 @@
         verify(mMockCarWatchdogDaemon)
                 .notifySystemStateChange(
                         eq(StateType.GARAGE_MODE), eq(GarageMode.GARAGE_MODE_ON), eq(-1));
+        verify(mMockWatchdogStorage).shrinkDatabase();
     }
 
     @Test
@@ -224,86 +279,320 @@
         verify(mMockCarWatchdogDaemon)
                 .notifySystemStateChange(
                         eq(StateType.GARAGE_MODE), eq(GarageMode.GARAGE_MODE_OFF), eq(-1));
+        verify(mMockWatchdogStorage, never()).shrinkDatabase();
     }
 
     @Test
     public void testGetResourceOveruseStats() throws Exception {
-        mPackageNamesByUids.put(Binder.getCallingUid(), mMockContext.getPackageName());
+        int uid = Binder.getCallingUid();
+        injectPackageInfos(Collections.singletonList(
+                constructPackageManagerPackageInfo(
+                        mMockContext.getPackageName(), uid, null, ApplicationInfo.FLAG_SYSTEM, 0)));
 
-        List<PackageIoOveruseStats> packageIoOveruseStats = Collections.singletonList(
-                constructPackageIoOveruseStats(
-                        Binder.getCallingUid(), /* shouldNotify= */false,
-                        constructInternalIoOveruseStats(/* killableOnOveruse= */false,
-                                /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
-                                /* writtenBytes= */constructPerStateBytes(100, 200, 300),
-                                /* totalOveruses= */2)));
-        mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+        SparseArray<PackageIoOveruseStats> packageIoOveruseStatsByUid =
+                injectIoOveruseStatsForPackages(
+                        mGenericPackageNameByUid, /* killablePackages= */ new ArraySet<>(),
+                        /* shouldNotifyPackages= */ new ArraySet<>());
 
         ResourceOveruseStats expectedStats =
-                constructResourceOveruseStats(mPackageNamesByUids.keyAt(0),
-                        mPackageNamesByUids.valueAt(0),
-                        packageIoOveruseStats.get(0).ioOveruseStats);
+                constructResourceOveruseStats(uid, mMockContext.getPackageName(), 0,
+                        packageIoOveruseStatsByUid.get(uid).ioOveruseStats);
 
         ResourceOveruseStats actualStats = mCarWatchdogService.getResourceOveruseStats(
                 CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
                 CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
 
-        assertWithMessage("Expected: " + expectedStats.toString() + "\nActual: "
-                + actualStats.toString())
-                .that(ResourceOveruseStatsSubject.isEquals(actualStats, expectedStats)).isTrue();
+        ResourceOveruseStatsSubject.assertEquals(actualStats, expectedStats);
+
+        verifyNoMoreInteractions(mMockWatchdogStorage);
+    }
+
+    @Test
+    public void testGetResourceOveruseStatsForPast7days() throws Exception {
+        int uid = Binder.getCallingUid();
+        String packageName = mMockContext.getPackageName();
+        injectPackageInfos(Collections.singletonList(constructPackageManagerPackageInfo(
+                packageName, uid, null, ApplicationInfo.FLAG_SYSTEM, 0)));
+
+        long startTime = mTimeSource.now().atZone(ZONE_OFFSET).minusDays(4).toEpochSecond();
+        long duration = mTimeSource.now().getEpochSecond() - startTime;
+        when(mMockWatchdogStorage.getHistoricalIoOveruseStats(
+                UserHandle.getUserId(uid), packageName, 6))
+                .thenReturn(new IoOveruseStats.Builder(startTime, duration).setTotalOveruses(5)
+                        .setTotalTimesKilled(2).setTotalBytesWritten(24_000).build());
+
+        injectIoOveruseStatsForPackages(mGenericPackageNameByUid,
+                /* killablePackages= */ Collections.singleton(packageName),
+                /* shouldNotifyPackages= */ new ArraySet<>());
+
+        ResourceOveruseStats actualStats = mCarWatchdogService.getResourceOveruseStats(
+                CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+                CarWatchdogManager.STATS_PERIOD_PAST_7_DAYS);
+
+        IoOveruseStats ioOveruseStats =
+                new IoOveruseStats.Builder(startTime, duration + STATS_DURATION_SECONDS)
+                        .setKillableOnOveruse(true).setTotalOveruses(7).setTotalBytesWritten(24_600)
+                        .setTotalTimesKilled(2)
+                        .setRemainingWriteBytes(new PerStateBytes(20, 20, 20)).build();
+
+        ResourceOveruseStats expectedStats =
+                new ResourceOveruseStats.Builder(packageName, UserHandle.getUserHandleForUid(uid))
+                        .setIoOveruseStats(ioOveruseStats).build();
+
+        ResourceOveruseStatsSubject.assertEquals(actualStats, expectedStats);
+    }
+
+    @Test
+    public void testGetResourceOveruseStatsForPast7daysWithNoHistory() throws Exception {
+        int uid = Binder.getCallingUid();
+        String packageName = mMockContext.getPackageName();
+        injectPackageInfos(Collections.singletonList(constructPackageManagerPackageInfo(
+                packageName, uid, null, ApplicationInfo.FLAG_SYSTEM, 0)));
+
+        when(mMockWatchdogStorage.getHistoricalIoOveruseStats(
+                UserHandle.getUserId(uid), packageName, 6)).thenReturn(null);
+
+        injectIoOveruseStatsForPackages(mGenericPackageNameByUid,
+                /* killablePackages= */ Collections.singleton(packageName),
+                /* shouldNotifyPackages= */ new ArraySet<>());
+
+        ResourceOveruseStats actualStats = mCarWatchdogService.getResourceOveruseStats(
+                CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+                CarWatchdogManager.STATS_PERIOD_PAST_7_DAYS);
+
+        ResourceOveruseStats expectedStats =
+                new ResourceOveruseStats.Builder(packageName, UserHandle.getUserHandleForUid(uid))
+                        .setIoOveruseStats(new IoOveruseStats.Builder(
+                                mTimeSource.now().getEpochSecond(), STATS_DURATION_SECONDS)
+                                .setKillableOnOveruse(true).setTotalOveruses(2)
+                                .setTotalBytesWritten(600)
+                                .setRemainingWriteBytes(new PerStateBytes(20, 20, 20)).build())
+                        .build();
+
+        ResourceOveruseStatsSubject.assertEquals(actualStats, expectedStats);
+    }
+
+    @Test
+    public void testGetResourceOveruseStatsForPast7daysWithNoCurrentStats() throws Exception {
+        int uid = Binder.getCallingUid();
+        String packageName = mMockContext.getPackageName();
+        injectPackageInfos(Collections.singletonList(constructPackageManagerPackageInfo(
+                packageName, uid, null, ApplicationInfo.FLAG_SYSTEM, 0)));
+
+        long startTime = mTimeSource.now().atZone(ZONE_OFFSET).minusDays(4).toEpochSecond();
+        long duration = mTimeSource.now().getEpochSecond() - startTime;
+        when(mMockWatchdogStorage.getHistoricalIoOveruseStats(
+                UserHandle.getUserId(uid), packageName, 6))
+                .thenReturn(new IoOveruseStats.Builder(startTime, duration).setTotalOveruses(5)
+                        .setTotalTimesKilled(2).setTotalBytesWritten(24_000).build());
+
+        ResourceOveruseStats actualStats = mCarWatchdogService.getResourceOveruseStats(
+                CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+                CarWatchdogManager.STATS_PERIOD_PAST_7_DAYS);
+
+        ResourceOveruseStats expectedStats =
+                new ResourceOveruseStats.Builder(packageName, UserHandle.getUserHandleForUid(uid))
+                .build();
+
+        ResourceOveruseStatsSubject.assertEquals(actualStats, expectedStats);
+    }
+
+    @Test
+    public void testGetResourceOveruseStatsForSharedUid() throws Exception {
+        int sharedUid = Binder.getCallingUid();
+        injectPackageInfos(Collections.singletonList(
+                constructPackageManagerPackageInfo(
+                        mMockContext.getPackageName(), sharedUid, "system_shared_package",
+                        ApplicationInfo.FLAG_SYSTEM, 0)));
+
+        SparseArray<PackageIoOveruseStats> packageIoOveruseStatsByUid =
+                injectIoOveruseStatsForPackages(
+                        mGenericPackageNameByUid, /* killablePackages= */ new ArraySet<>(),
+                        /* shouldNotifyPackages= */ new ArraySet<>());
+
+        ResourceOveruseStats expectedStats =
+                constructResourceOveruseStats(sharedUid, "shared:system_shared_package", 0,
+                        packageIoOveruseStatsByUid.get(sharedUid).ioOveruseStats);
+
+        ResourceOveruseStats actualStats = mCarWatchdogService.getResourceOveruseStats(
+                CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+                CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
+
+        ResourceOveruseStatsSubject.assertEquals(actualStats, expectedStats);
     }
 
     @Test
     public void testFailsGetResourceOveruseStatsWithInvalidArgs() throws Exception {
         assertThrows(IllegalArgumentException.class,
-                () -> mCarWatchdogService.getResourceOveruseStats(/* resourceOveruseFlag= */0,
+                () -> mCarWatchdogService.getResourceOveruseStats(/* resourceOveruseFlag= */ 0,
                         CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
 
         assertThrows(IllegalArgumentException.class,
                 () -> mCarWatchdogService.getResourceOveruseStats(
-                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* maxStatsPeriod= */0));
+                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* maxStatsPeriod= */ 0));
     }
 
     @Test
     public void testGetAllResourceOveruseStatsWithNoMinimum() throws Exception {
-        mPackageNamesByUids.put(1103456, "third_party_package");
-        mPackageNamesByUids.put(1201278, "vendor_package.critical");
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo("third_party_package", 1103456, null),
+                constructPackageManagerPackageInfo("vendor_package.critical", 1201278, null)));
 
         List<PackageIoOveruseStats> packageIoOveruseStats = Arrays.asList(
-                constructPackageIoOveruseStats(mPackageNamesByUids.keyAt(0),
-                        /* shouldNotify= */false,
-                        constructInternalIoOveruseStats(/* killableOnOveruse= */true,
-                                /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
-                                /* writtenBytes= */constructPerStateBytes(100, 200, 300),
-                                /* totalOveruses= */2)),
-                constructPackageIoOveruseStats(mPackageNamesByUids.keyAt(1),
-                        /* shouldNotify= */false,
-                        constructInternalIoOveruseStats(/* killableOnOveruse= */false,
-                                /* remainingWriteBytes= */constructPerStateBytes(450, 120, 340),
-                                /* writtenBytes= */constructPerStateBytes(5000, 6000, 9000),
-                                /* totalOveruses= */2)));
-        mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+                constructPackageIoOveruseStats(1103456,
+                        /* shouldNotify= */ true,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ true,
+                                /* remainingWriteBytes= */ constructPerStateBytes(0, 0, 0),
+                                /* writtenBytes= */ constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */ 2)),
+                constructPackageIoOveruseStats(1201278,
+                        /* shouldNotify= */ false,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ false,
+                                /* remainingWriteBytes= */ constructPerStateBytes(450, 120, 340),
+                                /* writtenBytes= */ constructPerStateBytes(5000, 6000, 9000),
+                                /* totalOveruses= */ 2)));
+        pushLatestIoOveruseStatsAndWait(packageIoOveruseStats);
 
         List<ResourceOveruseStats> expectedStats = Arrays.asList(
-                constructResourceOveruseStats(mPackageNamesByUids.keyAt(0),
-                        mPackageNamesByUids.valueAt(0),
+                constructResourceOveruseStats(1103456, "third_party_package", 1,
                         packageIoOveruseStats.get(0).ioOveruseStats),
-                constructResourceOveruseStats(mPackageNamesByUids.keyAt(1),
-                        mPackageNamesByUids.valueAt(1),
+                constructResourceOveruseStats(1201278, "vendor_package.critical", 0,
                         packageIoOveruseStats.get(1).ioOveruseStats));
 
         List<ResourceOveruseStats> actualStats = mCarWatchdogService.getAllResourceOveruseStats(
-                CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* minimumStatsFlag= */0,
+                CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* minimumStatsFlag= */ 0,
                 CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
 
         ResourceOveruseStatsSubject.assertThat(actualStats)
                 .containsExactlyElementsIn(expectedStats);
+
+        verifyNoMoreInteractions(mMockWatchdogStorage);
+    }
+
+    @Test
+    public void testGetAllResourceOveruseStatsWithNoMinimumForPast7days() throws Exception {
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo("third_party_package", 1103456, null),
+                constructPackageManagerPackageInfo("vendor_package.critical", 1201278, null)));
+
+        List<PackageIoOveruseStats> packageIoOveruseStats = Arrays.asList(
+                constructPackageIoOveruseStats(1103456,
+                        /* shouldNotify= */ true,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ true,
+                                /* remainingWriteBytes= */ constructPerStateBytes(0, 0, 0),
+                                /* writtenBytes= */ constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */ 2)),
+                constructPackageIoOveruseStats(1201278,
+                        /* shouldNotify= */ false,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ false,
+                                /* remainingWriteBytes= */ constructPerStateBytes(450, 120, 340),
+                                /* writtenBytes= */ constructPerStateBytes(5000, 6000, 9000),
+                                /* totalOveruses= */ 0)));
+        pushLatestIoOveruseStatsAndWait(packageIoOveruseStats);
+
+        ZonedDateTime now = mTimeSource.now().atZone(ZONE_OFFSET);
+        long startTime = now.minusDays(4).toEpochSecond();
+        IoOveruseStats thirdPartyPkgOldStats = new IoOveruseStats.Builder(
+                startTime, now.toEpochSecond() - startTime).setTotalOveruses(5)
+                .setTotalTimesKilled(2).setTotalBytesWritten(24_000).build();
+        when(mMockWatchdogStorage.getHistoricalIoOveruseStats(11, "third_party_package", 6))
+                .thenReturn(thirdPartyPkgOldStats);
+
+        startTime = now.minusDays(6).toEpochSecond();
+        IoOveruseStats vendorPkgOldStats = new IoOveruseStats.Builder(
+                startTime, now.toEpochSecond() - startTime).setTotalOveruses(2)
+                .setTotalTimesKilled(0).setTotalBytesWritten(35_000).build();
+        when(mMockWatchdogStorage.getHistoricalIoOveruseStats(12, "vendor_package.critical", 6))
+                .thenReturn(vendorPkgOldStats);
+
+
+        List<ResourceOveruseStats> actualStats = mCarWatchdogService.getAllResourceOveruseStats(
+                CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* minimumStatsFlag= */ 0,
+                CarWatchdogManager.STATS_PERIOD_PAST_7_DAYS);
+
+        IoOveruseStats thirdPartyIoStats = new IoOveruseStats.Builder(
+                thirdPartyPkgOldStats.getStartTime(),
+                thirdPartyPkgOldStats.getDurationInSeconds() + STATS_DURATION_SECONDS)
+                .setKillableOnOveruse(true).setTotalOveruses(7).setTotalBytesWritten(24_600)
+                .setTotalTimesKilled(3).setRemainingWriteBytes(new PerStateBytes(0, 0, 0))
+                .build();
+        IoOveruseStats vendorIoStats = new IoOveruseStats.Builder(
+                vendorPkgOldStats.getStartTime(),
+                vendorPkgOldStats.getDurationInSeconds() + STATS_DURATION_SECONDS)
+                .setKillableOnOveruse(false).setTotalOveruses(2).setTotalBytesWritten(55_000)
+                .setTotalTimesKilled(0).setRemainingWriteBytes(new PerStateBytes(450, 120, 340))
+                .build();
+
+        List<ResourceOveruseStats> expectedStats = Arrays.asList(
+                new ResourceOveruseStats.Builder("third_party_package", new UserHandle(11))
+                        .setIoOveruseStats(thirdPartyIoStats).build(),
+                new ResourceOveruseStats.Builder("vendor_package.critical", new UserHandle(12))
+                        .setIoOveruseStats(vendorIoStats).build());
+
+        ResourceOveruseStatsSubject.assertThat(actualStats)
+                .containsExactlyElementsIn(expectedStats);
     }
 
     @Test
+    public void testGetAllResourceOveruseStatsForSharedPackage() throws Exception {
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo(
+                        "vendor_package.A", 1103456, "vendor_shared_package"),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.B", 1103456, "vendor_shared_package"),
+                constructPackageManagerPackageInfo(
+                        "system_package.C", 1201000, "system_shared_package"),
+                constructPackageManagerPackageInfo(
+                        "system_package.D", 1201000, "system_shared_package"),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.A", 1303456, "vendor_shared_package"),
+                constructPackageManagerPackageInfo(
+                        "vendor_package.B", 1303456, "vendor_shared_package")));
+
+        List<PackageIoOveruseStats> packageIoOveruseStats = Arrays.asList(
+                constructPackageIoOveruseStats(1103456,
+                        /* shouldNotify= */ false,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ true,
+                                /* remainingWriteBytes= */ constructPerStateBytes(20, 20, 20),
+                                /* writtenBytes= */ constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */ 2)),
+                constructPackageIoOveruseStats(1201000,
+                        /* shouldNotify= */ false,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ false,
+                                /* remainingWriteBytes= */ constructPerStateBytes(450, 120, 340),
+                                /* writtenBytes= */ constructPerStateBytes(5000, 6000, 9000),
+                                /* totalOveruses= */ 0)),
+                constructPackageIoOveruseStats(1303456,
+                        /* shouldNotify= */ true,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ true,
+                                /* remainingWriteBytes= */ constructPerStateBytes(0, 0, 0),
+                                /* writtenBytes= */ constructPerStateBytes(80, 170, 260),
+                                /* totalOveruses= */ 1)));
+
+        pushLatestIoOveruseStatsAndWait(packageIoOveruseStats);
+
+        List<ResourceOveruseStats> expectedStats = Arrays.asList(
+                constructResourceOveruseStats(1103456, "shared:vendor_shared_package", 0,
+                        packageIoOveruseStats.get(0).ioOveruseStats),
+                constructResourceOveruseStats(1201278, "shared:system_shared_package", 0,
+                        packageIoOveruseStats.get(1).ioOveruseStats),
+                constructResourceOveruseStats(1303456, "shared:vendor_shared_package", 1,
+                        packageIoOveruseStats.get(2).ioOveruseStats));
+
+        List<ResourceOveruseStats> actualStats = mCarWatchdogService.getAllResourceOveruseStats(
+                CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* minimumStatsFlag= */ 0,
+                CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
+
+        ResourceOveruseStatsSubject.assertThat(actualStats)
+                .containsExactlyElementsIn(expectedStats);
+
+        verifyNoMoreInteractions(mMockWatchdogStorage);
+    }
+
+    @Test
     public void testFailsGetAllResourceOveruseStatsWithInvalidArgs() throws Exception {
         assertThrows(IllegalArgumentException.class,
-                () -> mCarWatchdogService.getAllResourceOveruseStats(0, /* minimumStatsFlag= */0,
+                () -> mCarWatchdogService.getAllResourceOveruseStats(0, /* minimumStatsFlag= */ 0,
                         CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
 
         assertThrows(IllegalArgumentException.class,
@@ -315,38 +604,36 @@
 
         assertThrows(IllegalArgumentException.class,
                 () -> mCarWatchdogService.getAllResourceOveruseStats(
-                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* minimumStatsFlag= */1 << 5,
+                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* minimumStatsFlag= */ 1 << 5,
                         CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
 
         assertThrows(IllegalArgumentException.class,
                 () -> mCarWatchdogService.getAllResourceOveruseStats(
-                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* minimumStatsFlag= */0,
-                        /* maxStatsPeriod= */0));
+                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* minimumStatsFlag= */ 0,
+                        /* maxStatsPeriod= */ 0));
     }
 
     @Test
     public void testGetAllResourceOveruseStatsWithMinimum() throws Exception {
-        mPackageNamesByUids.put(1103456, "third_party_package");
-        mPackageNamesByUids.put(1201278, "vendor_package.critical");
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo("third_party_package", 1103456, null),
+                constructPackageManagerPackageInfo("vendor_package.critical", 1201278, null)));
 
         List<PackageIoOveruseStats> packageIoOveruseStats = Arrays.asList(
-                constructPackageIoOveruseStats(mPackageNamesByUids.keyAt(0),
-                        /* shouldNotify= */false,
-                        constructInternalIoOveruseStats(/* killableOnOveruse= */true,
-                                /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
-                                /* writtenBytes= */constructPerStateBytes(100, 200, 300),
-                                /* totalOveruses= */2)),
-                constructPackageIoOveruseStats(mPackageNamesByUids.keyAt(1),
-                        /* shouldNotify= */false,
-                        constructInternalIoOveruseStats(/* killableOnOveruse= */false,
-                                /* remainingWriteBytes= */constructPerStateBytes(450, 120, 340),
-                                /* writtenBytes= */constructPerStateBytes(7000000, 6000, 9000),
-                                /* totalOveruses= */2)));
-        mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+                constructPackageIoOveruseStats(1103456, /* shouldNotify= */ false,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ true,
+                                /* remainingWriteBytes= */ constructPerStateBytes(20, 20, 20),
+                                /* writtenBytes= */ constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */ 2)),
+                constructPackageIoOveruseStats(1201278, /* shouldNotify= */ false,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ false,
+                                /* remainingWriteBytes= */ constructPerStateBytes(450, 120, 340),
+                                /* writtenBytes= */ constructPerStateBytes(7_000_000, 6000, 9000),
+                                /* totalOveruses= */ 2)));
+        pushLatestIoOveruseStatsAndWait(packageIoOveruseStats);
 
         List<ResourceOveruseStats> expectedStats = Collections.singletonList(
-                constructResourceOveruseStats(mPackageNamesByUids.keyAt(1),
-                        mPackageNamesByUids.valueAt(1),
+                constructResourceOveruseStats(1201278, "vendor_package.critical", 0,
                         packageIoOveruseStats.get(1).ioOveruseStats));
 
         List<ResourceOveruseStats> actualStats = mCarWatchdogService.getAllResourceOveruseStats(
@@ -356,31 +643,89 @@
 
         ResourceOveruseStatsSubject.assertThat(actualStats)
                 .containsExactlyElementsIn(expectedStats);
+
+        verifyNoMoreInteractions(mMockWatchdogStorage);
+    }
+
+    @Test
+    public void testGetAllResourceOveruseStatsWithMinimumForPast7days() throws Exception {
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo("third_party_package", 1103456, null),
+                constructPackageManagerPackageInfo("vendor_package.critical", 1201278, null)));
+
+        List<PackageIoOveruseStats> packageIoOveruseStats = Arrays.asList(
+                constructPackageIoOveruseStats(1103456,
+                        /* shouldNotify= */ true,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ true,
+                                /* remainingWriteBytes= */ constructPerStateBytes(20, 20, 20),
+                                /* writtenBytes= */ constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */ 2)),
+                constructPackageIoOveruseStats(1201278,
+                        /* shouldNotify= */ false,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ false,
+                                /* remainingWriteBytes= */ constructPerStateBytes(450, 120, 340),
+                                /* writtenBytes= */ constructPerStateBytes(100_000, 6000, 9000),
+                                /* totalOveruses= */ 0)));
+        pushLatestIoOveruseStatsAndWait(packageIoOveruseStats);
+
+        ZonedDateTime now = mTimeSource.now().atZone(ZONE_OFFSET);
+        long startTime = now.minusDays(4).toEpochSecond();
+        IoOveruseStats thirdPartyPkgOldStats = new IoOveruseStats.Builder(
+                startTime, now.toEpochSecond() - startTime).setTotalOveruses(5)
+                .setTotalTimesKilled(2).setTotalBytesWritten(24_000).build();
+        when(mMockWatchdogStorage.getHistoricalIoOveruseStats(11, "third_party_package", 6))
+                .thenReturn(thirdPartyPkgOldStats);
+
+        startTime = now.minusDays(6).toEpochSecond();
+        IoOveruseStats vendorPkgOldStats = new IoOveruseStats.Builder(
+                startTime, now.toEpochSecond() - startTime).setTotalOveruses(2)
+                .setTotalTimesKilled(0).setTotalBytesWritten(6_900_000).build();
+        when(mMockWatchdogStorage.getHistoricalIoOveruseStats(12, "vendor_package.critical", 6))
+                .thenReturn(vendorPkgOldStats);
+
+        List<ResourceOveruseStats> actualStats = mCarWatchdogService.getAllResourceOveruseStats(
+                CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+                CarWatchdogManager.FLAG_MINIMUM_STATS_IO_1_MB,
+                CarWatchdogManager.STATS_PERIOD_PAST_7_DAYS);
+
+        IoOveruseStats vendorIoStats = new IoOveruseStats.Builder(
+                vendorPkgOldStats.getStartTime(),
+                vendorPkgOldStats.getDurationInSeconds() + STATS_DURATION_SECONDS)
+                .setKillableOnOveruse(false).setTotalOveruses(2).setTotalBytesWritten(7_015_000)
+                .setTotalTimesKilled(0).setRemainingWriteBytes(new PerStateBytes(450, 120, 340))
+                .build();
+
+        List<ResourceOveruseStats> expectedStats = Collections.singletonList(
+                new ResourceOveruseStats.Builder("vendor_package.critical", new UserHandle(12))
+                        .setIoOveruseStats(vendorIoStats).build());
+
+        ResourceOveruseStatsSubject.assertThat(actualStats)
+                .containsExactlyElementsIn(expectedStats);
     }
 
     @Test
     public void testGetResourceOveruseStatsForUserPackage() throws Exception {
-        mPackageNamesByUids.put(1103456, "third_party_package");
-        mPackageNamesByUids.put(1201278, "vendor_package.critical");
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo("third_party_package", 1103456, null),
+                constructPackageManagerPackageInfo("vendor_package.critical", 1201278, null)));
 
         List<PackageIoOveruseStats> packageIoOveruseStats = Arrays.asList(
-                constructPackageIoOveruseStats(mPackageNamesByUids.keyAt(0),
-                        /* shouldNotify= */false,
-                        constructInternalIoOveruseStats(/* killableOnOveruse= */true,
-                                /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
-                                /* writtenBytes= */constructPerStateBytes(100, 200, 300),
-                                /* totalOveruses= */2)),
-                constructPackageIoOveruseStats(mPackageNamesByUids.keyAt(1),
-                        /* shouldNotify= */false,
-                        constructInternalIoOveruseStats(/* killableOnOveruse= */false,
-                                /* remainingWriteBytes= */constructPerStateBytes(450, 120, 340),
-                                /* writtenBytes= */constructPerStateBytes(500, 600, 900),
-                                /* totalOveruses= */2)));
-        mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+                constructPackageIoOveruseStats(1103456,
+                        /* shouldNotify= */ false,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ true,
+                                /* remainingWriteBytes= */ constructPerStateBytes(20, 20, 20),
+                                /* writtenBytes= */ constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */ 2)),
+                constructPackageIoOveruseStats(1201278,
+                        /* shouldNotify= */ false,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ false,
+                                /* remainingWriteBytes= */ constructPerStateBytes(450, 120, 340),
+                                /* writtenBytes= */ constructPerStateBytes(500, 600, 900),
+                                /* totalOveruses= */ 2)));
+        pushLatestIoOveruseStatsAndWait(packageIoOveruseStats);
 
         ResourceOveruseStats expectedStats =
-                constructResourceOveruseStats(mPackageNamesByUids.keyAt(1),
-                        mPackageNamesByUids.valueAt(1),
+                constructResourceOveruseStats(1201278, "vendor_package.critical", 0,
                         packageIoOveruseStats.get(1).ioOveruseStats);
 
         ResourceOveruseStats actualStats =
@@ -389,22 +734,98 @@
                         CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
                         CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
 
-        assertWithMessage("Expected: " + expectedStats.toString() + "\nActual: "
-                + actualStats.toString())
-                .that(ResourceOveruseStatsSubject.isEquals(actualStats, expectedStats)).isTrue();
+        ResourceOveruseStatsSubject.assertEquals(actualStats, expectedStats);
+    }
+
+    @Test
+    public void testGetResourceOveruseStatsForUserPackageForPast7days() throws Exception {
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo("third_party_package", 1103456, null),
+                constructPackageManagerPackageInfo("vendor_package.critical", 1201278, null)));
+
+        List<PackageIoOveruseStats> packageIoOveruseStats = Arrays.asList(
+                constructPackageIoOveruseStats(1103456,
+                        /* shouldNotify= */ false,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ true,
+                                /* remainingWriteBytes= */ constructPerStateBytes(20, 20, 20),
+                                /* writtenBytes= */ constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */ 2)),
+                constructPackageIoOveruseStats(1201278,
+                        /* shouldNotify= */ false,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ false,
+                                /* remainingWriteBytes= */ constructPerStateBytes(450, 120, 340),
+                                /* writtenBytes= */ constructPerStateBytes(500, 600, 900),
+                                /* totalOveruses= */ 2)));
+        pushLatestIoOveruseStatsAndWait(packageIoOveruseStats);
+
+        ZonedDateTime now = mTimeSource.now().atZone(ZONE_OFFSET);
+        long startTime = now.minusDays(4).toEpochSecond();
+        IoOveruseStats vendorPkgOldStats = new IoOveruseStats.Builder(
+                startTime, now.toEpochSecond() - startTime).setTotalOveruses(2)
+                .setTotalTimesKilled(0).setTotalBytesWritten(6_900_000).build();
+        when(mMockWatchdogStorage.getHistoricalIoOveruseStats(12, "vendor_package.critical", 6))
+                .thenReturn(vendorPkgOldStats);
+
+        ResourceOveruseStats actualStats =
+                mCarWatchdogService.getResourceOveruseStatsForUserPackage(
+                        "vendor_package.critical", new UserHandle(12),
+                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+                        CarWatchdogManager.STATS_PERIOD_PAST_7_DAYS);
+
+        IoOveruseStats vendorIoStats = new IoOveruseStats.Builder(
+                vendorPkgOldStats.getStartTime(),
+                vendorPkgOldStats.getDurationInSeconds() + STATS_DURATION_SECONDS)
+                .setKillableOnOveruse(false).setTotalOveruses(4).setTotalBytesWritten(6_902_000)
+                .setTotalTimesKilled(0).setRemainingWriteBytes(new PerStateBytes(450, 120, 340))
+                .build();
+
+        ResourceOveruseStats expectedStats = new ResourceOveruseStats.Builder(
+                "vendor_package.critical", new UserHandle(12)).setIoOveruseStats(vendorIoStats)
+                .build();
+
+        ResourceOveruseStatsSubject.assertEquals(actualStats, expectedStats);
+    }
+
+    @Test
+    public void testGetResourceOveruseStatsForUserPackageWithSharedUids() throws Exception {
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo(
+                        "third_party_package", 1103456, "vendor_shared_package"),
+                constructPackageManagerPackageInfo(
+                        "vendor_package", 1103456, "vendor_shared_package"),
+                constructPackageManagerPackageInfo("system_package", 1101100,
+                        "shared_system_package")));
+
+        SparseArray<PackageIoOveruseStats> packageIoOveruseStatsByUid =
+                injectIoOveruseStatsForPackages(
+                        mGenericPackageNameByUid, /* killablePackages= */ new ArraySet<>(
+                                Collections.singleton("shared:vendor_shared_package")),
+                        /* shouldNotifyPackages= */ new ArraySet<>());
+
+        ResourceOveruseStats expectedStats =
+                constructResourceOveruseStats(1103456, "shared:vendor_shared_package", 0,
+                        packageIoOveruseStatsByUid.get(1103456).ioOveruseStats);
+
+        ResourceOveruseStats actualStats =
+                mCarWatchdogService.getResourceOveruseStatsForUserPackage(
+                        "vendor_package", new UserHandle(11),
+                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+                        CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
+
+        ResourceOveruseStatsSubject.assertEquals(actualStats, expectedStats);
     }
 
     @Test
     public void testFailsGetResourceOveruseStatsForUserPackageWithInvalidArgs() throws Exception {
         assertThrows(NullPointerException.class,
                 () -> mCarWatchdogService.getResourceOveruseStatsForUserPackage(
-                        /* packageName= */null, new UserHandle(10),
+                        /* packageName= */ null, new UserHandle(10),
                         CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
                         CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
 
         assertThrows(NullPointerException.class,
                 () -> mCarWatchdogService.getResourceOveruseStatsForUserPackage("some.package",
-                        /* userHandle= */null, CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+                        /* userHandle= */ null, CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
                         CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
 
         assertThrows(IllegalArgumentException.class,
@@ -414,13 +835,13 @@
 
         assertThrows(IllegalArgumentException.class,
                 () -> mCarWatchdogService.getResourceOveruseStatsForUserPackage("some.package",
-                        new UserHandle(10), /* resourceOveruseFlag= */0,
+                        new UserHandle(10), /* resourceOveruseFlag= */ 0,
                         CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
 
         assertThrows(IllegalArgumentException.class,
                 () -> mCarWatchdogService.getResourceOveruseStatsForUserPackage("some.package",
                         new UserHandle(10), CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
-                        /* maxStatsPeriod= */0));
+                        /* maxStatsPeriod= */ 0));
     }
 
     @Test
@@ -433,7 +854,7 @@
 
     @Test
     public void testResourceOveruseListener() throws Exception {
-        mPackageNamesByUids.put(Binder.getCallingUid(), mMockContext.getPackageName());
+        mGenericPackageNameByUid.put(Binder.getCallingUid(), mMockContext.getPackageName());
 
         IResourceOveruseListener mockListener = createMockResourceOveruseListener();
         IBinder mockBinder = mockListener.asBinder();
@@ -443,9 +864,9 @@
 
         verify(mockBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
 
-        injectIoOveruseStatsForPackages(mPackageNamesByUids,
-                /* killablePackages= */new ArraySet<>(),
-                /* shouldNotifyPackages= */new ArraySet<>(
+        injectIoOveruseStatsForPackages(
+                mGenericPackageNameByUid, /* killablePackages= */ new ArraySet<>(),
+                /* shouldNotifyPackages= */ new ArraySet<>(
                         Collections.singleton(mMockContext.getPackageName())));
 
         verify(mockListener).onOveruse(any());
@@ -455,9 +876,9 @@
         verify(mockListener, atLeastOnce()).asBinder();
         verify(mockBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
 
-        injectIoOveruseStatsForPackages(mPackageNamesByUids,
-                /* killablePackages= */new ArraySet<>(),
-                /* shouldNotifyPackages= */new ArraySet<>(
+        injectIoOveruseStatsForPackages(
+                mGenericPackageNameByUid, /* killablePackages= */ new ArraySet<>(),
+                /* shouldNotifyPackages= */ new ArraySet<>(
                         Collections.singletonList(mMockContext.getPackageName())));
 
         verifyNoMoreInteractions(mockListener);
@@ -465,7 +886,7 @@
 
     @Test
     public void testDuplicateAddResourceOveruseListener() throws Exception {
-        mPackageNamesByUids.put(Binder.getCallingUid(), mMockContext.getPackageName());
+        mGenericPackageNameByUid.put(Binder.getCallingUid(), mMockContext.getPackageName());
 
         IResourceOveruseListener mockListener = createMockResourceOveruseListener();
         IBinder mockBinder = mockListener.asBinder();
@@ -489,7 +910,7 @@
 
     @Test
     public void testAddMultipleResourceOveruseListeners() throws Exception {
-        mPackageNamesByUids.put(Binder.getCallingUid(), mMockContext.getPackageName());
+        mGenericPackageNameByUid.put(Binder.getCallingUid(), mMockContext.getPackageName());
 
         IResourceOveruseListener firstMockListener = createMockResourceOveruseListener();
         IBinder firstMockBinder = firstMockListener.asBinder();
@@ -504,9 +925,9 @@
         verify(firstMockBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
         verify(secondMockBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
 
-        injectIoOveruseStatsForPackages(mPackageNamesByUids,
-                /* killablePackages= */new ArraySet<>(),
-                /* shouldNotifyPackages= */new ArraySet<>(
+        injectIoOveruseStatsForPackages(
+                mGenericPackageNameByUid, /* killablePackages= */ new ArraySet<>(),
+                /* shouldNotifyPackages= */ new ArraySet<>(
                         Collections.singleton(mMockContext.getPackageName())));
 
         verify(firstMockListener).onOveruse(any());
@@ -516,9 +937,9 @@
         verify(firstMockListener, atLeastOnce()).asBinder();
         verify(firstMockBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
 
-        injectIoOveruseStatsForPackages(mPackageNamesByUids,
-                /* killablePackages= */new ArraySet<>(),
-                /* shouldNotifyPackages= */new ArraySet<>(
+        injectIoOveruseStatsForPackages(
+                mGenericPackageNameByUid, /* killablePackages= */ new ArraySet<>(),
+                /* shouldNotifyPackages= */ new ArraySet<>(
                         Collections.singletonList(mMockContext.getPackageName())));
 
         verify(secondMockListener, times(2)).onOveruse(any());
@@ -528,9 +949,9 @@
         verify(secondMockListener, atLeastOnce()).asBinder();
         verify(secondMockBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
 
-        injectIoOveruseStatsForPackages(mPackageNamesByUids,
-                /* killablePackages= */new ArraySet<>(),
-                /* shouldNotifyPackages= */new ArraySet<>(
+        injectIoOveruseStatsForPackages(
+                mGenericPackageNameByUid, /* killablePackages= */ new ArraySet<>(),
+                /* shouldNotifyPackages= */ new ArraySet<>(
                         Collections.singletonList(mMockContext.getPackageName())));
 
         verifyNoMoreInteractions(firstMockListener);
@@ -548,7 +969,7 @@
     @Test
     public void testResourceOveruseListenerForSystem() throws Exception {
         int callingUid = Binder.getCallingUid();
-        mPackageNamesByUids.put(callingUid, "critical.system.package");
+        mGenericPackageNameByUid.put(callingUid, "system_package.critical");
 
         IResourceOveruseListener mockListener = createMockResourceOveruseListener();
         mCarWatchdogService.addResourceOveruseListenerForSystem(
@@ -558,13 +979,13 @@
         verify(mockBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
 
         List<PackageIoOveruseStats> packageIoOveruseStats = Collections.singletonList(
-                constructPackageIoOveruseStats(callingUid, /* shouldNotify= */true,
-                        constructInternalIoOveruseStats(/* killableOnOveruse= */true,
-                                /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
-                                /* writtenBytes= */constructPerStateBytes(100, 200, 300),
-                                /* totalOveruses= */2)));
+                constructPackageIoOveruseStats(callingUid, /* shouldNotify= */ true,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ true,
+                                /* remainingWriteBytes= */ constructPerStateBytes(20, 20, 20),
+                                /* writtenBytes= */ constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */ 2)));
 
-        mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+        pushLatestIoOveruseStatsAndWait(packageIoOveruseStats);
 
         verify(mockListener).onOveruse(any());
 
@@ -573,118 +994,136 @@
         verify(mockListener, atLeastOnce()).asBinder();
         verify(mockBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
 
-        mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+        pushLatestIoOveruseStatsAndWait(packageIoOveruseStats);
 
         verifyNoMoreInteractions(mockListener);
     }
 
     @Test
-    public void testSetKillablePackageAsUserWithPackageStats() throws Exception {
+    public void testSetKillablePackageAsUser() throws Exception {
         mockUmGetAliveUsers(mMockUserManager, 11, 12);
-        injectPackageInfos(Arrays.asList("third_party_package", "vendor_package.critical"));
-
-        mPackageNamesByUids.put(1103456, "third_party_package");
-        mPackageNamesByUids.put(1101278, "vendor_package.critical");
-        injectIoOveruseStatsForPackages(mPackageNamesByUids,
-                /* killablePackages= */new ArraySet<>(
-                        Collections.singletonList("third_party_package")),
-                /* shouldNotifyPackages= */new ArraySet<>());
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo("third_party_package", 1103456, null),
+                constructPackageManagerPackageInfo("vendor_package.critical", 1101278, null),
+                constructPackageManagerPackageInfo("third_party_package", 1203456, null),
+                constructPackageManagerPackageInfo("vendor_package.critical", 1201278, null)));
 
         UserHandle userHandle = new UserHandle(11);
-
         mCarWatchdogService.setKillablePackageAsUser("third_party_package", userHandle,
-                /* isKillable= */ true);
+                /* isKillable= */ false);
+        mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+                userHandle, /* isKillable= */ false);
+
+        PackageKillableStateSubject.assertThat(
+                mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
+                new PackageKillableState("third_party_package", 11,
+                        PackageKillableState.KILLABLE_STATE_NO),
+                new PackageKillableState("vendor_package.critical", 11,
+                        PackageKillableState.KILLABLE_STATE_NEVER),
+                new PackageKillableState("third_party_package", 12,
+                        PackageKillableState.KILLABLE_STATE_YES),
+                new PackageKillableState("vendor_package.critical", 12,
+                        PackageKillableState.KILLABLE_STATE_NEVER));
+
         assertThrows(IllegalArgumentException.class,
                 () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
                         userHandle, /* isKillable= */ true));
 
-        PackageKillableStateSubject.assertThat(
-                mCarWatchdogService.getPackageKillableStatesAsUser(userHandle)).containsExactly(
-                new PackageKillableState("third_party_package", 11,
-                        PackageKillableState.KILLABLE_STATE_YES),
-                new PackageKillableState("vendor_package.critical", 11,
-                        PackageKillableState.KILLABLE_STATE_NEVER));
-
-        mCarWatchdogService.setKillablePackageAsUser("third_party_package", userHandle,
-                /* isKillable= */ false);
-        assertThrows(IllegalArgumentException.class,
-                () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
-                        userHandle, /* isKillable= */ false));
+        mockUmGetAliveUsers(mMockUserManager, 11, 12, 13);
+        injectPackageInfos(Collections.singletonList(
+                constructPackageManagerPackageInfo("third_party_package", 1303456, null)));
 
         PackageKillableStateSubject.assertThat(
-                mCarWatchdogService.getPackageKillableStatesAsUser(userHandle)).containsExactly(
+                mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
                 new PackageKillableState("third_party_package", 11,
                         PackageKillableState.KILLABLE_STATE_NO),
                 new PackageKillableState("vendor_package.critical", 11,
-                        PackageKillableState.KILLABLE_STATE_NEVER));
+                        PackageKillableState.KILLABLE_STATE_NEVER),
+                new PackageKillableState("third_party_package", 12,
+                        PackageKillableState.KILLABLE_STATE_YES),
+                new PackageKillableState("vendor_package.critical", 12,
+                        PackageKillableState.KILLABLE_STATE_NEVER),
+                new PackageKillableState("third_party_package", 13,
+                        PackageKillableState.KILLABLE_STATE_YES));
     }
 
     @Test
-    public void testSetKillablePackageAsUserWithNoPackageStats() throws Exception {
+    public void testSetKillablePackageAsUserWithSharedUids() throws Exception {
         mockUmGetAliveUsers(mMockUserManager, 11, 12);
-        injectPackageInfos(Arrays.asList("third_party_package", "vendor_package.critical"));
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo(
+                        "third_party_package.A", 1103456, "third_party_shared_package.A"),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.B", 1103456, "third_party_shared_package.A"),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.C", 1101356, "third_party_shared_package.B"),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.D", 1101356, "third_party_shared_package.B")));
 
         UserHandle userHandle = new UserHandle(11);
-        mCarWatchdogService.setKillablePackageAsUser("third_party_package", userHandle,
-                /* isKillable= */ true);
-        mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
-                userHandle, /* isKillable= */ true);
-
-        PackageKillableStateSubject.assertThat(
-                mCarWatchdogService.getPackageKillableStatesAsUser(userHandle)).containsExactly(
-                new PackageKillableState("third_party_package", 11,
-                        PackageKillableState.KILLABLE_STATE_YES),
-                new PackageKillableState("vendor_package.critical", 11,
-                        PackageKillableState.KILLABLE_STATE_NEVER));
-
-        mCarWatchdogService.setKillablePackageAsUser("third_party_package", userHandle,
+        mCarWatchdogService.setKillablePackageAsUser("third_party_package.A", userHandle,
                 /* isKillable= */ false);
-        assertThrows(IllegalArgumentException.class,
-                () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
-                        userHandle, /* isKillable= */ false));
 
         PackageKillableStateSubject.assertThat(
-                mCarWatchdogService.getPackageKillableStatesAsUser(userHandle)).containsExactly(
-                new PackageKillableState("third_party_package", 11,
+                mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
+                new PackageKillableState("third_party_package.A", 11,
                         PackageKillableState.KILLABLE_STATE_NO),
-                new PackageKillableState("vendor_package.critical", 11,
-                        PackageKillableState.KILLABLE_STATE_NEVER));
+                new PackageKillableState("third_party_package.B", 11,
+                        PackageKillableState.KILLABLE_STATE_NO),
+                new PackageKillableState("third_party_package.C", 11,
+                        PackageKillableState.KILLABLE_STATE_YES),
+                new PackageKillableState("third_party_package.D", 11,
+                        PackageKillableState.KILLABLE_STATE_YES));
+
+        mCarWatchdogService.setKillablePackageAsUser("third_party_package.B", userHandle,
+                /* isKillable= */ true);
+        mCarWatchdogService.setKillablePackageAsUser("third_party_package.C", userHandle,
+                /* isKillable= */ false);
+
+        PackageKillableStateSubject.assertThat(
+                mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
+                new PackageKillableState("third_party_package.A", 11,
+                        PackageKillableState.KILLABLE_STATE_YES),
+                new PackageKillableState("third_party_package.B", 11,
+                        PackageKillableState.KILLABLE_STATE_YES),
+                new PackageKillableState("third_party_package.C", 11,
+                        PackageKillableState.KILLABLE_STATE_NO),
+                new PackageKillableState("third_party_package.D", 11,
+                        PackageKillableState.KILLABLE_STATE_NO));
     }
 
     @Test
-    public void testSetKillablePackageAsUserForAllUsersWithPackageStats() throws Exception {
+    public void testSetKillablePackageAsUserForAllUsers() throws Exception {
         mockUmGetAliveUsers(mMockUserManager, 11, 12);
-        injectPackageInfos(Arrays.asList("third_party_package", "vendor_package.critical"));
-
-        mPackageNamesByUids.put(1103456, "third_party_package");
-        mPackageNamesByUids.put(1101278, "vendor_package.critical");
-        injectIoOveruseStatsForPackages(mPackageNamesByUids,
-                /* killablePackages= */new ArraySet<>(
-                        Collections.singletonList("third_party_package")),
-                /* shouldNotifyPackages= */new ArraySet<>());
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo("third_party_package", 1103456, null),
+                constructPackageManagerPackageInfo("vendor_package.critical", 1101278, null),
+                constructPackageManagerPackageInfo("third_party_package", 1203456, null),
+                constructPackageManagerPackageInfo("vendor_package.critical", 1201278, null)));
 
         mCarWatchdogService.setKillablePackageAsUser("third_party_package", UserHandle.ALL,
-                /* isKillable= */ true);
+                /* isKillable= */ false);
+        mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+                UserHandle.ALL, /* isKillable= */ false);
+
+        PackageKillableStateSubject.assertThat(
+                mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
+                new PackageKillableState("third_party_package", 11,
+                        PackageKillableState.KILLABLE_STATE_NO),
+                new PackageKillableState("vendor_package.critical", 11,
+                        PackageKillableState.KILLABLE_STATE_NEVER),
+                new PackageKillableState("third_party_package", 12,
+                        PackageKillableState.KILLABLE_STATE_NO),
+                new PackageKillableState("vendor_package.critical", 12,
+                        PackageKillableState.KILLABLE_STATE_NEVER));
+
         assertThrows(IllegalArgumentException.class,
                 () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
                         UserHandle.ALL, /* isKillable= */ true));
 
-        PackageKillableStateSubject.assertThat(
-                mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
-                new PackageKillableState("third_party_package", 11,
-                        PackageKillableState.KILLABLE_STATE_YES),
-                new PackageKillableState("vendor_package.critical", 11,
-                        PackageKillableState.KILLABLE_STATE_NEVER),
-                new PackageKillableState("third_party_package", 12,
-                        PackageKillableState.KILLABLE_STATE_YES),
-                new PackageKillableState("vendor_package.critical", 12,
-                        PackageKillableState.KILLABLE_STATE_NEVER));
-
-        mCarWatchdogService.setKillablePackageAsUser("third_party_package", UserHandle.ALL,
-                /* isKillable= */ false);
-        assertThrows(IllegalArgumentException.class,
-                () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
-                        UserHandle.ALL, /* isKillable= */ false));
+        mockUmGetAliveUsers(mMockUserManager, 11, 12, 13);
+        injectPackageInfos(Collections.singletonList(
+                constructPackageManagerPackageInfo("third_party_package", 1303456, null)));
 
         PackageKillableStateSubject.assertThat(
                 mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
@@ -695,52 +1134,71 @@
                 new PackageKillableState("third_party_package", 12,
                         PackageKillableState.KILLABLE_STATE_NO),
                 new PackageKillableState("vendor_package.critical", 12,
-                        PackageKillableState.KILLABLE_STATE_NEVER));
+                        PackageKillableState.KILLABLE_STATE_NEVER),
+                new PackageKillableState("third_party_package", 13,
+                        PackageKillableState.KILLABLE_STATE_NO));
     }
 
     @Test
-    public void testSetKillablePackageAsUserForAllUsersWithNoPackageStats() throws Exception {
+    public void testSetKillablePackageAsUsersForAllUsersWithSharedUids() throws Exception {
         mockUmGetAliveUsers(mMockUserManager, 11, 12);
-        injectPackageInfos(Arrays.asList("third_party_package", "vendor_package.critical"));
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo(
+                        "third_party_package.A", 1103456, "third_party_shared_package.A"),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.B", 1103456, "third_party_shared_package.A"),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.C", 1101356, "third_party_shared_package.B"),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.D", 1101356, "third_party_shared_package.B"),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.A", 1203456, "third_party_shared_package.A"),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.B", 1203456, "third_party_shared_package.A")));
 
-        mCarWatchdogService.setKillablePackageAsUser("third_party_package", UserHandle.ALL,
-                /* isKillable= */ true);
-        mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
-                UserHandle.ALL, /* isKillable= */ true);
-
-        PackageKillableStateSubject.assertThat(
-                mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
-                new PackageKillableState("third_party_package", 11,
-                        PackageKillableState.KILLABLE_STATE_YES),
-                new PackageKillableState("vendor_package.critical", 11,
-                        PackageKillableState.KILLABLE_STATE_NEVER),
-                new PackageKillableState("third_party_package", 12,
-                        PackageKillableState.KILLABLE_STATE_YES),
-                new PackageKillableState("vendor_package.critical", 12,
-                        PackageKillableState.KILLABLE_STATE_NEVER));
-
-        mCarWatchdogService.setKillablePackageAsUser("third_party_package", UserHandle.ALL,
+        mCarWatchdogService.setKillablePackageAsUser("third_party_package.A", UserHandle.ALL,
                 /* isKillable= */ false);
-        assertThrows(IllegalArgumentException.class,
-                () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
-                        UserHandle.ALL, /* isKillable= */ false));
 
         PackageKillableStateSubject.assertThat(
                 mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
-                new PackageKillableState("third_party_package", 11,
+                new PackageKillableState("third_party_package.A", 11,
                         PackageKillableState.KILLABLE_STATE_NO),
-                new PackageKillableState("vendor_package.critical", 11,
-                        PackageKillableState.KILLABLE_STATE_NEVER),
-                new PackageKillableState("third_party_package", 12,
+                new PackageKillableState("third_party_package.B", 11,
                         PackageKillableState.KILLABLE_STATE_NO),
-                new PackageKillableState("vendor_package.critical", 12,
-                        PackageKillableState.KILLABLE_STATE_NEVER));
+                new PackageKillableState("third_party_package.C", 11,
+                        PackageKillableState.KILLABLE_STATE_YES),
+                new PackageKillableState("third_party_package.D", 11,
+                        PackageKillableState.KILLABLE_STATE_YES),
+                new PackageKillableState("third_party_package.A", 12,
+                        PackageKillableState.KILLABLE_STATE_NO),
+                new PackageKillableState("third_party_package.B", 12,
+                        PackageKillableState.KILLABLE_STATE_NO));
+
+        mockUmGetAliveUsers(mMockUserManager, 11, 12, 13);
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo(
+                        "third_party_package.A", 1303456, "third_party_shared_package.A"),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.B", 1303456, "third_party_shared_package.A")));
+
+        PackageKillableStateSubject.assertThat(
+                mCarWatchdogService.getPackageKillableStatesAsUser(new UserHandle(13)))
+                .containsExactly(
+                new PackageKillableState("third_party_package.A", 13,
+                        PackageKillableState.KILLABLE_STATE_NO),
+                new PackageKillableState("third_party_package.B", 13,
+                        PackageKillableState.KILLABLE_STATE_NO));
     }
 
     @Test
     public void testGetPackageKillableStatesAsUser() throws Exception {
         mockUmGetAliveUsers(mMockUserManager, 11, 12);
-        injectPackageInfos(Arrays.asList("third_party_package", "vendor_package.critical"));
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo("third_party_package", 1103456, null),
+                constructPackageManagerPackageInfo("vendor_package.critical", 1101278, null),
+                constructPackageManagerPackageInfo("third_party_package", 1203456, null),
+                constructPackageManagerPackageInfo("vendor_package.critical", 1201278, null)));
+
         PackageKillableStateSubject.assertThat(
                 mCarWatchdogService.getPackageKillableStatesAsUser(new UserHandle(11)))
                 .containsExactly(
@@ -751,9 +1209,144 @@
     }
 
     @Test
+    public void testGetPackageKillableStatesAsUserWithSafeToKillPackages() throws Exception {
+        mockUmGetAliveUsers(mMockUserManager, 11, 12);
+        List<android.automotive.watchdog.internal.ResourceOveruseConfiguration> configs =
+                sampleInternalResourceOveruseConfigurations();
+        injectResourceOveruseConfigsAndWait(configs);
+
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo("system_package.non_critical.A", 1102459, null),
+                constructPackageManagerPackageInfo("third_party_package", 1103456, null),
+                constructPackageManagerPackageInfo("vendor_package.critical.B", 1101278, null),
+                constructPackageManagerPackageInfo("vendor_package.non_critical.A", 1105573, null),
+                constructPackageManagerPackageInfo("third_party_package", 1203456, null),
+                constructPackageManagerPackageInfo("vendor_package.critical.B", 1201278, null)));
+
+        PackageKillableStateSubject.assertThat(
+                mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL))
+                .containsExactly(
+                        new PackageKillableState("system_package.non_critical.A", 11,
+                                PackageKillableState.KILLABLE_STATE_YES),
+                        new PackageKillableState("third_party_package", 11,
+                                PackageKillableState.KILLABLE_STATE_YES),
+                        new PackageKillableState("vendor_package.critical.B", 11,
+                                PackageKillableState.KILLABLE_STATE_NEVER),
+                        new PackageKillableState("vendor_package.non_critical.A", 11,
+                                PackageKillableState.KILLABLE_STATE_YES),
+                        new PackageKillableState("third_party_package", 12,
+                                PackageKillableState.KILLABLE_STATE_YES),
+                        new PackageKillableState("vendor_package.critical.B", 12,
+                                PackageKillableState.KILLABLE_STATE_NEVER));
+    }
+
+    @Test
+    public void testGetPackageKillableStatesAsUserWithSharedUids() throws Exception {
+        mockUmGetAliveUsers(mMockUserManager, 11, 12);
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo(
+                        "system_package.A", 1103456, "vendor_shared_package.A"),
+                constructPackageManagerPackageInfo(
+                        "vendor_package.B", 1103456, "vendor_shared_package.A"),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.C", 1105678, "third_party_shared_package"),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.D", 1105678, "third_party_shared_package"),
+                constructPackageManagerPackageInfo(
+                        "system_package.A", 1203456, "vendor_shared_package.A"),
+                constructPackageManagerPackageInfo(
+                        "vendor_package.B", 1203456, "vendor_shared_package.A")));
+
+        PackageKillableStateSubject.assertThat(
+                mCarWatchdogService.getPackageKillableStatesAsUser(new UserHandle(11)))
+                .containsExactly(
+                        new PackageKillableState("system_package.A", 11,
+                                PackageKillableState.KILLABLE_STATE_NEVER),
+                        new PackageKillableState("vendor_package.B", 11,
+                                PackageKillableState.KILLABLE_STATE_NEVER),
+                        new PackageKillableState("third_party_package.C", 11,
+                                PackageKillableState.KILLABLE_STATE_YES),
+                        new PackageKillableState("third_party_package.D", 11,
+                                PackageKillableState.KILLABLE_STATE_YES));
+    }
+
+    @Test
+    public void testGetPackageKillableStatesAsUserWithSharedUidsAndSafeToKillPackages()
+            throws Exception {
+        mockUmGetAliveUsers(mMockUserManager, 11);
+        android.automotive.watchdog.internal.ResourceOveruseConfiguration vendorConfig =
+                new android.automotive.watchdog.internal.ResourceOveruseConfiguration();
+        vendorConfig.componentType = ComponentType.VENDOR;
+        vendorConfig.safeToKillPackages = Collections.singletonList(
+                "vendor_package.non_critical.A");
+        injectResourceOveruseConfigsAndWait(Collections.singletonList(vendorConfig));
+
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo(
+                        "vendor_package.non_critical.A", 1103456, "vendor_shared_package.A"),
+                constructPackageManagerPackageInfo(
+                        "system_package.A", 1103456, "vendor_shared_package.A"),
+                constructPackageManagerPackageInfo(
+                        "vendor_package.B", 1103456, "vendor_shared_package.A"),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.C", 1105678, "third_party_shared_package"),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.D", 1105678, "third_party_shared_package")));
+
+        PackageKillableStateSubject.assertThat(
+                mCarWatchdogService.getPackageKillableStatesAsUser(new UserHandle(11)))
+                .containsExactly(
+                        new PackageKillableState("vendor_package.non_critical.A", 11,
+                                PackageKillableState.KILLABLE_STATE_YES),
+                        new PackageKillableState("system_package.A", 11,
+                                PackageKillableState.KILLABLE_STATE_YES),
+                        new PackageKillableState("vendor_package.B", 11,
+                                PackageKillableState.KILLABLE_STATE_YES),
+                        new PackageKillableState("third_party_package.C", 11,
+                                PackageKillableState.KILLABLE_STATE_YES),
+                        new PackageKillableState("third_party_package.D", 11,
+                                PackageKillableState.KILLABLE_STATE_YES));
+    }
+
+    @Test
+    public void testGetPackageKillableStatesAsUserWithSharedUidsAndSafeToKillSharedPackage()
+            throws Exception {
+        mockUmGetAliveUsers(mMockUserManager, 11);
+        android.automotive.watchdog.internal.ResourceOveruseConfiguration vendorConfig =
+                new android.automotive.watchdog.internal.ResourceOveruseConfiguration();
+        vendorConfig.componentType = ComponentType.VENDOR;
+        vendorConfig.safeToKillPackages = Collections.singletonList(
+                "shared:vendor_shared_package.B");
+        injectResourceOveruseConfigsAndWait(Collections.singletonList(vendorConfig));
+
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo(
+                        "vendor_package.non_critical.A", 1103456, "vendor_shared_package.B"),
+                constructPackageManagerPackageInfo(
+                        "system_package.non_critical.A", 1103456, "vendor_shared_package.B"),
+                constructPackageManagerPackageInfo(
+                        "vendor_package.non_critical.B", 1103456, "vendor_shared_package.B")));
+
+        PackageKillableStateSubject.assertThat(
+                mCarWatchdogService.getPackageKillableStatesAsUser(new UserHandle(11)))
+                .containsExactly(
+                        new PackageKillableState("vendor_package.non_critical.A", 11,
+                                PackageKillableState.KILLABLE_STATE_YES),
+                        new PackageKillableState("system_package.non_critical.A", 11,
+                                PackageKillableState.KILLABLE_STATE_YES),
+                        new PackageKillableState("vendor_package.non_critical.B", 11,
+                                PackageKillableState.KILLABLE_STATE_YES));
+    }
+
+    @Test
     public void testGetPackageKillableStatesAsUserForAllUsers() throws Exception {
         mockUmGetAliveUsers(mMockUserManager, 11, 12);
-        injectPackageInfos(Arrays.asList("third_party_package", "vendor_package.critical"));
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo("third_party_package", 1103456, null),
+                constructPackageManagerPackageInfo("vendor_package.critical", 1101278, null),
+                constructPackageManagerPackageInfo("third_party_package", 1203456, null),
+                constructPackageManagerPackageInfo("vendor_package.critical", 1201278, null)));
+
         PackageKillableStateSubject.assertThat(
                 mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
                 new PackageKillableState("third_party_package", 11,
@@ -767,11 +1360,48 @@
     }
 
     @Test
+    public void testGetPackageKillableStatesAsUserForAllUsersWithSharedUids() throws Exception {
+        mockUmGetAliveUsers(mMockUserManager, 11, 12);
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo(
+                        "system_package.A", 1103456, "vendor_shared_package.A"),
+                constructPackageManagerPackageInfo(
+                        "vendor_package.B", 1103456, "vendor_shared_package.A"),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.C", 1105678, "third_party_shared_package"),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.D", 1105678, "third_party_shared_package"),
+                constructPackageManagerPackageInfo(
+                        "system_package.A", 1203456, "vendor_shared_package.A"),
+                constructPackageManagerPackageInfo(
+                        "vendor_package.B", 1203456, "vendor_shared_package.A")));
+
+        PackageKillableStateSubject.assertThat(
+                mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL))
+                .containsExactly(
+                        new PackageKillableState("system_package.A", 11,
+                                PackageKillableState.KILLABLE_STATE_NEVER),
+                        new PackageKillableState("vendor_package.B", 11,
+                                PackageKillableState.KILLABLE_STATE_NEVER),
+                        new PackageKillableState("third_party_package.C", 11,
+                                PackageKillableState.KILLABLE_STATE_YES),
+                        new PackageKillableState("third_party_package.D", 11,
+                                PackageKillableState.KILLABLE_STATE_YES),
+                        new PackageKillableState("system_package.A", 12,
+                                PackageKillableState.KILLABLE_STATE_NEVER),
+                        new PackageKillableState("vendor_package.B", 12,
+                                PackageKillableState.KILLABLE_STATE_NEVER));
+    }
+
+    @Test
     public void testSetResourceOveruseConfigurations() throws Exception {
         assertThat(mCarWatchdogService.setResourceOveruseConfigurations(
                 sampleResourceOveruseConfigurations(), CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO))
                 .isEqualTo(CarWatchdogManager.RETURN_CODE_SUCCESS);
 
+        /* Expect two calls, the first is made at car watchdog service init */
+        verifyResourceOveruseConfigurationsSynced(2);
+
         InternalResourceOveruseConfigurationSubject
                 .assertThat(captureOnSetResourceOveruseConfigurations())
                 .containsExactlyElementsIn(sampleInternalResourceOveruseConfigurations());
@@ -886,6 +1516,64 @@
     }
 
     @Test
+    public void testFailsSetResourceOveruseConfigurationsOnZeroComponentLevelIoOveruseThresholds()
+            throws Exception {
+        List<ResourceOveruseConfiguration> resourceOveruseConfigs =
+                Collections.singletonList(
+                        sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
+                                sampleIoOveruseConfigurationBuilder(ComponentType.SYSTEM)
+                                        .setComponentLevelThresholds(new PerStateBytes(200, 0, 200))
+                                        .build())
+                                .build());
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.setResourceOveruseConfigurations(resourceOveruseConfigs,
+                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO));
+    }
+
+    @Test
+    public void testFailsSetResourceOveruseConfigurationsOnEmptyIoOveruseSystemWideThresholds()
+            throws Exception {
+        List<ResourceOveruseConfiguration> resourceOveruseConfigs =
+                Collections.singletonList(
+                        sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
+                                sampleIoOveruseConfigurationBuilder(ComponentType.SYSTEM)
+                                        .setSystemWideThresholds(new ArrayList<>())
+                                        .build())
+                                .build());
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.setResourceOveruseConfigurations(resourceOveruseConfigs,
+                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO));
+    }
+
+    @Test
+    public void testFailsSetResourceOveruseConfigurationsOnIoOveruseInvalidSystemWideThreshold()
+            throws Exception {
+        List<ResourceOveruseConfiguration> resourceOveruseConfigs = new ArrayList<>();
+        resourceOveruseConfigs.add(sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
+                sampleIoOveruseConfigurationBuilder(ComponentType.SYSTEM)
+                        .setSystemWideThresholds(Collections.singletonList(
+                                new IoOveruseAlertThreshold(30, 0)))
+                        .build())
+                .build());
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.setResourceOveruseConfigurations(
+                        resourceOveruseConfigs,
+                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO));
+
+        resourceOveruseConfigs.set(0,
+                sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
+                        sampleIoOveruseConfigurationBuilder(ComponentType.SYSTEM)
+                                .setSystemWideThresholds(Collections.singletonList(
+                                        new IoOveruseAlertThreshold(0, 300)))
+                                .build())
+                        .build());
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.setResourceOveruseConfigurations(
+                        resourceOveruseConfigs,
+                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO));
+    }
+
+    @Test
     public void testFailsSetResourceOveruseConfigurationsOnNullIoOveruseConfiguration()
             throws Exception {
         List<ResourceOveruseConfiguration> resourceOveruseConfigs = Collections.singletonList(
@@ -916,7 +1604,8 @@
                 () -> mCarWatchdogService.getResourceOveruseConfigurations(
                         CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO));
 
-        verify(mMockCarWatchdogDaemon, never()).getResourceOveruseConfigurations();
+        /* Method initially called in CarWatchdogService init */
+        verify(mMockCarWatchdogDaemon).getResourceOveruseConfigurations();
     }
 
     @Test
@@ -991,14 +1680,19 @@
     @Test
     public void testLatestIoOveruseStats() throws Exception {
         int criticalSysPkgUid = Binder.getCallingUid();
-        int nonCriticalSysPkgUid = getUid(1056);
-        int nonCriticalVndrPkgUid = getUid(2564);
-        int thirdPartyPkgUid = getUid(2044);
+        int nonCriticalSysPkgUid = 1001056;
+        int nonCriticalVndrPkgUid = 1002564;
+        int thirdPartyPkgUid = 1002044;
 
-        mPackageNamesByUids.put(criticalSysPkgUid, "critical.system.package");
-        mPackageNamesByUids.put(nonCriticalSysPkgUid, "non_critical.system.package");
-        mPackageNamesByUids.put(nonCriticalVndrPkgUid, "non_critical.vendor.package");
-        mPackageNamesByUids.put(thirdPartyPkgUid, "third_party.package");
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo(
+                        "system_package.critical", criticalSysPkgUid, null),
+                constructPackageManagerPackageInfo(
+                        "system_package.non_critical", nonCriticalSysPkgUid, null),
+                constructPackageManagerPackageInfo(
+                        "vendor_package.non_critical", nonCriticalVndrPkgUid, null),
+                constructPackageManagerPackageInfo(
+                        "third_party_package", thirdPartyPkgUid, null)));
 
         IResourceOveruseListener mockSystemListener = createMockResourceOveruseListener();
         mCarWatchdogService.addResourceOveruseListenerForSystem(
@@ -1008,67 +1702,149 @@
         mCarWatchdogService.addResourceOveruseListener(
                 CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, mockListener);
 
-        IPackageManager packageManagerService = Mockito.spy(ActivityThread.getPackageManager());
-        when(ActivityThread.getPackageManager()).thenReturn(packageManagerService);
-        mockApplicationEnabledSettingAccessors(packageManagerService);
-
         List<PackageIoOveruseStats> packageIoOveruseStats = Arrays.asList(
                 /* Overuse occurred but cannot be killed/disabled. */
-                constructPackageIoOveruseStats(criticalSysPkgUid, /* shouldNotify= */true,
-                        constructInternalIoOveruseStats(/* killableOnOveruse= */false,
-                                /* remainingWriteBytes= */constructPerStateBytes(0, 0, 0),
-                                /* writtenBytes= */constructPerStateBytes(100, 200, 300),
-                                /* totalOveruses= */2)),
+                constructPackageIoOveruseStats(criticalSysPkgUid, /* shouldNotify= */ true,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ false,
+                                /* remainingWriteBytes= */ constructPerStateBytes(0, 0, 0),
+                                /* writtenBytes= */ constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */ 2)),
                 /* No overuse occurred but should be notified. */
-                constructPackageIoOveruseStats(nonCriticalSysPkgUid, /* shouldNotify= */true,
-                        constructInternalIoOveruseStats(/* killableOnOveruse= */true,
-                                /* remainingWriteBytes= */constructPerStateBytes(20, 30, 40),
-                                /* writtenBytes= */constructPerStateBytes(100, 200, 300),
-                                /* totalOveruses= */2)),
+                constructPackageIoOveruseStats(nonCriticalSysPkgUid, /* shouldNotify= */ true,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ true,
+                                /* remainingWriteBytes= */ constructPerStateBytes(20, 30, 40),
+                                /* writtenBytes= */ constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */ 2)),
                 /* Neither overuse occurred nor be notified. */
-                constructPackageIoOveruseStats(nonCriticalVndrPkgUid, /* shouldNotify= */false,
-                        constructInternalIoOveruseStats(/* killableOnOveruse= */true,
-                                /* remainingWriteBytes= */constructPerStateBytes(200, 300, 400),
-                                /* writtenBytes= */constructPerStateBytes(100, 200, 300),
-                                /* totalOveruses= */2)),
+                constructPackageIoOveruseStats(nonCriticalVndrPkgUid, /* shouldNotify= */ false,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ true,
+                                /* remainingWriteBytes= */ constructPerStateBytes(200, 300, 400),
+                                /* writtenBytes= */ constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */ 2)),
                 /* Overuse occurred and can be killed/disabled. */
-                constructPackageIoOveruseStats(thirdPartyPkgUid, /* shouldNotify= */true,
-                        constructInternalIoOveruseStats(/* killableOnOveruse= */true,
-                                /* remainingWriteBytes= */constructPerStateBytes(0, 0, 0),
-                                /* writtenBytes= */constructPerStateBytes(100, 200, 300),
-                                /* totalOveruses= */2)));
+                constructPackageIoOveruseStats(thirdPartyPkgUid, /* shouldNotify= */ true,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ true,
+                                /* remainingWriteBytes= */ constructPerStateBytes(0, 0, 0),
+                                /* writtenBytes= */ constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */ 2)));
 
-        mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+        pushLatestIoOveruseStatsAndWait(packageIoOveruseStats);
+
+        assertThat(mDisabledUserPackages).containsExactlyElementsIn(Collections.singleton(
+                "10:third_party_package"));
 
         List<ResourceOveruseStats> expectedStats = new ArrayList<>();
 
-        expectedStats.add(constructResourceOveruseStats(criticalSysPkgUid,
-                mPackageNamesByUids.get(criticalSysPkgUid),
+        expectedStats.add(constructResourceOveruseStats(
+                criticalSysPkgUid, "system_package.critical", 0,
                 packageIoOveruseStats.get(0).ioOveruseStats));
 
         verifyOnOveruseCalled(expectedStats, mockListener);
 
-        expectedStats.add(constructResourceOveruseStats(nonCriticalSysPkgUid,
-                mPackageNamesByUids.get(nonCriticalSysPkgUid),
+        expectedStats.add(constructResourceOveruseStats(
+                nonCriticalSysPkgUid, "system_package.non_critical", 0,
                 packageIoOveruseStats.get(1).ioOveruseStats));
 
-        expectedStats.add(constructResourceOveruseStats(thirdPartyPkgUid,
-                mPackageNamesByUids.get(thirdPartyPkgUid),
+        /*
+         * When the package receives overuse notification, the package is not yet killed so the
+         * totalTimesKilled counter is not yet incremented.
+         */
+        expectedStats.add(constructResourceOveruseStats(thirdPartyPkgUid, "third_party_package", 0,
                 packageIoOveruseStats.get(3).ioOveruseStats));
 
         verifyOnOveruseCalled(expectedStats, mockSystemListener);
 
-        verify(packageManagerService).getApplicationEnabledSetting(
-                mPackageNamesByUids.get(thirdPartyPkgUid), UserHandle.getUserId(thirdPartyPkgUid));
-        verify(packageManagerService).setApplicationEnabledSetting(
-                eq(mPackageNamesByUids.get(thirdPartyPkgUid)), anyInt(), anyInt(),
-                eq(UserHandle.getUserId(thirdPartyPkgUid)), anyString());
+        List<PackageResourceOveruseAction> expectedActions = Arrays.asList(
+                constructPackageResourceOveruseAction(
+                        "system_package.critical",
+                        criticalSysPkgUid, new int[]{ResourceType.IO}, NOT_KILLED),
+                constructPackageResourceOveruseAction(
+                        "third_party_package",
+                        thirdPartyPkgUid, new int[]{ResourceType.IO}, KILLED));
+        verifyActionsTakenOnResourceOveruse(expectedActions);
+    }
+
+    @Test
+    public void testLatestIoOveruseStatsWithSharedUid() throws Exception {
+        int criticalSysSharedUid = Binder.getCallingUid();
+        int nonCriticalVndrSharedUid = 1002564;
+        int thirdPartySharedUid = 1002044;
+
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo(
+                        "system_package.A", criticalSysSharedUid, "system_shared_package"),
+                constructPackageManagerPackageInfo(
+                        "system_package.B", criticalSysSharedUid, "system_shared_package"),
+                constructPackageManagerPackageInfo("vendor_package.non_critical",
+                        nonCriticalVndrSharedUid, "vendor_shared_package"),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.A", thirdPartySharedUid, "third_party_shared_package"),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.B", thirdPartySharedUid, "third_party_shared_package")
+        ));
+
+        IResourceOveruseListener mockSystemListener = createMockResourceOveruseListener();
+        mCarWatchdogService.addResourceOveruseListenerForSystem(
+                CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, mockSystemListener);
+
+        IResourceOveruseListener mockListener = createMockResourceOveruseListener();
+        mCarWatchdogService.addResourceOveruseListener(
+                CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, mockListener);
+
+        List<PackageIoOveruseStats> packageIoOveruseStats = Arrays.asList(
+                /* Overuse occurred but cannot be killed/disabled. */
+                constructPackageIoOveruseStats(criticalSysSharedUid, /* shouldNotify= */ true,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ false,
+                                /* remainingWriteBytes= */ constructPerStateBytes(0, 0, 0),
+                                /* writtenBytes= */ constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */ 2)),
+                /* No overuse occurred but should be notified. */
+                constructPackageIoOveruseStats(nonCriticalVndrSharedUid, /* shouldNotify= */ true,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ true,
+                                /* remainingWriteBytes= */ constructPerStateBytes(200, 300, 400),
+                                /* writtenBytes= */ constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */ 2)),
+                /* Overuse occurred and can be killed/disabled. */
+                constructPackageIoOveruseStats(thirdPartySharedUid, /* shouldNotify= */ true,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ true,
+                                /* remainingWriteBytes= */ constructPerStateBytes(0, 0, 0),
+                                /* writtenBytes= */ constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */ 2)));
+
+        pushLatestIoOveruseStatsAndWait(packageIoOveruseStats);
+
+        assertThat(mDisabledUserPackages).containsExactlyElementsIn(Arrays.asList(
+                "10:third_party_package.A", "10:third_party_package.B"));
+
+        List<ResourceOveruseStats> expectedStats = new ArrayList<>();
+
+        expectedStats.add(constructResourceOveruseStats(
+                criticalSysSharedUid, "shared:system_shared_package", 0,
+                packageIoOveruseStats.get(0).ioOveruseStats));
+
+        verifyOnOveruseCalled(expectedStats, mockListener);
+
+        expectedStats.add(constructResourceOveruseStats(
+                nonCriticalVndrSharedUid, "shared:vendor_shared_package", 0,
+                packageIoOveruseStats.get(1).ioOveruseStats));
+
+        /*
+         * When the package receives overuse notification, the package is not yet killed so the
+         * totalTimesKilled counter is not yet incremented.
+         */
+        expectedStats.add(constructResourceOveruseStats(
+                thirdPartySharedUid, "shared:third_party_shared_package", 0,
+                packageIoOveruseStats.get(2).ioOveruseStats));
+
+        verifyOnOveruseCalled(expectedStats, mockSystemListener);
 
         List<PackageResourceOveruseAction> expectedActions = Arrays.asList(
-                constructPackageResourceOveruseAction(mPackageNamesByUids.get(criticalSysPkgUid),
-                        criticalSysPkgUid, new int[]{ResourceType.IO}, NOT_KILLED),
-                constructPackageResourceOveruseAction(mPackageNamesByUids.get(thirdPartyPkgUid),
-                        thirdPartyPkgUid, new int[]{ResourceType.IO}, KILLED));
+                constructPackageResourceOveruseAction(
+                        "shared:system_shared_package",
+                        criticalSysSharedUid, new int[]{ResourceType.IO}, NOT_KILLED),
+                constructPackageResourceOveruseAction(
+                        "shared:third_party_shared_package",
+                        thirdPartySharedUid, new int[]{ResourceType.IO}, KILLED));
         verifyActionsTakenOnResourceOveruse(expectedActions);
     }
 
@@ -1086,12 +1862,141 @@
     }
 
     @Test
+    public void testPersistStatsOnShutdownEnter() throws Exception {
+        mockUmGetAliveUsers(mMockUserManager, 10, 11, 12);
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo(
+                        "third_party_package", 1103456, "vendor_shared_package.critical"),
+                constructPackageManagerPackageInfo(
+                        "vendor_package", 1103456, "vendor_shared_package.critical"),
+                constructPackageManagerPackageInfo("third_party_package.A", 1001100, null),
+                constructPackageManagerPackageInfo("third_party_package.A", 1201100, null)));
+
+        SparseArray<PackageIoOveruseStats> packageIoOveruseStatsByUid =
+                injectIoOveruseStatsForPackages(
+                        mGenericPackageNameByUid,
+                        /* killablePackages= */ new ArraySet<>(Collections.singletonList(
+                                "third_party_package.A")),
+                        /* shouldNotifyPackages= */ new ArraySet<>());
+
+        mCarWatchdogService.setKillablePackageAsUser(
+                "third_party_package.A", new UserHandle(12), /* isKillable= */ false);
+
+        mCarPowerStateListener.onStateChanged(CarPowerStateListener.SHUTDOWN_ENTER);
+        verify(mMockWatchdogStorage).saveIoUsageStats(any());
+        verify(mMockWatchdogStorage).saveUserPackageSettings(any());
+        mCarWatchdogService.release();
+        verify(mMockWatchdogStorage).release();
+        mCarWatchdogService = new CarWatchdogService(mMockContext, mMockWatchdogStorage);
+        mCarWatchdogService.init();
+        verifyDatabaseInit(/* wantedInvocations= */ 2);
+
+        List<ResourceOveruseStats> actualStats = mCarWatchdogService.getAllResourceOveruseStats(
+                CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* minimumStatsFlag= */ 0,
+                CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
+
+        List<ResourceOveruseStats> expectedStats = Arrays.asList(
+                constructResourceOveruseStats(
+                        /* uid= */ 1103456, "shared:vendor_shared_package.critical",
+                        /* totalTimesKilled= */ 0,
+                        packageIoOveruseStatsByUid.get(1103456).ioOveruseStats),
+                constructResourceOveruseStats(
+                        /* uid= */ 1001100, "third_party_package.A", /* totalTimesKilled= */ 0,
+                        packageIoOveruseStatsByUid.get(1001100).ioOveruseStats),
+                constructResourceOveruseStats(
+                        /* uid= */ 1201100, "third_party_package.A", /* totalTimesKilled= */ 0,
+                        packageIoOveruseStatsByUid.get(1201100).ioOveruseStats));
+
+        PackageKillableStateSubject.assertThat(
+                mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL))
+                .containsExactly(
+                        new PackageKillableState("third_party_package", 11,
+                                PackageKillableState.KILLABLE_STATE_NEVER),
+                        new PackageKillableState("vendor_package", 11,
+                                PackageKillableState.KILLABLE_STATE_NEVER),
+                        new PackageKillableState("third_party_package.A", 10,
+                                PackageKillableState.KILLABLE_STATE_YES),
+                        new PackageKillableState("third_party_package.A", 12,
+                                PackageKillableState.KILLABLE_STATE_NO));
+
+        ResourceOveruseStatsSubject.assertThat(actualStats)
+                .containsExactlyElementsIn(expectedStats);
+
+        verifyNoMoreInteractions(mMockWatchdogStorage);
+    }
+
+    @Test
+    public void testPersistIoOveruseStatsOnDateChange() throws Exception {
+        mockUmGetAliveUsers(mMockUserManager, 10);
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo("system_package", 1011200, null),
+                constructPackageManagerPackageInfo("third_party_package", 1001100, null)));
+
+        setDate(1);
+        List<PackageIoOveruseStats> prevDayStats = Arrays.asList(
+                constructPackageIoOveruseStats(1011200, /* shouldNotify= */ false,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ true,
+                                /* remainingWriteBytes= */ constructPerStateBytes(0, 0, 0),
+                                /* writtenBytes= */ constructPerStateBytes(600, 700, 800),
+                                /* totalOveruses= */ 2)),
+                constructPackageIoOveruseStats(1001100, /* shouldNotify= */ false,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ true,
+                                /* remainingWriteBytes= */ constructPerStateBytes(50, 60, 70),
+                                /* writtenBytes= */ constructPerStateBytes(1100, 1200, 1300),
+                                /* totalOveruses= */ 5)));
+        pushLatestIoOveruseStatsAndWait(prevDayStats);
+
+        List<WatchdogStorage.IoUsageStatsEntry> expectedSavedEntries = Arrays.asList(
+                new WatchdogStorage.IoUsageStatsEntry(/* userId= */ 10, "system_package",
+                new WatchdogPerfHandler.PackageIoUsage(prevDayStats.get(0).ioOveruseStats,
+                        /* forgivenWriteBytes= */ constructPerStateBytes(600, 700, 800),
+                        /* totalTimesKilled= */ 1)),
+                new WatchdogStorage.IoUsageStatsEntry(/* userId= */ 10, "third_party_package",
+                        new WatchdogPerfHandler.PackageIoUsage(prevDayStats.get(1).ioOveruseStats,
+                                /* forgivenWriteBytes= */ constructPerStateBytes(0, 0, 0),
+                                /* totalTimesKilled= */ 0)));
+
+        setDate(0);
+        List<PackageIoOveruseStats> currentDayStats = Arrays.asList(
+                constructPackageIoOveruseStats(1011200, /* shouldNotify= */ false,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ true,
+                                /* remainingWriteBytes= */ constructPerStateBytes(500, 550, 600),
+                                /* writtenBytes= */ constructPerStateBytes(100, 150, 200),
+                                /* totalOveruses= */ 0)),
+                constructPackageIoOveruseStats(1001100, /* shouldNotify= */ false,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */ true,
+                                /* remainingWriteBytes= */ constructPerStateBytes(250, 360, 470),
+                                /* writtenBytes= */ constructPerStateBytes(900, 900, 900),
+                                /* totalOveruses= */ 0)));
+        pushLatestIoOveruseStatsAndWait(currentDayStats);
+
+        IoUsageStatsEntrySubject.assertThat(mIoUsageStatsEntries)
+                .containsExactlyElementsIn(expectedSavedEntries);
+
+        List<ResourceOveruseStats> actualCurrentDayStats =
+                mCarWatchdogService.getAllResourceOveruseStats(
+                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* minimumStatsFlag= */ 0,
+                        CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
+
+        List<ResourceOveruseStats> expectedCurrentDayStats = Arrays.asList(
+                constructResourceOveruseStats(
+                        /* uid= */ 1011200, "system_package", /* totalTimesKilled= */ 0,
+                        currentDayStats.get(0).ioOveruseStats),
+                constructResourceOveruseStats(
+                        /* uid= */ 1001100, "third_party_package", /* totalTimesKilled= */ 0,
+                        currentDayStats.get(1).ioOveruseStats));
+
+        ResourceOveruseStatsSubject.assertThat(actualCurrentDayStats)
+                .containsExactlyElementsIn(expectedCurrentDayStats);
+    }
+
+    @Test
     public void testResetResourceOveruseStats() throws Exception {
-        mPackageNamesByUids.put(Binder.getCallingUid(), mMockContext.getPackageName());
-        mPackageNamesByUids.put(1101278, "vendor_package.critical");
-        injectIoOveruseStatsForPackages(mPackageNamesByUids,
-                /* killablePackages= */new ArraySet<>(),
-                /* shouldNotifyPackages= */new ArraySet<>());
+        mGenericPackageNameByUid.put(Binder.getCallingUid(), mMockContext.getPackageName());
+        mGenericPackageNameByUid.put(1101278, "vendor_package.critical");
+        injectIoOveruseStatsForPackages(
+                mGenericPackageNameByUid, /* killablePackages= */ new ArraySet<>(),
+                /* shouldNotifyPackages= */ new ArraySet<>());
 
         mWatchdogServiceForSystemImpl.resetResourceOveruseStats(
                 Collections.singletonList(mMockContext.getPackageName()));
@@ -1104,179 +2009,235 @@
                 mMockContext.getPackageName(),
                 UserHandle.getUserHandleForUid(Binder.getCallingUid())).build();
 
-        assertWithMessage("Expected: " + expectedStats.toString() + "\nActual: "
-                + actualStats.toString())
-                .that(ResourceOveruseStatsSubject.isEquals(actualStats, expectedStats)).isTrue();
+        ResourceOveruseStatsSubject.assertEquals(actualStats, expectedStats);
     }
 
     @Test
     public void testGetPackageInfosForUids() throws Exception {
-        int[] uids = new int[]{6001, 6050, 5100, 110035, 120056, 120078, 1345678};
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo(
+                        "system_package.A", 6001, null, ApplicationInfo.FLAG_SYSTEM, 0),
+                constructPackageManagerPackageInfo(
+                        "vendor_package.B", 5100, null, 0, ApplicationInfo.PRIVATE_FLAG_OEM),
+                constructPackageManagerPackageInfo(
+                        "vendor_package.C", 1345678, null, 0, ApplicationInfo.PRIVATE_FLAG_ODM),
+                constructPackageManagerPackageInfo("third_party_package.D", 120056, null)));
+
+        int[] uids = new int[]{6001, 5100, 120056, 1345678};
+        List<PackageInfo> actualPackageInfos = mWatchdogServiceForSystemImpl.getPackageInfosForUids(
+                uids, new ArrayList<>());
+
         List<PackageInfo> expectedPackageInfos = Arrays.asList(
-                constructPackageInfo("system.package.A", 6001, new ArrayList<>(),
+                constructPackageInfo("system_package.A", 6001, new ArrayList<>(),
                         UidType.NATIVE, ComponentType.SYSTEM, ApplicationCategoryType.OTHERS),
-                constructPackageInfo("shared:system.package", 6050,
-                        Arrays.asList("system.package.B", "third_party.package.C"),
-                        UidType.NATIVE, ComponentType.SYSTEM, ApplicationCategoryType.OTHERS),
-                constructPackageInfo("vendor.package.D", 5100, new ArrayList<>(),
+                constructPackageInfo("vendor_package.B", 5100, new ArrayList<>(),
                         UidType.NATIVE, ComponentType.VENDOR, ApplicationCategoryType.OTHERS),
-                constructPackageInfo("shared:vendor.package", 110035,
-                        Arrays.asList("vendor.package.E", "system.package.F",
-                                "third_party.package.G"), UidType.APPLICATION,
-                        ComponentType.VENDOR, ApplicationCategoryType.OTHERS),
-                constructPackageInfo("third_party.package.H", 120056, new ArrayList<>(),
+                constructPackageInfo("third_party_package.D", 120056, new ArrayList<>(),
                         UidType.APPLICATION, ComponentType.THIRD_PARTY,
                         ApplicationCategoryType.OTHERS),
-                constructPackageInfo("shared:third_party.package", 120078,
-                        Collections.singletonList("third_party.package.I"),
-                        UidType.APPLICATION,  ComponentType.THIRD_PARTY,
-                        ApplicationCategoryType.OTHERS),
-                constructPackageInfo("vendor.package.J", 1345678, new ArrayList<>(),
+                constructPackageInfo("vendor_package.C", 1345678, new ArrayList<>(),
                         UidType.APPLICATION, ComponentType.VENDOR,
                         ApplicationCategoryType.OTHERS));
 
-        for (PackageInfo packageInfo : expectedPackageInfos) {
-            mPackageNamesByUids.put(packageInfo.packageIdentifier.uid,
-                    packageInfo.packageIdentifier.name);
-            mSharedPackagesByUids.put(packageInfo.packageIdentifier.uid,
-                    packageInfo.sharedUidPackages.toArray(new String[0]));
-        }
+        assertPackageInfoEquals(actualPackageInfos, expectedPackageInfos);
+    }
 
-        mApplicationInfosByPackages.put("system.package.A",
-                constructApplicationInfo(ApplicationInfo.FLAG_SYSTEM, 0));
-        mApplicationInfosByPackages.put("system.package.B",
-                constructApplicationInfo(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP, 0));
-        mApplicationInfosByPackages.put("system.package.F",
-                constructApplicationInfo(0, ApplicationInfo.PRIVATE_FLAG_PRODUCT));
-        mApplicationInfosByPackages.put("vendor.package.D",
-                constructApplicationInfo(0, ApplicationInfo.PRIVATE_FLAG_OEM));
-        mApplicationInfosByPackages.put("vendor.package.E",
-                constructApplicationInfo(0, ApplicationInfo.PRIVATE_FLAG_VENDOR));
-        mApplicationInfosByPackages.put("vendor.package.J",
-                constructApplicationInfo(0, ApplicationInfo.PRIVATE_FLAG_ODM));
-        mApplicationInfosByPackages.put("third_party.package.C", constructApplicationInfo(0, 0));
-        mApplicationInfosByPackages.put("third_party.package.G", constructApplicationInfo(0, 0));
-        mApplicationInfosByPackages.put("third_party.package.H", constructApplicationInfo(0, 0));
-        mApplicationInfosByPackages.put("third_party.package.I", constructApplicationInfo(0, 0));
+    @Test
+    public void testGetPackageInfosWithSharedUids() throws Exception {
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo("system_package.A", 6050,
+                        "system_shared_package", ApplicationInfo.FLAG_UPDATED_SYSTEM_APP, 0),
+                constructPackageManagerPackageInfo("system_package.B", 110035,
+                        "vendor_shared_package", 0, ApplicationInfo.PRIVATE_FLAG_PRODUCT),
+                constructPackageManagerPackageInfo("vendor_package.C", 110035,
+                        "vendor_shared_package", 0, ApplicationInfo.PRIVATE_FLAG_VENDOR),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.D", 6050, "system_shared_package"),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.E", 110035, "vendor_shared_package"),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.F", 120078, "third_party_shared_package")));
 
+        int[] uids = new int[]{6050, 110035, 120056, 120078};
         List<PackageInfo> actualPackageInfos = mWatchdogServiceForSystemImpl.getPackageInfosForUids(
                 uids, new ArrayList<>());
 
+        List<PackageInfo> expectedPackageInfos = Arrays.asList(
+                constructPackageInfo("shared:system_shared_package", 6050,
+                        Arrays.asList("system_package.A", "third_party_package.D"),
+                        UidType.NATIVE, ComponentType.SYSTEM, ApplicationCategoryType.OTHERS),
+                constructPackageInfo("shared:vendor_shared_package", 110035,
+                        Arrays.asList("vendor_package.C", "system_package.B",
+                                "third_party_package.E"), UidType.APPLICATION,
+                        ComponentType.VENDOR, ApplicationCategoryType.OTHERS),
+                constructPackageInfo("shared:third_party_shared_package", 120078,
+                        Collections.singletonList("third_party_package.F"),
+                        UidType.APPLICATION,  ComponentType.THIRD_PARTY,
+                        ApplicationCategoryType.OTHERS));
+
         assertPackageInfoEquals(actualPackageInfos, expectedPackageInfos);
     }
 
     @Test
     public void testGetPackageInfosForUidsWithVendorPackagePrefixes() throws Exception {
-        int[] uids = new int[]{110034, 110035, 123456, 120078};
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo(
+                        "vendor_package.A", 110034, null, 0, ApplicationInfo.PRIVATE_FLAG_PRODUCT),
+                constructPackageManagerPackageInfo("vendor_pkg.B", 110035,
+                        "vendor_shared_package", ApplicationInfo.FLAG_SYSTEM, 0),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.C", 110035, "vendor_shared_package"),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.D", 110035, "vendor_shared_package"),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.F", 120078, "third_party_shared_package"),
+                constructPackageManagerPackageInfo("vndr_pkg.G", 126345, "vendor_package.shared",
+                        ApplicationInfo.FLAG_SYSTEM, 0),
+                /*
+                 * A 3p package pretending to be a vendor package because 3p packages won't have the
+                 * required flags.
+                 */
+                constructPackageManagerPackageInfo("vendor_package.imposter", 123456, null, 0, 0)));
+
+        int[] uids = new int[]{110034, 110035, 120078, 126345, 123456};
+        List<PackageInfo> actualPackageInfos = mWatchdogServiceForSystemImpl.getPackageInfosForUids(
+                uids, Arrays.asList("vendor_package.", "vendor_pkg.", "shared:vendor_package."));
+
         List<PackageInfo> expectedPackageInfos = Arrays.asList(
-                constructPackageInfo("vendor.package.D", 110034, new ArrayList<>(),
+                constructPackageInfo("vendor_package.A", 110034, new ArrayList<>(),
                         UidType.APPLICATION, ComponentType.VENDOR, ApplicationCategoryType.OTHERS),
-                constructPackageInfo("shared:vendor.package", 110035,
-                        Arrays.asList("vendor.pkg.E", "third_party.package.F",
-                                "third_party.package.G"), UidType.APPLICATION,
+                constructPackageInfo("shared:vendor_shared_package", 110035,
+                        Arrays.asList("vendor_pkg.B", "third_party_package.C",
+                                "third_party_package.D"), UidType.APPLICATION,
                         ComponentType.VENDOR, ApplicationCategoryType.OTHERS),
-                constructPackageInfo("vendor.package.imposter", 123456,
-                        new ArrayList<>(), UidType.APPLICATION, ComponentType.THIRD_PARTY,
-                        ApplicationCategoryType.OTHERS),
-                constructPackageInfo("third_party.package.H", 120078,
+                constructPackageInfo("shared:third_party_shared_package", 120078,
+                        Collections.singletonList("third_party_package.F"), UidType.APPLICATION,
+                        ComponentType.THIRD_PARTY, ApplicationCategoryType.OTHERS),
+                constructPackageInfo("shared:vendor_package.shared", 126345,
+                        Collections.singletonList("vndr_pkg.G"), UidType.APPLICATION,
+                        ComponentType.VENDOR, ApplicationCategoryType.OTHERS),
+                constructPackageInfo("vendor_package.imposter", 123456,
                         new ArrayList<>(), UidType.APPLICATION, ComponentType.THIRD_PARTY,
                         ApplicationCategoryType.OTHERS));
 
-        for (PackageInfo packageInfo : expectedPackageInfos) {
-            mPackageNamesByUids.put(packageInfo.packageIdentifier.uid,
-                    packageInfo.packageIdentifier.name);
-            mSharedPackagesByUids.put(packageInfo.packageIdentifier.uid,
-                    packageInfo.sharedUidPackages.toArray(new String[0]));
-        }
-
-        mApplicationInfosByPackages.put("vendor.package.D", constructApplicationInfo(0,
-                ApplicationInfo.PRIVATE_FLAG_PRODUCT));
-        mApplicationInfosByPackages.put("vendor.pkg.E",
-                constructApplicationInfo(ApplicationInfo.FLAG_SYSTEM, 0));
-        /*
-         * A 3p package pretending to be a vendor package because 3p packages won't have the
-         * required flags.
-         */
-        mApplicationInfosByPackages.put("vendor.package.imposter", constructApplicationInfo(0, 0));
-        mApplicationInfosByPackages.put("third_party.package.F", constructApplicationInfo(0, 0));
-        mApplicationInfosByPackages.put("third_party.package.G", constructApplicationInfo(0, 0));
-        mApplicationInfosByPackages.put("third_party.package.H", constructApplicationInfo(0, 0));
-
-        List<PackageInfo> actualPackageInfos = mWatchdogServiceForSystemImpl.getPackageInfosForUids(
-                uids, Arrays.asList("vendor.package.", "vendor.pkg."));
-
         assertPackageInfoEquals(actualPackageInfos, expectedPackageInfos);
     }
 
     @Test
     public void testGetPackageInfosForUidsWithMissingApplicationInfos() throws Exception {
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo(
+                        "vendor_package.A", 110034, null, 0, ApplicationInfo.PRIVATE_FLAG_OEM),
+                constructPackageManagerPackageInfo("vendor_package.B", 110035,
+                        "vendor_shared_package", 0, ApplicationInfo.PRIVATE_FLAG_VENDOR),
+                constructPackageManagerPackageInfo(
+                        "third_party_package.C", 110035, "vendor_shared_package")));
+
+        BiConsumer<Integer, String> addPackageToSharedUid = (uid, packageName) -> {
+            List<String> packages = mPackagesBySharedUid.get(uid);
+            if (packages == null) {
+                packages = new ArrayList<>();
+            }
+            packages.add(packageName);
+            mPackagesBySharedUid.put(uid, packages);
+        };
+
+        addPackageToSharedUid.accept(110035, "third_party.package.G");
+        mGenericPackageNameByUid.put(120056, "third_party.package.H");
+        mGenericPackageNameByUid.put(120078, "shared:third_party_shared_package");
+        addPackageToSharedUid.accept(120078, "third_party_package.I");
+
+
         int[] uids = new int[]{110034, 110035, 120056, 120078};
+
+        List<PackageInfo> actualPackageInfos = mWatchdogServiceForSystemImpl.getPackageInfosForUids(
+                uids, new ArrayList<>());
+
         List<PackageInfo> expectedPackageInfos = Arrays.asList(
-                constructPackageInfo("vendor.package.D", 110034, new ArrayList<>(),
+                constructPackageInfo("vendor_package.A", 110034, new ArrayList<>(),
                         UidType.APPLICATION, ComponentType.VENDOR, ApplicationCategoryType.OTHERS),
-                constructPackageInfo("shared:vendor.package", 110035,
-                        Arrays.asList("vendor.package.E", "third_party.package.F",
+                constructPackageInfo("shared:vendor_shared_package", 110035,
+                        Arrays.asList("vendor_package.B", "third_party_package.C",
                                 "third_party.package.G"),
                         UidType.APPLICATION, ComponentType.VENDOR, ApplicationCategoryType.OTHERS),
                 constructPackageInfo("third_party.package.H", 120056, new ArrayList<>(),
                         UidType.APPLICATION, ComponentType.UNKNOWN,
                         ApplicationCategoryType.OTHERS),
-                constructPackageInfo("shared:third_party.package", 120078,
-                        Collections.singletonList("third_party.package.I"),
+                constructPackageInfo("shared:third_party_shared_package", 120078,
+                        Collections.singletonList("third_party_package.I"),
                         UidType.APPLICATION, ComponentType.UNKNOWN,
                         ApplicationCategoryType.OTHERS));
 
-        for (PackageInfo packageInfo : expectedPackageInfos) {
-            mPackageNamesByUids.put(packageInfo.packageIdentifier.uid,
-                    packageInfo.packageIdentifier.name);
-            mSharedPackagesByUids.put(packageInfo.packageIdentifier.uid,
-                    packageInfo.sharedUidPackages.toArray(new String[0]));
-        }
-
-        mApplicationInfosByPackages.put("vendor.package.D", constructApplicationInfo(0,
-                ApplicationInfo.PRIVATE_FLAG_VENDOR));
-        mApplicationInfosByPackages.put("vendor.package.E", constructApplicationInfo(0,
-                ApplicationInfo.PRIVATE_FLAG_VENDOR));
-        mApplicationInfosByPackages.put("third_party.package.F", constructApplicationInfo(0, 0));
-
-        List<PackageInfo> actualPackageInfos = mWatchdogServiceForSystemImpl.getPackageInfosForUids(
-                uids, new ArrayList<>());
-
         assertPackageInfoEquals(actualPackageInfos, expectedPackageInfos);
     }
 
+    @Test
+    public void testSetProcessHealthCheckEnabled() throws Exception {
+        mCarWatchdogService.controlProcessHealthCheck(true);
+
+        verify(mMockCarWatchdogDaemon).controlProcessHealthCheck(eq(true));
+    }
+
+    @Test
+    public void testSetProcessHealthCheckEnabledWithDisconnectedDaemon() throws Exception {
+        crashWatchdogDaemon();
+
+        assertThrows(IllegalStateException.class,
+                () -> mCarWatchdogService.controlProcessHealthCheck(false));
+
+        verify(mMockCarWatchdogDaemon, never()).controlProcessHealthCheck(anyBoolean());
+    }
+
+    public static android.automotive.watchdog.PerStateBytes constructPerStateBytes(
+            long fgBytes, long bgBytes, long gmBytes) {
+        android.automotive.watchdog.PerStateBytes perStateBytes =
+                new android.automotive.watchdog.PerStateBytes();
+        perStateBytes.foregroundBytes = fgBytes;
+        perStateBytes.backgroundBytes = bgBytes;
+        perStateBytes.garageModeBytes = gmBytes;
+        return perStateBytes;
+    }
+
     private void mockWatchdogDaemon() {
         when(mMockBinder.queryLocalInterface(anyString())).thenReturn(mMockCarWatchdogDaemon);
         when(mMockCarWatchdogDaemon.asBinder()).thenReturn(mMockBinder);
         doReturn(mMockBinder).when(() -> ServiceManager.getService(CAR_WATCHDOG_DAEMON_INTERFACE));
+        mIsDaemonCrashed = false;
     }
 
-    private void mockPackageManager() throws Exception {
-        mPackageNamesByUids.clear();
-        mSharedPackagesByUids.clear();
-        mApplicationInfosByPackages.clear();
-        when(mMockPackageManager.getNamesForUids(any())).thenAnswer(args -> {
-            int[] uids = args.getArgument(0);
-            String[] packageNames = new String[uids.length];
-            for (int i = 0; i < uids.length; ++i) {
-                packageNames[i] = mPackageNamesByUids.get(uids[i], null);
-            }
-            return packageNames;
+    private void mockWatchdogStorage() {
+        when(mMockWatchdogStorage.saveUserPackageSettings(any())).thenAnswer((args) -> {
+            mUserPackageSettingsEntries.addAll(args.getArgument(0));
+            return true;
         });
-        when(mMockPackageManager.getPackagesForUid(anyInt())).thenAnswer(
-                args -> mSharedPackagesByUids.get(args.getArgument(0), null));
+        when(mMockWatchdogStorage.saveIoUsageStats(any())).thenAnswer((args) -> {
+            List<WatchdogStorage.IoUsageStatsEntry> ioUsageStatsEntries = args.getArgument(0);
+            for (WatchdogStorage.IoUsageStatsEntry entry : ioUsageStatsEntries) {
+                mIoUsageStatsEntries.add(
+                        new WatchdogStorage.IoUsageStatsEntry(entry.userId, entry.packageName,
+                                new WatchdogPerfHandler.PackageIoUsage(
+                                        entry.ioUsage.getInternalIoOveruseStats(),
+                                        entry.ioUsage.getForgivenWriteBytes(),
+                                        entry.ioUsage.getTotalTimesKilled())));
+            }
+            return true;
+        });
+        when(mMockWatchdogStorage.getUserPackageSettings()).thenReturn(mUserPackageSettingsEntries);
+        when(mMockWatchdogStorage.getTodayIoUsageStats()).thenReturn(mIoUsageStatsEntries);
+    }
 
-        when(mMockPackageManager.getApplicationInfoAsUser(any(), anyInt(), anyInt())).thenAnswer(
-                args -> {
-                    String packageName = args.getArgument(0);
-                    ApplicationInfo applicationInfo = mApplicationInfosByPackages
-                            .getOrDefault(packageName, /* defaultValue= */ null);
-                    if (applicationInfo == null) {
-                        throw new PackageManager.NameNotFoundException(
-                                "Package " + packageName + " not found exception");
-                    }
-                    return applicationInfo;
-                });
+    private void setupUsers() {
+        when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager);
+        mockUmGetAllUsers(mMockUserManager, new UserInfo[0]);
+    }
+
+    private void captureCarPowerStateListener() {
+        ArgumentCaptor<ICarPowerStateListener> receiverArgumentCaptor =
+                ArgumentCaptor.forClass(ICarPowerStateListener.class);
+        verify(mMockCarPowerManagementService).registerListener(receiverArgumentCaptor.capture());
+        mCarPowerStateListener = receiverArgumentCaptor.getValue();
+        assertWithMessage("Car power state listener must be non-null").that(mCarPowerStateListener)
+                .isNotNull();
     }
 
     private void captureBroadcastReceiver() {
@@ -1286,38 +2247,10 @@
                 .registerReceiverForAllUsers(receiverArgumentCaptor.capture(), any(), any(), any());
         mBroadcastReceiver = receiverArgumentCaptor.getValue();
         assertWithMessage("Broadcast receiver must be non-null").that(mBroadcastReceiver)
-                .isNotEqualTo(null);
+                .isNotNull();
     }
 
-    private void captureDaemonBinderDeathRecipient() throws Exception {
-        ArgumentCaptor<IBinder.DeathRecipient> deathRecipientCaptor =
-                ArgumentCaptor.forClass(IBinder.DeathRecipient.class);
-        verify(mMockBinder, timeout(MAX_WAIT_TIME_MS).atLeastOnce())
-                .linkToDeath(deathRecipientCaptor.capture(), anyInt());
-        mCarWatchdogDaemonBinderDeathRecipient = deathRecipientCaptor.getValue();
-    }
-
-    public void crashWatchdogDaemon() {
-        doReturn(null).when(() -> ServiceManager.getService(CAR_WATCHDOG_DAEMON_INTERFACE));
-        mCarWatchdogDaemonBinderDeathRecipient.binderDied();
-    }
-
-    public void restartWatchdogDaemonAndAwait() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-        doAnswer(args -> {
-            latch.countDown();
-            return null;
-        }).when(mMockBinder).linkToDeath(any(), anyInt());
-        mockWatchdogDaemon();
-        latch.await(MAX_WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-    }
-
-    private void setupUsers() {
-        when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager);
-        mockUmGetAllUsers(mMockUserManager, new UserInfo[0]);
-    }
-
-    private ICarWatchdogServiceForSystem registerCarWatchdogService() throws Exception {
+    private void captureWatchdogServiceForSystem() throws Exception {
         /* Registering to daemon is done on the main thread. To ensure the registration completes
          * before verification, execute an empty block on the main thread.
          */
@@ -1327,9 +2260,147 @@
                 ArgumentCaptor.forClass(ICarWatchdogServiceForSystem.class);
         verify(mMockCarWatchdogDaemon, atLeastOnce()).registerCarWatchdogService(
                 watchdogServiceForSystemImplCaptor.capture());
-        return watchdogServiceForSystemImplCaptor.getValue();
+        mWatchdogServiceForSystemImpl = watchdogServiceForSystemImplCaptor.getValue();
+        assertWithMessage("Car watchdog service for system must be non-null")
+                .that(mCarPowerStateListener).isNotNull();
     }
 
+    private void captureDaemonBinderDeathRecipient() throws Exception {
+        ArgumentCaptor<IBinder.DeathRecipient> deathRecipientCaptor =
+                ArgumentCaptor.forClass(IBinder.DeathRecipient.class);
+        verify(mMockBinder, timeout(MAX_WAIT_TIME_MS).atLeastOnce())
+                .linkToDeath(deathRecipientCaptor.capture(), anyInt());
+        mCarWatchdogDaemonBinderDeathRecipient = deathRecipientCaptor.getValue();
+        assertWithMessage("Binder death recipient must be non-null").that(mBroadcastReceiver)
+                .isNotNull();
+    }
+
+    private void verifyDatabaseInit(int wantedInvocations) throws Exception {
+        /*
+         * Database read is posted on a separate handler thread. Wait until the handler thread has
+         * processed the database read request before verifying.
+         */
+        CarServiceUtils.getHandlerThread(CarWatchdogService.class.getSimpleName())
+                .getThreadHandler().post(() -> {});
+        verify(mMockWatchdogStorage, times(wantedInvocations)).getUserPackageSettings();
+        verify(mMockWatchdogStorage, times(wantedInvocations)).getTodayIoUsageStats();
+    }
+
+    private void mockPackageManager() throws Exception {
+        when(mMockPackageManager.getNamesForUids(any())).thenAnswer(args -> {
+            int[] uids = args.getArgument(0);
+            String[] names = new String[uids.length];
+            for (int i = 0; i < uids.length; ++i) {
+                names[i] = mGenericPackageNameByUid.get(uids[i], null);
+            }
+            return names;
+        });
+        when(mMockPackageManager.getPackagesForUid(anyInt())).thenAnswer(args -> {
+            int uid = args.getArgument(0);
+            List<String> packages = mPackagesBySharedUid.get(uid);
+            return packages.toArray(new String[0]);
+        });
+        when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+                .thenAnswer(args -> {
+                    int userId = args.getArgument(2);
+                    String userPackageId = userId + ":" + args.getArgument(0);
+                    android.content.pm.PackageInfo packageInfo =
+                            mPmPackageInfoByUserPackage.get(userPackageId);
+                    if (packageInfo == null) {
+                        throw new PackageManager.NameNotFoundException(
+                                "User package id '" + userPackageId + "' not found");
+                    }
+                    return packageInfo.applicationInfo;
+                });
+        when(mMockPackageManager.getPackageInfoAsUser(anyString(), anyInt(), anyInt())).thenAnswer(
+                args -> {
+                    int userId = args.getArgument(2);
+                    String userPackageId = userId + ":" + args.getArgument(0);
+                    android.content.pm.PackageInfo packageInfo =
+                            mPmPackageInfoByUserPackage.get(userPackageId);
+                    if (packageInfo == null) {
+                        throw new PackageManager.NameNotFoundException(
+                                "User package id '" + userPackageId + "' not found");
+                    }
+                    return packageInfo;
+                });
+        when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt())).thenAnswer(
+                args -> {
+                    int userId = args.getArgument(1);
+                    List<android.content.pm.PackageInfo> packageInfos = new ArrayList<>();
+                    for (android.content.pm.PackageInfo packageInfo :
+                            mPmPackageInfoByUserPackage.values()) {
+                        if (UserHandle.getUserId(packageInfo.applicationInfo.uid) == userId) {
+                            packageInfos.add(packageInfo);
+                        }
+                    }
+                    return packageInfos;
+                });
+        IPackageManager pm = Mockito.spy(ActivityThread.getPackageManager());
+        when(ActivityThread.getPackageManager()).thenReturn(pm);
+        doAnswer((args) -> {
+            String value = args.getArgument(3) + ":" + args.getArgument(0);
+            mDisabledUserPackages.add(value);
+            return null;
+        }).when(pm).setApplicationEnabledSetting(
+                anyString(), eq(COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED), anyInt(),
+                anyInt(), anyString());
+        doReturn(COMPONENT_ENABLED_STATE_ENABLED).when(pm)
+                .getApplicationEnabledSetting(anyString(), anyInt());
+    }
+
+    private void crashWatchdogDaemon() {
+        doReturn(null).when(() -> ServiceManager.getService(CAR_WATCHDOG_DAEMON_INTERFACE));
+        mCarWatchdogDaemonBinderDeathRecipient.binderDied();
+        mIsDaemonCrashed = true;
+    }
+
+    private void restartWatchdogDaemonAndAwait() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        doAnswer(args -> {
+            latch.countDown();
+            return null;
+        }).when(mMockBinder).linkToDeath(any(), anyInt());
+        mockWatchdogDaemon();
+        latch.await(MAX_WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+        /* On daemon connect, CarWatchdogService posts a new message on the main thread to fetch
+         * the resource overuse configs. Post a message on the same thread and wait until the fetch
+         * completes, so the tests are deterministic.
+         */
+        CarServiceUtils.runOnMainSync(() -> {});
+    }
+
+    private void setDate(int numDaysAgo) {
+        TimeSourceInterface timeSource = new TimeSourceInterface() {
+            @Override
+            public Instant now() {
+                /* Return the same time, so the tests are deterministic. */
+                return mNow;
+            }
+
+            @Override
+            public String toString() {
+                return "Mocked date to " + now();
+            }
+
+            private final Instant mNow = Instant.now().minus(numDaysAgo, ChronoUnit.DAYS);
+        };
+        mCarWatchdogService.setTimeSource(timeSource);
+        mTimeSource = timeSource;
+    }
+
+    private void verifyResourceOveruseConfigurationsSynced(int wantedInvocations)
+            throws Exception {
+        /*
+         * Syncing the resource configuration in the service with the daemon is done on the main
+         * thread. To ensure the sync completes before verification, execute an empty block on the
+         * main thread.
+         */
+        CarServiceUtils.runOnMainSync(() -> {});
+        verify(mMockCarWatchdogDaemon, times(wantedInvocations)).getResourceOveruseConfigurations();
+    }
+
+
     private void testClientHealthCheck(TestClient client, int badClientCount) throws Exception {
         mCarWatchdogService.registerClient(client, TIMEOUT_CRITICAL);
         mWatchdogServiceForSystemImpl.checkIfAlive(123456, TIMEOUT_CRITICAL);
@@ -1352,54 +2423,75 @@
         return resourceOveruseConfigurationsCaptor.getValue();
     }
 
-    private void injectIoOveruseStatsForPackages(SparseArray<String> packageNamesByUid,
-            Set<String> killablePackages, Set<String> shouldNotifyPackages) throws Exception {
+    private void injectResourceOveruseConfigsAndWait(
+            List<android.automotive.watchdog.internal.ResourceOveruseConfiguration> configs)
+            throws Exception {
+        when(mMockCarWatchdogDaemon.getResourceOveruseConfigurations()).thenReturn(configs);
+        /* Trigger CarWatchdogService to fetch/sync resource overuse configurations by changing the
+         * daemon connection status from connected -> disconnected -> connected.
+         */
+        crashWatchdogDaemon();
+        restartWatchdogDaemonAndAwait();
+    }
+
+    private SparseArray<PackageIoOveruseStats> injectIoOveruseStatsForPackages(
+            SparseArray<String> genericPackageNameByUid, Set<String> killablePackages,
+            Set<String> shouldNotifyPackages) throws Exception {
+        SparseArray<PackageIoOveruseStats> packageIoOveruseStatsByUid = new SparseArray<>();
         List<PackageIoOveruseStats> packageIoOveruseStats = new ArrayList<>();
-        for (int i = 0; i < packageNamesByUid.size(); ++i) {
-            String packageName = packageNamesByUid.valueAt(i);
-            int uid = packageNamesByUid.keyAt(i);
-            packageIoOveruseStats.add(constructPackageIoOveruseStats(uid,
-                    shouldNotifyPackages.contains(packageName),
-                    constructInternalIoOveruseStats(killablePackages.contains(packageName),
-                            /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
-                            /* writtenBytes= */constructPerStateBytes(100, 200, 300),
-                            /* totalOveruses= */2)));
+        for (int i = 0; i < genericPackageNameByUid.size(); ++i) {
+            String name = genericPackageNameByUid.valueAt(i);
+            int uid = genericPackageNameByUid.keyAt(i);
+            PackageIoOveruseStats stats = constructPackageIoOveruseStats(uid,
+                    shouldNotifyPackages.contains(name),
+                    constructInternalIoOveruseStats(killablePackages.contains(name),
+                            /* remainingWriteBytes= */ constructPerStateBytes(20, 20, 20),
+                            /* writtenBytes= */ constructPerStateBytes(100, 200, 300),
+                            /* totalOveruses= */ 2));
+            packageIoOveruseStatsByUid.put(uid, stats);
+            packageIoOveruseStats.add(stats);
         }
-        mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+        pushLatestIoOveruseStatsAndWait(packageIoOveruseStats);
+        return packageIoOveruseStatsByUid;
     }
 
-    private void injectPackageInfos(List<String> packageNames) {
-        List<android.content.pm.PackageInfo> packageInfos = new ArrayList<>();
-        TriConsumer<String, Integer, Integer> addPackageInfo =
-                (packageName, flags, privateFlags) -> {
-                    android.content.pm.PackageInfo packageInfo =
-                            new android.content.pm.PackageInfo();
-                    packageInfo.packageName = packageName;
-                    packageInfo.applicationInfo = new ApplicationInfo();
-                    packageInfo.applicationInfo.flags = flags;
-                    packageInfo.applicationInfo.privateFlags = privateFlags;
-                    packageInfos.add(packageInfo);
-                };
-        for (String packageName : packageNames) {
-            if (packageName.startsWith("system")) {
-                addPackageInfo.accept(packageName, ApplicationInfo.FLAG_SYSTEM, 0);
-            } else if (packageName.startsWith("vendor")) {
-                addPackageInfo.accept(packageName, ApplicationInfo.FLAG_SYSTEM,
-                        ApplicationInfo.PRIVATE_FLAG_OEM);
-            } else {
-                addPackageInfo.accept(packageName, 0, 0);
+    private void injectPackageInfos(
+            List<android.content.pm.PackageInfo> packageInfos) {
+        for (android.content.pm.PackageInfo packageInfo : packageInfos) {
+            String genericPackageName = packageInfo.packageName;
+            int uid = packageInfo.applicationInfo.uid;
+            int userId = UserHandle.getUserId(uid);
+            if (packageInfo.sharedUserId != null) {
+                genericPackageName =
+                        PackageInfoHandler.SHARED_PACKAGE_PREFIX + packageInfo.sharedUserId;
+                List<String> packages = mPackagesBySharedUid.get(uid);
+                if (packages == null) {
+                    packages = new ArrayList<>();
+                }
+                packages.add(packageInfo.packageName);
+                mPackagesBySharedUid.put(uid, packages);
             }
+            String userPackageId = userId + ":" + packageInfo.packageName;
+            assertWithMessage("Duplicate package infos provided for user package id: %s",
+                    userPackageId).that(mPmPackageInfoByUserPackage.containsKey(userPackageId))
+                    .isFalse();
+            assertWithMessage("Mismatch generic package names for the same uid '%s'",
+                    uid).that(mGenericPackageNameByUid.get(uid, genericPackageName))
+                    .isEqualTo(genericPackageName);
+            mPmPackageInfoByUserPackage.put(userPackageId, packageInfo);
+            mGenericPackageNameByUid.put(uid, genericPackageName);
         }
-        when(mMockPackageManager.getInstalledPackagesAsUser(eq(0), anyInt()))
-                .thenReturn(packageInfos);
     }
 
-    private void mockApplicationEnabledSettingAccessors(IPackageManager pm) throws Exception {
-        doReturn(COMPONENT_ENABLED_STATE_ENABLED).when(pm)
-                .getApplicationEnabledSetting(anyString(), eq(UserHandle.myUserId()));
-
-        doNothing().when(pm).setApplicationEnabledSetting(anyString(), anyInt(),
-                anyInt(), eq(UserHandle.myUserId()), anyString());
+    private void pushLatestIoOveruseStatsAndWait(
+            List<PackageIoOveruseStats> packageIoOveruseStats) throws Exception {
+        mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+        /* The latestIoOveruseStats call performs resource overuse killing/disabling on the main
+         * thread by posting a new message with RESOURCE_OVERUSE_KILLING_DELAY_MILLS delay. Ensure
+         * this message is processed before returning so the effects of the killing/disabling is
+         * verified.
+         */
+        delayedRunOnMainSync(() -> {}, RESOURCE_OVERUSE_KILLING_DELAY_MILLS * 2);
     }
 
     private void verifyActionsTakenOnResourceOveruse(List<PackageResourceOveruseAction> expected)
@@ -1407,9 +2499,14 @@
         ArgumentCaptor<List<PackageResourceOveruseAction>> resourceOveruseActionsCaptor =
                 ArgumentCaptor.forClass((Class) List.class);
 
-        verify(mMockCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).actionTakenOnResourceOveruse(
+        verify(mMockCarWatchdogDaemon,
+                timeout(MAX_WAIT_TIME_MS).times(2)).actionTakenOnResourceOveruse(
                 resourceOveruseActionsCaptor.capture());
-        List<PackageResourceOveruseAction> actual = resourceOveruseActionsCaptor.getValue();
+        List<PackageResourceOveruseAction> actual = new ArrayList<>();
+        for (List<PackageResourceOveruseAction> actions :
+                resourceOveruseActionsCaptor.getAllValues()) {
+            actual.addAll(actions);
+        }
 
         assertThat(actual).comparingElementsUsing(
                 Correspondence.from(
@@ -1436,10 +2533,6 @@
                 .containsExactlyElementsIn(expectedStats);
     }
 
-    private static int getUid(int appId) {
-        return UserHandle.getUid(UserHandle.myUserId(), appId);
-    }
-
     private static List<ResourceOveruseConfiguration> sampleResourceOveruseConfigurations() {
         return Arrays.asList(
                 sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
@@ -1464,11 +2557,13 @@
 
     private static ResourceOveruseConfiguration.Builder sampleResourceOveruseConfigurationBuilder(
             int componentType, IoOveruseConfiguration ioOveruseConfig) {
-        String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType);
-        List<String> safeToKill = Arrays.asList(prefix + "_package.A", prefix + "_pkg.B");
+        String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType).toLowerCase();
+        List<String> safeToKill = Arrays.asList(prefix + "_package.non_critical.A",
+                prefix + "_pkg.non_critical.B");
         List<String> vendorPrefixes = Arrays.asList(prefix + "_package", prefix + "_pkg");
         Map<String, String> pkgToAppCategory = new ArrayMap<>();
-        pkgToAppCategory.put(prefix + "_package.A", "android.car.watchdog.app.category.MEDIA");
+        pkgToAppCategory.put(prefix + "_package.non_critical.A",
+                "android.car.watchdog.app.category.MEDIA");
         ResourceOveruseConfiguration.Builder configBuilder =
                 new ResourceOveruseConfiguration.Builder(componentType, safeToKill,
                         vendorPrefixes, pkgToAppCategory);
@@ -1478,28 +2573,28 @@
 
     private static IoOveruseConfiguration.Builder sampleIoOveruseConfigurationBuilder(
             int componentType) {
-        String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType);
+        String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType).toLowerCase();
         PerStateBytes componentLevelThresholds = new PerStateBytes(
-                /* foregroundModeBytes= */10, /* backgroundModeBytes= */20,
-                /* garageModeBytes= */30);
+                /* foregroundModeBytes= */ 10, /* backgroundModeBytes= */ 20,
+                /* garageModeBytes= */ 30);
         Map<String, PerStateBytes> packageSpecificThresholds = new ArrayMap<>();
         packageSpecificThresholds.put(prefix + "_package.A", new PerStateBytes(
-                /* foregroundModeBytes= */40, /* backgroundModeBytes= */50,
-                /* garageModeBytes= */60));
+                /* foregroundModeBytes= */ 40, /* backgroundModeBytes= */ 50,
+                /* garageModeBytes= */ 60));
 
         Map<String, PerStateBytes> appCategorySpecificThresholds = new ArrayMap<>();
         appCategorySpecificThresholds.put(
                 ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MEDIA,
-                new PerStateBytes(/* foregroundModeBytes= */100, /* backgroundModeBytes= */200,
-                        /* garageModeBytes= */300));
+                new PerStateBytes(/* foregroundModeBytes= */ 100, /* backgroundModeBytes= */ 200,
+                        /* garageModeBytes= */ 300));
         appCategorySpecificThresholds.put(
                 ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MAPS,
-                new PerStateBytes(/* foregroundModeBytes= */1100, /* backgroundModeBytes= */2200,
-                        /* garageModeBytes= */3300));
+                new PerStateBytes(/* foregroundModeBytes= */ 1100, /* backgroundModeBytes= */ 2200,
+                        /* garageModeBytes= */ 3300));
 
         List<IoOveruseAlertThreshold> systemWideThresholds = Collections.singletonList(
-                new IoOveruseAlertThreshold(/* durationInSeconds= */10,
-                        /* writtenBytesPerSecond= */200));
+                new IoOveruseAlertThreshold(/* durationInSeconds= */ 10,
+                        /* writtenBytesPerSecond= */ 200));
 
         return new IoOveruseConfiguration.Builder(componentLevelThresholds,
                 packageSpecificThresholds, appCategorySpecificThresholds, systemWideThresholds);
@@ -1508,15 +2603,16 @@
     private static android.automotive.watchdog.internal.ResourceOveruseConfiguration
             sampleInternalResourceOveruseConfiguration(int componentType,
             android.automotive.watchdog.internal.IoOveruseConfiguration ioOveruseConfig) {
-        String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType);
+        String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType).toLowerCase();
         android.automotive.watchdog.internal.ResourceOveruseConfiguration config =
                 new android.automotive.watchdog.internal.ResourceOveruseConfiguration();
         config.componentType = componentType;
-        config.safeToKillPackages = Arrays.asList(prefix + "_package.A", prefix + "_pkg.B");
+        config.safeToKillPackages = Arrays.asList(prefix + "_package.non_critical.A",
+                prefix + "_pkg.non_critical.B");
         config.vendorPackagePrefixes = Arrays.asList(prefix + "_package", prefix + "_pkg");
 
         PackageMetadata metadata = new PackageMetadata();
-        metadata.packageName = prefix + "_package.A";
+        metadata.packageName = prefix + "_package.non_critical.A";
         metadata.appCategoryType = ApplicationCategoryType.MEDIA;
         config.packageMetadata = Collections.singletonList(metadata);
 
@@ -1529,23 +2625,24 @@
 
     private static android.automotive.watchdog.internal.IoOveruseConfiguration
             sampleInternalIoOveruseConfiguration(int componentType) {
-        String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType);
+        String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType).toLowerCase();
         android.automotive.watchdog.internal.IoOveruseConfiguration config =
                 new android.automotive.watchdog.internal.IoOveruseConfiguration();
-        config.componentLevelThresholds = constructPerStateIoOveruseThreshold(prefix,
-                /* fgBytes= */10, /* bgBytes= */20, /* gmBytes= */30);
+        config.componentLevelThresholds = constructPerStateIoOveruseThreshold(
+                WatchdogPerfHandler.toComponentTypeStr(componentType), /* fgBytes= */ 10,
+                /* bgBytes= */ 20, /* gmBytes= */ 30);
         config.packageSpecificThresholds = Collections.singletonList(
-                constructPerStateIoOveruseThreshold(prefix + "_package.A", /* fgBytes= */40,
-                        /* bgBytes= */50, /* gmBytes= */60));
+                constructPerStateIoOveruseThreshold(prefix + "_package.A", /* fgBytes= */ 40,
+                        /* bgBytes= */ 50, /* gmBytes= */ 60));
         config.categorySpecificThresholds = Arrays.asList(
                 constructPerStateIoOveruseThreshold(
                         WatchdogPerfHandler.INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA,
-                        /* fgBytes= */100, /* bgBytes= */200, /* gmBytes= */300),
+                        /* fgBytes= */ 100, /* bgBytes= */ 200, /* gmBytes= */ 300),
                 constructPerStateIoOveruseThreshold(
                         WatchdogPerfHandler.INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS,
-                        /* fgBytes= */1100, /* bgBytes= */2200, /* gmBytes= */3300));
+                        /* fgBytes= */ 1100, /* bgBytes= */ 2200, /* gmBytes= */ 3300));
         config.systemWideThresholds = Collections.singletonList(
-                constructInternalIoOveruseAlertThreshold(/* duration= */10, /* writeBPS= */200));
+                constructInternalIoOveruseAlertThreshold(/* duration= */ 10, /* writeBPS= */ 200));
         return config;
     }
 
@@ -1578,22 +2675,33 @@
         return stats;
     }
 
-    private static ResourceOveruseStats constructResourceOveruseStats(int uid, String packageName,
+    private static ResourceOveruseStats constructResourceOveruseStats(
+            int uid, String packageName, int totalTimesKilled,
             android.automotive.watchdog.IoOveruseStats internalIoOveruseStats) {
-        IoOveruseStats ioOveruseStats =
-                WatchdogPerfHandler.toIoOveruseStatsBuilder(internalIoOveruseStats)
-                        .setKillableOnOveruse(internalIoOveruseStats.killableOnOveruse).build();
+        IoOveruseStats ioOveruseStats = WatchdogPerfHandler.toIoOveruseStatsBuilder(
+                internalIoOveruseStats, totalTimesKilled, internalIoOveruseStats.killableOnOveruse)
+                .build();
 
         return new ResourceOveruseStats.Builder(packageName, UserHandle.getUserHandleForUid(uid))
                 .setIoOveruseStats(ioOveruseStats).build();
     }
 
-    private static android.automotive.watchdog.IoOveruseStats constructInternalIoOveruseStats(
+    private android.automotive.watchdog.IoOveruseStats constructInternalIoOveruseStats(
             boolean killableOnOveruse,
             android.automotive.watchdog.PerStateBytes remainingWriteBytes,
             android.automotive.watchdog.PerStateBytes writtenBytes, int totalOveruses) {
+        return constructInternalIoOveruseStats(killableOnOveruse, STATS_DURATION_SECONDS,
+                remainingWriteBytes, writtenBytes, totalOveruses);
+    }
+
+    private android.automotive.watchdog.IoOveruseStats constructInternalIoOveruseStats(
+            boolean killableOnOveruse, long durationInSecs,
+            android.automotive.watchdog.PerStateBytes remainingWriteBytes,
+            android.automotive.watchdog.PerStateBytes writtenBytes, int totalOveruses) {
         android.automotive.watchdog.IoOveruseStats stats =
                 new android.automotive.watchdog.IoOveruseStats();
+        stats.startTime = mTimeSource.now().getEpochSecond();
+        stats.durationInSeconds = durationInSecs;
         stats.killableOnOveruse = killableOnOveruse;
         stats.remainingWriteBytes = remainingWriteBytes;
         stats.writtenBytes = writtenBytes;
@@ -1601,16 +2709,6 @@
         return stats;
     }
 
-    private static android.automotive.watchdog.PerStateBytes constructPerStateBytes(long fgBytes,
-            long bgBytes, long gmBytes) {
-        android.automotive.watchdog.PerStateBytes perStateBytes =
-                new android.automotive.watchdog.PerStateBytes();
-        perStateBytes.foregroundBytes = fgBytes;
-        perStateBytes.backgroundBytes = bgBytes;
-        perStateBytes.garageModeBytes = gmBytes;
-        return perStateBytes;
-    }
-
     private static PackageResourceOveruseAction constructPackageResourceOveruseAction(
             String packageName, int uid, int[] resourceTypes, int resourceOveruseActionType) {
         PackageResourceOveruseAction action = new PackageResourceOveruseAction();
@@ -1622,6 +2720,24 @@
         return action;
     }
 
+    private static void delayedRunOnMainSync(Runnable action, long delayMillis)
+            throws InterruptedException {
+        AtomicBoolean isComplete = new AtomicBoolean();
+        Handler handler = new Handler(Looper.getMainLooper());
+        handler.postDelayed(() -> {
+            action.run();
+            synchronized (action) {
+                isComplete.set(true);
+                action.notifyAll();
+            }
+        }, delayMillis);
+        synchronized (action) {
+            while (!isComplete.get()) {
+                action.wait();
+            }
+        }
+    }
+
     private class TestClient extends ICarWatchdogServiceCallback.Stub {
         protected int mLastSessionId = INVALID_SESSION_ID;
 
@@ -1718,7 +2834,7 @@
 
     private static boolean isPackageInfoEquals(PackageInfo lhs, PackageInfo rhs) {
         return isEquals(lhs.packageIdentifier, rhs.packageIdentifier)
-                && lhs.sharedUidPackages.equals(rhs.sharedUidPackages)
+                && lhs.sharedUidPackages.containsAll(rhs.sharedUidPackages)
                 && lhs.componentType == rhs.componentType
                 && lhs.appCategoryType == rhs.appCategoryType;
     }
@@ -1726,4 +2842,31 @@
     private static boolean isEquals(PackageIdentifier lhs, PackageIdentifier rhs) {
         return lhs.name.equals(rhs.name) && lhs.uid == rhs.uid;
     }
+
+    private static android.content.pm.PackageInfo constructPackageManagerPackageInfo(
+            String packageName, int uid, String sharedUserId) {
+        if (packageName.startsWith("system")) {
+            return constructPackageManagerPackageInfo(
+                    packageName, uid, sharedUserId, ApplicationInfo.FLAG_SYSTEM, 0);
+        }
+        if (packageName.startsWith("vendor")) {
+            return constructPackageManagerPackageInfo(
+                    packageName, uid, sharedUserId, ApplicationInfo.FLAG_SYSTEM,
+                    ApplicationInfo.PRIVATE_FLAG_OEM);
+        }
+        return constructPackageManagerPackageInfo(packageName, uid, sharedUserId, 0, 0);
+    }
+
+    private static android.content.pm.PackageInfo constructPackageManagerPackageInfo(
+            String packageName, int uid, String sharedUserId, int flags, int privateFlags) {
+        android.content.pm.PackageInfo packageInfo = new android.content.pm.PackageInfo();
+        packageInfo.packageName = packageName;
+        packageInfo.sharedUserId = sharedUserId;
+        packageInfo.applicationInfo = new ApplicationInfo();
+        packageInfo.applicationInfo.packageName = packageName;
+        packageInfo.applicationInfo.uid = uid;
+        packageInfo.applicationInfo.flags = flags;
+        packageInfo.applicationInfo.privateFlags = privateFlags;
+        return packageInfo;
+    }
 }
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/IoOveruseStatsSubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/IoOveruseStatsSubject.java
index 48b632f..d5afaf1 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/IoOveruseStatsSubject.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/IoOveruseStatsSubject.java
@@ -17,28 +17,34 @@
 package com.android.car.watchdog;
 
 import static com.google.common.truth.Truth.assertAbout;
-import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.annotation.Nullable;
 import android.car.watchdog.IoOveruseStats;
 
 import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.SimpleSubjectBuilder;
 import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
 
 public final class IoOveruseStatsSubject extends Subject {
-    // Boiler-plate Subject.Factory for IoOveruseStatsSubject
+    /* Boiler-plate Subject.Factory for IoOveruseStatsSubject. */
     private static final Subject.Factory<com.android.car.watchdog.IoOveruseStatsSubject,
             IoOveruseStats> IO_OVERUSE_STATS_SUBJECT_FACTORY =
             com.android.car.watchdog.IoOveruseStatsSubject::new;
 
     private final IoOveruseStats mActual;
 
-    // User-defined entry point
+    /* User-defined entry point. */
     public static IoOveruseStatsSubject assertThat(@Nullable IoOveruseStats stats) {
         return assertAbout(IO_OVERUSE_STATS_SUBJECT_FACTORY).that(stats);
     }
 
-    // Static method for getting the subject factory (for use with assertAbout())
+    public static SimpleSubjectBuilder<IoOveruseStatsSubject, IoOveruseStats> assertWithMessage(
+            String format, Object... args) {
+        return Truth.assertWithMessage(format, args).about(IO_OVERUSE_STATS_SUBJECT_FACTORY);
+    }
+
+    /* Static method for getting the subject factory (for use with assertAbout()). */
     public static Subject.Factory<IoOveruseStatsSubject, IoOveruseStats> ioOveruseStats() {
         return IO_OVERUSE_STATS_SUBJECT_FACTORY;
     }
@@ -49,7 +55,7 @@
         this.mActual = subject;
     }
 
-    // User-defined test assertion SPI below this point
+    /* User-defined test assertion SPI below this point. */
     public void isEqualTo(IoOveruseStats expected) {
         if (mActual == expected) {
             return;
@@ -65,8 +71,8 @@
                 .isEqualTo(expected.getTotalBytesWritten());
         check("isKillableOnOveruse()").that(mActual.isKillableOnOveruse())
                 .isEqualTo(expected.isKillableOnOveruse());
-        assertWithMessage("getRemainingWriteBytes()").about(PerStateBytesSubject.perStateBytes())
-                .that(mActual.getRemainingWriteBytes())
+        Truth.assertWithMessage("getRemainingWriteBytes()")
+                .about(PerStateBytesSubject.perStateBytes()).that(mActual.getRemainingWriteBytes())
                 .isEqualTo(expected.getRemainingWriteBytes());
     }
 
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/IoUsageStatsEntrySubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/IoUsageStatsEntrySubject.java
new file mode 100644
index 0000000..e683618
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/IoUsageStatsEntrySubject.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.watchdog;
+
+import static com.google.common.truth.Truth.assertAbout;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.annotation.Nullable;
+import android.automotive.watchdog.IoOveruseStats;
+import android.automotive.watchdog.PerStateBytes;
+
+import com.google.common.truth.Correspondence;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+
+import java.util.Arrays;
+
+public final class IoUsageStatsEntrySubject extends Subject {
+    /* Boiler-plate Subject.Factory for IoUsageStatsEntrySubject. */
+    private static final Subject.Factory<
+            com.android.car.watchdog.IoUsageStatsEntrySubject,
+            Iterable<WatchdogStorage.IoUsageStatsEntry>> IO_OVERUSE_STATS_ENTRY_SUBJECT_FACTORY =
+            com.android.car.watchdog.IoUsageStatsEntrySubject::new;
+    private static final String NULL_ENTRY_STRING = "{NULL}";
+
+    private final Iterable<WatchdogStorage.IoUsageStatsEntry> mActual;
+
+    /* User-defined entry point. */
+    public static IoUsageStatsEntrySubject assertThat(
+            @Nullable Iterable<WatchdogStorage.IoUsageStatsEntry> stats) {
+        return assertAbout(IO_OVERUSE_STATS_ENTRY_SUBJECT_FACTORY).that(stats);
+    }
+
+    public static Subject.Factory<IoUsageStatsEntrySubject,
+            Iterable<WatchdogStorage.IoUsageStatsEntry>> resourceOveruseStats() {
+        return IO_OVERUSE_STATS_ENTRY_SUBJECT_FACTORY;
+    }
+
+    public void containsExactly(WatchdogStorage.IoUsageStatsEntry... stats) {
+        containsExactlyElementsIn(Arrays.asList(stats));
+    }
+
+    public void containsExactlyElementsIn(Iterable<WatchdogStorage.IoUsageStatsEntry> expected) {
+        assertWithMessage("Expected stats(%s) equals to actual stats(%s)", toString(expected),
+                toString(mActual)).that(mActual)
+                .comparingElementsUsing(Correspondence.from(
+                        IoUsageStatsEntrySubject::isEquals, "is equal to"))
+                .containsExactlyElementsIn(expected);
+    }
+
+    public static boolean isEquals(WatchdogStorage.IoUsageStatsEntry actual,
+            WatchdogStorage.IoUsageStatsEntry expected) {
+        if (actual == null || expected == null) {
+            return (actual == null) && (expected == null);
+        }
+        return actual.userId == expected.userId && actual.packageName.equals(expected.packageName)
+                && actual.ioUsage.getTotalTimesKilled() == expected.ioUsage.getTotalTimesKilled()
+                && isEqualsPerStateBytes(actual.ioUsage.getForgivenWriteBytes(),
+                expected.ioUsage.getForgivenWriteBytes())
+                && isEqualsIoOveruseStats(actual.ioUsage.getInternalIoOveruseStats(),
+                expected.ioUsage.getInternalIoOveruseStats());
+    }
+
+    private static boolean isEqualsIoOveruseStats(android.automotive.watchdog.IoOveruseStats actual,
+            android.automotive.watchdog.IoOveruseStats expected) {
+        if (actual == null || expected == null) {
+            return (actual == null) && (expected == null);
+        }
+        return actual.killableOnOveruse == expected.killableOnOveruse
+                && isEqualsPerStateBytes(actual.remainingWriteBytes, expected.remainingWriteBytes)
+                && actual.startTime == expected.startTime
+                && actual.durationInSeconds == expected.durationInSeconds
+                && isEqualsPerStateBytes(actual.writtenBytes, expected.writtenBytes)
+                && actual.totalOveruses == expected.totalOveruses;
+    }
+
+    private static boolean isEqualsPerStateBytes(PerStateBytes actual, PerStateBytes expected) {
+        if (actual == null || expected == null) {
+            return (actual == null) && (expected == null);
+        }
+        return actual.foregroundBytes == expected.foregroundBytes
+                && actual.backgroundBytes == expected.backgroundBytes
+                && actual.garageModeBytes == expected.garageModeBytes;
+    }
+
+    private static String toString(Iterable<WatchdogStorage.IoUsageStatsEntry> entries) {
+        StringBuilder builder = new StringBuilder();
+        builder.append('[');
+        for (WatchdogStorage.IoUsageStatsEntry entry : entries) {
+            toStringBuilder(builder, entry).append(", ");
+        }
+        if (builder.length() > 1) {
+            builder.delete(builder.length() - 2, builder.length());
+        }
+        builder.append(']');
+        return builder.toString();
+    }
+
+    private static StringBuilder toStringBuilder(StringBuilder builder,
+            WatchdogStorage.IoUsageStatsEntry entry) {
+        builder.append("{UserId: ").append(entry.userId)
+                .append(", Package name: ").append(entry.packageName)
+                .append(", IoUsage: ");
+        toStringBuilder(builder, entry.ioUsage);
+        return builder.append('}');
+    }
+
+    private static StringBuilder toStringBuilder(StringBuilder builder,
+            WatchdogPerfHandler.PackageIoUsage ioUsage) {
+        builder.append("{IoOveruseStats: ");
+        toStringBuilder(builder, ioUsage.getInternalIoOveruseStats());
+        builder.append(", ForgivenWriteBytes: ");
+        toStringBuilder(builder, ioUsage.getForgivenWriteBytes());
+        return builder.append(", Total times killed: ").append(ioUsage.getTotalTimesKilled())
+                .append('}');
+    }
+
+    private static StringBuilder toStringBuilder(StringBuilder builder,
+            IoOveruseStats stats) {
+        if (stats == null) {
+            return builder.append(NULL_ENTRY_STRING);
+        }
+        builder.append("{Killable on overuse: ").append(stats.killableOnOveruse)
+                .append(", Remaining write bytes: ");
+        toStringBuilder(builder, stats.remainingWriteBytes);
+        builder.append(", Start time: ").append(stats.startTime)
+                .append(", Duration: ").append(stats.durationInSeconds).append(" seconds")
+                .append(", Total overuses: ").append(stats.totalOveruses)
+                .append(", Written bytes: ");
+        toStringBuilder(builder, stats.writtenBytes);
+        return builder.append('}');
+    }
+
+    private static StringBuilder toStringBuilder(
+            StringBuilder builder, PerStateBytes perStateBytes) {
+        if (perStateBytes == null) {
+            return builder.append(NULL_ENTRY_STRING);
+        }
+        return builder.append("{Foreground bytes: ").append(perStateBytes.foregroundBytes)
+                .append(", Background bytes: ").append(perStateBytes.backgroundBytes)
+                .append(", Garage mode bytes: ").append(perStateBytes.garageModeBytes)
+                .append('}');
+    }
+
+    private IoUsageStatsEntrySubject(FailureMetadata failureMetadata,
+            @Nullable Iterable<WatchdogStorage.IoUsageStatsEntry> iterableSubject) {
+        super(failureMetadata, iterableSubject);
+
+        mActual = iterableSubject;
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/ResourceOveruseStatsSubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/ResourceOveruseStatsSubject.java
index 6b7b7ba..b0ce702 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/ResourceOveruseStatsSubject.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/ResourceOveruseStatsSubject.java
@@ -17,6 +17,7 @@
 package com.android.car.watchdog;
 
 import static com.google.common.truth.Truth.assertAbout;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.annotation.Nullable;
 import android.car.watchdog.ResourceOveruseStats;
@@ -42,6 +43,11 @@
         return assertAbout(RESOURCE_OVERUSE_STATS_SUBJECT_FACTORY).that(stats);
     }
 
+    public static void assertEquals(ResourceOveruseStats actual, ResourceOveruseStats expected) {
+        assertWithMessage("Expected stats (%s) equals to actual stats (%s)", expected, actual)
+                .that(isEquals(actual, expected)).isTrue();
+    }
+
     public static Subject.Factory<ResourceOveruseStatsSubject, Iterable<ResourceOveruseStats>>
             resourceOveruseStats() {
         return RESOURCE_OVERUSE_STATS_SUBJECT_FACTORY;
@@ -71,7 +77,7 @@
         if (actual == null || expected == null) {
             return false;
         }
-        return actual.getPackageName() == expected.getPackageName()
+        return actual.getPackageName().equals(expected.getPackageName())
                 && actual.getUserHandle().equals(expected.getUserHandle())
                 && IoOveruseStatsSubject.isEquals(actual.getIoOveruseStats(),
                     expected.getIoOveruseStats());
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/UserPackageSettingsEntrySubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/UserPackageSettingsEntrySubject.java
new file mode 100644
index 0000000..cca55f6
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/UserPackageSettingsEntrySubject.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.watchdog;
+
+import static com.google.common.truth.Truth.assertAbout;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.annotation.Nullable;
+
+import com.google.common.truth.Correspondence;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+
+import java.util.Arrays;
+
+public final class UserPackageSettingsEntrySubject extends Subject {
+    /* Boiler-plate Subject.Factory for UserPackageSettingsEntrySubject. */
+    private static final Subject.Factory<
+            com.android.car.watchdog.UserPackageSettingsEntrySubject,
+            Iterable<WatchdogStorage.UserPackageSettingsEntry>>
+            USER_PACKAGE_SETTINGS_ENTRY_SUBJECT_FACTORY =
+            com.android.car.watchdog.UserPackageSettingsEntrySubject::new;
+
+    private final Iterable<WatchdogStorage.UserPackageSettingsEntry> mActual;
+
+    /* User-defined entry point. */
+    public static UserPackageSettingsEntrySubject assertThat(
+            @Nullable Iterable<WatchdogStorage.UserPackageSettingsEntry> stats) {
+        return assertAbout(USER_PACKAGE_SETTINGS_ENTRY_SUBJECT_FACTORY).that(stats);
+    }
+
+    public static Subject.Factory<UserPackageSettingsEntrySubject,
+            Iterable<WatchdogStorage.UserPackageSettingsEntry>> resourceOveruseStats() {
+        return USER_PACKAGE_SETTINGS_ENTRY_SUBJECT_FACTORY;
+    }
+
+    public void containsExactly(WatchdogStorage.UserPackageSettingsEntry... stats) {
+        containsExactlyElementsIn(Arrays.asList(stats));
+    }
+
+    public void containsExactlyElementsIn(
+            Iterable<WatchdogStorage.UserPackageSettingsEntry> expected) {
+        assertWithMessage("Expected entries (%s) equals to actual entries (%s)",
+                toString(expected), toString(mActual)).that(mActual)
+                .comparingElementsUsing(Correspondence.from(
+                        UserPackageSettingsEntrySubject::isEquals, "is equal to"))
+                .containsExactlyElementsIn(expected);
+    }
+
+    public static boolean isEquals(WatchdogStorage.UserPackageSettingsEntry actual,
+            WatchdogStorage.UserPackageSettingsEntry expected) {
+        if (actual == null || expected == null) {
+            return (actual == null) && (expected == null);
+        }
+        return actual.userId == expected.userId && actual.packageName.equals(expected.packageName)
+                && actual.killableState == expected.killableState;
+    }
+
+    private static String toString(Iterable<WatchdogStorage.UserPackageSettingsEntry> entries) {
+        StringBuilder builder = new StringBuilder();
+        builder.append("[");
+        for (WatchdogStorage.UserPackageSettingsEntry entry : entries) {
+            toStringBuilder(builder, entry).append(", ");
+        }
+        if (builder.length() > 1) {
+            builder.delete(builder.length() - 2, builder.length());
+        }
+        builder.append("]");
+        return builder.toString();
+    }
+
+    private static StringBuilder toStringBuilder(StringBuilder builder,
+            WatchdogStorage.UserPackageSettingsEntry entry) {
+        return builder.append("{UserId: ").append(entry.userId)
+                .append(", Package name: ").append(entry.packageName)
+                .append(", Killable state: ").append(entry.killableState).append("}");
+    }
+
+    private UserPackageSettingsEntrySubject(FailureMetadata failureMetadata,
+            @Nullable Iterable<WatchdogStorage.UserPackageSettingsEntry> iterableSubject) {
+        super(failureMetadata, iterableSubject);
+        this.mActual = iterableSubject;
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogStorageUnitTest.java b/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogStorageUnitTest.java
new file mode 100644
index 0000000..014cc23
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogStorageUnitTest.java
@@ -0,0 +1,421 @@
+/*
+ * 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.watchdog;
+
+import static android.car.watchdog.PackageKillableState.KILLABLE_STATE_NEVER;
+import static android.car.watchdog.PackageKillableState.KILLABLE_STATE_NO;
+import static android.car.watchdog.PackageKillableState.KILLABLE_STATE_YES;
+
+import static com.android.car.watchdog.WatchdogStorage.RETENTION_PERIOD;
+import static com.android.car.watchdog.WatchdogStorage.STATS_TEMPORAL_UNIT;
+import static com.android.car.watchdog.WatchdogStorage.WatchdogDbHelper.DATABASE_NAME;
+import static com.android.car.watchdog.WatchdogStorage.ZONE_OFFSET;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.automotive.watchdog.PerStateBytes;
+import android.car.watchdog.IoOveruseStats;
+import android.content.Context;
+import android.util.Slog;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.time.Instant;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * <p>This class contains unit tests for the {@link WatchdogStorage}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class WatchdogStorageUnitTest {
+    private static final String TAG = WatchdogStorageUnitTest.class.getSimpleName();
+
+    private WatchdogStorage mService;
+    private File mDatabaseFile;
+    private TimeSourceInterface mTimeSource;
+
+    @Before
+    public void setUp() throws Exception {
+        Context context = InstrumentationRegistry.getTargetContext();
+        mDatabaseFile = context.createDeviceProtectedStorageContext()
+                .getDatabasePath(DATABASE_NAME);
+        mService = new WatchdogStorage(context, /* useDataSystemCarDir= */ false);
+        setDate(/* numDaysAgo= */ 0);
+    }
+
+    @After
+    public void tearDown() {
+        mService.release();
+        if (!mDatabaseFile.delete()) {
+            Slog.e(TAG, "Failed to delete the database file: " + mDatabaseFile.getAbsolutePath());
+        }
+    }
+
+    @Test
+    public void testSaveUserPackageSettings() throws Exception {
+        List<WatchdogStorage.UserPackageSettingsEntry> expected = sampleSettings();
+
+        assertThat(mService.saveUserPackageSettings(expected)).isTrue();
+
+        UserPackageSettingsEntrySubject.assertThat(mService.getUserPackageSettings())
+                .containsExactlyElementsIn(expected);
+    }
+
+    @Test
+    public void testOverwriteUserPackageSettings() throws Exception {
+        List<WatchdogStorage.UserPackageSettingsEntry> expected = Arrays.asList(
+                new WatchdogStorage.UserPackageSettingsEntry(
+                        /* userId= */ 100, "system_package.non_critical.A", KILLABLE_STATE_YES),
+                new WatchdogStorage.UserPackageSettingsEntry(
+                        /* userId= */ 100, "system_package.non_critical.B", KILLABLE_STATE_NO));
+
+        assertThat(mService.saveUserPackageSettings(expected)).isTrue();
+
+        expected = Arrays.asList(
+                new WatchdogStorage.UserPackageSettingsEntry(
+                        /* userId= */ 100, "system_package.non_critical.A", KILLABLE_STATE_NEVER),
+                new WatchdogStorage.UserPackageSettingsEntry(
+                        /* userId= */ 100, "system_package.non_critical.B", KILLABLE_STATE_NO));
+
+        assertThat(mService.saveUserPackageSettings(expected)).isTrue();
+
+        UserPackageSettingsEntrySubject.assertThat(mService.getUserPackageSettings())
+                .containsExactlyElementsIn(expected);
+    }
+
+    @Test
+    public void testSaveAndGetIoOveruseStats() throws Exception {
+        injectSampleUserPackageSettings();
+        /* Start time aligned to the beginning of the day. */
+        long startTime = mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT)
+                .toEpochSecond();
+
+        assertWithMessage("Saved I/O usage stats successfully")
+                .that(mService.saveIoUsageStats(sampleStatsForDate(startTime, /* duration= */ 60)))
+                .isTrue();
+
+        long expectedDuration =
+                mTimeSource.now().atZone(ZONE_OFFSET).toEpochSecond() - startTime;
+        List<WatchdogStorage.IoUsageStatsEntry> expected = sampleStatsForDate(
+                startTime, expectedDuration);
+
+        IoUsageStatsEntrySubject.assertThat(mService.getTodayIoUsageStats())
+                .containsExactlyElementsIn(expected);
+    }
+
+    @Test
+    public void testSaveAndGetIoOveruseStatsWithOffsettedStartTime() throws Exception {
+        injectSampleUserPackageSettings();
+        /* Start time in the middle of the day. */
+        long startTime = mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT)
+                .plusHours(12).toEpochSecond();
+        List<WatchdogStorage.IoUsageStatsEntry> entries = sampleStatsForDate(
+                startTime, /* duration= */ 60);
+
+        assertWithMessage("Saved I/O usage stats successfully")
+                .that(mService.saveIoUsageStats(entries)).isTrue();
+
+        long expectedStartTime = mTimeSource.now().atZone(ZONE_OFFSET)
+                .truncatedTo(STATS_TEMPORAL_UNIT).toEpochSecond();
+        long expectedDuration =
+                mTimeSource.now().atZone(ZONE_OFFSET).toEpochSecond() - expectedStartTime;
+        List<WatchdogStorage.IoUsageStatsEntry> expected = sampleStatsForDate(
+                expectedStartTime, expectedDuration);
+
+        IoUsageStatsEntrySubject.assertThat(mService.getTodayIoUsageStats())
+                .containsExactlyElementsIn(expected);
+    }
+
+    @Test
+    public void testOverwriteIoOveruseStats() throws Exception {
+        injectSampleUserPackageSettings();
+        long startTime = mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT)
+                .toEpochSecond();
+        long duration = mTimeSource.now().atZone(ZONE_OFFSET).toEpochSecond() - startTime;
+
+        List<WatchdogStorage.IoUsageStatsEntry> expected = Collections.singletonList(
+                constructIoUsageStatsEntry(
+                        /* userId= */ 100, "system_package.non_critical.A", startTime, duration,
+                        /* remainingWriteBytes= */
+                        CarWatchdogServiceUnitTest.constructPerStateBytes(200, 300, 400),
+                        /* writtenBytes= */
+                        CarWatchdogServiceUnitTest.constructPerStateBytes(1000, 2000, 3000),
+                        /* forgivenWriteBytes= */
+                        CarWatchdogServiceUnitTest.constructPerStateBytes(100, 100, 100),
+                        /* totalOveruses= */ 2, /* totalTimesKilled= */ 1));
+
+        assertWithMessage("Saved I/O usage stats successfully")
+                .that(mService.saveIoUsageStats(expected)).isTrue();
+
+        IoUsageStatsEntrySubject.assertThat(mService.getTodayIoUsageStats())
+                .containsExactlyElementsIn(expected);
+
+        expected = Collections.singletonList(
+                constructIoUsageStatsEntry(
+                        /* userId= */ 100, "system_package.non_critical.A", startTime, duration,
+                        /* remainingWriteBytes= */
+                        CarWatchdogServiceUnitTest.constructPerStateBytes(400, 600, 800),
+                        /* writtenBytes= */
+                        CarWatchdogServiceUnitTest.constructPerStateBytes(2000, 3000, 4000),
+                        /* forgivenWriteBytes= */
+                        CarWatchdogServiceUnitTest.constructPerStateBytes(1200, 2300, 3400),
+                        /* totalOveruses= */ 4, /* totalTimesKilled= */ 2));
+
+        assertWithMessage("Saved I/O usage stats successfully")
+                .that(mService.saveIoUsageStats(expected)).isTrue();
+
+        IoUsageStatsEntrySubject.assertThat(mService.getTodayIoUsageStats())
+                .containsExactlyElementsIn(expected);
+    }
+
+    @Test
+    public void testSaveIoOveruseStatsOutsideRetentionPeriod() throws Exception {
+        injectSampleUserPackageSettings();
+        int retentionDaysAgo = RETENTION_PERIOD.getDays();
+
+        assertWithMessage("Saved I/O usage stats successfully")
+                .that(mService.saveIoUsageStats(sampleStatsBetweenDates(
+                        /* includingStartDaysAgo= */ retentionDaysAgo,
+                        /* excludingEndDaysAgo= */ retentionDaysAgo + 1))).isTrue();
+
+        assertWithMessage("Didn't fetch I/O overuse stats outside retention period")
+                .that(mService.getHistoricalIoOveruseStats(
+                        /* userId= */ 100, "system_package.non_critical.A", retentionDaysAgo))
+                .isNull();
+    }
+
+    @Test
+    public void testGetHistoricalIoOveruseStats() throws Exception {
+        injectSampleUserPackageSettings();
+
+        assertThat(mService.saveIoUsageStats(sampleStatsBetweenDates(
+                /* includingStartDaysAgo= */ 0, /* excludingEndDaysAgo= */ 5))).isTrue();
+
+        IoOveruseStats actual  = mService.getHistoricalIoOveruseStats(
+                /* userId= */ 100, "system_package.non_critical.A", /* numDaysAgo= */ 7);
+
+        assertWithMessage("Fetched I/O overuse stats").that(actual).isNotNull();
+
+        /*
+         * Returned stats shouldn't include stats for the current date as WatchdogPerfHandler fills
+         * the current day's stats.
+         */
+        ZonedDateTime currentDate = mTimeSource.now().atZone(ZONE_OFFSET)
+                .truncatedTo(STATS_TEMPORAL_UNIT);
+        long startTime = currentDate.minus(4, STATS_TEMPORAL_UNIT).toEpochSecond();
+        long duration = currentDate.toEpochSecond() - startTime;
+        IoOveruseStats expected = new IoOveruseStats.Builder(startTime, duration)
+                .setTotalOveruses(8).setTotalTimesKilled(4).setTotalBytesWritten(24_000).build();
+
+        IoOveruseStatsSubject.assertWithMessage(
+                "Fetched stats only for 4 days. Expected stats (%s) equals actual stats (%s)",
+                expected.toString(), actual.toString()).that(actual)
+                .isEqualTo(expected);
+    }
+
+    @Test
+    public void testGetHistoricalIoOveruseStatsWithNoRecentStats() throws Exception {
+        injectSampleUserPackageSettings();
+
+        assertThat(mService.saveIoUsageStats(sampleStatsBetweenDates(
+                /* includingStartDaysAgo= */ 3, /* excludingEndDaysAgo= */ 5))).isTrue();
+
+        IoOveruseStats actual  = mService.getHistoricalIoOveruseStats(
+                /* userId= */ 100, "system_package.non_critical.A", /* numDaysAgo= */ 7);
+
+        assertWithMessage("Fetched I/O overuse stats").that(actual).isNotNull();
+
+        /*
+         * Returned stats shouldn't include stats for the current date as WatchdogPerfHandler fills
+         * the current day's stats.
+         */
+        ZonedDateTime currentDate = mTimeSource.now().atZone(ZONE_OFFSET)
+                .truncatedTo(STATS_TEMPORAL_UNIT);
+        long startTime = currentDate.minus(4, STATS_TEMPORAL_UNIT).toEpochSecond();
+        long duration = currentDate.toEpochSecond() - startTime;
+        IoOveruseStats expected = new IoOveruseStats.Builder(startTime, duration)
+                .setTotalOveruses(4).setTotalTimesKilled(2).setTotalBytesWritten(12_000).build();
+
+        IoOveruseStatsSubject.assertWithMessage(
+                "Fetched stats only for 2 days. Expected stats (%s) equals actual stats (%s)",
+                expected.toString(), actual.toString()).that(actual)
+                .isEqualTo(expected);
+    }
+
+    @Test
+    public void testTruncateStatsOutsideRetentionPeriodOnDateChange() throws Exception {
+        injectSampleUserPackageSettings();
+        setDate(/* numDaysAgo= */ 1);
+
+        assertThat(mService.saveIoUsageStats(sampleStatsBetweenDates(
+                /* includingStartDaysAgo= */ 0, /* excludingEndDaysAgo= */ 40),
+                /* shouldCheckRetention= */ false)).isTrue();
+
+        IoOveruseStats actual  = mService.getHistoricalIoOveruseStats(
+                /* userId= */ 100, "system_package.non_critical.A", /* numDaysAgo= */ 40);
+
+        assertWithMessage("Fetched I/O overuse stats").that(actual).isNotNull();
+
+        ZonedDateTime currentDate = mTimeSource.now().atZone(ZONE_OFFSET)
+                .truncatedTo(STATS_TEMPORAL_UNIT);
+        long startTime = currentDate.minus(39, STATS_TEMPORAL_UNIT).toEpochSecond();
+        long duration = currentDate.toEpochSecond() - startTime;
+        IoOveruseStats expected = new IoOveruseStats.Builder(startTime, duration)
+                .setTotalOveruses(78).setTotalTimesKilled(39).setTotalBytesWritten(234_000).build();
+
+        IoOveruseStatsSubject.assertWithMessage(
+                "Fetched stats only for 39 days. Expected stats (%s) equals actual stats (%s)",
+                expected.toString(), actual.toString()).that(actual)
+                .isEqualTo(expected);
+
+        setDate(/* numDaysAgo= */ 0);
+        mService.shrinkDatabase();
+
+        actual = mService.getHistoricalIoOveruseStats(
+                /* userId= */ 100, "system_package.non_critical.A", /* numDaysAgo= */ 40);
+
+        assertWithMessage("Fetched I/O overuse stats").that(actual).isNotNull();
+
+        currentDate = mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT);
+        startTime = currentDate.minus(RETENTION_PERIOD.minusDays(1)).toEpochSecond();
+        duration = currentDate.toEpochSecond() - startTime;
+        expected = new IoOveruseStats.Builder(startTime, duration)
+                .setTotalOveruses(58).setTotalTimesKilled(29).setTotalBytesWritten(174_000).build();
+
+        IoOveruseStatsSubject.assertWithMessage("Fetched stats only within retention period. "
+                        + "Expected stats (%s) equals actual stats (%s)",
+                expected.toString(), actual.toString()).that(actual).isEqualTo(expected);
+    }
+
+    private void setDate(int numDaysAgo) {
+        TimeSourceInterface timeSource = new TimeSourceInterface() {
+            @Override
+            public Instant now() {
+                /* Return the same time, so the tests are deterministic. */
+                return mNow;
+            }
+
+            @Override
+            public String toString() {
+                return "Mocked date to " + now();
+            }
+
+            private final Instant mNow = Instant.now().minus(numDaysAgo, ChronoUnit.DAYS);
+        };
+        mService.setTimeSource(timeSource);
+        mTimeSource = timeSource;
+    }
+
+    private void injectSampleUserPackageSettings() throws Exception {
+        List<WatchdogStorage.UserPackageSettingsEntry> expected = sampleSettings();
+
+        assertThat(mService.saveUserPackageSettings(expected)).isTrue();
+    }
+
+    private static List<WatchdogStorage.UserPackageSettingsEntry> sampleSettings() {
+        return Arrays.asList(
+                new WatchdogStorage.UserPackageSettingsEntry(
+                        /* userId= */ 100, "system_package.non_critical.A", KILLABLE_STATE_YES),
+                new WatchdogStorage.UserPackageSettingsEntry(
+                        /* userId= */ 100, "system_package.non_critical.B", KILLABLE_STATE_NO),
+                new WatchdogStorage.UserPackageSettingsEntry(
+                        /* userId= */ 100, "vendor_package.critical.C", KILLABLE_STATE_NEVER),
+                new WatchdogStorage.UserPackageSettingsEntry(
+                        /* userId= */ 101, "system_package.non_critical.A", KILLABLE_STATE_NO),
+                new WatchdogStorage.UserPackageSettingsEntry(
+                        /* userId= */ 101, "system_package.non_critical.B", KILLABLE_STATE_YES),
+                new WatchdogStorage.UserPackageSettingsEntry(
+                        /* userId= */ 101, "vendor_package.critical.C", KILLABLE_STATE_NEVER));
+    }
+
+    private List<WatchdogStorage.IoUsageStatsEntry> sampleStatsBetweenDates(
+            int includingStartDaysAgo, int excludingEndDaysAgo) {
+        ZonedDateTime currentDate = mTimeSource.now().atZone(ZONE_OFFSET)
+                .truncatedTo(STATS_TEMPORAL_UNIT);
+        List<WatchdogStorage.IoUsageStatsEntry> entries = new ArrayList<>();
+        for (int i = includingStartDaysAgo; i < excludingEndDaysAgo; ++i) {
+            entries.addAll(sampleStatsForDate(
+                    currentDate.minus(i, STATS_TEMPORAL_UNIT).toEpochSecond(),
+                    STATS_TEMPORAL_UNIT.getDuration().toSeconds()));
+        }
+        return entries;
+    }
+
+    private static List<WatchdogStorage.IoUsageStatsEntry> sampleStatsForDate(
+            long statsDateEpoch, long duration) {
+        List<WatchdogStorage.IoUsageStatsEntry> entries = new ArrayList<>();
+        for (int i = 100; i < 101; ++i) {
+            entries.add(constructIoUsageStatsEntry(
+                    /* userId= */ i, "system_package.non_critical.A", statsDateEpoch, duration,
+                    /* remainingWriteBytes= */
+                    CarWatchdogServiceUnitTest.constructPerStateBytes(200, 300, 400),
+                    /* writtenBytes= */
+                    CarWatchdogServiceUnitTest.constructPerStateBytes(1000, 2000, 3000),
+                    /* forgivenWriteBytes= */
+                    CarWatchdogServiceUnitTest.constructPerStateBytes(100, 100, 100),
+                    /* totalOveruses= */ 2, /* totalTimesKilled= */ 1));
+            entries.add(constructIoUsageStatsEntry(
+                    /* userId= */ i, "vendor_package.critical.C", statsDateEpoch, duration,
+                    /* remainingWriteBytes= */
+                    CarWatchdogServiceUnitTest.constructPerStateBytes(500, 600, 700),
+                    /* writtenBytes= */
+                    CarWatchdogServiceUnitTest.constructPerStateBytes(4000, 5000, 6000),
+                    /* forgivenWriteBytes= */
+                    CarWatchdogServiceUnitTest.constructPerStateBytes(200, 200, 200),
+                    /* totalOveruses= */ 1, /* totalTimesKilled= */ 0));
+        }
+        return entries;
+    }
+
+    private static WatchdogStorage.IoUsageStatsEntry constructIoUsageStatsEntry(
+            int userId, String packageName, long startTime, long duration,
+            PerStateBytes remainingWriteBytes, PerStateBytes writtenBytes,
+            PerStateBytes forgivenWriteBytes, int totalOveruses, int totalTimesKilled) {
+        WatchdogPerfHandler.PackageIoUsage ioUsage = new WatchdogPerfHandler.PackageIoUsage(
+                constructInternalIoOveruseStats(startTime, duration, remainingWriteBytes,
+                        writtenBytes, totalOveruses), forgivenWriteBytes, totalTimesKilled);
+        return new WatchdogStorage.IoUsageStatsEntry(userId, packageName, ioUsage);
+    }
+
+    private static android.automotive.watchdog.IoOveruseStats constructInternalIoOveruseStats(
+            long startTime, long duration, PerStateBytes remainingWriteBytes,
+            PerStateBytes writtenBytes, int totalOveruses) {
+        android.automotive.watchdog.IoOveruseStats stats =
+                new android.automotive.watchdog.IoOveruseStats();
+        stats.startTime = startTime;
+        stats.durationInSeconds = duration;
+        stats.remainingWriteBytes = remainingWriteBytes;
+        stats.writtenBytes = writtenBytes;
+        stats.totalOveruses = totalOveruses;
+        return stats;
+    }
+}
diff --git a/tests/common_utils/src/com/android/car/test/FakeHandlerWrapper.java b/tests/common_utils/src/com/android/car/test/FakeHandlerWrapper.java
new file mode 100644
index 0000000..fe3071f
--- /dev/null
+++ b/tests/common_utils/src/com/android/car/test/FakeHandlerWrapper.java
@@ -0,0 +1,94 @@
+/*
+ * 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.test;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+
+/**
+ * A handler that allows control over when to dispatch messages and callbacks.
+ *
+ * <p>NOTE: Currently only supports {@link Runnable} messages. It doesn't dispatch regular messages.
+ *
+ * <p>Usage: Create an instance of {@link FakeHandlerWrapper}, and use {@link #getMockHandler()}
+ * in your test classes.
+ *
+ * <p>The implementation uses {@link Mockito} to bypass {@code final} keywords.
+ */
+public class FakeHandlerWrapper {
+    private Mode mMode;
+    private ArrayList<Message> mQueuedMessages = new ArrayList<>();
+
+    private final Handler mMockHandler;
+
+    public FakeHandlerWrapper(Looper looper, Mode mode) {
+        mMockHandler = Mockito.spy(new Handler(looper));
+        mMode = mode;
+        // Stubbing #sendMessageAtTime(Message, long).
+        Mockito.doAnswer(invocation -> {
+            Message msg = invocation.getArgument(0);
+            msg.when = invocation.getArgument(1);  // uptimeMillis
+            mQueuedMessages.add(msg);
+            if (mMode == Mode.IMMEDIATE) {
+                dispatchQueuedMessages();
+            }
+            return true;
+        }).when(mMockHandler).sendMessageAtTime(Mockito.any(), Mockito.anyLong());
+        // Stubbing #removeCallbacks(Runnable).
+        Mockito.doAnswer(invocation -> {
+            Runnable callback = invocation.getArgument(0);
+            return mQueuedMessages.removeIf(msg -> msg.getCallback() == callback);
+        }).when(mMockHandler).removeCallbacks(Mockito.any());
+    }
+
+    public Handler getMockHandler() {
+        return mMockHandler;
+    }
+
+    public void setMode(Mode mode) {
+        mMode = mode;
+    }
+
+    /** Dispatch any messages that have been queued on the calling thread. */
+    public void dispatchQueuedMessages() {
+        ArrayList<Message> messages = new ArrayList<>(mQueuedMessages);
+        mQueuedMessages.clear();
+        for (Message msg : messages) {
+            Runnable callback = msg.getCallback();
+            if (callback != null) {
+                callback.run();
+            }
+        }
+    }
+
+    /** Returns the queued messages list. */
+    public ArrayList<Message> getQueuedMessages() {
+        return new ArrayList<>(mQueuedMessages);
+    }
+
+    public enum Mode {
+        /** Messages are dispatched immediately on the calling thread. */
+        IMMEDIATE,
+        /** Messages are queued until {@link #dispatchQueuedMessages()} is called. */
+        QUEUEING,
+    }
+}
diff --git a/tests/common_utils/src/com/android/car/test/FakeSharedPreferences.java b/tests/common_utils/src/com/android/car/test/FakeSharedPreferences.java
new file mode 100644
index 0000000..ac85e52
--- /dev/null
+++ b/tests/common_utils/src/com/android/car/test/FakeSharedPreferences.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.test;
+
+import android.content.SharedPreferences;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/** Fake class for {@link SharedPreferences} to be used in tests. */
+public class FakeSharedPreferences implements SharedPreferences, SharedPreferences.Editor {
+    private final HashMap<String, Object> mValues = new HashMap<>();
+    private final HashMap<String, Object> mTempValues = new HashMap<>();
+
+    @Override
+    public Editor edit() {
+        return this;
+    }
+
+    @Override
+    public boolean contains(String key) {
+        return mValues.containsKey(key);
+    }
+
+    @Override
+    public Map<String, ?> getAll() {
+        return new HashMap<>(mValues);
+    }
+
+    @Override
+    public boolean getBoolean(String key, boolean defValue) {
+        if (mValues.containsKey(key)) {
+            return ((Boolean) mValues.get(key)).booleanValue();
+        }
+        return defValue;
+    }
+
+    @Override
+    public float getFloat(String key, float defValue) {
+        if (mValues.containsKey(key)) {
+            return ((Float) mValues.get(key)).floatValue();
+        }
+        return defValue;
+    }
+
+    @Override
+    public int getInt(String key, int defValue) {
+        if (mValues.containsKey(key)) {
+            return ((Integer) mValues.get(key)).intValue();
+        }
+        return defValue;
+    }
+
+    @Override
+    public long getLong(String key, long defValue) {
+        if (mValues.containsKey(key)) {
+            return ((Long) mValues.get(key)).longValue();
+        }
+        return defValue;
+    }
+
+    @Override
+    public String getString(String key, String defValue) {
+        if (mValues.containsKey(key)) {
+            return (String) mValues.get(key);
+        }
+        return defValue;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Set<String> getStringSet(String key, Set<String> defValues) {
+        if (mValues.containsKey(key)) {
+            return (Set<String>) mValues.get(key);
+        }
+        return defValues;
+    }
+
+    @Override
+    public void registerOnSharedPreferenceChangeListener(
+            OnSharedPreferenceChangeListener listener) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void unregisterOnSharedPreferenceChangeListener(
+            OnSharedPreferenceChangeListener listener) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Editor putBoolean(String key, boolean value) {
+        mTempValues.put(key, Boolean.valueOf(value));
+        return this;
+    }
+
+    @Override
+    public Editor putFloat(String key, float value) {
+        mTempValues.put(key, value);
+        return this;
+    }
+
+    @Override
+    public Editor putInt(String key, int value) {
+        mTempValues.put(key, value);
+        return this;
+    }
+
+    @Override
+    public Editor putLong(String key, long value) {
+        mTempValues.put(key, value);
+        return this;
+    }
+
+    @Override
+    public Editor putString(String key, String value) {
+        mTempValues.put(key, value);
+        return this;
+    }
+
+    @Override
+    public Editor putStringSet(String key, Set<String> values) {
+        mTempValues.put(key, values);
+        return this;
+    }
+
+    @Override
+    public Editor remove(String key) {
+        mTempValues.remove(key);
+        return this;
+    }
+
+    @Override
+    public Editor clear() {
+        mTempValues.clear();
+        return this;
+    }
+
+    @Override
+    public boolean commit() {
+        mValues.clear();
+        mValues.putAll(mTempValues);
+        return true;
+    }
+
+    @Override
+    public void apply() {
+        commit();
+    }
+}