Merge "Apply Theme.CarUi to CarUiRecyclerView screen" into sc-v2-dev
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 1b105cc..926a882 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -12,6 +12,9 @@
           "include-filter": "android.car.apitest.PreInstalledPackagesTest"
         }
       ]
+    },
+    {
+      "name": "SampleCustomInputServiceTest"
     }
   ]
 }
\ No newline at end of file
diff --git a/car-lib/src/android/car/VehiclePropertyIds.java b/car-lib/src/android/car/VehiclePropertyIds.java
index f229e39..450732d 100644
--- a/car-lib/src/android/car/VehiclePropertyIds.java
+++ b/car-lib/src/android/car/VehiclePropertyIds.java
@@ -334,8 +334,11 @@
      *  <li>Long[4] = rear left ticks
      * </ul>
      *
-     * <p>configArray is used to indicate the micrometers-per-wheel-tick value and
-     * which wheels are supported. configArray is set as follows:
+     * <p>configArray is used to indicate the micrometers-per-wheel-tick values and
+     * which wheels are supported. Each micrometers-per-wheel-tick value is static (i.e. will not
+     * update based on wheel's status) and a best approximation. For example, if a vehicle has
+     * multiple rim/tire size options, the micrometers-per-wheel-tick values are set to those for
+     * the typically expected rim/tire size. configArray is set as follows:
      *
      * <ul>
      *  <li>configArray[0], bits [0:3] = supported wheels.  Uses {@link VehicleAreaWheel}.
diff --git a/car-lib/src/android/car/content/pm/CarPackageManager.java b/car-lib/src/android/car/content/pm/CarPackageManager.java
index e66d851..27eff37 100644
--- a/car-lib/src/android/car/content/pm/CarPackageManager.java
+++ b/car-lib/src/android/car/content/pm/CarPackageManager.java
@@ -16,20 +16,28 @@
 
 package android.car.content.pm;
 
+import static android.car.Car.PERMISSION_CONTROL_APP_BLOCKING;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.annotation.UserIdInt;
 import android.app.PendingIntent;
 import android.car.Car;
 import android.car.CarManagerBase;
 import android.content.ComponentName;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.os.ServiceSpecificException;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * Provides car specific API related with package management.
@@ -77,6 +85,36 @@
     @Deprecated
     public static final int FLAG_SET_POLICY_REMOVE = 0x4;
 
+    /**
+     * Represents support of all regions for driving safety.
+     *
+     * @hide
+     */
+    public static final String DRIVING_SAFETY_REGION_ALL = "android.car.drivingsafetyregion.all";
+
+    /**
+     * Metadata which Activity can use to specify the driving safety regions it is supporting.
+     *
+     * <p>Definition of driving safety region is car OEM specific for now and only OEM apps
+     * should use this. If there are multiple regions, it should be comma separated. Not specifying
+     * this means supporting all regions.
+     *
+     * <p>Some examples are:
+     *   <meta-data android:name="android.car.drivingsafetyregions"
+     *   android:value="com.android.drivingsafetyregion.1,com.android.drivingsafetyregion.2"/>
+     *
+     * @hide
+     */
+    public static final String DRIVING_SAFETY_ACTIVITY_METADATA_REGIONS =
+            "android.car.drivingsafetyregions";
+
+    /**
+     * Internal error code for throwing {@code NameNotFoundException} from service.
+     *
+     * @hide
+     */
+    public static final int ERROR_CODE_NO_PACKAGE = -100;
+
     /** @hide */
     @IntDef(flag = true,
             value = {FLAG_SET_POLICY_WAIT_FOR_CHANGE, FLAG_SET_POLICY_ADD, FLAG_SET_POLICY_REMOVE})
@@ -242,4 +280,108 @@
             return handleRemoteExceptionFromCarService(e, false);
         }
     }
+
+    /**
+     * Returns the current driving safety region of the system. It will return OEM specific regions
+     * or {@link #DRIVING_SAFETY_REGION_ALL} when all regions are supported.
+     *
+     * <p> System's driving safety region is static and does not change until system restarts.
+     *
+     * @hide
+     */
+    @RequiresPermission(anyOf = {PERMISSION_CONTROL_APP_BLOCKING,
+            Car.PERMISSION_CAR_DRIVING_STATE})
+    @NonNull
+    public String getCurrentDrivingSafetyRegion() {
+        try {
+            return mService.getCurrentDrivingSafetyRegion();
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, DRIVING_SAFETY_REGION_ALL);
+        }
+    }
+
+    /**
+     * Enables or disables bypassing of unsafe {@code Activity} blocking for a specific
+     * {@code Activity} temporarily.
+     *
+     * <p> Enabling bypassing only lasts until the user stops using the car or until a user
+     * switching happens. Apps like launcher may ask user's consent to bypass. Note that bypassing
+     * is done for the package for all android users including the current user and user 0.
+     * <p> If bypassing is disabled and if the unsafe app is in foreground with driving state, the
+     * app will be immediately blocked.
+     *
+     * @param packageName Target package name.
+     * @param activityClassName Target Activity name (in full class name).
+     * @param bypass Bypass {@code Activity} blocking when true. Do not bypass anymore when false.
+     * @param userId User Id where the package is installed. Even if the bypassing is enabled for
+     *               all android users, the package should be available for the specified user id.
+     *
+     * @throws NameNotFoundException If the given package / Activity class does not exist for the
+     *         user.
+     *
+     * @hide
+     */
+    @RequiresPermission(allOf = {PERMISSION_CONTROL_APP_BLOCKING,
+            android.Manifest.permission.QUERY_ALL_PACKAGES})
+    public void controlTemporaryActivityBlockingBypassingAsUser(String packageName,
+            String activityClassName, boolean bypass, @UserIdInt int userId)
+            throws NameNotFoundException {
+        try {
+            mService.controlOneTimeActivityBlockingBypassingAsUser(packageName, activityClassName,
+                    bypass, userId);
+        } catch (ServiceSpecificException e) {
+            handleServiceSpecificFromCarService(e, packageName, activityClassName, userId);
+        } catch (RemoteException e) {
+            handleRemoteExceptionFromCarService(e);
+        }
+    }
+
+    /**
+     * Returns all supported driving safety regions for the given Activity. If the Activity supports
+     * all regions, it will only include {@link #DRIVING_SAFETY_REGION_ALL}.
+     *
+     * <p> The permission specification requires {@code PERMISSION_CONTROL_APP_BLOCKING} and
+     * {@code QUERY_ALL_PACKAGES} but this API will also work if the client has
+     * {@link Car#PERMISSION_CAR_DRIVING_STATE} and {@code QUERY_ALL_PACKAGES} permissions.
+     *
+     * @param packageName Target package name.
+     * @param activityClassName Target Activity name (in full class name).
+     * @param userId Android user Id to check the package.
+     *
+     * @return Empty list if the Activity does not support driving safety (=no
+     *         {@code distractionOptimized} metadata). Otherwise returns full list of all supported
+     *         regions.
+     *
+     * @throws NameNotFoundException If the given package / Activity class does not exist for the
+     *         user.
+     *
+     * @hide
+     */
+    @RequiresPermission(allOf = {PERMISSION_CONTROL_APP_BLOCKING,
+            android.Manifest.permission.QUERY_ALL_PACKAGES})
+    @NonNull
+    public List<String> getSupportedDrivingSafetyRegionsForActivityAsUser(String packageName,
+            String activityClassName, @UserIdInt int userId) throws NameNotFoundException {
+        try {
+            return mService.getSupportedDrivingSafetyRegionsForActivityAsUser(packageName,
+                    activityClassName, userId);
+        } catch (ServiceSpecificException e) {
+            handleServiceSpecificFromCarService(e, packageName, activityClassName, userId);
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, Collections.EMPTY_LIST);
+        }
+        return Collections.EMPTY_LIST; // cannot reach here but the compiler complains.
+    }
+
+    private void handleServiceSpecificFromCarService(ServiceSpecificException e,
+            String packageName, String activityClassName, @UserIdInt int userId)
+            throws NameNotFoundException {
+        if (e.errorCode == ERROR_CODE_NO_PACKAGE) {
+            throw new NameNotFoundException(
+                    "cannot find " + packageName + "/" + activityClassName + " for user id:"
+                            + userId);
+        }
+        // don't know what this is
+        throw new IllegalStateException(e);
+    }
 }
diff --git a/car-lib/src/android/car/content/pm/ICarPackageManager.aidl b/car-lib/src/android/car/content/pm/ICarPackageManager.aidl
index b8261f2..b0bdc7c 100644
--- a/car-lib/src/android/car/content/pm/ICarPackageManager.aidl
+++ b/car-lib/src/android/car/content/pm/ICarPackageManager.aidl
@@ -29,4 +29,9 @@
     void setEnableActivityBlocking(boolean enable) = 4;
     void restartTask(int taskId) = 5;
     boolean isPendingIntentDistractionOptimized(in PendingIntent pendingIntent) = 6;
+    String getCurrentDrivingSafetyRegion() = 7;
+    void controlOneTimeActivityBlockingBypassingAsUser(String packageName, String activityClassName,
+            boolean bypass, int userId) = 8;
+    List<String> getSupportedDrivingSafetyRegionsForActivityAsUser(String packageName,
+            String activityClassName, int userId) = 9;
 }
diff --git a/car-lib/src/android/car/telemetry/CarTelemetryManager.java b/car-lib/src/android/car/telemetry/CarTelemetryManager.java
index 3d3cf50..b0067b1 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,77 @@
     }
 
     /**
-     * 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}. This
+     * will also remove outputs produced by the MetricsConfig. 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}. This
+     * will also remove all MetricsConfig outputs.
      *
      * @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 +385,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 +399,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..8343c34 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,29 @@
     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. This will also remove outputs produced by the
+     * MetricsConfig.
      */
-    boolean removeManifest(in ManifestKey key);
+    void removeMetricsConfig(in MetricsConfigKey key);
 
     /**
-     * Removes all manifests.
+     * Removes all MetricsConfigs. This will also remove all MetricsConfig outputs.
      */
-    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/ManifestKey.aidl b/car-lib/src/android/car/telemetry/MetricsConfigKey.aidl
similarity index 95%
rename from car-lib/src/android/car/telemetry/ManifestKey.aidl
rename to car-lib/src/android/car/telemetry/MetricsConfigKey.aidl
index 25097df..2c00127 100644
--- a/car-lib/src/android/car/telemetry/ManifestKey.aidl
+++ b/car-lib/src/android/car/telemetry/MetricsConfigKey.aidl
@@ -19,4 +19,4 @@
 /**
  * @hide
  */
-parcelable ManifestKey;
\ No newline at end of file
+parcelable MetricsConfigKey;
\ No newline at end of file
diff --git a/car-lib/src/android/car/telemetry/ManifestKey.java b/car-lib/src/android/car/telemetry/MetricsConfigKey.java
similarity index 61%
rename from car-lib/src/android/car/telemetry/ManifestKey.java
rename to car-lib/src/android/car/telemetry/MetricsConfigKey.java
index b0a69c2..ddc9718 100644
--- a/car-lib/src/android/car/telemetry/ManifestKey.java
+++ b/car-lib/src/android/car/telemetry/MetricsConfigKey.java
@@ -20,12 +20,14 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Objects;
+
 /**
  * A parcelable that wraps around the Manifest name and version.
  *
  * @hide
  */
-public final class ManifestKey implements Parcelable {
+public final class MetricsConfigKey implements Parcelable {
 
     @NonNull
     private String mName;
@@ -46,12 +48,12 @@
         out.writeInt(mVersion);
     }
 
-    private ManifestKey(Parcel in) {
+    private MetricsConfigKey(Parcel in) {
         mName = in.readString();
         mVersion = in.readInt();
     }
 
-    public ManifestKey(@NonNull String name, int version) {
+    public MetricsConfigKey(@NonNull String name, int version) {
         mName = name;
         mVersion = version;
     }
@@ -61,16 +63,30 @@
         return 0;
     }
 
-    public static final @NonNull Parcelable.Creator<ManifestKey> CREATOR =
-            new Parcelable.Creator<ManifestKey>() {
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof MetricsConfigKey)) {
+            return false;
+        }
+        MetricsConfigKey other = (MetricsConfigKey) o;
+        return mName.equals(other.getName()) && mVersion == other.getVersion();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mName, mVersion);
+    }
+
+    public static final @NonNull Parcelable.Creator<MetricsConfigKey> CREATOR =
+            new Parcelable.Creator<MetricsConfigKey>() {
                 @Override
-                public ManifestKey createFromParcel(Parcel in) {
-                    return new ManifestKey(in);
+                public MetricsConfigKey createFromParcel(Parcel in) {
+                    return new MetricsConfigKey(in);
                 }
 
                 @Override
-                public ManifestKey[] newArray(int size) {
-                    return new ManifestKey[size];
+                public MetricsConfigKey[] newArray(int size) {
+                    return new MetricsConfigKey[size];
                 }
             };
 }
diff --git a/car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java b/car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java
index deb3946..29b1471 100644
--- a/car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java
+++ b/car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java
@@ -50,6 +50,7 @@
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
 
 /**
  * Provides common Mockito calls for core Android classes.
@@ -133,6 +134,16 @@
     }
 
     /**
+     * Mocks {@code UserManager#getUserHandles()} to return the simple users with the given ids.
+     */
+    public static void mockUmGetUserHandles(@NonNull UserManager um, boolean excludeDying,
+            @NonNull @UserIdInt int... userIds) {
+        List<UserHandle> result = UserTestingHelper.newUsers(userIds).stream().map(
+                UserInfo::getUserHandle).collect(Collectors.toList());
+        when(um.getUserHandles(excludeDying)).thenReturn(result);
+    }
+
+    /**
      * Mocks {@code UserManager#getUsers(excludePartial, excludeDying, excludeDying)} to return the
      * given users.
      */
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_base.mk b/car_product/build/car_base.mk
index 76967a8..4d3cf5e 100644
--- a/car_product/build/car_base.mk
+++ b/car_product/build/car_base.mk
@@ -83,8 +83,7 @@
 include packages/services/Car/cpp/evs/sampleDriver/sepolicy/evsdriver.mk
 endif
 ifeq ($(ENABLE_CAREVSSERVICE_SAMPLE), true)
-PRODUCT_PACKAGES += CarEvsCameraPreviewApp \
-                    CarSystemUIEvsRRO
+PRODUCT_PACKAGES += CarEvsCameraPreviewApp
 endif
 ifeq ($(ENABLE_REAR_VIEW_CAMERA_SAMPLE), true)
 PRODUCT_PACKAGES += SampleRearViewCamera
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
index d6efa1b..7104440 100644
--- 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
@@ -17,7 +17,7 @@
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
     <item>
         <shape android:shape="rectangle">
-            <corners android:radius="@dimen/hvac_panel_on_button_radius"/>
+            <corners android:radius="@dimen/hvac_panel_seek_bar_radius"/>
             <solid android:color="@color/hvac_off_background_color" />
         </shape>
     </item>
@@ -27,13 +27,13 @@
         <selector>
             <item android:state_selected="true">
                 <shape android:shape="rectangle">
-                    <corners android:radius="@dimen/hvac_panel_on_button_radius"/>
+                    <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_on_button_radius"/>
+                    <corners android:radius="@dimen/hvac_panel_seek_bar_radius"/>
                     <solid android:color="@color/hvac_off_background_color" />
                 </shape>
             </item>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_driver_seat_cool_high.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_driver_seat_cool_high.xml
deleted file mode 100644
index afc41fd..0000000
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_driver_seat_cool_high.xml
+++ /dev/null
@@ -1,40 +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.
-  -->
-
-<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="108">
-  <path
-      android:pathData="M24,79L24,79A5,5 0,0 1,29 84L29,84A5,5 0,0 1,24 89L24,89A5,5 0,0 1,19 84L19,84A5,5 0,0 1,24 79z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M24,97L24,97A5,5 0,0 1,29 102L29,102A5,5 0,0 1,24 107L24,107A5,5 0,0 1,19 102L19,102A5,5 0,0 1,24 97z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M24,61L24,61A5,5 0,0 1,29 66L29,66A5,5 0,0 1,24 71L24,71A5,5 0,0 1,19 66L19,66A5,5 0,0 1,24 61z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M9.708,0H11.522C12.022,0 12.5,0.177 12.854,0.492C14.146,1.646 14.324,3.461 13.276,4.797L12.949,5.213C11.271,7.354 11.081,10.155 12.459,12.461L17.528,20.946C19.855,24.842 19.399,29.596 16.362,33.081L12.495,37.52C12.136,37.932 11.404,37.79 11.268,37.284L4.898,13.59C3.834,9.63 4.369,5.463 6.408,1.821C7.033,0.705 8.311,0 9.708,0Z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M25.871,36.811H38.065C41.142,36.811 43.636,39.037 43.636,41.784V43.027C43.636,45.773 41.142,48 38.065,48H22.607C20.021,48 17.542,47.083 15.714,45.451L14.161,44.065C13.552,43.521 13.636,42.62 14.336,42.174L20.205,38.432C21.858,37.378 23.839,36.811 25.871,36.811Z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M39.701,13.057L37.221,13.945C37.026,13.804 36.809,13.674 36.592,13.577C36.777,12.677 37.275,11.865 38.066,11.345C40.254,9.893 39.528,6.167 36.506,6.167C31.75,6.167 29.757,9.427 31.056,12.298L31.945,14.779C31.804,14.974 31.674,15.191 31.576,15.408C30.677,15.223 29.865,14.725 29.345,13.934C27.893,11.746 24.167,12.472 24.167,15.494C24.167,20.261 27.427,22.254 30.298,20.943L32.779,20.055C32.974,20.196 33.191,20.326 33.407,20.423C33.223,21.323 32.725,22.135 31.934,22.655C29.746,24.107 30.472,27.833 33.494,27.833C38.261,27.833 40.254,24.573 38.943,21.702L38.055,19.221C38.196,19.026 38.326,18.809 38.423,18.593C39.322,18.777 40.135,19.275 40.655,20.066C42.106,22.243 45.822,21.517 45.822,18.495C45.833,13.75 42.572,11.757 39.701,13.057ZM35,18.625C34.101,18.625 33.375,17.899 33.375,17C33.375,16.101 34.101,15.375 35,15.375C35.899,15.375 36.625,16.101 36.625,17C36.625,17.899 35.899,18.625 35,18.625Z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_driver_seat_cool_low.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_driver_seat_cool_low.xml
deleted file mode 100644
index 2888403..0000000
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_driver_seat_cool_low.xml
+++ /dev/null
@@ -1,42 +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.
-  -->
-
-<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="108">
-  <path
-      android:pathData="M24,79L24,79A5,5 0,0 1,29 84L29,84A5,5 0,0 1,24 89L24,89A5,5 0,0 1,19 84L19,84A5,5 0,0 1,24 79z"
-      android:fillColor="@color/hvac_on_icon_fill_color"
-      android:fillAlpha="@dimen/hvac_heat_or_cool_off_alpha"/>
-  <path
-      android:pathData="M24,97L24,97A5,5 0,0 1,29 102L29,102A5,5 0,0 1,24 107L24,107A5,5 0,0 1,19 102L19,102A5,5 0,0 1,24 97z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M24,61L24,61A5,5 0,0 1,29 66L29,66A5,5 0,0 1,24 71L24,71A5,5 0,0 1,19 66L19,66A5,5 0,0 1,24 61z"
-      android:fillColor="@color/hvac_on_icon_fill_color"
-      android:fillAlpha="@dimen/hvac_heat_or_cool_off_alpha"/>
-  <path
-      android:pathData="M9.708,0H11.522C12.022,0 12.5,0.177 12.854,0.492C14.146,1.646 14.324,3.461 13.276,4.797L12.949,5.213C11.271,7.354 11.081,10.155 12.459,12.461L17.528,20.946C19.855,24.842 19.399,29.596 16.362,33.081L12.495,37.52C12.136,37.932 11.404,37.79 11.268,37.284L4.898,13.59C3.834,9.63 4.369,5.463 6.408,1.821C7.033,0.705 8.311,0 9.708,0Z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M25.871,36.811H38.065C41.142,36.811 43.636,39.037 43.636,41.784V43.027C43.636,45.773 41.142,48 38.065,48H22.607C20.021,48 17.542,47.083 15.714,45.451L14.161,44.065C13.552,43.521 13.636,42.62 14.336,42.174L20.205,38.432C21.858,37.378 23.839,36.811 25.871,36.811Z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M39.701,13.057L37.221,13.945C37.026,13.804 36.809,13.674 36.592,13.577C36.777,12.677 37.275,11.865 38.066,11.345C40.254,9.893 39.528,6.167 36.506,6.167C31.75,6.167 29.757,9.427 31.056,12.298L31.945,14.779C31.804,14.974 31.674,15.191 31.576,15.408C30.677,15.223 29.865,14.725 29.345,13.934C27.893,11.746 24.167,12.472 24.167,15.494C24.167,20.261 27.427,22.254 30.298,20.943L32.779,20.055C32.974,20.196 33.191,20.326 33.407,20.423C33.223,21.323 32.725,22.135 31.934,22.655C29.746,24.107 30.472,27.833 33.494,27.833C38.261,27.833 40.254,24.573 38.943,21.702L38.055,19.221C38.196,19.026 38.326,18.809 38.423,18.593C39.322,18.777 40.135,19.275 40.655,20.066C42.106,22.243 45.822,21.517 45.822,18.495C45.833,13.75 42.572,11.757 39.701,13.057ZM35,18.625C34.101,18.625 33.375,17.899 33.375,17C33.375,16.101 34.101,15.375 35,15.375C35.899,15.375 36.625,16.101 36.625,17C36.625,17.899 35.899,18.625 35,18.625Z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_driver_seat_cool_med.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_driver_seat_cool_med.xml
deleted file mode 100644
index 98c5042..0000000
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_driver_seat_cool_med.xml
+++ /dev/null
@@ -1,41 +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.
-  -->
-
-<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="108">
-  <path
-      android:pathData="M24,79L24,79A5,5 0,0 1,29 84L29,84A5,5 0,0 1,24 89L24,89A5,5 0,0 1,19 84L19,84A5,5 0,0 1,24 79z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M24,97L24,97A5,5 0,0 1,29 102L29,102A5,5 0,0 1,24 107L24,107A5,5 0,0 1,19 102L19,102A5,5 0,0 1,24 97z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M24,61L24,61A5,5 0,0 1,29 66L29,66A5,5 0,0 1,24 71L24,71A5,5 0,0 1,19 66L19,66A5,5 0,0 1,24 61z"
-      android:fillColor="@color/hvac_on_icon_fill_color"
-      android:fillAlpha="@dimen/hvac_heat_or_cool_off_alpha"/>
-  <path
-      android:pathData="M9.708,0H11.522C12.022,0 12.5,0.177 12.854,0.492C14.146,1.646 14.324,3.461 13.276,4.797L12.949,5.213C11.271,7.354 11.081,10.155 12.459,12.461L17.528,20.946C19.855,24.842 19.399,29.596 16.362,33.081L12.495,37.52C12.136,37.932 11.404,37.79 11.268,37.284L4.898,13.59C3.834,9.63 4.369,5.463 6.408,1.821C7.033,0.705 8.311,0 9.708,0Z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M25.871,36.811H38.065C41.142,36.811 43.636,39.037 43.636,41.784V43.027C43.636,45.773 41.142,48 38.065,48H22.607C20.021,48 17.542,47.083 15.714,45.451L14.161,44.065C13.552,43.521 13.636,42.62 14.336,42.174L20.205,38.432C21.858,37.378 23.839,36.811 25.871,36.811Z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M39.701,13.057L37.221,13.945C37.026,13.804 36.809,13.674 36.592,13.577C36.777,12.677 37.275,11.865 38.066,11.345C40.254,9.893 39.528,6.167 36.506,6.167C31.75,6.167 29.757,9.427 31.056,12.298L31.945,14.779C31.804,14.974 31.674,15.191 31.576,15.408C30.677,15.223 29.865,14.725 29.345,13.934C27.893,11.746 24.167,12.472 24.167,15.494C24.167,20.261 27.427,22.254 30.298,20.943L32.779,20.055C32.974,20.196 33.191,20.326 33.407,20.423C33.223,21.323 32.725,22.135 31.934,22.655C29.746,24.107 30.472,27.833 33.494,27.833C38.261,27.833 40.254,24.573 38.943,21.702L38.055,19.221C38.196,19.026 38.326,18.809 38.423,18.593C39.322,18.777 40.135,19.275 40.655,20.066C42.106,22.243 45.822,21.517 45.822,18.495C45.833,13.75 42.572,11.757 39.701,13.057ZM35,18.625C34.101,18.625 33.375,17.899 33.375,17C33.375,16.101 34.101,15.375 35,15.375C35.899,15.375 36.625,16.101 36.625,17C36.625,17.899 35.899,18.625 35,18.625Z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_driver_seat_cool_off.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_driver_seat_cool_off.xml
deleted file mode 100644
index 5453dd5..0000000
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_driver_seat_cool_off.xml
+++ /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.
-  -->
-
-<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="108">
-  <path
-      android:pathData="M24,79L24,79A5,5 0,0 1,29 84L29,84A5,5 0,0 1,24 89L24,89A5,5 0,0 1,19 84L19,84A5,5 0,0 1,24 79z"
-      android:fillColor="@color/hvac_off_icon_fill_color"
-      android:fillAlpha="@dimen/hvac_heat_or_cool_off_alpha"/>
-  <path
-      android:pathData="M24,97L24,97A5,5 0,0 1,29 102L29,102A5,5 0,0 1,24 107L24,107A5,5 0,0 1,19 102L19,102A5,5 0,0 1,24 97z"
-      android:fillColor="@color/hvac_off_icon_fill_color"
-      android:fillAlpha="@dimen/hvac_heat_or_cool_off_alpha"/>
-  <path
-      android:pathData="M24,61L24,61A5,5 0,0 1,29 66L29,66A5,5 0,0 1,24 71L24,71A5,5 0,0 1,19 66L19,66A5,5 0,0 1,24 61z"
-      android:fillColor="@color/hvac_off_icon_fill_color"
-      android:fillAlpha="@dimen/hvac_heat_or_cool_off_alpha"/>
-  <path
-      android:pathData="M9.708,0H11.522C12.022,0 12.5,0.177 12.854,0.492C14.146,1.646 14.324,3.461 13.276,4.797L12.949,5.213C11.271,7.354 11.081,10.155 12.459,12.461L17.528,20.946C19.855,24.842 19.399,29.596 16.362,33.081L12.495,37.52C12.136,37.932 11.404,37.79 11.268,37.284L4.898,13.59C3.834,9.63 4.369,5.463 6.408,1.821C7.033,0.705 8.311,0 9.708,0Z"
-      android:fillColor="@color/hvac_off_icon_fill_color"/>
-  <path
-      android:pathData="M25.871,36.811H38.065C41.142,36.811 43.636,39.037 43.636,41.784V43.027C43.636,45.773 41.142,48 38.065,48H22.607C20.021,48 17.542,47.083 15.714,45.451L14.161,44.065C13.552,43.521 13.636,42.62 14.336,42.174L20.205,38.432C21.858,37.378 23.839,36.811 25.871,36.811Z"
-      android:fillColor="@color/hvac_off_icon_fill_color"/>
-  <path
-      android:pathData="M39.701,13.057L37.221,13.945C37.026,13.804 36.809,13.674 36.592,13.577C36.777,12.677 37.275,11.865 38.066,11.345C40.254,9.893 39.528,6.167 36.506,6.167C31.75,6.167 29.757,9.427 31.056,12.298L31.945,14.779C31.804,14.974 31.674,15.191 31.576,15.408C30.677,15.223 29.865,14.725 29.345,13.934C27.893,11.746 24.167,12.472 24.167,15.494C24.167,20.261 27.427,22.254 30.298,20.943L32.779,20.055C32.974,20.196 33.191,20.326 33.407,20.423C33.223,21.323 32.725,22.135 31.934,22.655C29.746,24.107 30.472,27.833 33.494,27.833C38.261,27.833 40.254,24.573 38.943,21.702L38.055,19.221C38.196,19.026 38.326,18.809 38.423,18.593C39.322,18.777 40.135,19.275 40.655,20.066C42.106,22.243 45.822,21.517 45.822,18.495C45.833,13.75 42.572,11.757 39.701,13.057ZM35,18.625C34.101,18.625 33.375,17.899 33.375,17C33.375,16.101 34.101,15.375 35,15.375C35.899,15.375 36.625,16.101 36.625,17C36.625,17.899 35.899,18.625 35,18.625Z"
-      android:fillColor="@color/hvac_off_icon_fill_color"/>
-</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
index 326b9b9..650fd9e 100644
--- 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
@@ -15,21 +15,18 @@
   -->
 
 <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="108">
+        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,79L24,79A5,5 0,0 1,29 84L29,84A5,5 0,0 1,24 89L24,89A5,5 0,0 1,19 84L19,84A5,5 0,0 1,24 79z"
+      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,97L24,97A5,5 0,0 1,29 102L29,102A5,5 0,0 1,24 107L24,107A5,5 0,0 1,19 102L19,102A5,5 0,0 1,24 97z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M24,61L24,61A5,5 0,0 1,29 66L29,66A5,5 0,0 1,24 71L24,71A5,5 0,0 1,19 66L19,66A5,5 0,0 1,24 61z"
+      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
index 0528b68..a89ba0a 100644
--- 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
@@ -15,23 +15,19 @@
   -->
 
 <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="108">
+        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,79L24,79A5,5 0,0 1,29 84L29,84A5,5 0,0 1,24 89L24,89A5,5 0,0 1,19 84L19,84A5,5 0,0 1,24 79z"
-      android:fillColor="@color/hvac_on_icon_fill_color"
-      android:fillAlpha="@dimen/hvac_heat_or_cool_off_alpha"/>
-  <path
-      android:pathData="M24,97L24,97A5,5 0,0 1,29 102L29,102A5,5 0,0 1,24 107L24,107A5,5 0,0 1,19 102L19,102A5,5 0,0 1,24 97z"
+      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,61L24,61A5,5 0,0 1,29 66L29,66A5,5 0,0 1,24 71L24,71A5,5 0,0 1,19 66L19,66A5,5 0,0 1,24 61z"
+      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_med.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_driver_seat_heat_med.xml
deleted file mode 100644
index 326ec7f..0000000
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_driver_seat_heat_med.xml
+++ /dev/null
@@ -1,36 +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.
-  -->
-
-<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="108">
-  <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,79L24,79A5,5 0,0 1,29 84L29,84A5,5 0,0 1,24 89L24,89A5,5 0,0 1,19 84L19,84A5,5 0,0 1,24 79z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M24,97L24,97A5,5 0,0 1,29 102L29,102A5,5 0,0 1,24 107L24,107A5,5 0,0 1,19 102L19,102A5,5 0,0 1,24 97z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M24,61L24,61A5,5 0,0 1,29 66L29,66A5,5 0,0 1,24 71L24,71A5,5 0,0 1,19 66L19,66A5,5 0,0 1,24 61z"
-      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
index 78d0236..9117cac 100644
--- 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
@@ -15,24 +15,20 @@
   -->
 
 <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="108">
+        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,79L24,79A5,5 0,0 1,29 84L29,84A5,5 0,0 1,24 89L24,89A5,5 0,0 1,19 84L19,84A5,5 0,0 1,24 79z"
+      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,97L24,97A5,5 0,0 1,29 102L29,102A5,5 0,0 1,24 107L24,107A5,5 0,0 1,19 102L19,102A5,5 0,0 1,24 97z"
-      android:fillColor="@color/hvac_off_icon_fill_color"
-      android:fillAlpha="@dimen/hvac_heat_or_cool_off_alpha"/>
-  <path
-      android:pathData="M24,61L24,61A5,5 0,0 1,29 66L29,66A5,5 0,0 1,24 71L24,71A5,5 0,0 1,19 66L19,66A5,5 0,0 1,24 61z"
+      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_minimize.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_minimize.xml
new file mode 100644
index 0000000..721cec4
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_minimize.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.
+  -->
+
+<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:pathData="M12,15.375l-6,-6 1.4,-1.4 4.6,4.6 4.6,-4.6 1.4,1.4z"
+                android:fillColor="@color/car_nav_minimize_icon_fill_color"/>
+        </vector>
+    </item>
+</selector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_off.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_off.xml
deleted file mode 100644
index 9d55f5c..0000000
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_mode_fan_off.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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_off_icon_fill_color"/>
-</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_passenger_seat_cool_high.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_passenger_seat_cool_high.xml
deleted file mode 100644
index 8d92c3f..0000000
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_passenger_seat_cool_high.xml
+++ /dev/null
@@ -1,40 +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.
-  -->
-
-<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="108">
-  <path
-      android:pathData="M38.293,0H36.479C35.979,0 35.5,0.177 35.147,0.492C33.855,1.646 33.677,3.461 34.725,4.797L35.051,5.213C36.73,7.354 36.92,10.155 35.542,12.461L30.473,20.946C28.146,24.842 28.602,29.596 31.639,33.081L35.506,37.52C35.865,37.932 36.597,37.79 36.733,37.284L43.103,13.59C44.167,9.63 43.632,5.463 41.593,1.821C40.968,0.705 39.69,0 38.293,0Z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M22.13,36.811H9.935C6.859,36.811 4.365,39.037 4.365,41.784V43.027C4.365,45.773 6.859,48 9.935,48H25.394C27.98,48 30.459,47.083 32.287,45.451L33.84,44.065C34.449,43.521 34.365,42.62 33.665,42.174L27.796,38.432C26.143,37.378 24.162,36.811 22.13,36.811Z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M17.701,13.057L15.221,13.945C15.026,13.804 14.809,13.674 14.592,13.577C14.776,12.677 15.275,11.865 16.066,11.345C18.254,9.893 17.528,6.167 14.506,6.167C9.75,6.167 7.756,9.427 9.057,12.298L9.945,14.779C9.804,14.974 9.674,15.191 9.576,15.408C8.677,15.223 7.865,14.725 7.345,13.934C5.893,11.746 2.167,12.472 2.167,15.494C2.167,20.261 5.427,22.254 8.298,20.943L10.779,20.055C10.974,20.196 11.191,20.326 11.407,20.423C11.223,21.323 10.725,22.135 9.934,22.655C7.746,24.107 8.472,27.833 11.494,27.833C16.261,27.833 18.254,24.573 16.943,21.702L16.055,19.221C16.196,19.026 16.326,18.809 16.423,18.593C17.322,18.777 18.135,19.275 18.655,20.066C20.107,22.243 23.822,21.517 23.822,18.495C23.833,13.75 20.572,11.757 17.701,13.057ZM13,18.625C12.101,18.625 11.375,17.899 11.375,17C11.375,16.101 12.101,15.375 13,15.375C13.899,15.375 14.625,16.101 14.625,17C14.625,17.899 13.899,18.625 13,18.625Z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M24,79L24,79A5,5 0,0 1,29 84L29,84A5,5 0,0 1,24 89L24,89A5,5 0,0 1,19 84L19,84A5,5 0,0 1,24 79z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M24,97L24,97A5,5 0,0 1,29 102L29,102A5,5 0,0 1,24 107L24,107A5,5 0,0 1,19 102L19,102A5,5 0,0 1,24 97z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M24,61L24,61A5,5 0,0 1,29 66L29,66A5,5 0,0 1,24 71L24,71A5,5 0,0 1,19 66L19,66A5,5 0,0 1,24 61z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_passenger_seat_cool_low.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_passenger_seat_cool_low.xml
deleted file mode 100644
index eb0927a..0000000
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_passenger_seat_cool_low.xml
+++ /dev/null
@@ -1,42 +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.
-  -->
-
-<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="108">
-  <path
-      android:pathData="M38.293,0H36.479C35.979,0 35.5,0.177 35.147,0.492C33.855,1.646 33.677,3.461 34.725,4.797L35.051,5.213C36.73,7.354 36.92,10.155 35.542,12.461L30.473,20.946C28.146,24.842 28.602,29.596 31.639,33.081L35.506,37.52C35.865,37.932 36.597,37.79 36.733,37.284L43.103,13.59C44.167,9.63 43.632,5.463 41.593,1.821C40.968,0.705 39.69,0 38.293,0Z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M22.13,36.811H9.935C6.859,36.811 4.365,39.037 4.365,41.784V43.027C4.365,45.773 6.859,48 9.935,48H25.394C27.98,48 30.459,47.083 32.287,45.451L33.84,44.065C34.449,43.521 34.365,42.62 33.665,42.174L27.796,38.432C26.143,37.378 24.162,36.811 22.13,36.811Z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M17.701,13.057L15.221,13.945C15.026,13.804 14.809,13.674 14.592,13.577C14.776,12.677 15.275,11.865 16.066,11.345C18.254,9.893 17.528,6.167 14.506,6.167C9.75,6.167 7.756,9.427 9.057,12.298L9.945,14.779C9.804,14.974 9.674,15.191 9.576,15.408C8.677,15.223 7.865,14.725 7.345,13.934C5.893,11.746 2.167,12.472 2.167,15.494C2.167,20.261 5.427,22.254 8.298,20.943L10.779,20.055C10.974,20.196 11.191,20.326 11.407,20.423C11.223,21.323 10.725,22.135 9.934,22.655C7.746,24.107 8.472,27.833 11.494,27.833C16.261,27.833 18.254,24.573 16.943,21.702L16.055,19.221C16.196,19.026 16.326,18.809 16.423,18.593C17.322,18.777 18.135,19.275 18.655,20.066C20.107,22.243 23.822,21.517 23.822,18.495C23.833,13.75 20.572,11.757 17.701,13.057ZM13,18.625C12.101,18.625 11.375,17.899 11.375,17C11.375,16.101 12.101,15.375 13,15.375C13.899,15.375 14.625,16.101 14.625,17C14.625,17.899 13.899,18.625 13,18.625Z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M24,79L24,79A5,5 0,0 1,29 84L29,84A5,5 0,0 1,24 89L24,89A5,5 0,0 1,19 84L19,84A5,5 0,0 1,24 79z"
-      android:fillColor="@color/hvac_on_icon_fill_color"
-      android:fillAlpha="@dimen/hvac_heat_or_cool_off_alpha"/>
-  <path
-      android:pathData="M24,97L24,97A5,5 0,0 1,29 102L29,102A5,5 0,0 1,24 107L24,107A5,5 0,0 1,19 102L19,102A5,5 0,0 1,24 97z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M24,61L24,61A5,5 0,0 1,29 66L29,66A5,5 0,0 1,24 71L24,71A5,5 0,0 1,19 66L19,66A5,5 0,0 1,24 61z"
-      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_cool_med.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_passenger_seat_cool_med.xml
deleted file mode 100644
index c7471a8..0000000
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_passenger_seat_cool_med.xml
+++ /dev/null
@@ -1,41 +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.
-  -->
-
-<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="108">
-  <path
-      android:pathData="M38.293,0H36.479C35.979,0 35.5,0.177 35.147,0.492C33.855,1.646 33.677,3.461 34.725,4.797L35.051,5.213C36.73,7.354 36.92,10.155 35.542,12.461L30.473,20.946C28.146,24.842 28.602,29.596 31.639,33.081L35.506,37.52C35.865,37.932 36.597,37.79 36.733,37.284L43.103,13.59C44.167,9.63 43.632,5.463 41.593,1.821C40.968,0.705 39.69,0 38.293,0Z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M22.13,36.811H9.935C6.859,36.811 4.365,39.037 4.365,41.784V43.027C4.365,45.773 6.859,48 9.935,48H25.394C27.98,48 30.459,47.083 32.287,45.451L33.84,44.065C34.449,43.521 34.365,42.62 33.665,42.174L27.796,38.432C26.143,37.378 24.162,36.811 22.13,36.811Z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M17.701,13.057L15.221,13.945C15.026,13.804 14.809,13.674 14.592,13.577C14.776,12.677 15.275,11.865 16.066,11.345C18.254,9.893 17.528,6.167 14.506,6.167C9.75,6.167 7.756,9.427 9.057,12.298L9.945,14.779C9.804,14.974 9.674,15.191 9.576,15.408C8.677,15.223 7.865,14.725 7.345,13.934C5.893,11.746 2.167,12.472 2.167,15.494C2.167,20.261 5.427,22.254 8.298,20.943L10.779,20.055C10.974,20.196 11.191,20.326 11.407,20.423C11.223,21.323 10.725,22.135 9.934,22.655C7.746,24.107 8.472,27.833 11.494,27.833C16.261,27.833 18.254,24.573 16.943,21.702L16.055,19.221C16.196,19.026 16.326,18.809 16.423,18.593C17.322,18.777 18.135,19.275 18.655,20.066C20.107,22.243 23.822,21.517 23.822,18.495C23.833,13.75 20.572,11.757 17.701,13.057ZM13,18.625C12.101,18.625 11.375,17.899 11.375,17C11.375,16.101 12.101,15.375 13,15.375C13.899,15.375 14.625,16.101 14.625,17C14.625,17.899 13.899,18.625 13,18.625Z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M24,79L24,79A5,5 0,0 1,29 84L29,84A5,5 0,0 1,24 89L24,89A5,5 0,0 1,19 84L19,84A5,5 0,0 1,24 79z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M24,97L24,97A5,5 0,0 1,29 102L29,102A5,5 0,0 1,24 107L24,107A5,5 0,0 1,19 102L19,102A5,5 0,0 1,24 97z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M24,61L24,61A5,5 0,0 1,29 66L29,66A5,5 0,0 1,24 71L24,71A5,5 0,0 1,19 66L19,66A5,5 0,0 1,24 61z"
-      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_cool_off.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_passenger_seat_cool_off.xml
deleted file mode 100644
index f0df11c..0000000
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_passenger_seat_cool_off.xml
+++ /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.
-  -->
-
-<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="108">
-  <path
-      android:pathData="M38.293,0H36.479C35.979,0 35.5,0.177 35.147,0.492C33.855,1.646 33.677,3.461 34.725,4.797L35.051,5.213C36.73,7.354 36.92,10.155 35.542,12.461L30.473,20.946C28.146,24.842 28.602,29.596 31.639,33.081L35.506,37.52C35.865,37.932 36.597,37.79 36.733,37.284L43.103,13.59C44.167,9.63 43.632,5.463 41.593,1.821C40.968,0.705 39.69,0 38.293,0Z"
-      android:fillColor="@color/hvac_off_icon_fill_color"/>
-  <path
-      android:pathData="M22.13,36.811H9.935C6.859,36.811 4.365,39.037 4.365,41.784V43.027C4.365,45.773 6.859,48 9.935,48H25.394C27.98,48 30.459,47.083 32.287,45.451L33.84,44.065C34.449,43.521 34.365,42.62 33.665,42.174L27.796,38.432C26.143,37.378 24.162,36.811 22.13,36.811Z"
-      android:fillColor="@color/hvac_off_icon_fill_color"/>
-  <path
-      android:pathData="M17.701,13.057L15.221,13.945C15.026,13.804 14.809,13.674 14.592,13.577C14.776,12.677 15.275,11.865 16.066,11.345C18.254,9.893 17.528,6.167 14.506,6.167C9.75,6.167 7.756,9.427 9.057,12.298L9.945,14.779C9.804,14.974 9.674,15.191 9.576,15.408C8.677,15.223 7.865,14.725 7.345,13.934C5.893,11.746 2.167,12.472 2.167,15.494C2.167,20.261 5.427,22.254 8.298,20.943L10.779,20.055C10.974,20.196 11.191,20.326 11.407,20.423C11.223,21.323 10.725,22.135 9.934,22.655C7.746,24.107 8.472,27.833 11.494,27.833C16.261,27.833 18.254,24.573 16.943,21.702L16.055,19.221C16.196,19.026 16.326,18.809 16.423,18.593C17.322,18.777 18.135,19.275 18.655,20.066C20.107,22.243 23.822,21.517 23.822,18.495C23.833,13.75 20.572,11.757 17.701,13.057ZM13,18.625C12.101,18.625 11.375,17.899 11.375,17C11.375,16.101 12.101,15.375 13,15.375C13.899,15.375 14.625,16.101 14.625,17C14.625,17.899 13.899,18.625 13,18.625Z"
-      android:fillColor="@color/hvac_off_icon_fill_color"/>
-  <path
-      android:pathData="M24,79L24,79A5,5 0,0 1,29 84L29,84A5,5 0,0 1,24 89L24,89A5,5 0,0 1,19 84L19,84A5,5 0,0 1,24 79z"
-      android:fillColor="@color/hvac_off_icon_fill_color"
-      android:fillAlpha="@dimen/hvac_heat_or_cool_off_alpha"/>
-  <path
-      android:pathData="M24,97L24,97A5,5 0,0 1,29 102L29,102A5,5 0,0 1,24 107L24,107A5,5 0,0 1,19 102L19,102A5,5 0,0 1,24 97z"
-      android:fillColor="@color/hvac_off_icon_fill_color"
-      android:fillAlpha="@dimen/hvac_heat_or_cool_off_alpha"/>
-  <path
-      android:pathData="M24,61L24,61A5,5 0,0 1,29 66L29,66A5,5 0,0 1,24 71L24,71A5,5 0,0 1,19 66L19,66A5,5 0,0 1,24 61z"
-      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_passenger_seat_heat_high.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_passenger_seat_heat_high.xml
index 8e5c5e3..0d02e85 100644
--- 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
@@ -15,21 +15,18 @@
   -->
 
 <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="108">
+        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.479,0H38.293C39.69,0 40.968,0.705 41.593,1.821C43.632,5.463 44.167,9.63 43.103,13.59L36.733,37.284C36.597,37.79 35.865,37.932 35.506,37.52L31.639,33.081C28.602,29.596 28.146,24.842 30.473,20.946L35.542,12.461C36.92,10.155 36.73,7.354 35.051,5.213L34.725,4.797C33.677,3.461 33.855,1.646 35.147,0.492C35.5,0.177 35.979,0 36.479,0ZM9.935,36.811H22.13C24.162,36.811 26.143,37.378 27.796,38.432L33.665,42.174C34.365,42.62 34.449,43.521 33.84,44.065L32.287,45.451C30.459,47.083 27.98,48 25.394,48H9.935C6.859,48 4.365,45.773 4.365,43.027V41.784C4.365,39.037 6.859,36.811 9.935,36.811ZM23.103,13.84C22.448,14.319 22.132,14.746 21.977,15.091C21.826,15.427 21.782,15.786 21.842,16.215C21.976,17.179 22.58,18.27 23.416,19.673L23.55,19.898C24.271,21.106 25.164,22.602 25.421,24.088C25.568,24.941 25.524,25.86 25.112,26.766C24.704,27.662 23.996,28.41 23.045,29.039C22.354,29.496 21.423,29.306 20.966,28.615C20.509,27.924 20.699,26.993 21.39,26.536C22.003,26.131 22.265,25.779 22.382,25.522C22.494,25.275 22.532,24.989 22.464,24.598C22.308,23.691 21.691,22.641 20.838,21.207L20.818,21.175C20.063,19.905 19.102,18.292 18.871,16.629C18.746,15.737 18.821,14.794 19.241,13.861C19.656,12.937 20.362,12.128 21.332,11.419C22,10.93 22.939,11.075 23.428,11.744C23.917,12.412 23.772,13.351 23.103,13.84ZM15.843,15.091C15.998,14.746 16.315,14.319 16.969,13.84C17.638,13.351 17.783,12.412 17.294,11.744C16.805,11.075 15.866,10.93 15.198,11.419C14.228,12.128 13.522,12.937 13.107,13.861C12.687,14.794 12.612,15.737 12.736,16.629C12.968,18.292 13.929,19.905 14.684,21.175L14.704,21.207C15.557,22.641 16.174,23.691 16.33,24.598C16.398,24.989 16.36,25.275 16.248,25.522C16.131,25.779 15.869,26.131 15.256,26.536C14.565,26.993 14.375,27.924 14.832,28.615C15.289,29.306 16.22,29.496 16.911,29.039C17.862,28.41 18.57,27.662 18.978,26.766C19.39,25.86 19.434,24.941 19.287,24.088C19.031,22.602 18.137,21.106 17.416,19.898L17.282,19.673C16.446,18.27 15.842,17.179 15.708,16.215C15.648,15.786 15.692,15.427 15.843,15.091ZM10.831,13.84C10.177,14.319 9.86,14.746 9.705,15.091C9.554,15.427 9.51,15.786 9.57,16.215C9.705,17.179 10.309,18.27 11.144,19.673L11.278,19.898C12,21.106 12.893,22.602 13.149,24.088C13.296,24.941 13.253,25.86 12.84,26.766C12.432,27.662 11.724,28.41 10.773,29.039C10.082,29.496 9.151,29.306 8.694,28.615C8.238,27.924 8.427,26.993 9.119,26.536C9.731,26.131 9.993,25.779 10.11,25.522C10.223,25.275 10.26,24.989 10.193,24.598C10.036,23.691 9.419,22.641 8.566,21.207L8.547,21.175C7.791,19.905 6.831,18.292 6.599,16.629C6.474,15.737 6.549,14.794 6.969,13.861C7.384,12.937 8.09,12.128 9.06,11.419C9.729,10.93 10.667,11.075 11.156,11.744C11.646,12.412 11.5,13.351 10.831,13.84Z"
+      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,79L24,79A5,5 0,0 1,29 84L29,84A5,5 0,0 1,24 89L24,89A5,5 0,0 1,19 84L19,84A5,5 0,0 1,24 79z"
+      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,97L24,97A5,5 0,0 1,29 102L29,102A5,5 0,0 1,24 107L24,107A5,5 0,0 1,19 102L19,102A5,5 0,0 1,24 97z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M24,61L24,61A5,5 0,0 1,29 66L29,66A5,5 0,0 1,24 71L24,71A5,5 0,0 1,19 66L19,66A5,5 0,0 1,24 61z"
+      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
index 52cf98b..b95b31a 100644
--- 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
@@ -15,23 +15,19 @@
   -->
 
 <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="108">
+        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.479,0H38.293C39.69,0 40.968,0.705 41.593,1.821C43.632,5.463 44.167,9.63 43.103,13.59L36.733,37.284C36.597,37.79 35.865,37.932 35.506,37.52L31.639,33.081C28.602,29.596 28.146,24.842 30.473,20.946L35.542,12.461C36.92,10.155 36.73,7.354 35.051,5.213L34.725,4.797C33.677,3.461 33.855,1.646 35.147,0.492C35.5,0.177 35.979,0 36.479,0ZM9.935,36.811H22.13C24.162,36.811 26.143,37.378 27.796,38.432L33.665,42.174C34.365,42.62 34.449,43.521 33.84,44.065L32.287,45.451C30.459,47.083 27.98,48 25.394,48H9.935C6.859,48 4.365,45.773 4.365,43.027V41.784C4.365,39.037 6.859,36.811 9.935,36.811ZM23.103,13.84C22.448,14.319 22.132,14.746 21.977,15.091C21.826,15.427 21.782,15.786 21.842,16.215C21.976,17.179 22.58,18.27 23.416,19.673L23.55,19.898C24.271,21.106 25.164,22.602 25.421,24.088C25.568,24.941 25.524,25.86 25.112,26.766C24.704,27.662 23.996,28.41 23.045,29.039C22.354,29.496 21.423,29.306 20.966,28.615C20.509,27.924 20.699,26.993 21.39,26.536C22.003,26.131 22.265,25.779 22.382,25.522C22.494,25.275 22.532,24.989 22.464,24.598C22.308,23.691 21.691,22.641 20.838,21.207L20.818,21.175C20.063,19.905 19.102,18.292 18.871,16.629C18.746,15.737 18.821,14.794 19.241,13.861C19.656,12.937 20.362,12.128 21.332,11.419C22,10.93 22.939,11.075 23.428,11.744C23.917,12.412 23.772,13.351 23.103,13.84ZM15.843,15.091C15.998,14.746 16.315,14.319 16.969,13.84C17.638,13.351 17.783,12.412 17.294,11.744C16.805,11.075 15.866,10.93 15.198,11.419C14.228,12.128 13.522,12.937 13.107,13.861C12.687,14.794 12.612,15.737 12.736,16.629C12.968,18.292 13.929,19.905 14.684,21.175L14.704,21.207C15.557,22.641 16.174,23.691 16.33,24.598C16.398,24.989 16.36,25.275 16.248,25.522C16.131,25.779 15.869,26.131 15.256,26.536C14.565,26.993 14.375,27.924 14.832,28.615C15.289,29.306 16.22,29.496 16.911,29.039C17.862,28.41 18.57,27.662 18.978,26.766C19.39,25.86 19.434,24.941 19.287,24.088C19.031,22.602 18.137,21.106 17.416,19.898L17.282,19.673C16.446,18.27 15.842,17.179 15.708,16.215C15.648,15.786 15.692,15.427 15.843,15.091ZM10.831,13.84C10.177,14.319 9.86,14.746 9.705,15.091C9.554,15.427 9.51,15.786 9.57,16.215C9.705,17.179 10.309,18.27 11.144,19.673L11.278,19.898C12,21.106 12.893,22.602 13.149,24.088C13.296,24.941 13.253,25.86 12.84,26.766C12.432,27.662 11.724,28.41 10.773,29.039C10.082,29.496 9.151,29.306 8.694,28.615C8.238,27.924 8.427,26.993 9.119,26.536C9.731,26.131 9.993,25.779 10.11,25.522C10.223,25.275 10.26,24.989 10.193,24.598C10.036,23.691 9.419,22.641 8.566,21.207L8.547,21.175C7.791,19.905 6.831,18.292 6.599,16.629C6.474,15.737 6.549,14.794 6.969,13.861C7.384,12.937 8.09,12.128 9.06,11.419C9.729,10.93 10.667,11.075 11.156,11.744C11.646,12.412 11.5,13.351 10.831,13.84Z"
+      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,79L24,79A5,5 0,0 1,29 84L29,84A5,5 0,0 1,24 89L24,89A5,5 0,0 1,19 84L19,84A5,5 0,0 1,24 79z"
-      android:fillColor="@color/hvac_on_icon_fill_color"
-      android:fillAlpha="@dimen/hvac_heat_or_cool_off_alpha"/>
-  <path
-      android:pathData="M24,97L24,97A5,5 0,0 1,29 102L29,102A5,5 0,0 1,24 107L24,107A5,5 0,0 1,19 102L19,102A5,5 0,0 1,24 97z"
+      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,61L24,61A5,5 0,0 1,29 66L29,66A5,5 0,0 1,24 71L24,71A5,5 0,0 1,19 66L19,66A5,5 0,0 1,24 61z"
+      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_med.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_passenger_seat_heat_med.xml
deleted file mode 100644
index 8a02789..0000000
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_passenger_seat_heat_med.xml
+++ /dev/null
@@ -1,36 +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.
-  -->
-
-<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="108">
-  <path
-      android:pathData="M36.479,0H38.293C39.69,0 40.968,0.705 41.593,1.821C43.632,5.463 44.167,9.63 43.103,13.59L36.733,37.284C36.597,37.79 35.865,37.932 35.506,37.52L31.639,33.081C28.602,29.596 28.146,24.842 30.473,20.946L35.542,12.461C36.92,10.155 36.73,7.354 35.051,5.213L34.725,4.797C33.677,3.461 33.855,1.646 35.147,0.492C35.5,0.177 35.979,0 36.479,0ZM9.935,36.811H22.13C24.162,36.811 26.143,37.378 27.796,38.432L33.665,42.174C34.365,42.62 34.449,43.521 33.84,44.065L32.287,45.451C30.459,47.083 27.98,48 25.394,48H9.935C6.859,48 4.365,45.773 4.365,43.027V41.784C4.365,39.037 6.859,36.811 9.935,36.811ZM23.103,13.84C22.448,14.319 22.132,14.746 21.977,15.091C21.826,15.427 21.782,15.786 21.842,16.215C21.976,17.179 22.58,18.27 23.416,19.673L23.55,19.898C24.271,21.106 25.164,22.602 25.421,24.088C25.568,24.941 25.524,25.86 25.112,26.766C24.704,27.662 23.996,28.41 23.045,29.039C22.354,29.496 21.423,29.306 20.966,28.615C20.509,27.924 20.699,26.993 21.39,26.536C22.003,26.131 22.265,25.779 22.382,25.522C22.494,25.275 22.532,24.989 22.464,24.598C22.308,23.691 21.691,22.641 20.838,21.207L20.818,21.175C20.063,19.905 19.102,18.292 18.871,16.629C18.746,15.737 18.821,14.794 19.241,13.861C19.656,12.937 20.362,12.128 21.332,11.419C22,10.93 22.939,11.075 23.428,11.744C23.917,12.412 23.772,13.351 23.103,13.84ZM15.843,15.091C15.998,14.746 16.315,14.319 16.969,13.84C17.638,13.351 17.783,12.412 17.294,11.744C16.805,11.075 15.866,10.93 15.198,11.419C14.228,12.128 13.522,12.937 13.107,13.861C12.687,14.794 12.612,15.737 12.736,16.629C12.968,18.292 13.929,19.905 14.684,21.175L14.704,21.207C15.557,22.641 16.174,23.691 16.33,24.598C16.398,24.989 16.36,25.275 16.248,25.522C16.131,25.779 15.869,26.131 15.256,26.536C14.565,26.993 14.375,27.924 14.832,28.615C15.289,29.306 16.22,29.496 16.911,29.039C17.862,28.41 18.57,27.662 18.978,26.766C19.39,25.86 19.434,24.941 19.287,24.088C19.031,22.602 18.137,21.106 17.416,19.898L17.282,19.673C16.446,18.27 15.842,17.179 15.708,16.215C15.648,15.786 15.692,15.427 15.843,15.091ZM10.831,13.84C10.177,14.319 9.86,14.746 9.705,15.091C9.554,15.427 9.51,15.786 9.57,16.215C9.705,17.179 10.309,18.27 11.144,19.673L11.278,19.898C12,21.106 12.893,22.602 13.149,24.088C13.296,24.941 13.253,25.86 12.84,26.766C12.432,27.662 11.724,28.41 10.773,29.039C10.082,29.496 9.151,29.306 8.694,28.615C8.238,27.924 8.427,26.993 9.119,26.536C9.731,26.131 9.993,25.779 10.11,25.522C10.223,25.275 10.26,24.989 10.193,24.598C10.036,23.691 9.419,22.641 8.566,21.207L8.547,21.175C7.791,19.905 6.831,18.292 6.599,16.629C6.474,15.737 6.549,14.794 6.969,13.861C7.384,12.937 8.09,12.128 9.06,11.419C9.729,10.93 10.667,11.075 11.156,11.744C11.646,12.412 11.5,13.351 10.831,13.84Z"
-      android:fillColor="@color/hvac_on_icon_fill_color"
-      android:fillType="evenOdd"/>
-  <path
-      android:pathData="M24,79L24,79A5,5 0,0 1,29 84L29,84A5,5 0,0 1,24 89L24,89A5,5 0,0 1,19 84L19,84A5,5 0,0 1,24 79z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M24,97L24,97A5,5 0,0 1,29 102L29,102A5,5 0,0 1,24 107L24,107A5,5 0,0 1,19 102L19,102A5,5 0,0 1,24 97z"
-      android:fillColor="@color/hvac_on_icon_fill_color"/>
-  <path
-      android:pathData="M24,61L24,61A5,5 0,0 1,29 66L29,66A5,5 0,0 1,24 71L24,71A5,5 0,0 1,19 66L19,66A5,5 0,0 1,24 61z"
-      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
index b407df6..cbf3fd1 100644
--- 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
@@ -15,24 +15,20 @@
   -->
 
 <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="108">
+        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.479,0H38.293C39.69,0 40.968,0.705 41.593,1.821C43.632,5.463 44.167,9.63 43.103,13.59L36.733,37.284C36.597,37.79 35.865,37.932 35.506,37.52L31.639,33.081C28.602,29.596 28.146,24.842 30.473,20.946L35.542,12.461C36.92,10.155 36.73,7.354 35.051,5.213L34.725,4.797C33.677,3.461 33.855,1.646 35.147,0.492C35.5,0.177 35.979,0 36.479,0ZM9.935,36.811H22.13C24.162,36.811 26.143,37.378 27.796,38.432L33.665,42.174C34.365,42.62 34.449,43.521 33.84,44.065L32.287,45.451C30.459,47.083 27.98,48 25.394,48H9.935C6.859,48 4.365,45.773 4.365,43.027V41.784C4.365,39.037 6.859,36.811 9.935,36.811ZM23.103,13.84C22.448,14.319 22.132,14.746 21.977,15.091C21.826,15.427 21.782,15.786 21.842,16.215C21.976,17.179 22.58,18.27 23.416,19.673L23.55,19.898C24.271,21.106 25.164,22.602 25.421,24.088C25.568,24.941 25.524,25.86 25.112,26.766C24.704,27.662 23.996,28.41 23.045,29.039C22.354,29.496 21.423,29.306 20.966,28.615C20.509,27.924 20.699,26.993 21.39,26.536C22.003,26.131 22.265,25.779 22.382,25.522C22.494,25.275 22.532,24.989 22.464,24.598C22.308,23.691 21.691,22.641 20.838,21.207L20.818,21.175C20.063,19.905 19.102,18.292 18.871,16.629C18.746,15.737 18.821,14.794 19.241,13.861C19.656,12.937 20.362,12.128 21.332,11.419C22,10.93 22.939,11.075 23.428,11.744C23.917,12.412 23.772,13.351 23.103,13.84ZM15.843,15.091C15.998,14.746 16.315,14.319 16.969,13.84C17.638,13.351 17.783,12.412 17.294,11.744C16.805,11.075 15.866,10.93 15.198,11.419C14.228,12.128 13.522,12.937 13.107,13.861C12.687,14.794 12.612,15.737 12.736,16.629C12.968,18.292 13.929,19.905 14.684,21.175L14.704,21.207C15.557,22.641 16.174,23.691 16.33,24.598C16.398,24.989 16.36,25.275 16.248,25.522C16.131,25.779 15.869,26.131 15.256,26.536C14.565,26.993 14.375,27.924 14.832,28.615C15.289,29.306 16.22,29.496 16.911,29.039C17.862,28.41 18.57,27.662 18.978,26.766C19.39,25.86 19.434,24.941 19.287,24.088C19.031,22.602 18.137,21.106 17.416,19.898L17.282,19.673C16.446,18.27 15.842,17.179 15.708,16.215C15.648,15.786 15.692,15.427 15.843,15.091ZM10.831,13.84C10.177,14.319 9.86,14.746 9.705,15.091C9.554,15.427 9.51,15.786 9.57,16.215C9.705,17.179 10.309,18.27 11.144,19.673L11.278,19.898C12,21.106 12.893,22.602 13.149,24.088C13.296,24.941 13.253,25.86 12.84,26.766C12.432,27.662 11.724,28.41 10.773,29.039C10.082,29.496 9.151,29.306 8.694,28.615C8.238,27.924 8.427,26.993 9.119,26.536C9.731,26.131 9.993,25.779 10.11,25.522C10.223,25.275 10.26,24.989 10.193,24.598C10.036,23.691 9.419,22.641 8.566,21.207L8.547,21.175C7.791,19.905 6.831,18.292 6.599,16.629C6.474,15.737 6.549,14.794 6.969,13.861C7.384,12.937 8.09,12.128 9.06,11.419C9.729,10.93 10.667,11.075 11.156,11.744C11.646,12.412 11.5,13.351 10.831,13.84Z"
+      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,79L24,79A5,5 0,0 1,29 84L29,84A5,5 0,0 1,24 89L24,89A5,5 0,0 1,19 84L19,84A5,5 0,0 1,24 79z"
+      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,97L24,97A5,5 0,0 1,29 102L29,102A5,5 0,0 1,24 107L24,107A5,5 0,0 1,19 102L19,102A5,5 0,0 1,24 97z"
-      android:fillColor="@color/hvac_off_icon_fill_color"
-      android:fillAlpha="@dimen/hvac_heat_or_cool_off_alpha"/>
-  <path
-      android:pathData="M24,61L24,61A5,5 0,0 1,29 66L29,66A5,5 0,0 1,24 71L24,71A5,5 0,0 1,19 66L19,66A5,5 0,0 1,24 61z"
+      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/nav_bar_button_background.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/nav_bar_button_background.xml
index de76f55..7c8b669 100644
--- 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
@@ -15,19 +15,9 @@
   -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android"
           xmlns:aapt="http://schemas.android.com/aapt">
-    <item android:state_activated="true">
+    <item android:state_selected="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">
@@ -36,26 +26,18 @@
                             <corners
                                 android:radius="@dimen/system_bar_button_corner_radius"/>
                             <solid
-                                android:color="@color/car_nav_icon_fill_color"/>
+                                android:color="?android:attr/colorAccent"/>
                         </shape>
                     </item>
                 </ripple>
             </item>
+            <item android:drawable="@drawable/ic_minimize"
+                  android:gravity="center"/>
         </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">
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
index 974cec2..69c3d30 100644
--- 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
@@ -52,8 +52,9 @@
                 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:highlightWhenSelected="true"
+                systemui:toggleSelected="true"
                 systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end"
                 systemui:clearBackStack="true"/>
 
@@ -61,23 +62,25 @@
                 android:id="@+id/standalone_notifications"
                 style="@style/SystemBarButton"
                 systemui:componentNames="com.android.car.notification/.CarNotificationCenterActivity"
-                systemui:highlightWhenSelected="true"
+                systemui:packages="com.android.car.notification"
                 systemui:icon="@drawable/car_ic_notification"
+                systemui:highlightWhenSelected="true"
+                systemui:toggleSelected="true"
                 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:highlightWhenSelected="true"
                 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:highlightWhenSelected="true"
                 systemui:useDefaultAppIconForRole="true"/>
         </LinearLayout>
 
@@ -93,4 +96,35 @@
         </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/fan_direction.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/fan_direction.xml
index 5cbe9e7..1770796 100644
--- 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
@@ -23,7 +23,7 @@
         android:orientation="horizontal">
         <com.android.systemui.car.hvac.toggle.HvacIntegerToggleButton
             android:id="@+id/direction_face"
-            android:layout_width="0dp"
+            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"
@@ -40,7 +40,7 @@
             android:layout_height="match_parent"/>
         <com.android.systemui.car.hvac.toggle.HvacIntegerToggleButton
             android:id="@+id/direction_floor"
-            android:layout_width="0dp"
+            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"
@@ -57,7 +57,7 @@
             android:layout_height="match_parent"/>
         <com.android.systemui.car.hvac.toggle.HvacIntegerToggleButton
             android:id="@+id/direction_defrost_front_and_floor"
-            android:layout_width="0dp"
+            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"
@@ -79,7 +79,7 @@
         android:orientation="horizontal">
         <com.android.systemui.car.hvac.toggle.HvacBooleanToggleButton
             android:id="@+id/direction_defrost_front"
-            android:layout_width="0dp"
+            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"
@@ -95,7 +95,7 @@
             android:layout_height="match_parent"/>
         <com.android.systemui.car.hvac.toggle.HvacBooleanToggleButton
             android:id="@+id/direction_defrost_rear"
-            android:layout_width="0dp"
+            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"
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
index d834e38..86df240 100644
--- 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
@@ -38,19 +38,16 @@
             app:layout_constraintGuide_begin="@dimen/hvac_panel_buttons_guideline"/>
 
         <!-- ************************ -->
-        <!-- First column of buttons. -->
+        <!-- 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_button_dimen"
-            android:layout_marginBottom="@dimen/hvac_panel_button_internal_margin"
+            android:layout_height="@dimen/hvac_panel_long_button_dimen"
             android:layout_marginLeft="@dimen/hvac_panel_button_external_margin"
-            android:layout_marginRight="@dimen/hvac_panel_button_internal_margin"
             android:layout_marginTop="@dimen/hvac_panel_button_external_top_margin"
             android:background="@drawable/hvac_default_background"
-            app:layout_constraintBottom_toTopOf="@+id/steering_wheel_heat_on_off"
             app:layout_constraintLeft_toLeftOf="parent"
             app:layout_constraintTop_toBottomOf="@+id/top_guideline"
             systemui:hvacAreaId="117"
@@ -59,107 +56,59 @@
             systemui:hvacToggleOnButtonDrawable="@drawable/ic_ac_on"
             systemui:hvacTurnOffIfAutoOn="true"/>
 
-        <com.android.systemui.car.hvac.toggle.HvacIntegerToggleButton
-            android:id="@+id/steering_wheel_heat_on_off"
+        <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_button_dimen"
-            android:layout_marginBottom="@dimen/hvac_panel_button_internal_margin"
-            android:layout_marginLeft="@dimen/hvac_panel_button_external_margin"
-            android:layout_marginRight="@dimen/hvac_panel_button_internal_margin"
-            android:layout_marginTop="@dimen/hvac_panel_button_internal_margin"
+            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_constraintBottom_toTopOf="@+id/hvac_on_off"
-            app:layout_constraintLeft_toLeftOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/cooling_on_off"
-            systemui:hvacAreaId="117"
-            systemui:hvacPropertyId="289408269"
-            systemui:hvacToggleOffButtonDrawable="@drawable/ic_heated_steering_off"
-            systemui:hvacToggleOnButtonDrawable="@drawable/ic_heated_steering_on"
-            systemui:hvacTurnOffIfAutoOn="true"
-            systemui:onValue="1"
-            systemui:offValue="0"/>
+            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_button_dimen"
+            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_margin"
+            android:layout_marginBottom="@dimen/hvac_panel_button_external_bottom_margin"
             android:layout_marginLeft="@dimen/hvac_panel_button_external_margin"
-            android:layout_marginRight="@dimen/hvac_panel_button_internal_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/steering_wheel_heat_on_off"
+            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 column of buttons. -->
-        <!-- ************************* -->
-
-        <com.android.systemui.car.hvac.SeatTemperatureLevelButton
-            android:id="@+id/seat_cooler_driver_on_off"
-            android:layout_width="@dimen/hvac_panel_button_dimen"
-            android:layout_height="@dimen/hvac_panel_tall_button_height"
-            android:layout_marginBottom="@dimen/hvac_panel_button_internal_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_external_top_margin"
-            android:background="@drawable/hvac_cool_background"
-            app:layout_constraintBottom_toTopOf="@+id/seat_heater_driver_on_off"
-            app:layout_constraintLeft_toRightOf="@+id/cooling_on_off"
-            app:layout_constraintRight_toLeftOf="@+id/airflow_group"
-            app:layout_constraintTop_toBottomOf="@+id/top_guideline"
-            systemui:hvacAreaId="1"
-            systemui:seatTemperatureType="cooling"
-            systemui:seatTemperatureIconDrawableList="@array/hvac_driver_seat_cool_icons"/>
-
-        <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_tall_button_height"
-            android:layout_marginBottom="@dimen/hvac_panel_button_external_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:background="@drawable/hvac_heat_background"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintLeft_toRightOf="@+id/hvac_on_off"
-            app:layout_constraintRight_toLeftOf="@+id/fan_speed_control"
-            app:layout_constraintTop_toBottomOf="@+id/seat_cooler_driver_on_off"
-            systemui:hvacAreaId="1"
-            systemui:seatTemperatureType="heating"
-            systemui:seatTemperatureIconDrawableList="@array/hvac_driver_seat_heat_icons"/>
-
         <!-- ************************ -->
-        <!-- Third column of buttons. -->
+        <!-- Second group of buttons. -->
         <!-- ************************ -->
 
         <LinearLayout
             android:id="@+id/airflow_group"
-            android:layout_width="0dp"
+            android:layout_width="@dimen/hvac_panel_slider_width"
             android:layout_height="0dp"
-            android:layout_marginBottom="@dimen/hvac_panel_button_internal_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_external_top_margin"
             android:orientation="vertical"
-            app:layout_constraintBottom_toTopOf="@+id/fan_speed_control"
-            app:layout_constraintLeft_toRightOf="@+id/seat_cooler_driver_on_off"
-            app:layout_constraintRight_toLeftOf="@+id/seat_cooler_passenger_on_off"
+            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="0dp"
+            android:layout_width="@dimen/hvac_panel_slider_width"
             android:layout_height="@dimen/hvac_panel_button_dimen"
-            android:layout_marginBottom="@dimen/hvac_panel_button_external_margin"
+            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"
@@ -170,60 +119,34 @@
             android:background="@drawable/fan_speed_seek_bar_background"
             android:splitTrack="false"
             app:layout_constraintBottom_toBottomOf="parent"
-            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/airflow_windshield"/>
+            app:layout_constraintLeft_toLeftOf="@+id/airflow_group"
+            app:layout_constraintRight_toRightOf="@+id/airflow_group"
+            app:layout_constraintTop_toBottomOf="@+id/airflow_group"/>
 
         <!-- ************************* -->
-        <!-- Fourth column of buttons. -->
+        <!-- Third group of buttons. -->
         <!-- ************************* -->
 
         <com.android.systemui.car.hvac.SeatTemperatureLevelButton
-            android:id="@+id/seat_cooler_passenger_on_off"
-            android:layout_width="@dimen/hvac_panel_button_dimen"
-            android:layout_height="@dimen/hvac_panel_tall_button_height"
-            android:layout_marginBottom="@dimen/hvac_panel_button_internal_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_external_top_margin"
-            android:background="@drawable/hvac_cool_background"
-            app:layout_constraintBottom_toTopOf="@+id/seat_heater_passenger_on_off"
-            app:layout_constraintRight_toLeftOf="@+id/recycle_air_on_off"
-            app:layout_constraintTop_toBottomOf="@+id/top_guideline"
-            systemui:hvacAreaId="4"
-            systemui:seatTemperatureType="cooling"
-            systemui:seatTemperatureIconDrawableList="@array/hvac_passenger_seat_cool_icons"/>
-
-        <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_tall_button_height"
-            android:layout_marginBottom="@dimen/hvac_panel_button_external_margin"
-            android:layout_marginLeft="@dimen/hvac_panel_button_internal_margin"
+            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_internal_margin"
+            android:layout_marginTop="@dimen/hvac_panel_button_external_top_margin"
             android:background="@drawable/hvac_heat_background"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintRight_toLeftOf="@+id/hvac_driver_passenger_sync"
-            app:layout_constraintTop_toBottomOf="@+id/seat_cooler_passenger_on_off"
+            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"/>
 
-        <!-- ************************ -->
-        <!-- Fifth column of buttons. -->
-        <!-- ************************ -->
-
         <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_marginBottom="@dimen/hvac_panel_button_internal_margin"
-            android:layout_marginLeft="@dimen/hvac_panel_button_internal_margin"
             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_constraintBottom_toTopOf="@+id/hvac_driver_passenger_sync"
             app:layout_constraintRight_toRightOf="parent"
             app:layout_constraintTop_toBottomOf="@+id/top_guideline"
             systemui:hvacAreaId="117"
@@ -233,39 +156,35 @@
             systemui:hvacToggleOffButtonDrawable="@drawable/ic_recirculate_off"/>
 
         <com.android.systemui.car.hvac.toggle.HvacBooleanToggleButton
-            android:id="@+id/hvac_driver_passenger_sync"
-            android:layout_width="@dimen/hvac_panel_button_dimen"
-            android:layout_height="@dimen/hvac_panel_button_dimen"
-            android:layout_marginBottom="@dimen/hvac_panel_button_internal_margin"
-            android:layout_marginLeft="@dimen/hvac_panel_button_internal_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_toTopOf="@+id/auto_temperature_on_off"
-            app:layout_constraintRight_toRightOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/recycle_air_on_off"
-            systemui:hvacAreaId="117"
-            systemui:hvacPropertyId="354419977"
-            systemui:hvacToggleOffButtonDrawable="@drawable/ic_sync_off"
-            systemui:hvacToggleOnButtonDrawable="@drawable/ic_sync_on"
-            systemui:hvacTurnOffIfAutoOn="true"/>
-
-        <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_marginBottom="@dimen/hvac_panel_button_external_margin"
-            android:layout_marginLeft="@dimen/hvac_panel_button_internal_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_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/hvac_driver_passenger_sync"
+            app:layout_constraintTop_toBottomOf="@+id/auto_temperature_on_off"
             systemui:hvacAreaId="117"
-            systemui:hvacPropertyId="354419978"
-            systemui:hvacToggleOnButtonDrawable="@drawable/ic_auto_on"
-            systemui:hvacToggleOffButtonDrawable="@drawable/ic_auto_off"/>
+            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"/>
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
index c1c2119..828003f 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/arrays.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/arrays.xml
@@ -19,29 +19,14 @@
     <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_med</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_med</item>
         <item>@drawable/ic_passenger_seat_heat_high</item>
     </array>
-    <array name="hvac_driver_seat_cool_icons">
-        <item>@drawable/ic_driver_seat_cool_off</item>
-        <item>@drawable/ic_driver_seat_cool_low</item>
-        <item>@drawable/ic_driver_seat_cool_med</item>
-        <item>@drawable/ic_driver_seat_cool_high</item>
-    </array>
-    <array name="hvac_passenger_seat_cool_icons">
-        <item>@drawable/ic_passenger_seat_cool_off</item>
-        <item>@drawable/ic_passenger_seat_cool_low</item>
-        <item>@drawable/ic_passenger_seat_cool_med</item>
-        <item>@drawable/ic_passenger_seat_cool_high</item>
-    </array>
     <array name="hvac_fan_speed_icons">
-        <item>@drawable/fan_speed_seek_bar_thumb_off</item>
         <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>
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
index 2c5c903..3f62d35 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/colors.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/colors.xml
@@ -20,6 +20,7 @@
 
     <color name="car_nav_icon_fill_color">#FFFFFF</color>
     <color name="car_nav_icon_background_color">#282A2D</color>
+    <color name="car_nav_minimize_icon_fill_color">@android:color/black</color>
 
     <drawable name="system_bar_background">#000000</drawable>
     <color name="system_bar_text_color">#E8EAED</color>
@@ -38,6 +39,7 @@
     <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>
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
index 11cca9c..75918eb 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/config.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/config.xml
@@ -23,11 +23,13 @@
     <!-- 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.hvac.AutoDismissHvacPanelOverlayViewMediator</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">9</integer>
+    <integer name="hvac_num_fan_speeds">8</integer>
+
+    <integer name="config_hvacAutoDismissDurationMs">15000</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
index 0ec6298..f900e57 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/dimens.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/dimens.xml
@@ -42,6 +42,9 @@
     <dimen name="system_bar_user_icon_drawing_size">44dp</dimen>
     <dimen name="status_bar_system_icon_spacing">32dp</dimen>
 
+    <dimen name="system_bar_minimize_icon_height">17dp</dimen>
+    <dimen name="system_bar_minimize_icon_width">28dp</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>
@@ -49,20 +52,27 @@
     <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">44dp</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_tall_button_height">148dp</dimen>
-    <dimen name="hvac_panel_wide_button_width">374dp</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_internal_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>
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/integers.xml
similarity index 81%
copy from car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml
copy to car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/integers.xml
index 36785dc..282373c 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/integers.xml
@@ -14,7 +14,6 @@
   ~ limitations under the License.
   -->
 
-<overlay>
-    <item target="string/config_rearViewCameraActivity"
-          value="@string/config_rearViewCameraActivity"/>
-</overlay>
+<resources>
+    <integer name="hvac_seat_heat_level_count">3</integer>
+</resources>
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
index 6e400e5..b5f46f8 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/styles.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/styles.xml
@@ -17,12 +17,19 @@
 <resources
     xmlns:android="http://schemas.android.com/apk/res/android">
 
+    <!--
+        Note on selected/unselected icons:
+        The icon is always tinted with @color/car_nav_icon_fill_color_selected in @layout/car_system_bar_button
+        Unselected: keep this behavior so all icons have consistent color (eg. tint a multi-colored default app icon)
+        Selected: set selected alpha 0, making icon transparent. Use state list nav_bar_button_background to show selected icon (in addition to background).
+    -->
     <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>
+        <item name="selectedAlpha">0</item>
     </style>
 
     <style name="TextAppearance.SystemBar.Username"
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
index a7cddb8..255d70e 100644
--- 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
@@ -16,9 +16,11 @@
 
 package com.android.systemui;
 
+import com.android.systemui.car.window.ExtendedOverlayWindowModule;
+
 import dagger.Module;
 
 /** Binder for AAECarSystemUI specific {@link SystemUI} modules and components. */
-@Module
+@Module(includes = {ExtendedOverlayWindowModule.class})
 abstract class CarUiPortraitSystemUIBinder extends CarSystemUIBinder {
 }
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/hvac/AutoDismissHvacPanelOverlayViewController.java b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/hvac/AutoDismissHvacPanelOverlayViewController.java
new file mode 100644
index 0000000..bf0b34d
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/hvac/AutoDismissHvacPanelOverlayViewController.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car.hvac;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Handler;
+
+import com.android.systemui.R;
+import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.car.window.OverlayViewGlobalStateController;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.wm.shell.animation.FlingAnimationUtils;
+
+import javax.inject.Inject;
+
+/**
+ *  An extension of {@link HvacPanelOverlayViewController} which auto dismisses the panel if there
+ *  is no activity for some configured amount of time.
+ */
+@SysUISingleton
+public class AutoDismissHvacPanelOverlayViewController extends HvacPanelOverlayViewController {
+
+    private final Resources mResources;
+    private final Handler mHandler;
+
+    private HvacPanelView mHvacPanelView;
+    private int mAutoDismissDurationMs;
+
+    private final Runnable mAutoDismiss = () -> {
+        if (isPanelExpanded()) {
+            toggle();
+        }
+    };
+
+    @Inject
+    public AutoDismissHvacPanelOverlayViewController(Context context,
+            @Main Resources resources,
+            HvacController hvacController,
+            OverlayViewGlobalStateController overlayViewGlobalStateController,
+            FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
+            CarDeviceProvisionedController carDeviceProvisionedController,
+            @Main Handler handler) {
+        super(context, resources, hvacController, overlayViewGlobalStateController,
+                flingAnimationUtilsBuilder, carDeviceProvisionedController);
+        mResources = resources;
+        mHandler = handler;
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mAutoDismissDurationMs = mResources.getInteger(R.integer.config_hvacAutoDismissDurationMs);
+
+        mHvacPanelView = getLayout().findViewById(R.id.hvac_panel);
+        mHvacPanelView.setMotionEventHandler(event -> {
+            if (!isPanelExpanded()) {
+                return;
+            }
+
+            mHandler.removeCallbacks(mAutoDismiss);
+            mHandler.postDelayed(mAutoDismiss, mAutoDismissDurationMs);
+        });
+    }
+
+    @Override
+    protected void onAnimateExpandPanel() {
+        super.onAnimateExpandPanel();
+
+        mHandler.postDelayed(mAutoDismiss, mAutoDismissDurationMs);
+    }
+
+    @Override
+    protected void onAnimateCollapsePanel() {
+        super.onAnimateCollapsePanel();
+
+        mHandler.removeCallbacks(mAutoDismiss);
+    }
+}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/hvac/AutoDismissHvacPanelOverlayViewMediator.java b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/hvac/AutoDismissHvacPanelOverlayViewMediator.java
new file mode 100644
index 0000000..ce30c43
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/hvac/AutoDismissHvacPanelOverlayViewMediator.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car.hvac;
+
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.car.systembar.CarSystemBarController;
+import com.android.systemui.dagger.SysUISingleton;
+
+import javax.inject.Inject;
+
+/**
+ * Instance of {@link HvacPanelOverlayViewMediator} which uses {@link
+ * AutoDismissHvacPanelOverlayViewController}.
+ */
+@SysUISingleton
+public class AutoDismissHvacPanelOverlayViewMediator extends HvacPanelOverlayViewMediator {
+
+    @Inject
+    public AutoDismissHvacPanelOverlayViewMediator(
+            CarSystemBarController carSystemBarController,
+            AutoDismissHvacPanelOverlayViewController hvacPanelOverlayViewController,
+            BroadcastDispatcher broadcastDispatcher) {
+        super(carSystemBarController, hvacPanelOverlayViewController, broadcastDispatcher);
+    }
+}
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
index bf409a3..bc7f4f2 100644
--- 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
@@ -106,12 +106,15 @@
 
     private void init(AttributeSet attrs) {
         int speeds = mContext.getResources().getInteger(R.integer.hvac_num_fan_speeds);
-        setMin(0);
+        if (speeds < 1) {
+            throw new IllegalArgumentException("The nuer of fan speeds should be > 1");
+        }
+
+        setMin(1);
         incrementProgressBy(1);
-        // Subtract 1 since we're starting from 0.
-        setMax(speeds - 1);
+        setMax(speeds);
         int thumbRadius = mContext.getResources().getDimensionPixelSize(
-                R.dimen.hvac_panel_on_button_radius);
+                R.dimen.hvac_panel_seek_bar_radius);
         setPadding(thumbRadius, 0, thumbRadius, 0);
         mHvacGlobalAreaId = mContext.getResources().getInteger(R.integer.hvac_global_area_id);
 
@@ -136,7 +139,7 @@
         }
 
         for (int i = 0; i < speeds; i++) {
-            mIcons.set(i, fanSpeedThumbIcons.getDrawable(i));
+            mIcons.set(i + 1, fanSpeedThumbIcons.getDrawable(i));
         }
         fanSpeedThumbIcons.recycle();
         typedArray.recycle();
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/window/ExtendedOverlayWindowModule.java b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/window/ExtendedOverlayWindowModule.java
new file mode 100644
index 0000000..21f1500
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/window/ExtendedOverlayWindowModule.java
@@ -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 com.android.systemui.car.window;
+
+import com.android.systemui.car.hvac.AutoDismissHvacPanelOverlayViewMediator;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+
+/** Lists additional {@link OverlayViewMediator} that apply to the CarUiPortraitSystemUI. */
+@Module
+public abstract class ExtendedOverlayWindowModule {
+
+    /** Injects RearViewCameraViewMediator. */
+    @Binds
+    @IntoMap
+    @ClassKey(AutoDismissHvacPanelOverlayViewMediator.class)
+    public abstract OverlayViewMediator bindAutoDismissHvacPanelViewMediator(
+            AutoDismissHvacPanelOverlayViewMediator overlayViewsMediator);
+}
diff --git a/car_product/car_ui_portrait/bootanimation/bootanimation.zip b/car_product/car_ui_portrait/bootanimation/bootanimation.zip
index d7b90b3..2843730 100644
--- a/car_product/car_ui_portrait/bootanimation/bootanimation.zip
+++ 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
index a4ebbac..abfa895 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/desc.txt
+++ b/car_product/car_ui_portrait/bootanimation/parts/desc.txt
@@ -1,2 +1,3 @@
-986 1000 60
+1224 2175 24
 c 1 0 part0
+p 0 0 part1
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00023.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00023.png
new file mode 100644
index 0000000..29499a6
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00023.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00024.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00024.png
new file mode 100644
index 0000000..29499a6
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00024.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00025.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00025.png
new file mode 100644
index 0000000..7d1314e
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00025.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00026.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00026.png
new file mode 100644
index 0000000..f50bbda
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00026.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00027.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00027.png
new file mode 100644
index 0000000..2a90baf
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00027.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00028.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00028.png
new file mode 100644
index 0000000..02ed17b
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00028.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00029.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00029.png
new file mode 100644
index 0000000..8a22023
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00029.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00030.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00030.png
new file mode 100644
index 0000000..6cf674b
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00030.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00031.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00031.png
new file mode 100644
index 0000000..482193d
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00031.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00032.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00032.png
new file mode 100644
index 0000000..aa224f8
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00032.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00033.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00033.png
new file mode 100644
index 0000000..4cd9877
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00033.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00034.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00034.png
new file mode 100644
index 0000000..af5ab61
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00034.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00035.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00035.png
new file mode 100644
index 0000000..691c0df
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00035.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00036.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00036.png
new file mode 100644
index 0000000..ec45285
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00036.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00037.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00037.png
new file mode 100644
index 0000000..8962084
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00037.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00038.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00038.png
new file mode 100644
index 0000000..1640075
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00038.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00039.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00039.png
new file mode 100644
index 0000000..130924a
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00039.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00040.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00040.png
new file mode 100644
index 0000000..19802da
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00040.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00041.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00041.png
new file mode 100644
index 0000000..dfd77a0
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00041.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00042.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00042.png
new file mode 100644
index 0000000..c97189e
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00042.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00043.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00043.png
new file mode 100644
index 0000000..664c312
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00043.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00044.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00044.png
new file mode 100644
index 0000000..275e75c
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00044.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00045.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00045.png
new file mode 100644
index 0000000..4a98b77
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00045.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00046.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00046.png
new file mode 100644
index 0000000..f62b3bc
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00046.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00047.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00047.png
new file mode 100644
index 0000000..23915b9
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00047.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00048.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00048.png
new file mode 100644
index 0000000..ad64499
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00048.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00049.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00049.png
new file mode 100644
index 0000000..b259965
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00049.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00050.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00050.png
new file mode 100644
index 0000000..7e17a93
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00050.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00051.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00051.png
new file mode 100644
index 0000000..eaa32b6
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00051.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00052.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00052.png
new file mode 100644
index 0000000..c683cb8
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00052.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00053.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00053.png
new file mode 100644
index 0000000..d7eb3fd
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00053.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00054.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00054.png
new file mode 100644
index 0000000..aaeb6db
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00054.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00055.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00055.png
new file mode 100644
index 0000000..8b577e4
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00055.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00056.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00056.png
new file mode 100644
index 0000000..953cd95
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00056.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00057.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00057.png
new file mode 100644
index 0000000..52e5ec3
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00057.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00058.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00058.png
new file mode 100644
index 0000000..681a0f9
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00058.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00059.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00059.png
new file mode 100644
index 0000000..83840b6
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00059.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00060.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00060.png
index 3a12b7e..8d8eb51 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00060.png
+++ 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
index 3a12b7e..f4abe38 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00061.png
+++ 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
index 455560c..d7f9e56 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00062.png
+++ 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
index 7450c5f..42d0bd0 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00063.png
+++ 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
index 7795a77..d52b3e6 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00064.png
+++ 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
index fae1212..16c454e 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00065.png
+++ 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
index df8f1cd..e1c0b1d 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00066.png
+++ 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
index f936d27..15935ff 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00067.png
+++ 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
index 2a6bb73..981883c 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00068.png
+++ 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
index e565ea5..43f1c9f 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00069.png
+++ 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
index 41cbf3c..addd8c1 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00070.png
+++ 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
index 956259a..0d964e9 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00071.png
+++ 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
index e86e314..e1ea8ce 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00072.png
+++ 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
index 4cbd91c..4314159 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00073.png
+++ 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
index a00c625..c9c9543 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00074.png
+++ 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
index 9deae48..3db4b49 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00075.png
+++ 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
index 3fad600..cf76b15 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00076.png
+++ 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
index 17f8728..45213e3 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00077.png
+++ 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
index caaad59..86c56a7 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00078.png
+++ 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
index 0b106e5..82c4f55 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00079.png
+++ 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
index fb9e49a..d4d8deb 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00080.png
+++ 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
index ac81644..5e50025 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00081.png
+++ 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
index f25cec5..b840d1d 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00082.png
+++ 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
index 3a4e680..0398cdc 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00083.png
+++ 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
index 7a9c203..0d8228e 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00084.png
+++ 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
index 765afd9..8496bc6 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00085.png
+++ 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
index a3a233c..7885233 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00086.png
+++ 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
index f2aa138..751f94b 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00087.png
+++ 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
index a1e35ec..7d96719 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00088.png
+++ 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
index e49f6ee..7d96719 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00089.png
+++ 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
index 6934ac3..7d96719 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00090.png
+++ 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
index 0dc6b52..7d96719 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00091.png
+++ 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
index d0cc426..7d96719 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00092.png
+++ 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
index 5e5a8da..7d96719 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00093.png
+++ 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
index 608e17f..7d96719 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00094.png
+++ 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
index a802840..7d96719 100644
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00095.png
+++ 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
deleted file mode 100644
index 5f600f1..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00096.png
+++ /dev/null
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
deleted file mode 100644
index 76448c2..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00097.png
+++ /dev/null
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
deleted file mode 100644
index fbbb879..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00098.png
+++ /dev/null
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
deleted file mode 100644
index 35f6feb..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00099.png
+++ /dev/null
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
deleted file mode 100644
index b980f13..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00100.png
+++ /dev/null
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
deleted file mode 100644
index 11617e7..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00101.png
+++ /dev/null
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
deleted file mode 100644
index 77f660c..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00102.png
+++ /dev/null
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
deleted file mode 100644
index 7653dd8..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00103.png
+++ /dev/null
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
deleted file mode 100644
index 5afc5f3..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00104.png
+++ /dev/null
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
deleted file mode 100644
index 7331f7b..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00105.png
+++ /dev/null
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
deleted file mode 100644
index 45b3473..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00106.png
+++ /dev/null
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
deleted file mode 100644
index 8f48b1d..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00107.png
+++ /dev/null
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
deleted file mode 100644
index 7c1dfbb..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00108.png
+++ /dev/null
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
deleted file mode 100644
index 60240eb..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00109.png
+++ /dev/null
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
deleted file mode 100644
index 7f7821f..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00110.png
+++ /dev/null
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
deleted file mode 100644
index 1d4fc02..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00111.png
+++ /dev/null
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
deleted file mode 100644
index 4272e3b..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00112.png
+++ /dev/null
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
deleted file mode 100644
index 74aa68b..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00113.png
+++ /dev/null
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
deleted file mode 100644
index 9f9962a..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00114.png
+++ /dev/null
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
deleted file mode 100644
index 1afd28a..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00115.png
+++ /dev/null
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
deleted file mode 100644
index 5f6eab0..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00116.png
+++ /dev/null
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
deleted file mode 100644
index 7640188..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00117.png
+++ /dev/null
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
deleted file mode 100644
index b8691e5..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00118.png
+++ /dev/null
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
deleted file mode 100644
index 8c75267..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00119.png
+++ /dev/null
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
deleted file mode 100644
index 38a22cc..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00120.png
+++ /dev/null
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
deleted file mode 100644
index 52b1a3d..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00121.png
+++ /dev/null
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
deleted file mode 100644
index 1e56cea..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00122.png
+++ /dev/null
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
deleted file mode 100644
index a5c7458..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00123.png
+++ /dev/null
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
deleted file mode 100644
index 89f4952..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00124.png
+++ /dev/null
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
deleted file mode 100644
index 3044353..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00125.png
+++ /dev/null
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
deleted file mode 100644
index d9e42d7..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00126.png
+++ /dev/null
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
deleted file mode 100644
index ea03d6a..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00127.png
+++ /dev/null
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
deleted file mode 100644
index 39b7176..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00128.png
+++ /dev/null
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
deleted file mode 100644
index 39cd88f..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00129.png
+++ /dev/null
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
deleted file mode 100644
index 1fb3509..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00130.png
+++ /dev/null
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
deleted file mode 100644
index 3f35990..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00131.png
+++ /dev/null
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
deleted file mode 100644
index aa6ad9a..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00132.png
+++ /dev/null
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
deleted file mode 100644
index 8075ac8..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00133.png
+++ /dev/null
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
deleted file mode 100644
index 7b9c3b0..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00134.png
+++ /dev/null
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
deleted file mode 100644
index ec203ec..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00135.png
+++ /dev/null
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
deleted file mode 100644
index cbd7182..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00136.png
+++ /dev/null
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
deleted file mode 100644
index 8319819..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00137.png
+++ /dev/null
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
deleted file mode 100644
index 3729ade..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00138.png
+++ /dev/null
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
deleted file mode 100644
index b41c045..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00139.png
+++ /dev/null
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
deleted file mode 100644
index f2a840a..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00140.png
+++ /dev/null
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
deleted file mode 100644
index 9bdd6db..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00141.png
+++ /dev/null
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
deleted file mode 100644
index 63dd4d0..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00142.png
+++ /dev/null
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
deleted file mode 100644
index de44f52..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00143.png
+++ /dev/null
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
deleted file mode 100644
index 20e5242..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00144.png
+++ /dev/null
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
deleted file mode 100644
index 9db1d26..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00145.png
+++ /dev/null
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
deleted file mode 100644
index aa55e1e..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00146.png
+++ /dev/null
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
deleted file mode 100644
index 077c3b0..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00147.png
+++ /dev/null
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
deleted file mode 100644
index 46cf822..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00148.png
+++ /dev/null
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
deleted file mode 100644
index 179870f..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00149.png
+++ /dev/null
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
deleted file mode 100644
index 55cc405..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00150.png
+++ /dev/null
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
deleted file mode 100644
index 6adcea3..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00151.png
+++ /dev/null
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
deleted file mode 100644
index b11dc75..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00152.png
+++ /dev/null
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
deleted file mode 100644
index cb87e84..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00153.png
+++ /dev/null
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
deleted file mode 100644
index af1acad..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00154.png
+++ /dev/null
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
deleted file mode 100644
index 0abfa3d..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00155.png
+++ /dev/null
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
deleted file mode 100644
index b2cf2ae..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00156.png
+++ /dev/null
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
deleted file mode 100644
index 2e4648d..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00157.png
+++ /dev/null
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
deleted file mode 100644
index bc05bf9..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00158.png
+++ /dev/null
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
deleted file mode 100644
index ff331f7..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00159.png
+++ /dev/null
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
deleted file mode 100644
index fe111e6..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00160.png
+++ /dev/null
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
deleted file mode 100644
index 9e81d38..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00161.png
+++ /dev/null
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
deleted file mode 100644
index 1cf960c..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00162.png
+++ /dev/null
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
deleted file mode 100644
index f5e4543..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00163.png
+++ /dev/null
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
deleted file mode 100644
index cac88e2..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00164.png
+++ /dev/null
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
deleted file mode 100644
index c843493..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00165.png
+++ /dev/null
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
deleted file mode 100644
index 761eeba..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00166.png
+++ /dev/null
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
deleted file mode 100644
index 0f3402e..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00167.png
+++ /dev/null
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
deleted file mode 100644
index bb74e3c..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00168.png
+++ /dev/null
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
deleted file mode 100644
index 23c31e0..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00169.png
+++ /dev/null
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
deleted file mode 100644
index 989f4a8..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00170.png
+++ /dev/null
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
deleted file mode 100644
index 91f2130..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00171.png
+++ /dev/null
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
deleted file mode 100644
index 90bfe15..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00172.png
+++ /dev/null
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
deleted file mode 100644
index cdf1e30..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00173.png
+++ /dev/null
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
deleted file mode 100644
index e00a7ce..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00174.png
+++ /dev/null
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
deleted file mode 100644
index 5f7cf40..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00175.png
+++ /dev/null
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
deleted file mode 100644
index a4d9bdf..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00176.png
+++ /dev/null
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
deleted file mode 100644
index e044dbc..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00177.png
+++ /dev/null
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
deleted file mode 100644
index 52b0244..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00178.png
+++ /dev/null
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
deleted file mode 100644
index 8873cde..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00179.png
+++ /dev/null
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
deleted file mode 100644
index 81385e5..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00180.png
+++ /dev/null
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
deleted file mode 100644
index 104cd1e..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00181.png
+++ /dev/null
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
deleted file mode 100644
index 0ea0bd1..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00182.png
+++ /dev/null
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
deleted file mode 100644
index c70211e..0000000
--- a/car_product/car_ui_portrait/bootanimation/parts/part0/00183.png
+++ /dev/null
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00096.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00096.png
new file mode 100644
index 0000000..33ad84f
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00096.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00097.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00097.png
new file mode 100644
index 0000000..765ec1a
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00097.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00098.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00098.png
new file mode 100644
index 0000000..9947c94
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00098.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00099.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00099.png
new file mode 100644
index 0000000..db0ac24
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00099.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00100.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00100.png
new file mode 100644
index 0000000..ae93d95
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00100.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00101.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00101.png
new file mode 100644
index 0000000..1b729c0
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00101.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00102.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00102.png
new file mode 100644
index 0000000..22b9527
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00102.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00103.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00103.png
new file mode 100644
index 0000000..8ce8992
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00103.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00104.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00104.png
new file mode 100644
index 0000000..f16dbc2
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00104.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00105.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00105.png
new file mode 100644
index 0000000..5ef6b89
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00105.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00106.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00106.png
new file mode 100644
index 0000000..53ce2bc
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00106.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00107.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00107.png
new file mode 100644
index 0000000..fd8ffef
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00107.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00108.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00108.png
new file mode 100644
index 0000000..49b39ed
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00108.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00109.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00109.png
new file mode 100644
index 0000000..aaa55db
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00109.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00110.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00110.png
new file mode 100644
index 0000000..3442634
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00110.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00111.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00111.png
new file mode 100644
index 0000000..7aecef1
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00111.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00112.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00112.png
new file mode 100644
index 0000000..a5278ba
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00112.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00113.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00113.png
new file mode 100644
index 0000000..7e3dc9e
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00113.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00114.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00114.png
new file mode 100644
index 0000000..9b93101
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00114.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00115.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00115.png
new file mode 100644
index 0000000..ab90998
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00115.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00116.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00116.png
new file mode 100644
index 0000000..38b3f8f
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00116.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00117.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00117.png
new file mode 100644
index 0000000..5fc2e5c
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00117.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00118.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00118.png
new file mode 100644
index 0000000..ccb9eea
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00118.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00119.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00119.png
new file mode 100644
index 0000000..f910866
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00119.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00120.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00120.png
new file mode 100644
index 0000000..ecf8c0c
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00120.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00121.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00121.png
new file mode 100644
index 0000000..a0199e5
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00121.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00122.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00122.png
new file mode 100644
index 0000000..a697982
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00122.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00123.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00123.png
new file mode 100644
index 0000000..3c88c58
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00123.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00124.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00124.png
new file mode 100644
index 0000000..626e5e5
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00124.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00125.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00125.png
new file mode 100644
index 0000000..ae9d3df
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00125.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00126.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00126.png
new file mode 100644
index 0000000..ae5f460
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00126.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00127.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00127.png
new file mode 100644
index 0000000..146759c
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00127.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00128.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00128.png
new file mode 100644
index 0000000..064f01d
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00128.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00129.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00129.png
new file mode 100644
index 0000000..73d88d5
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00129.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00130.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00130.png
new file mode 100644
index 0000000..689df07
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00130.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00131.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00131.png
new file mode 100644
index 0000000..247f995
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00131.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00132.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00132.png
new file mode 100644
index 0000000..f8c5a02
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00132.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00133.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00133.png
new file mode 100644
index 0000000..a0379c7
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00133.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00134.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00134.png
new file mode 100644
index 0000000..90b94ec
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00134.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00135.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00135.png
new file mode 100644
index 0000000..5230de4
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00135.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00136.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00136.png
new file mode 100644
index 0000000..3eb96d5
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00136.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00137.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00137.png
new file mode 100644
index 0000000..37774d9
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00137.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00138.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00138.png
new file mode 100644
index 0000000..01c95d0
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00138.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00139.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00139.png
new file mode 100644
index 0000000..f496ce3
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00139.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00140.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00140.png
new file mode 100644
index 0000000..0e13dfb
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00140.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00141.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00141.png
new file mode 100644
index 0000000..46cad3a
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00141.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00142.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00142.png
new file mode 100644
index 0000000..ab40ddc
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00142.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00143.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00143.png
new file mode 100644
index 0000000..a70a6cc
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00143.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00144.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00144.png
new file mode 100644
index 0000000..e357b73
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00144.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00145.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00145.png
new file mode 100644
index 0000000..813fa51
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00145.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00146.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00146.png
new file mode 100644
index 0000000..469f0a6
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00146.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00147.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00147.png
new file mode 100644
index 0000000..9f3f355
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00147.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00148.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00148.png
new file mode 100644
index 0000000..e4f3c2b
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00148.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00149.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00149.png
new file mode 100644
index 0000000..434a379
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00149.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00150.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00150.png
new file mode 100644
index 0000000..12ef618
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00150.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00151.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00151.png
new file mode 100644
index 0000000..a4d2e09
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00151.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00152.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00152.png
new file mode 100644
index 0000000..c657de6
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00152.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00153.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00153.png
new file mode 100644
index 0000000..c745f96
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00153.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00154.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00154.png
new file mode 100644
index 0000000..5f389dc
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00154.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00155.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00155.png
new file mode 100644
index 0000000..50f51b7
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00155.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00156.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00156.png
new file mode 100644
index 0000000..c905a6c
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00156.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00157.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00157.png
new file mode 100644
index 0000000..23f509d
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00157.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00158.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00158.png
new file mode 100644
index 0000000..cbd9ebe
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00158.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00159.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00159.png
new file mode 100644
index 0000000..afe61e4
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00159.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00160.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00160.png
new file mode 100644
index 0000000..35eadb5
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00160.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00161.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00161.png
new file mode 100644
index 0000000..6d43b14
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00161.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00162.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00162.png
new file mode 100644
index 0000000..f5ac837
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00162.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00163.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00163.png
new file mode 100644
index 0000000..657f59a
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00163.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00164.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00164.png
new file mode 100644
index 0000000..6352deb
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00164.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00165.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00165.png
new file mode 100644
index 0000000..8150c65
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00165.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00166.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00166.png
new file mode 100644
index 0000000..1199a55
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00166.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00167.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00167.png
new file mode 100644
index 0000000..7d96719
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00167.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00168.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00168.png
new file mode 100644
index 0000000..7d96719
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00168.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part1/00169.png b/car_product/car_ui_portrait/bootanimation/parts/part1/00169.png
new file mode 100644
index 0000000..7d96719
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part1/00169.png
Binary files differ
diff --git a/car_product/rro/CarSystemUIEvsRRO/Android.bp b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/Android.bp
similarity index 79%
copy from car_product/rro/CarSystemUIEvsRRO/Android.bp
copy to car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/Android.bp
index ebb09ef..2a4357c 100644
--- a/car_product/rro/CarSystemUIEvsRRO/Android.bp
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/Android.bp
@@ -15,11 +15,16 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 android_app {
-    name: "CarSystemUIEvsRRO",
+    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/rro/CarSystemUIEvsRRO/AndroidManifest.xml b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/AndroidManifest.xml
similarity index 76%
copy from car_product/rro/CarSystemUIEvsRRO/AndroidManifest.xml
copy to car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/AndroidManifest.xml
index f1dc7b8..c878ee1 100644
--- a/car_product/rro/CarSystemUIEvsRRO/AndroidManifest.xml
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/AndroidManifest.xml
@@ -15,10 +15,11 @@
   ~ limitations under the License.
   -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.systemui.car.evs.rro">
+          package="com.google.android.car.evs.caruiportrait.rro">
     <application android:hasCode="false"/>
-    <overlay android:targetName="CarSystemUI"
-             android:targetPackage="com.android.systemui"
+    <overlay android:priority="20"
+             android:targetName="CarEvsCameraPreviewApp"
+             android:targetPackage="com.google.android.car.evs"
              android:resourcesMap="@xml/overlays"
-             android:isStatic="true" />
+             android:isStatic="true"/>
 </manifest>
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/drawable/close_bg.xml
similarity index 72%
copy from car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
copy to car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/drawable/close_bg.xml
index 4fab05c..9a4596b 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/drawable/close_bg.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <!--
   ~ Copyright (C) 2021 The Android Open Source Project
   ~
@@ -14,8 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<resources>
-    <string name="config_rearViewCameraActivity" translatable="false">
-        com.google.android.car.evs/com.google.android.car.evs.CarEvsCameraPreviewActivity
-    </string>
-</resources>
+<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/rro/CarSystemUIEvsRRO/res/values/config.xml b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/config.xml
similarity index 64%
copy from car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
copy to car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/config.xml
index 4fab05c..532ba92 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/config.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2021 The Android Open Source Project
   ~
@@ -15,7 +14,10 @@
   ~ limitations under the License.
   -->
 <resources>
-    <string name="config_rearViewCameraActivity" translatable="false">
-        com.google.android.car.evs/com.google.android.car.evs.CarEvsCameraPreviewActivity
-    </string>
-</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>
+
+    <!-- In-plane rotation angle of the rearview camera device in degree -->
+    <integer name="config_evsRearviewCameraInPlaneRotationAngle">180</integer>
+</resources>
\ No newline at end of file
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/dimens.xml
similarity index 63%
copy from car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
copy to car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/dimens.xml
index 4fab05c..ea3ce97 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/dimens.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2021 The Android Open Source Project
   ~
@@ -15,7 +14,12 @@
   ~ limitations under the License.
   -->
 <resources>
-    <string name="config_rearViewCameraActivity" translatable="false">
-        com.google.android.car.evs/com.google.android.car.evs.CarEvsCameraPreviewActivity
-    </string>
-</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/rro/CarSystemUIEvsRRO/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/strings.xml
similarity index 81%
copy from car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml
copy to car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/strings.xml
index 36785dc..77cc4d1 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/strings.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2021 The Android Open Source Project
   ~
@@ -14,7 +15,6 @@
   ~ limitations under the License.
   -->
 
-<overlay>
-    <item target="string/config_rearViewCameraActivity"
-          value="@string/config_rearViewCameraActivity"/>
-</overlay>
+<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..13266b6
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/xml/overlays.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.
+  -->
+<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"/>
+    <item target="integer/config_evsRearviewCameraInPlaneRotationAngle" value="@integer/config_evsRearviewCameraInPlaneRotationAngle"/>
+</overlay>
diff --git a/car_product/rro/CarSystemUIEvsRRO/Android.bp b/car_product/car_ui_portrait/rro/CarUiPortraitCarServiceRRO/Android.bp
similarity index 95%
rename from car_product/rro/CarSystemUIEvsRRO/Android.bp
rename to car_product/car_ui_portrait/rro/CarUiPortraitCarServiceRRO/Android.bp
index ebb09ef..28a8c0c 100644
--- a/car_product/rro/CarSystemUIEvsRRO/Android.bp
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitCarServiceRRO/Android.bp
@@ -15,7 +15,7 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 android_app {
-    name: "CarSystemUIEvsRRO",
+    name: "CarUiPortraitCarServiceRRO",
     resource_dirs: ["res"],
     platform_apis: true,
     aaptflags: [
diff --git a/car_product/rro/CarSystemUIEvsRRO/AndroidManifest.xml b/car_product/car_ui_portrait/rro/CarUiPortraitCarServiceRRO/AndroidManifest.xml
similarity index 80%
rename from car_product/rro/CarSystemUIEvsRRO/AndroidManifest.xml
rename to car_product/car_ui_portrait/rro/CarUiPortraitCarServiceRRO/AndroidManifest.xml
index f1dc7b8..3831ee7 100644
--- a/car_product/rro/CarSystemUIEvsRRO/AndroidManifest.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitCarServiceRRO/AndroidManifest.xml
@@ -15,10 +15,10 @@
   ~ limitations under the License.
   -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.systemui.car.evs.rro">
-    <application android:hasCode="false"/>
-    <overlay android:targetName="CarSystemUI"
-             android:targetPackage="com.android.systemui"
+          package="com.android.car.caruiportrait.rro">
+    <application android:hasCode="false" />
+    <overlay android:priority="20"
+             android:targetPackage="com.android.car"
              android:resourcesMap="@xml/overlays"
              android:isStatic="true" />
 </manifest>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitCarServiceRRO/res/values/config.xml b/car_product/car_ui_portrait/rro/CarUiPortraitCarServiceRRO/res/values/config.xml
new file mode 100644
index 0000000..43b6e65
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitCarServiceRRO/res/values/config.xml
@@ -0,0 +1,42 @@
+<?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>
+    <!--
+        Specifies optional features that can be enabled by this image. Note that vhal can disable
+        them depending on product variation.
+        Feature name can be either service name defined in Car.*_SERVICE for Car*Manager or any
+        optional feature defined under @OptionalFeature annotation.
+        Note that '/' is used to have subfeature under main feature like "MAIN_FEATURE/SUB_FEATURE".
+
+        Some examples are:
+        <item>storage_monitoring</item>
+        <item>com.android.car.user.CarUserNoticeService</item>
+        <item>com.example.Feature/SubFeature</item>
+
+        The default list defined below will enable all optional features defined.
+    -->
+    <string-array translatable="false" name="config_allowed_optional_car_features">
+        <item>car_navigation_service</item>
+        <item>cluster_service</item>
+        <item>com.android.car.user.CarUserNoticeService</item>
+        <item>diagnostic</item>
+        <item>storage_monitoring</item>
+        <item>vehicle_map_service</item>
+        <item>car_evs_service</item>
+        <item>car_telemetry_service</item>
+    </string-array>
+</resources>
\ No newline at end of file
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/CarUiPortraitCarServiceRRO/res/xml/overlays.xml
similarity index 84%
rename from car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml
rename to car_product/car_ui_portrait/rro/CarUiPortraitCarServiceRRO/res/xml/overlays.xml
index 36785dc..61d1bc5 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitCarServiceRRO/res/xml/overlays.xml
@@ -13,8 +13,6 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
 <overlay>
-    <item target="string/config_rearViewCameraActivity"
-          value="@string/config_rearViewCameraActivity"/>
-</overlay>
+    <item target="array/config_allowed_optional_car_features" value="@array/config_allowed_optional_car_features"/>
+</overlay>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/Android.bp b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/Android.bp
index fecf167..92bd722 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/Android.bp
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/Android.bp
@@ -25,5 +25,7 @@
     static_libs: [
         "androidx-constraintlayout_constraintlayout",
         "androidx-constraintlayout_constraintlayout-solver",
+        "car-apps-common",
+        "car-ui-lib",
     ],
 }
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_off.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/dialer_ripple_background.xml
similarity index 62%
rename from car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_off.xml
rename to car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/dialer_ripple_background.xml
index b6c1cb9..131afd5 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_off.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/dialer_ripple_background.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2021 The Android Open Source Project
   ~
@@ -13,18 +14,15 @@
   ~ 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" >
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
     <item>
-        <shape android:shape="oval">
-            <solid android:color="@color/hvac_off_background_color"/>
-            <size android:height="@dimen/hvac_panel_button_dimen"
-                  android:width="@dimen/hvac_panel_button_dimen"/>
+        <shape
+            android:shape="oval">
+            <solid
+                android:color="@color/keypad_background_color" />
+            <size
+                android:width="@dimen/dialer_keypad_button_size"
+                android:height="@dimen/dialer_keypad_button_size"/>
         </shape>
     </item>
-    <item
-        android:drawable="@drawable/ic_mode_fan_off"
-        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/rro/CarUiPortraitDialerRRO/res/drawable/ic_arrow_right.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/ic_arrow_right.xml
new file mode 100644
index 0000000..b02823b
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/ic_arrow_right.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="88dp"
+        android:height="88dp"
+        android:viewportWidth="48"
+        android:viewportHeight="48"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="?android:attr/textColorPrimary"
+        android:pathData="M12,6c1.1,0 2,0.9 2,2s-0.9,2 -2,2 -2,-0.9 -2,-2 0.9,-2 2,-2m0,10c2.7,0 5.8,1.29 6,2L6,18c0.23,-0.72 3.31,-2 6,-2m0,-12C9.79,4 8,5.79 8,8s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
+</vector>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/ic_backspace.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/ic_backspace.xml
new file mode 100644
index 0000000..4770409
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/ic_backspace.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="32dp"
+        android:height="32dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M22,3L7,3c-0.69,0 -1.23,0.35 -1.59,0.88L0,12l5.41,8.11c0.36,0.53 0.9,0.89 1.59,0.89h15c1.1,0 2,-0.9 2,-2L24,5c0,-1.1 -0.9,-2 -2,-2zM22,19L7.07,19L2.4,12l4.66,-7L22,5v14zM10.41,17L14,13.41 17.59,17 19,15.59 15.41,12 19,8.41 17.59,7 14,10.59 10.41,7 9,8.41 12.59,12 9,15.59z"/>
+</vector>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/ic_bluetooth.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/ic_bluetooth.xml
new file mode 100644
index 0000000..13f130e
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/ic_bluetooth.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="44dp"
+        android:height="44dp"
+        android:viewportWidth="48"
+        android:viewportHeight="48"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="?android:attr/textColorPrimary"
+        android:pathData="M17.71 7.71L12 2h-1v7.59L6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 11 14.41V22h1l5.71-5.71-4.3-4.29 4.3-4.29zM13 5.83l1.88 1.88L13 9.59V5.83zm1.88 10.46L13 18.17v-3.76l1.88 1.88z"/>
+</vector>
\ No newline at end of file
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
index b659da1..3e2e824 100644
--- 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
@@ -22,7 +22,7 @@
     <path
         android:pathData="M0 0h24v24H0z" />
     <path
-        android:fillColor="#fffafafa"
+        android:fillColor="?android:attr/textColorPrimary"
         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
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
index 748a1d0..d048af9 100644
--- 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
@@ -19,11 +19,11 @@
         <shape
             android:shape="rectangle">
             <solid
-                android:color="#52CCB0" />
+                android:color="#29cb86" />
             <corners android:radius="100dp"/>
             <size
-                android:width="424dp"
-                android:height="120dp"/>
+                android:width="416dp"
+                android:height="@dimen/dialer_keypad_button_size"/>
         </shape>
     </item>
     <item android:drawable="@drawable/ic_phone" android:gravity="center"/>
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
index bf49159..131afd5 100644
--- 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
@@ -19,10 +19,10 @@
         <shape
             android:shape="oval">
             <solid
-                android:color="#282A2D" />
+                android:color="@color/keypad_background_color" />
             <size
-                android:width="120dp"
-                android:height="120dp"/>
+                android:width="@dimen/dialer_keypad_button_size"
+                android:height="@dimen/dialer_keypad_button_size"/>
         </shape>
     </item>
 </layer-list>
\ No newline at end of file
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/restricted_dialing_mode_label_background.xml
similarity index 76%
copy from car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml
copy to car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/restricted_dialing_mode_label_background.xml
index 36785dc..ea5a715 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/restricted_dialing_mode_label_background.xml
@@ -14,7 +14,7 @@
   ~ limitations under the License.
   -->
 
-<overlay>
-    <item target="string/config_rearViewCameraActivity"
-          value="@string/config_rearViewCameraActivity"/>
-</overlay>
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+    <corners android:radius="4dp"/>
+    <solid android:color="@color/car_red_500a"/>
+</shape>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/layout/dialpad_fragment_with_type_down.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/layout/dialpad_fragment_with_type_down.xml
new file mode 100644
index 0000000..d192bc9
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/layout/dialpad_fragment_with_type_down.xml
@@ -0,0 +1,94 @@
+<?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_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginLeft="100dp">
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="match_parent"
+        android:layout_height="45dp"
+        android:layout_marginBottom="48dp"
+        android:layout_alignParentTop="true"
+        android:layout_alignLeft="@id/dialpad_fragment"
+        android:layout_alignRight="@id/dialpad_fragment"
+        android:textAppearance="@style/TextAppearance.DialNumber"
+        android:gravity="center"/>
+
+    <com.android.car.ui.recyclerview.CarUiRecyclerView
+        android:id="@+id/list_view"
+        android:layout_width="536dp"
+        android:layout_height="266dp"
+        android:layout_marginBottom="10dp"
+        android:layout_marginLeft="70dp"
+        android:layout_below="@id/title"/>
+    <fragment
+        android:id="@+id/dialpad_fragment"
+        android:name="com.android.car.dialer.ui.dialpad.KeypadFragment"
+        android:layout_height="456dp"
+        android:layout_width="416dp"
+        android:layout_marginLeft="120dp"
+        android:layout_below="@id/list_view"/>
+
+    <RelativeLayout
+        android:layout_height="@dimen/dialer_keypad_button_size"
+        android:layout_width="0dp"
+        android:layout_below="@id/dialpad_fragment"
+        android:layout_marginTop="38dp"
+        android:layout_alignLeft="@id/dialpad_fragment"
+        android:layout_alignRight="@id/dialpad_fragment">
+
+        <ImageView
+            android:id="@+id/call_button"
+            android:layout_height="match_parent"
+            android:layout_width="match_parent"
+            android:adjustViewBounds="true"
+            android:scaleType="fitXY"
+            android:src="@drawable/icon_call_button"
+            android:layout_toLeftOf="@id/delete_button"/>
+
+        <ImageButton
+            android:id="@+id/delete_button"
+            android:layout_width="@dimen/dialer_keypad_button_size"
+            android:layout_height="@dimen/dialer_keypad_button_size"
+            style="@style/DialpadSecondaryButton"
+            android:src="@drawable/ic_backspace"
+            android:layout_marginLeft="64dp"
+            android:visibility="gone"
+            android:layout_alignParentRight="true"/>
+    </RelativeLayout>
+
+    <include
+        layout="@layout/dialpad_user_profile"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_marginTop="10dp"
+        android:layout_below="@id/title"
+        android:layout_centerHorizontal="true"/>
+
+    <include
+        layout="@layout/restricted_dialing_mode_label"
+        android:id="@+id/restricted_dialing_mode_label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="8dp"
+        android:layout_below="@id/title"
+        android:visibility="invisible"/>
+</RelativeLayout>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/layout/dialpad_user_profile.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/layout/dialpad_user_profile.xml
new file mode 100644
index 0000000..bb4e11f
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/layout/dialpad_user_profile.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.
+  -->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/display_name"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.DialpadDisplayName"
+        android:singleLine="true"
+        android:layout_centerHorizontal="true"
+        android:layout_alignParentTop="true"/>
+
+    <TextView
+        android:id="@+id/label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:singleLine="true"
+        android:layout_marginTop="12dp"
+        android:layout_below="@id/display_name"
+        android:layout_centerHorizontal="true"/>
+
+    <ImageView
+        android:id="@+id/dialpad_contact_avatar"
+        android:layout_height="@dimen/dialpad_contact_avatar_size"
+        android:layout_width="@dimen/dialpad_contact_avatar_size"
+        android:layout_below="@id/label"
+        android:layout_alignParentBottom="true"
+        android:layout_centerHorizontal="true"/>
+
+    <TextView
+        android:id="@+id/dialpad_contact_initials"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:layout_width="10dp"
+        android:layout_height="10dp"/>
+</RelativeLayout>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/layout/no_hfp.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/layout/no_hfp.xml
new file mode 100644
index 0000000..881d0d5
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/layout/no_hfp.xml
@@ -0,0 +1,67 @@
+<?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">
+
+        <RelativeLayout
+            android:id="@+id/no_hfp_error_container"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+            <Button
+                android:id="@+id/emergency_call_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/emergency_button_text"
+                android:minWidth="156dp"
+                android:minHeight="76dp"
+                android:background="?android:attr/selectableItemBackground"
+                android:textColor="#ffd50000"
+                android:layout_marginBottom="@dimen/car_ui_padding_4"
+                android:layout_alignParentBottom="true"
+                android:layout_centerHorizontal="true"/>
+
+            <ImageView
+                android:id="@+id/error_icon"
+                android:layout_width="96dp"
+                android:layout_height="96dp"
+                android:src="@drawable/ic_bluetooth"
+                android:layout_marginBottom="@dimen/car_ui_padding_3"
+                android:layout_alignLeft="@id/error_string"
+                android:layout_alignRight="@id/error_string"
+                android:layout_above="@id/error_string"
+                android:gravity="center"/>
+
+            <TextView
+                android:id="@+id/error_string"
+                style="@style/FullScreenErrorMessageStyle"
+                android:text="@string/no_hfp"
+                android:layout_centerInParent="true"/>
+
+            <com.android.car.apps.common.UxrButton
+                android:id="@+id/connect_bluetooth_button"
+                style="@style/FullScreenErrorButtonStyle"
+                android:background="@color/keypad_background_color"
+                android:text="@string/connect_bluetooth_button_text"
+                android:layout_marginTop="@dimen/car_ui_padding_5"
+                android:layout_below="@id/error_string"
+                android:layout_centerHorizontal="true"/>
+        </RelativeLayout>
+
+</FrameLayout>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/layout/restricted_dialing_mode_label.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/layout/restricted_dialing_mode_label.xml
new file mode 100644
index 0000000..6a23fd5
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/layout/restricted_dialing_mode_label.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.
+  -->
+
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/restricted_dialing_mode_label"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_marginTop="8dp"
+    android:padding="8dp"
+    android:textAppearance="@style/TextAppearance.Body2"
+    android:text="@string/restricted_dialing_mode_label"
+    android:alpha="0.8"
+    android:background="@drawable/restricted_dialing_mode_label_background"/>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/layout/type_down_list_item.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/layout/type_down_list_item.xml
new file mode 100644
index 0000000..68cf567
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/layout/type_down_list_item.xml
@@ -0,0 +1,55 @@
+<?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/contact_result"
+    android:foreground="?android:attr/selectableItemBackground"
+    android:layout_width="match_parent"
+    android:layout_height="112dp">
+
+    <ImageView
+        android:id="@+id/contact_picture"
+        android:layout_width="72dp"
+        android:layout_height="72dp"
+        android:scaleType="centerCrop"
+        android:layout_centerVertical="true"
+        android:layout_alignParentLeft="true"/>
+
+    <TextView
+        android:id="@+id/contact_name"
+        android:layout_marginStart="24dp"
+        android:layout_marginTop="14dp"
+        android:layout_marginBottom="8dp"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:textAppearance="@style/TextAppearance.ContactResultTitle"
+        android:duplicateParentState="true"
+        android:layout_alignParentTop="true"
+        android:layout_toRightOf="@id/contact_picture"/>
+
+    <TextView
+        android:id="@+id/phone_number"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="14dp"
+        android:theme="@style/Theme.CarUi.WithToolbar"
+        android:singleLine="true"
+        android:layout_alignParentBottom="true"
+        android:layout_alignLeft="@id/contact_name"/>
+</RelativeLayout>
\ No newline at end of file
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/values-night/colors.xml
similarity index 78%
copy from car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
copy to car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/values-night/colors.xml
index 4fab05c..937c35b 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/values-night/colors.xml
@@ -14,8 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<resources>
-    <string name="config_rearViewCameraActivity" translatable="false">
-        com.google.android.car.evs/com.google.android.car.evs.CarEvsCameraPreviewActivity
-    </string>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <color name="keypad_background_color">#282A2D</color>
+    <color name="divider_color">#2e3134</color>
 </resources>
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/values/colors.xml
similarity index 78%
copy from car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
copy to car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/values/colors.xml
index 4fab05c..9b5d97c 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/values/colors.xml
@@ -14,8 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<resources>
-    <string name="config_rearViewCameraActivity" translatable="false">
-        com.google.android.car.evs/com.google.android.car.evs.CarEvsCameraPreviewActivity
-    </string>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <color name="keypad_background_color">#E8EAED</color>
+    <color name="divider_color">#E8EAED</color>
 </resources>
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/values/configs.xml
similarity index 81%
copy from car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml
copy to car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/values/configs.xml
index 36785dc..2f28c76 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/values/configs.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2021 The Android Open Source Project
   ~
@@ -14,7 +15,6 @@
   ~ limitations under the License.
   -->
 
-<overlay>
-    <item target="string/config_rearViewCameraActivity"
-          value="@string/config_rearViewCameraActivity"/>
-</overlay>
+<resources>
+    <integer name="config_dialed_number_gravity">1</integer>
+</resources>
\ No newline at end of file
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/values/dimens.xml
similarity index 79%
copy from car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
copy to car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/values/dimens.xml
index 4fab05c..108cfb1 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/values/dimens.xml
@@ -15,7 +15,6 @@
   ~ limitations under the License.
   -->
 <resources>
-    <string name="config_rearViewCameraActivity" translatable="false">
-        com.google.android.car.evs/com.google.android.car.evs.CarEvsCameraPreviewActivity
-    </string>
-</resources>
+    <dimen name="dialer_keypad_button_size">96dp</dimen>
+    <dimen name="dialpad_contact_avatar_size">64dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/values/strings.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/values/strings.xml
new file mode 100644
index 0000000..5f273dc
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/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">
+    <string name="restricted_dialing_mode_label">Dialpad usage is restricted while driving</string>
+    <string name="emergency_button_text">Emergency</string>
+    <string name="connect_bluetooth_button_text">Connect to Bluetooth</string>
+    <string name="no_hfp">
+        To complete your call, first connect your phone to your car via Bluetooth.
+    </string>
+</resources>
\ 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
index 678cf0c..d67d485 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/values/styles.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/values/styles.xml
@@ -20,18 +20,53 @@
         <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:layout_marginTop">12dp</item>
+        <item name="android:layout_marginRight">32dp</item>
+        <item name="android:layout_marginBottom">12dp</item>
+        <item name="android:layout_marginLeft">32dp</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 name="TextAppearance.DialNumber" parent="android:style/TextAppearance">
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textSize">32sp</item>
+    </style>
+
+    <style name="SubheaderText" parent="android:style/TextAppearance">
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textSize">24sp</item>
+        <item name="android:textFontWeight">500</item>
+        <item name="android:textStyle">normal</item>
+    </style>
+
+    <style name="AddFavoriteText">
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
+    <!-- Call history -->
+    <style name="TextAppearance.CallLogTitleDefault" parent="TextAppearance.Body1">
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+    <!-- Customized text color for missed calls can be added here -->
+    <style name="TextAppearance.CallLogTitleMissedCall" parent="TextAppearance.Body1">
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
+    <style name="DialpadSecondaryButton">
+        <item name="android:background">@drawable/dialer_ripple_background</item>
+        <item name="android:scaleType">centerInside</item>
+        <item name="android:tint">?android:attr/textColorPrimary</item>
+    </style>
+
+    <style name="TextAppearance.DialpadDisplayName" parent="TextAppearance.Body1"/>
+
+    <style name="TextAppearance.ContactResultTitle" parent="TextAppearance.Body1">
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
+    <style name="TextAppearance.TypeDownListSpan" parent="TextAppearance.Body3">
+        <item name="android:textSize">32sp</item>
+        <item name="android:textColor">#29cb86</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
index a404157..848c5eb 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/xml/overlays.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/xml/overlays.xml
@@ -15,9 +15,52 @@
   ~ 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"/>
+    <item target="drawable/ic_backspace" value="@drawable/ic_backspace"/>
+    <item target="drawable/dialer_ripple_background" value="@drawable/dialer_ripple_background"/>
+    <item target="drawable/restricted_dialing_mode_label_background" value="@drawable/restricted_dialing_mode_label_background"/>
+    <item target="drawable/ic_arrow_right" value="@drawable/ic_arrow_right"/>
+    <item target="drawable/ic_bluetooth" value="@drawable/ic_bluetooth"/>
+
+    <item target="id/dialpad_fragment" value="@id/dialpad_fragment" />
+    <item target="id/call_button" value="@id/call_button" />
+    <item target="id/title" value="@id/title" />
+    <item target="id/delete_button" value="@id/delete_button" />
+    <item target="id/display_name" value="@id/display_name" />
+    <item target="id/label" value="@id/label" />
+    <item target="id/dialpad_contact_avatar" value="@id/dialpad_contact_avatar" />
+    <item target="id/dialpad_contact_initials" value="@id/dialpad_contact_initials" />
+    <item target="id/list_view" value="@id/list_view" />
+    <item target="id/restricted_dialing_mode_label" value="@id/restricted_dialing_mode_label" />
+    <item target="id/contact_picture" value="@id/contact_picture" />
+    <item target="id/contact_result" value="@id/contact_result" />
+    <item target="id/contact_name" value="@id/contact_name" />
+    <item target="id/phone_number" value="@id/phone_number" />
+    <item target="id/no_hfp_error_container" value="@id/no_hfp_error_container" />
+    <item target="id/emergency_call_button" value="@id/emergency_call_button" />
+    <item target="id/error_icon" value="@id/error_icon" />
+    <item target="id/error_string" value="@id/error_string" />
+    <item target="id/connect_bluetooth_button" value="@id/connect_bluetooth_button" />
+
+    <item target="color/divider_color" value="@color/divider_color" />
+    <item target="color/hero_button_background_color" value="@color/hero_button_background_color" />
+
+    <item target="integer/config_dialed_number_gravity" value="@integer/config_dialed_number_gravity" />
+
+    <item target="layout/dialpad_fragment_with_type_down" value="@layout/dialpad_fragment_with_type_down"/>
+    <item target="layout/dialpad_user_profile" value="@layout/dialpad_user_profile"/>
+    <item target="layout/type_down_list_item" value="@layout/type_down_list_item"/>
+    <item target="layout/restricted_dialing_mode_label" value="@layout/restricted_dialing_mode_label"/>
+    <item target="layout/no_hfp" value="@layout/no_hfp"/>
+
+    <item target="style/KeypadButtonStyle" value="@style/KeypadButtonStyle"/>
+    <item target="style/TextAppearance.DialNumber" value="@style/TextAppearance.DialNumber"/>
+    <item target="style/SubheaderText" value="@style/SubheaderText"/>
+    <item target="style/AddFavoriteText" value="@style/AddFavoriteText"/>
+    <item target="style/TextAppearance.CallLogTitleDefault" value="@style/TextAppearance.CallLogTitleDefault"/>
+    <item target="style/TextAppearance.DialpadDisplayName" value="@style/TextAppearance.DialpadDisplayName"/>
+    <item target="style/TextAppearance.ContactResultTitle" value="@style/TextAppearance.ContactResultTitle"/>
+    <item target="style/TextAppearance.TypeDownListSpan" value="@style/TextAppearance.TypeDownListSpan"/>
+
 </overlay>
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/drawable/control_bar_image_background.xml
similarity index 73%
copy from car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml
copy to car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/drawable/control_bar_image_background.xml
index 36785dc..9a3c589 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/drawable/control_bar_image_background.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8" ?>
 <!--
   ~ Copyright (C) 2021 The Android Open Source Project
   ~
@@ -14,7 +15,8 @@
   ~ limitations under the License.
   -->
 
-<overlay>
-    <item target="string/config_rearViewCameraActivity"
-          value="@string/config_rearViewCameraActivity"/>
-</overlay>
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners android:radius="@dimen/control_bar_image_background_radius"/>
+</shape>
\ No newline at end of file
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
index f00f03b..c1fd0e9 100644
--- 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
@@ -16,6 +16,11 @@
   -->
 
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape android:shape="rectangle">
+            <solid android:color="@color/default_audio_background_color"/>
+        </shape>
+    </item>
     <item android:bottom="@dimen/default_audio_icon_padding"
           android:top="@dimen/default_audio_icon_padding"
           android:right="@dimen/default_audio_icon_padding"
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
index 48efb6f..5078a1b 100644
--- 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
@@ -18,7 +18,7 @@
 <androidx.cardview.widget.CardView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/card_view"
-    android:foreground="?android:attr/selectableItemBackground"
+    android:background="?android:attr/colorBackgroundFloating"
     android:layout_height="match_parent"
     android:layout_width="match_parent"
     android:visibility="gone">
@@ -37,10 +37,13 @@
             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_centerVertical="true"
             android:layout_marginStart="@dimen/card_icon_margin_start">
 
             <com.android.car.apps.common.CrossfadeImageView
                 android:id="@+id/card_background_image"
+                android:background="@drawable/control_bar_image_background"
+                android:clipToOutline="true"
                 android:layout_height="match_parent"
                 android:layout_width="match_parent"/>
 
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout/title_bar_display_area_view.xml b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout/title_bar_display_area_view.xml
new file mode 100644
index 0000000..940e413
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/layout/title_bar_display_area_view.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.
+  -->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/title_bar"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/title_bar_display_area_height"
+    android:background="@color/title_bar_display_area_background_color">
+    <View
+        android:layout_width="120dp"
+        android:layout_height="6dp"
+        android:background="@color/title_bar_display_area_handle_bar_color"
+        android:layout_marginTop="17dp"
+        android:layout_centerInParent="true" />
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"/>
+</RelativeLayout>
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values-night/colors.xml
similarity index 63%
copy from car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
copy to car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values-night/colors.xml
index 4fab05c..59ed06b 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values-night/colors.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8" ?>
 <!--
   ~ Copyright (C) 2021 The Android Open Source Project
   ~
@@ -14,8 +14,12 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
+
 <resources>
-    <string name="config_rearViewCameraActivity" translatable="false">
-        com.google.android.car.evs/com.google.android.car.evs.CarEvsCameraPreviewActivity
-    </string>
-</resources>
+    <color name="default_audio_background_image_color">#515355</color>
+    <color name="default_audio_background_color">#1E2125</color>
+    <color name="title_bar_display_area_handle_bar_color">#282a2d</color>
+    <color name="title_bar_display_area_background_color">#000000</color>
+
+    <color name="icon_tint">#e8eaed</color>
+</resources>
\ 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
index a3a7f24..266da92 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/colors.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/colors.xml
@@ -16,5 +16,12 @@
   -->
 
 <resources>
-    <color name="default_audio_background_image_color">#515355</color>
+    <color name="default_audio_background_image_color">#DADCE0</color>
+    <color name="default_audio_background_color">#BDC1C6</color>
+    <color name="title_bar_display_area_handle_bar_color">#e8eaed</color>
+    <color name="title_bar_display_area_background_color">#ffffff</color>
+
+    <color name="icon_tint">#000000</color>
+    <color name="media_button_tint">@color/icon_tint</color>
+    <color name="dialer_button_icon_color">@color/icon_tint</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
index afc5169..c912ab9 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/config.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/config.xml
@@ -21,4 +21,8 @@
         <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
index d90de2d..9dcb0e9 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/dimens.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/dimens.xml
@@ -18,7 +18,7 @@
     <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="descriptive_text_vertical_margin">23dp</dimen>
 
     <dimen name="control_bar_image_size">120dp</dimen>
     <dimen name="control_bar_app_icon_size">36dp</dimen>
@@ -37,6 +37,7 @@
     <dimen name="total_screen_width">1224dp</dimen>
 
     <dimen name="control_bar_height">136dp</dimen>
+    <dimen name="control_bar_padding">0dp</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>
@@ -47,4 +48,8 @@
     <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>
+
+    <dimen name="control_bar_image_background_radius">24dp</dimen>
+
+    <dimen name="button_tap_target_size">88dp</dimen>
 </resources>
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
index 8b153fa..44c23f8 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/xml/overlays.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/xml/overlays.xml
@@ -14,6 +14,10 @@
   ~ limitations under the License.
   -->
 <overlay>
+    <item target="color/dialer_button_icon_color" value="@color/dialer_button_icon_color"/>
+    <item target="color/icon_tint" value="@color/icon_tint" />
+    <item target="color/media_button_tint" value="@color/media_button_tint"/>
+
     <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" />
@@ -31,28 +35,33 @@
     <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/title" value="@id/title"/>
 
     <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="integer/enter_exit_animation_foreground_display_area_duration_ms" value="@integer/enter_exit_animation_foreground_display_area_duration_ms"/>
+
     <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="layout/title_bar_display_area_view" value="@layout/title_bar_display_area_view" />
 
     <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/control_bar_padding" value="@dimen/control_bar_padding"/>
     <item target="dimen/default_app_display_area_height" value="@dimen/default_app_display_area_height"/>
+    <item target="dimen/button_tap_target_size" value="@dimen/button_tap_target_size"/>
     <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="dimen/total_screen_height" value="@dimen/total_screen_height"/>
+    <item target="dimen/total_screen_width" value="@dimen/total_screen_width"/>
 
     <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
index a36ec21..a6beaa0 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/Android.bp
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/Android.bp
@@ -22,4 +22,9 @@
         "--no-resource-deduping",
         "--no-resource-removal"
     ],
+    static_libs: [
+        "androidx-constraintlayout_constraintlayout",
+        "car-apps-common",
+
+    ],
 }
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/drawable/image_background.xml
similarity index 75%
copy from car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml
copy to car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/drawable/image_background.xml
index 36785dc..e5f9fa5 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/drawable/image_background.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <!--
   ~ Copyright (C) 2021 The Android Open Source Project
   ~
@@ -14,7 +15,8 @@
   ~ limitations under the License.
   -->
 
-<overlay>
-    <item target="string/config_rearViewCameraActivity"
-          value="@string/config_rearViewCameraActivity"/>
-</overlay>
+<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/layout/fragment_error.xml b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/layout/fragment_error.xml
new file mode 100644
index 0000000..dc03e18
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/layout/fragment_error.xml
@@ -0,0 +1,88 @@
+<?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="match_parent"
+    android:layout_height="match_parent">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="0dp"
+        android:layout_height="0dp">
+        <Space
+            android:id="@+id/ui_content_start_guideline"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_marginLeft="0dp"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent"
+        />
+
+        <Space
+            android:id="@+id/ui_content_top_guideline"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_marginTop="96dp"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+        />
+
+        <Space
+            android:id="@+id/ui_content_end_guideline"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_marginRight="0dp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent"
+        />
+
+        <Space
+            android:id="@+id/ui_content_bottom_guideline"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_marginBottom="0dp"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent"
+        />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <com.android.car.apps.common.UxrTextView
+        android:id="@+id/error_message"
+        android:layout_width="520dp"
+        android:layout_height="44dp"
+        android:gravity="center"
+        android:layout_marginTop="440dp"
+        android:layout_alignParentTop="true"
+        android:layout_centerHorizontal="true"/>
+
+    <com.android.car.apps.common.UxrButton
+        android:id="@+id/error_button"
+        android:layout_width="760dp"
+        android:layout_height="88dp"
+        android:background="@color/button_background_color"
+        android:textAlignment="center"
+        android:gravity="center"
+        android:layout_marginTop="120dp"
+        android:layout_centerHorizontal="true"
+        android:layout_below="@id/error_message"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/values-night/colors.xml
similarity index 78%
copy from car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
copy to car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/values-night/colors.xml
index 4fab05c..83db0e3 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/values-night/colors.xml
@@ -14,8 +14,6 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<resources>
-    <string name="config_rearViewCameraActivity" translatable="false">
-        com.google.android.car.evs/com.google.android.car.evs.CarEvsCameraPreviewActivity
-    </string>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <color name="button_background_color">#282A2D</color>
 </resources>
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/values/colors.xml
similarity index 78%
copy from car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
copy to car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/values/colors.xml
index 4fab05c..14bc52b 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/values/colors.xml
@@ -14,8 +14,6 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<resources>
-    <string name="config_rearViewCameraActivity" translatable="false">
-        com.google.android.car.evs/com.google.android.car.evs.CarEvsCameraPreviewActivity
-    </string>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <color name="button_background_color">#E8EAED</color>
 </resources>
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/values/dimens.xml
similarity index 79%
copy from car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml
copy to car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/values/dimens.xml
index 36785dc..9726095 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/values/dimens.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2021 The Android Open Source Project
   ~
@@ -14,7 +15,6 @@
   ~ limitations under the License.
   -->
 
-<overlay>
-    <item target="string/config_rearViewCameraActivity"
-          value="@string/config_rearViewCameraActivity"/>
-</overlay>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <dimen name="image_radius">24dp</dimen>
+</resources>
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/values/styles.xml
similarity index 72%
copy from car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
copy to car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/values/styles.xml
index 4fab05c..dbc8eec 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/values/styles.xml
@@ -14,8 +14,10 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<resources>
-    <string name="config_rearViewCameraActivity" translatable="false">
-        com.google.android.car.evs/com.google.android.car.evs.CarEvsCameraPreviewActivity
-    </string>
+
+<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
index 744f5c1..01f2039 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/xml/overlays.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/xml/overlays.xml
@@ -15,6 +15,33 @@
   -->
 
 <overlay>
+    <item target="attr/layout_constraintBottom_toBottomOf" value="@attr/layout_constraintBottom_toBottomOf"/>
+    <item target="attr/layout_constraintBottom_toTopOf" value="@attr/layout_constraintBottom_toTopOf"/>
+    <item target="attr/layout_constraintEnd_toEndOf" value="@attr/layout_constraintEnd_toEndOf"/>
+    <item target="attr/layout_constraintEnd_toStartOf" value="@attr/layout_constraintEnd_toStartOf"/>
+    <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_constraintHorizontal_bias" value="@attr/layout_constraintHorizontal_bias"/>
+    <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_constraintStart_toEndOf" value="@attr/layout_constraintStart_toEndOf"/>
+    <item target="attr/layout_constraintStart_toStartOf" value="@attr/layout_constraintStart_toStartOf"/>
+    <item target="attr/layout_constraintTop_toBottomOf" value="@attr/layout_constraintTop_toBottomOf"/>
+    <item target="attr/layout_constraintTop_toTopOf" value="@attr/layout_constraintTop_toTopOf"/>
+
     <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="id/error_message" value="@id/error_message" />
+    <item target="id/error_button" value="@id/error_button" />
+    <item target="id/ui_content_start_guideline" value="@id/ui_content_start_guideline" />
+    <item target="id/ui_content_top_guideline" value="@id/ui_content_top_guideline" />
+    <item target="id/ui_content_end_guideline" value="@id/ui_content_end_guideline" />
+    <item target="id/ui_content_bottom_guideline" value="@id/ui_content_bottom_guideline" />
+
+    <item target="layout/fragment_error" value="@layout/fragment_error"/>
+
+    <item target="style/MediaIconContainerStyle" value="@style/MediaIconContainerStyle" />
 </overlay>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/call_headsup_notification_template.xml b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/call_headsup_notification_template.xml
new file mode 100644
index 0000000..41095c4
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/call_headsup_notification_template.xml
@@ -0,0 +1,74 @@
+<?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_alignParentStart="true"
+                android:layout_alignParentTop="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_alignParentEnd="true"
+                android:layout_marginStart="@dimen/card_body_margin_start"
+                app:maxLines="@integer/config_headsUpNotificationMaxBodyLines"
+                app:showBigIcon="true"
+                app:isHeadsUp="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"
+                    app:categoryCall="true"/>
+            </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/car_headsup_notification_body_view.xml b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/car_headsup_notification_body_view.xml
new file mode 100644
index 0000000..a373e8a
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/car_headsup_notification_body_view.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.
+  -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <ImageView
+        android:id="@+id/notification_body_icon"
+        android:layout_width="@dimen/notification_touch_target_size"
+        android:layout_height="@dimen/notification_touch_target_size"
+        android:layout_alignParentStart="true"
+        android:layout_alignParentTop="true"
+        android:layout_marginStart="@dimen/body_big_icon_margin"
+        style="@style/NotificationBodyImageIcon"/>
+
+    <TextView
+        android:id="@+id/notification_body_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_toEndOf="@id/notification_body_icon"
+        android:layout_alignTop="@id/notification_body_icon"
+        android:layout_marginStart="@dimen/card_start_margin"
+        android:layout_alignWithParentIfMissing="true"
+        android:layout_marginTop="8dp"
+        style="@style/NotificationBodyTitleText"/>
+
+    <TextView
+        android:id="@+id/notification_body_content"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_toEndOf="@id/notification_body_icon"
+        android:layout_below="@id/notification_body_title"
+        android:layout_marginStart="@dimen/card_start_margin"
+        android:layout_marginTop="4dp"
+        android:layout_alignWithParentIfMissing="true"
+        style="@style/NotificationBodyContentText"/>
+</merge>
diff --git a/car_product/rro/CarSystemUIEvsRRO/AndroidManifest.xml b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/car_headsup_notification_header_view.xml
similarity index 60%
copy from car_product/rro/CarSystemUIEvsRRO/AndroidManifest.xml
copy to car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/car_headsup_notification_header_view.xml
index f1dc7b8..7a77a81 100644
--- a/car_product/rro/CarSystemUIEvsRRO/AndroidManifest.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/car_headsup_notification_header_view.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8" ?>
 <!--
   ~ Copyright (C) 2021 The Android Open Source Project
   ~
@@ -14,11 +14,16 @@
   ~ 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.systemui.car.evs.rro">
-    <application android:hasCode="false"/>
-    <overlay android:targetName="CarSystemUI"
-             android:targetPackage="com.android.systemui"
-             android:resourcesMap="@xml/overlays"
-             android:isStatic="true" />
-</manifest>
+
+<!-- Do not show app icon or name for HUNs -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <ImageView
+        android:id="@+id/app_icon"
+        android:layout_width="0dp"
+        android:layout_height="0dp"/>
+
+    <TextView
+        android:id="@+id/header_text"
+        android:layout_width="0dp"
+        android:layout_height="0dp"/>
+</merge>
\ No newline at end of file
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
index c3e5db0..a3c2ce3 100644
--- 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
@@ -22,7 +22,7 @@
         android:layout_height="wrap_content"
         android:orientation="horizontal">
 
-        <Button
+        <com.android.car.notification.template.CarNotificationActionButton
             android:id="@+id/action_1"
             style="@style/NotificationActionButton1"
             android:layout_width="0dp"
@@ -33,7 +33,7 @@
             android:paddingTop="@dimen/action_button_padding_top"
             android:visibility="gone"/>
 
-        <Button
+        <com.android.car.notification.template.CarNotificationActionButton
             android:id="@+id/action_2"
             style="@style/NotificationActionButton2"
             android:layout_weight="1"
@@ -45,7 +45,7 @@
             android:paddingTop="@dimen/action_button_padding_top"
             android:visibility="gone"/>
 
-        <Button
+        <com.android.car.notification.template.CarNotificationActionButton
             android:id="@+id/action_3"
             style="@style/NotificationActionButton3"
             android:layout_width="0dp"
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
index 18f3304..4fd0492 100644
--- 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
@@ -50,8 +50,9 @@
                 android:gravity="center_vertical"
                 android:layout_alignParentTop="true"
                 android:layout_alignParentStart="true"
-                android:layout_toStartOf="@id/message_count"
+                android:layout_alignParentEnd="true"
                 android:layout_marginStart="@dimen/card_body_margin_start"
+                app:isHeadsUp="true"
                 app:maxLines="@integer/config_headsUpNotificationMaxBodyLines"
                 app:showBigIcon="true"/>
 
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values-night/colors.xml
similarity index 70%
copy from car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
copy to car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values-night/colors.xml
index 4fab05c..0cc16f9 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values-night/colors.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8" ?>
 <!--
   ~ Copyright (C) 2021 The Android Open Source Project
   ~
@@ -14,8 +14,9 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
+
 <resources>
-    <string name="config_rearViewCameraActivity" translatable="false">
-        com.google.android.car.evs/com.google.android.car.evs.CarEvsCameraPreviewActivity
-    </string>
+    <color name="action_button_background_color">#2e3134</color>
+    <color name="primary_text_color">@android:color/system_neutral1_50</color>
+    <color name="secondary_text_color">@android:color/system_neutral2_400</color>
 </resources>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/colors.xml b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/colors.xml
new file mode 100644
index 0000000..6cf3e5a
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/colors.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.
+  -->
+
+<resources>
+    <color name="action_button_background_color">#e8eaed</color>
+    <color name="clear_all_button_background_color">@color/action_button_background_color</color>
+    <color name="primary_text_color">@android:color/system_neutral1_900</color>
+    <color name="secondary_text_color">@android:color/system_neutral2_500</color>
+    <color name="icon_tint">@color/primary_text_color</color>
+
+    <color name="call_accept_button">#29cb86</color>
+    <color name="call_decline_button">#e46962</color>
+</resources>
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
index 4862022..9e25a62 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/config.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/config.xml
@@ -33,4 +33,5 @@
 
     <!-- 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
index cc5397d..32659ce 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/dimens.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/dimens.xml
@@ -27,16 +27,18 @@
     <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>
+    <dimen name="headsup_notification_bottom_margin">160dp</dimen>
 
     <!-- Icons -->
     <dimen name="notification_touch_target_size">120dp</dimen>
+    <dimen name="body_big_icon_margin">8dp</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="action_button_padding_bottom">8dp</dimen>
 
     <dimen name="card_min_bottom_padding">8dp</dimen>
     <dimen name="card_min_top_padding">8dp</dimen>
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
index d588c34..2727581 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/styles.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/styles.xml
@@ -16,17 +16,15 @@
   -->
 
 <resources>
-    <style name="NotificationBodyTitleText">
-        <item name="android:textSize">32sp</item>
+    <style name="NotificationBodyTitleText" parent="@android:TextAppearance.DeviceDefault.Large">
         <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>
+    <style name="NotificationBodyContentText" parent="@android:TextAppearance.DeviceDefault.Small">
+        <item name="android:maxLines">1</item>
         <item name="android:ellipsize">end</item>
-        <item name="android:textColor">@color/secondary_text_color</item>
         <item name="android:textAlignment">viewStart</item>
     </style>
 
@@ -48,4 +46,21 @@
         <item name="android:ellipsize">end</item>
         <item name="android:background">@drawable/action_button_background</item>
     </style>
+
+    <style name="NotificationActionButtonText" parent="@android:TextAppearance.DeviceDefault.Large">
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textAllCaps">false</item>
+        <item name="android:maxLines">1</item>
+        <item name="android:ellipsize">end</item>
+    </style>
+
+    <style name="ClearAllButton" parent="@android:Widget.DeviceDefault.Button.Borderless.Colored">
+        <item name="android:minWidth">@dimen/clear_all_button_min_width</item>
+        <item name="android:paddingStart">@dimen/clear_all_button_padding</item>
+        <item name="android:paddingEnd">@dimen/clear_all_button_padding</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:gravity">center</item>
+        <item name="android:textAllCaps">false</item>
+        <item name="android:background">@drawable/clear_all_button_background</item>
+    </style>
 </resources>
\ No newline at end of file
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/themes.xml
similarity index 76%
copy from car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml
copy to car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/themes.xml
index 36785dc..36f372b 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/themes.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8" ?>
 <!--
   ~ Copyright (C) 2021 The Android Open Source Project
   ~
@@ -14,7 +15,7 @@
   ~ limitations under the License.
   -->
 
-<overlay>
-    <item target="string/config_rearViewCameraActivity"
-          value="@string/config_rearViewCameraActivity"/>
-</overlay>
+<resources>
+    <style name="Theme.DeviceDefault.NoActionBar.Notification" parent="@android:Theme.DeviceDefault.NoActionBar">
+    </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
index b768ecb..18124ea 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/xml/overlays.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/xml/overlays.xml
@@ -16,6 +16,7 @@
   -->
 
 <overlay>
+    <item target="attr/categoryCall" value="@attr/categoryCall"/>
     <item target="attr/cardCornerRadius" value="@attr/cardCornerRadius"/>
     <item target="attr/isHeadsUp" value="@attr/isHeadsUp"/>
     <item target="attr/maxLines" value="@attr/maxLines"/>
@@ -27,10 +28,21 @@
     <item target="bool/config_showFooterForNotifications" value="@bool/config_showFooterForNotifications"/>
     <item target="bool/config_showRecentAndOldHeaders" value="@bool/config_showRecentAndOldHeaders"/>
 
+    <item target="color/icon_tint" value="@color/icon_tint"/>
+    <item target="color/call_accept_button" value="@color/call_accept_button"/>
+    <item target="color/call_decline_button" value="@color/call_decline_button"/>
+
+    <item target="color/action_button_background_color" value="@color/action_button_background_color"/>
+    <item target="color/clear_all_button_background_color" value="@color/clear_all_button_background_color"/>
+    <item target="color/primary_text_color" value="@color/primary_text_color"/>
+    <item target="color/secondary_text_color" value="@color/secondary_text_color"/>
+
     <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_button_padding_bottom" value="@dimen/action_button_padding_bottom"/>
     <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/body_big_icon_margin" value="@dimen/body_big_icon_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" />
@@ -66,11 +78,25 @@
     <item target="string/config_headsUpNotificationAnimationHelper" value="@string/config_headsUpNotificationAnimationHelper" />
     <item target="string/notification_header" value="@string/notification_header"/>
 
+    <item target="style/ClearAllButton" value="@style/ClearAllButton"/>
     <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="style/NotificationActionButtonText" value="@style/NotificationActionButtonText"/>
+    <item target="style/Theme.DeviceDefault.NoActionBar.Notification" value="@style/Theme.DeviceDefault.NoActionBar.Notification"/>
 
     <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"/>
+
+    <item target="layout/car_headsup_notification_header_view" value="@layout/car_headsup_notification_header_view"/>
+    <item target="id/app_icon" value="@id/app_icon"/>
+    <item target="id/header_text" value="@id/header_text"/>
+
+    <item target="layout/car_headsup_notification_body_view" value="@layout/car_headsup_notification_body_view"/>
+    <item target="id/notification_body_icon" value="@id/notification_body_icon"/>
+    <item target="id/notification_body_title" value="@id/notification_body_title"/>
+    <item target="id/notification_body_content" value="@id/notification_body_content"/>
+
+    <item target="layout/call_headsup_notification_template" value="@layout/call_headsup_notification_template"/>
 </overlay>
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
index 4182cf1..39181e4 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitSettingsProviderRRO/res/values/defaults.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsProviderRRO/res/values/defaults.xml
@@ -16,5 +16,5 @@
   -->
 <resources>
     <!-- Set default screen orientation. -->
-    <integer name="def_user_rotation">1</integer>
+    <integer name="def_user_rotation">0</integer>
 </resources>
\ No newline at end of file
diff --git a/car_product/rro/CarSystemUIEvsRRO/Android.bp b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/Android.bp
similarity index 95%
copy from car_product/rro/CarSystemUIEvsRRO/Android.bp
copy to car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/Android.bp
index ebb09ef..5b9f206 100644
--- a/car_product/rro/CarSystemUIEvsRRO/Android.bp
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/Android.bp
@@ -15,7 +15,7 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 android_app {
-    name: "CarSystemUIEvsRRO",
+    name: "CarUiPortraitSettingsRRO",
     resource_dirs: ["res"],
     platform_apis: true,
     aaptflags: [
diff --git a/car_product/rro/CarSystemUIEvsRRO/AndroidManifest.xml b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/AndroidManifest.xml
similarity index 80%
copy from car_product/rro/CarSystemUIEvsRRO/AndroidManifest.xml
copy to car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/AndroidManifest.xml
index f1dc7b8..8d15347 100644
--- a/car_product/rro/CarSystemUIEvsRRO/AndroidManifest.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/AndroidManifest.xml
@@ -15,10 +15,11 @@
   ~ limitations under the License.
   -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.systemui.car.evs.rro">
+          package="com.android.car.settings.caruiportrait.rro">
     <application android:hasCode="false"/>
-    <overlay android:targetName="CarSystemUI"
-             android:targetPackage="com.android.systemui"
+    <overlay android:priority="20"
+             android:targetName="CarSettings"
+             android:targetPackage="com.android.car.settings"
              android:resourcesMap="@xml/overlays"
              android:isStatic="true" />
 </manifest>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/res/drawable/top_level_preference_background.xml b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/res/drawable/top_level_preference_background.xml
new file mode 100644
index 0000000..59b968b
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/res/drawable/top_level_preference_background.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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:state_pressed="true">
+        <shape android:shape="rectangle">
+            <solid android:color="@color/top_level_preference_background_color"/>
+        </shape>
+    </item>
+    <item android:state_focused="true">
+        <shape android:shape="rectangle">
+            <solid android:color="@color/top_level_preference_background_color"/>
+        </shape>
+    </item>
+    <item>
+        <shape android:shape="rectangle">
+            <solid android:color="@color/top_level_preference_background_color"/>
+        </shape>
+    </item>
+</selector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_off.xml b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/res/drawable/top_level_preference_highlight.xml
similarity index 62%
copy from car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_off.xml
copy to car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/res/drawable/top_level_preference_highlight.xml
index b6c1cb9..d2b21a3 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_off.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/res/drawable/top_level_preference_highlight.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2021 The Android Open Source Project
   ~
@@ -13,18 +14,18 @@
   ~ 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_off_background_color"/>
-            <size android:height="@dimen/hvac_panel_button_dimen"
-                  android:width="@dimen/hvac_panel_button_dimen"/>
+    <item
+        android:right="100dip">
+        <shape
+            android:shape="rectangle" >
+            <solid android:color="#91AFC6" />
         </shape>
     </item>
-    <item
-        android:drawable="@drawable/ic_mode_fan_off"
-        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
+    <item android:left="8dip">
+        <shape
+            android:shape="rectangle" >
+            <solid android:color="@color/top_level_preference_background_color" />
+        </shape>
+    </item>
+</layer-list>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/res/layout/top_level_preference.xml b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/res/layout/top_level_preference.xml
new file mode 100644
index 0000000..2466c5f
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/res/layout/top_level_preference.xml
@@ -0,0 +1,67 @@
+<?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_width="match_parent"
+    android:layout_height="wrap_content"
+    android:clipToPadding="false"
+    android:minHeight="96dp"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:tag="carUiPreference"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart">
+
+    <com.android.car.ui.uxr.DrawableStateImageView
+        android:id="@android:id/icon"
+        android:layout_width="44dp"
+        android:layout_height="44dp"
+        android:layout_alignParentStart="true"
+        android:layout_centerVertical="true"
+        android:layout_marginStart="23dp"
+        android:scaleType="fitCenter"/>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_marginStart="33dp"
+        android:layout_toEndOf="@android:id/icon"
+        android:layout_toStartOf="@android:id/widget_frame"
+        android:orientation="vertical">
+
+        <com.android.car.ui.uxr.DrawableStateTextView
+            android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:singleLine="true"/>
+
+        <com.android.car.ui.uxr.DrawableStateTextView
+            android:id="@android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
+
+    </LinearLayout>
+
+    <!-- Preference should place its actual preference widget here. -->
+    <FrameLayout
+        android:id="@android:id/widget_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentEnd="true"
+        android:layout_centerVertical="true"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/res/values-night/colors.xml
similarity index 78%
copy from car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
copy to car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/res/values-night/colors.xml
index 4fab05c..957a4b4 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/res/values-night/colors.xml
@@ -14,8 +14,6 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<resources>
-    <string name="config_rearViewCameraActivity" translatable="false">
-        com.google.android.car.evs/com.google.android.car.evs.CarEvsCameraPreviewActivity
-    </string>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <color name="top_level_preference_background_color">#282A2D</color>
 </resources>
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/res/values/colors.xml
similarity index 78%
copy from car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
copy to car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/res/values/colors.xml
index 4fab05c..59a99d0 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/res/values/colors.xml
@@ -14,8 +14,6 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<resources>
-    <string name="config_rearViewCameraActivity" translatable="false">
-        com.google.android.car.evs/com.google.android.car.evs.CarEvsCameraPreviewActivity
-    </string>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <color name="top_level_preference_background_color">#E8EAED</color>
 </resources>
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/res/xml/overlays.xml
similarity index 66%
copy from car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
copy to car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/res/xml/overlays.xml
index 4fab05c..c37acee 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitSettingsRRO/res/xml/overlays.xml
@@ -14,8 +14,10 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<resources>
-    <string name="config_rearViewCameraActivity" translatable="false">
-        com.google.android.car.evs/com.google.android.car.evs.CarEvsCameraPreviewActivity
-    </string>
-</resources>
+<overlay>
+    <item target="drawable/top_level_preference_background" value="@drawable/top_level_preference_background"/>
+    <item target="drawable/top_level_preference_highlight" value="@drawable/top_level_preference_highlight"/>
+
+    <item target="layout/top_level_preference" value="@layout/top_level_preference"/>
+
+</overlay>
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
index bb8aeb1..a551f99 100644
--- 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
@@ -18,7 +18,7 @@
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
     <item>
         <shape>
-            <color android:color="@color/car_alert_dialog_action_button_color"/>
+            <solid android:color="@color/car_alert_dialog_action_button_color"/>
             <corners android:radius="@dimen/alert_dialog_button_corner_radius"/>
         </shape>
     </item>
diff --git a/car_product/car_ui_portrait/rro/android/res/layout-car/car_alert_dialog.xml b/car_product/car_ui_portrait/rro/android/res/layout-car/car_alert_dialog.xml
new file mode 100644
index 0000000..272e274
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/layout-car/car_alert_dialog.xml
@@ -0,0 +1,77 @@
+<?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.internal.widget.AlertDialogLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@*android:id/parentPanel"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="400dp"
+    android:maxHeight="916dp"
+    android:gravity="start|top"
+    android:background="@color/alert_dialog_background_color"
+    android:orientation="vertical">
+    <include layout="@layout/car_alert_dialog_title" />
+    <FrameLayout
+        android:id="@*android:id/contentPanel"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/car_alert_dialog_margin"
+        android:layout_marginStart="@dimen/car_alert_dialog_margin"
+        android:layout_marginEnd="@dimen/car_alert_dialog_margin">
+        <ScrollView
+            android:id="@*android:id/scrollView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:clipToPadding="false">
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical">
+                <Space
+                    android:id="@*android:id/textSpacerNoTitle"
+                    android:visibility="gone"
+                    android:layout_width="match_parent"
+                    android:layout_height="36dp"/>
+                <TextView
+                    android:id="@android:id/message"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:textColor="@color/alert_dialog_message_text_color"/>
+                <!-- we don't need this spacer, but the id needs to be here for compatibility -->
+                <Space
+                    android:id="@*android:id/textSpacerNoButtons"
+                    android:visibility="gone"
+                    android:layout_width="match_parent"
+                    android:layout_height="0dp" />
+            </LinearLayout>
+        </ScrollView>
+    </FrameLayout>
+    <FrameLayout
+        android:id="@*android:id/customPanel"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="48dp">
+        <FrameLayout
+            android:id="@android:id/custom"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </FrameLayout>
+    <include
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        layout="@layout/car_alert_dialog_button_bar" />
+</com.android.internal.widget.AlertDialogLayout>
diff --git a/car_product/car_ui_portrait/rro/android/res/layout-car/car_alert_dialog_button_bar.xml b/car_product/car_ui_portrait/rro/android/res/layout-car/car_alert_dialog_button_bar.xml
new file mode 100644
index 0000000..72294cc
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/layout-car/car_alert_dialog_button_bar.xml
@@ -0,0 +1,63 @@
+<?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.
+  -->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+            android:id="@*android:id/buttonPanel"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:scrollbarAlwaysDrawVerticalTrack="true"
+            android:scrollIndicators="top|bottom"
+            android:fillViewport="true"
+            style="?android:attr/buttonBarStyle">
+    <com.android.internal.widget.ButtonBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layoutDirection="locale"
+        android:layout_marginTop="@dimen/car_alert_dialog_margin"
+        android:layout_marginLeft="@dimen/car_alert_dialog_margin"
+        android:layout_marginRight="@dimen/car_alert_dialog_margin"
+        android:orientation="horizontal"
+        android:gravity="center">
+        <Button
+            android:id="@android:id/button3"
+            android:background="@drawable/car_dialog_button_background"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/button_layout_height"
+            android:layout_marginRight="@dimen/car_alert_dialog_button_margin"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="@color/alert_dialog_message_text_color"/>
+        <Button
+            android:id="@android:id/button2"
+            android:background="@drawable/car_dialog_button_background"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/button_layout_height"
+            android:layout_marginRight="@dimen/car_alert_dialog_button_margin"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="@color/alert_dialog_message_text_color"/>
+        <Button
+            android:id="@android:id/button1"
+            android:background="@drawable/car_dialog_button_background"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/button_layout_height"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="@color/alert_dialog_message_text_color"/>
+        <Space
+            android:id="@*android:id/spacer"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:visibility="gone" />
+    </com.android.internal.widget.ButtonBarLayout>
+</ScrollView>
diff --git a/car_product/car_ui_portrait/rro/android/res/layout-car/car_alert_dialog_title.xml b/car_product/car_ui_portrait/rro/android/res/layout-car/car_alert_dialog_title.xml
new file mode 100644
index 0000000..3e08602
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/layout-car/car_alert_dialog_title.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.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@*android:id/topPanel"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:gravity="center_vertical"
+              android:orientation="vertical">
+    <!-- If the client uses a customTitle, it will be added here. -->
+    <RelativeLayout
+        android:id="@*android:id/title_template"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/car_card_header_height"
+        android:orientation="horizontal">
+        <ImageView
+            android:id="@android:id/icon"
+            android:layout_width="44dp"
+            android:layout_height="44dp"
+            android:layout_alignParentStart="true"
+            android:layout_centerVertical="true"
+            android:scaleType="fitCenter"
+            android:src="@null" />
+        <com.android.internal.widget.DialogTitle
+            android:id="@*android:id/alertTitle"
+            android:maxLines="1"
+            android:ellipsize="none"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_toEndOf="@+id/icon"
+            android:textAlignment="viewStart"
+            android:textColor="@color/alert_dialog_message_text_color"
+            android:layout_centerVertical="true"/>
+    </RelativeLayout>
+    <Space
+        android:id="@*android:id/titleDividerNoCustom"
+        android:visibility="gone"
+        android:layout_width="match_parent"
+        android:layout_height="0dp" />
+</LinearLayout>
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml b/car_product/car_ui_portrait/rro/android/res/values-night/colors.xml
similarity index 71%
copy from car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
copy to car_product/car_ui_portrait/rro/android/res/values-night/colors.xml
index 4fab05c..2a70fa4 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
+++ b/car_product/car_ui_portrait/rro/android/res/values-night/colors.xml
@@ -14,8 +14,8 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<resources>
-    <string name="config_rearViewCameraActivity" translatable="false">
-        com.google.android.car.evs/com.google.android.car.evs.CarEvsCameraPreviewActivity
-    </string>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <color name="alert_dialog_background_color">#202124</color>
+    <color name="alert_dialog_message_text_color">#fff</color>
+    <color name="car_alert_dialog_action_button_color">#2E3134</color>
 </resources>
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
index c26d6df..2328a0b 100644
--- 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
@@ -47,9 +47,9 @@
     <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_dark">@*android:color/system_neutral1_1000</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_dark">@*android:color/car_grey_900</color>
     <color name="background_floating_device_default_light">@*android:color/background_device_default_light</color>
 
     <!-- Surface colors -->
@@ -80,4 +80,6 @@
 
     <color name="edge_effect_device_default_light">@android:color/black</color>
     <color name="edge_effect_device_default_dark">@android:color/white</color>
+
+    <color name="floating_background_color">@*android:color/car_grey_900</color>
 </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
index 8fc21cc..22c0af0 100644
--- a/car_product/car_ui_portrait/rro/android/res/values/colors.xml
+++ b/car_product/car_ui_portrait/rro/android/res/values/colors.xml
@@ -15,7 +15,7 @@
   ~ 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_alert_dialog_action_button_color">#dadce0</color>
   <color name="car_card_ripple_background_dark">?android:attr/colorControlHighlight</color>
   <color name="car_card_ripple_background_light">?android:attr/colorControlHighlight</color>
 
@@ -65,4 +65,7 @@
   <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>
+  <color name="alert_dialog_background_color">#f1f3f4</color>
+  <color name="alert_dialog_message_text_color">#000</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
index e9c768b..c9e8d2d 100644
--- 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
@@ -51,7 +51,7 @@
     <!-- 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_dark">@*android:color/car_grey_300</color>
     <color name="background_floating_device_default_light">@*android:color/background_device_default_light</color>
 
     <!-- Surface colors -->
@@ -63,8 +63,8 @@
     <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="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>
 
     <color name="list_divider_color_light">@*android:color/system_neutral1_700</color>
     <color name="list_divider_color_dark">@*android:color/system_neutral1_200</color>
@@ -79,4 +79,5 @@
     <color name="edge_effect_device_default_light">@android:color/white</color>
     <color name="edge_effect_device_default_dark">@android:color/black</color>
 
+    <color name="floating_background_color">@*android:color/car_grey_300</color>
 </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
index de98f91..7a325ad 100644
--- a/car_product/car_ui_portrait/rro/android/res/values/dimens.xml
+++ b/car_product/car_ui_portrait/rro/android/res/values/dimens.xml
@@ -32,9 +32,11 @@
     <!-- 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>
+    <dimen name="car_alert_dialog_margin">36dp</dimen>
     <!-- Dialog button layout height -->
     <dimen name="button_layout_height">88dp</dimen>
+    <!-- Default dialog button margin -->
+    <dimen name="car_alert_dialog_button_margin">24dp</dimen>
 
     <!-- ****** Toast dimens ***** -->
 
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
index e1671e4..09731bb 100644
--- 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
@@ -15,7 +15,7 @@
 -->
 
 <!--
-    This is an override of frameworks/base/core/res/res/values/themes_device_default.xml
+    This is an override of frameworks/base/core/res/res/values/themes_device_defaults.xml
     It is how the device default is changed to match the desired look for a car theme.
 -->
 <resources>
@@ -39,12 +39,15 @@
 
         <item name="android:actionBarSize">@*android:dimen/car_app_bar_height</item>
 
-
         <!-- Color palette -->
+        <item name="android:colorBackgroundFloating">@color/floating_background_color</item>
         <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>
+        <item name="android:alertDialogTheme">@android:style/Theme.DeviceDefault.Dialog.Alert</item>
     </style>
 
     <style name="Theme.DeviceDefault.Dialog" parent="android:Theme.Material.Dialog">
@@ -73,6 +76,8 @@
     </style>
 
     <style name="Theme.DeviceDefault.Dialog.Alert" parent="android:Theme.Material.Dialog.Alert">
+        <item name="android:alertDialogTheme">?android:attr/alertDialogTheme</item>
+
         <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>
@@ -88,6 +93,7 @@
         <item name="android:windowTitleStyle">?android:attr/textAppearanceLarge</item>
         <!-- Color palette -->
         <item name="android:colorButtonNormal">@color/btn_device_default_dark</item>
+        <item name="android:background">@color/alert_dialog_background_color</item>
     </style>
 
     <style name="Theme.DeviceDefault.Settings.Dialog" parent="android:Theme.DeviceDefault.Dialog.Alert">
@@ -142,7 +148,6 @@
 
         <!-- 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>
@@ -178,7 +183,6 @@
 
         <!-- 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>
@@ -206,7 +210,6 @@
 
         <!-- 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>
@@ -234,7 +237,6 @@
 
         <!-- 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>
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
index faf9eeb..81cfb5c 100644
--- 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
@@ -25,9 +25,12 @@
          class. -->
     <ImageView
         android:id="@+id/car_ui_alert_icon"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
+        android:layout_width="96dp"
+        android:layout_height="96dp"
+        android:layout_marginStart="10dp"
+        android:layout_marginTop="@dimen/alert_dialog_margin"
         android:scaleType="fitCenter"
+        android:tint="@color/car_ui_text_color_primary"
         android:visibility="gone"/>
 
     <LinearLayout
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
index 17c973e..0049818 100644
--- 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
@@ -35,7 +35,6 @@
     <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" />
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
index 0162e5b..ffbeb18 100644
--- 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
@@ -17,7 +17,7 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_activated="true">
         <shape android:shape="rectangle">
-            <solid android:color="#2E3134"/>
+            <solid android:color="@color/tab_background_color"/>
         </shape>
     </item>
     <item android:state_activated="false">
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
index fe4c97f..642ea29 100644
--- 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
@@ -75,7 +75,7 @@
                     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"/>
+                    android:tint="?android:attr/textColorPrimary"/>
 
                 <ImageView
                     android:id="@+id/car_ui_toolbar_logo"
@@ -89,6 +89,7 @@
                 android:id="@+id/car_ui_toolbar_title_logo_container"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
+                android:layout_marginLeft="24dp"
                 app:layout_constraintBottom_toBottomOf="parent"
                 app:layout_constraintTop_toTopOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
@@ -162,7 +163,7 @@
             <View
                 android:layout_width="match_parent"
                 android:layout_height="2dp"
-                android:background="#F1F3F4"
+                android:background="@color/divider_color"
                 app:layout_constraintBottom_toBottomOf="parent"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent" />
@@ -175,6 +176,7 @@
         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">
@@ -190,9 +192,10 @@
     <View
         android:layout_width="2dp"
         android:layout_height="0dp"
-        android:background="#F1F3F4"
-        app:layout_constraintBottom_toBottomOf="@id/left_part_of_toolbar_focus_area"
-        app:layout_constraintTop_toTopOf="@id/left_part_of_toolbar_focus_area"
-        app:layout_constraintEnd_toEndOf="@id/left_part_of_toolbar_focus_area" />
+        android:background="@color/divider_color"
+        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/rro/CarSystemUIEvsRRO/res/values/config.xml b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/values-night/colors.xml
similarity index 78%
copy from car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
copy to car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/values-night/colors.xml
index 4fab05c..52db35b 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/values-night/colors.xml
@@ -14,8 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<resources>
-    <string name="config_rearViewCameraActivity" translatable="false">
-        com.google.android.car.evs/com.google.android.car.evs.CarEvsCameraPreviewActivity
-    </string>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <color name="tab_background_color">#282A2D</color>
+    <color name="divider_color">#2e3134</color>
 </resources>
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/values/bools.xml
similarity index 81%
copy from car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml
copy to car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/values/bools.xml
index 36785dc..c502242 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/xml/overlays.xml
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/values/bools.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2021 The Android Open Source Project
   ~
@@ -14,7 +15,8 @@
   ~ limitations under the License.
   -->
 
-<overlay>
-    <item target="string/config_rearViewCameraActivity"
-          value="@string/config_rearViewCameraActivity"/>
-</overlay>
+<resources>
+
+    <bool name="car_ui_toolbar_tabs_on_second_row">true</bool>
+
+</resources>
\ No newline at end of file
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/values/colors.xml
similarity index 78%
copy from car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
copy to car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/values/colors.xml
index 4fab05c..8c7d1da 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/res/values/colors.xml
@@ -14,8 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<resources>
-    <string name="config_rearViewCameraActivity" translatable="false">
-        com.google.android.car.evs/com.google.android.car.evs.CarEvsCameraPreviewActivity
-    </string>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <color name="tab_background_color">#E8EAED</color>
+    <color name="divider_color">#E8EAED</color>
 </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
index 4d4a97f..88772fb 100644
--- 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
@@ -21,6 +21,7 @@
     <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="bool/car_ui_toolbar_tabs_on_second_row" value="@bool/car_ui_toolbar_tabs_on_second_row" />
 
     <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" />
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
index 5029fcd..5b06ed2 100644
--- a/car_product/car_ui_portrait/rro/car_ui_portrait_rro.mk
+++ b/car_product/car_ui_portrait/rro/car_ui_portrait_rro.mk
@@ -19,12 +19,15 @@
 
 # All RROs to be included in car_ui_portrait builds.
 PRODUCT_PACKAGES += \
+    CarEvsCameraPreviewAppRRO \
     CarUiPortraitDialerRRO \
+    CarUiPortraitSettingsRRO \
     CarUiPortraitMediaRRO \
     CarUiPortraitLauncherRRO \
     CarUiPortraitNotificationRRO \
+    CarUiPortraitCarServiceRRO \
     CarUiPortraitFrameworkResRRO \
-    CarUiPortraitFrameworkResRROTest \
+    CarUiPortraitFrameworkResRROTest
 
 ifneq ($(INCLUDE_SEAHAWK_ONLY_RROS),)
 PRODUCT_PACKAGES += \
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 6bd87c4..5eaa58f 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 f73000b..4e238b2 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 5efe621..7ee176b 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 7b0fa92..149eca5 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 65ec685..097c211 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 346f0e2..9737263 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 dc483db..3430939 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 9842971..f781a33 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 22ee318..d977eee 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 346f0e2..9737263 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 e1a2044..918450c 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 ce005f1..d4a8365 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 1c751f9..6b0bab3 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
@@ -18,6 +18,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     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 placering i forgrunden"</string>
-    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Aktivér mikrofon"</string>
+    <string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"få kun adgang til omtrentlig lokation i forgrunden"</string>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 ba314a7..a105808 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 4eca60d..466097b 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 91594b0..93acecd 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 91594b0..93acecd 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 91594b0..93acecd 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 91594b0..93acecd 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 3e208b3..b754729 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 ced0bde..f6b2e17 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 af83a18..896e19a 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 1876c65..5b52882 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 0e06b76..b5848d0 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 4c9f73a..014de02 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 ce5daf3..7aef213 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 eb83b85..eb9354f 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 02b7a24..e717c18 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 30b25d4..28577e0 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 e157fd3..d92cc32 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 a5ac0f3..148b5b8 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 0ac52d4..64267b9 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 778f57c..694a475 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 0dc5851..38ab52e 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 52cda98..b0a9066 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 b922593..7da671c 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 10c4836..5de37e8 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 657ebf2..922c296 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 6da0c2c..b5de91b 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 7fe766c..91c1d27 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 36195f0..54bb5fa 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 1a74261..af09772 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 ecfa406..bb5ea0f 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 52c715d..025f3d9 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 e062223..d728a98 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 fabaa3d..55427f8 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 a02befb..ce99835 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 333add2..f1b351c 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 ed49dfe..830b074 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 a69ea5d..eedaa0f 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 5b3d6fb..d911240 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 30efa52..c087704 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 6e830f7..99c7604 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 43c3d37..f862346 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 cbc2918..2aefc64 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 eda9747..4121586 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 d79cba2..17c55ae 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 939b105..45ff971 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 2a2e55e..c4e258a 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 32fe8a4..5701348 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 016617e..40b1c1a 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 efca2bf..67a25f0 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 adf01f0..7931775 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 12a3f43..ff8690d 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 9d75b4f..853067b 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 125cb5a..f9351a5 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 52841ab..c4343fd 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 dc7bbe2..74c9118 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 2cfe36e..96d9765 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 1aa7c0c..d2e5456 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 60e5469..3193a4b 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </resources>
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml b/car_product/overlay/frameworks/base/core/res/res/values-sw600dp/config.xml
similarity index 67%
copy from car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
copy to car_product/overlay/frameworks/base/core/res/res/values-sw600dp/config.xml
index 4fab05c..f7873a2 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-sw600dp/config.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2021 The Android Open Source Project
   ~
@@ -14,8 +13,8 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<resources>
-    <string name="config_rearViewCameraActivity" translatable="false">
-        com.google.android.car.evs/com.google.android.car.evs.CarEvsCameraPreviewActivity
-    </string>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Enable dynamic keyguard positioning for large-width screens. This will cause the keyguard
+     to be aligned to one side of the screen when in landscape mode. -->
+    <bool name="config_enableDynamicKeyguardPositioning">false</bool>
 </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 38a2e03..c02fe3e 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 c95285e..5d6d76e 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 6f617e3..5697247 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 c42b2c8..7189a0b 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 0a13258..fa2ed17 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 c15df06..a541474 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 d3cea05..f3cf815 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 d15ff7c..d1e4978 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 1e19523..a9773d9 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 6556c7b..bb3c97d 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 0d8543b..86d3a3c 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 7340ecd..adc9279 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 c99c505..25f11d1 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,5 +19,6 @@
     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>
+    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
+    <skip />
 </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 a1b0cd2..73326f1 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
@@ -156,4 +156,10 @@
      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>
+
+    <!-- The component name of the activity for the companion-device-manager notification access
+         confirmation. -->
+    <string name="config_notificationAccessConfirmationActivity" translatable="false">
+        com.android.car.settings/com.android.car.settings.notifications.NotificationAccessConfirmationActivity
+    </string>
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values/required_apps_managed_user.xml b/car_product/overlay/frameworks/base/core/res/res/values/required_apps_managed_user.xml
new file mode 100644
index 0000000..a04429e
--- /dev/null
+++ b/car_product/overlay/frameworks/base/core/res/res/values/required_apps_managed_user.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>
+    <string-array translatable="false" name="required_apps_managed_user">
+
+        <!-- NOTE: apps below were copied from phone, replacing the equivalent
+             car app when needed -->
+        <item>com.android.car.settings</item>
+        <item>com.android.systemui</item>
+        <item>com.android.car.dialer</item>
+        <item>com.android.contacts</item>
+        <item>com.android.stk</item>
+        <item>com.android.providers.downloads</item>
+        <item>com.android.providers.downloads.ui</item>
+        <item>com.android.documentsui</item>
+
+        <!-- Car-specific apps -->
+        <item>com.android.car.bugreport</item>
+        <item>com.android.car.acast.source</item>
+        <item>com.android.car.calendar</item>
+        <item>com.android.car.dialer</item>
+        <item>com.android.car.messenger</item>
+        <item>com.android.car.radio</item>
+        <item>com.android.car.speedbump</item>
+        <item>com.android.car.themeplayground</item>
+        <item>com.android.car.voicecontrol</item>
+
+    </string-array>
+</resources>
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 eff76d4..826c9f6 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
@@ -20,5 +20,5 @@
     <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>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button">Turn on microphone</string>
 </resources>
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/evs/manager/sepolicy/private/evs_manager.te b/cpp/evs/manager/sepolicy/private/evs_manager.te
index d8dba4b..ae7ec68 100644
--- a/cpp/evs/manager/sepolicy/private/evs_manager.te
+++ b/cpp/evs/manager/sepolicy/private/evs_manager.te
@@ -15,3 +15,6 @@
 
 # allow to use carservice_app
 binder_call(evs_manager, carservice_app)
+
+# allow to use the graphics allocator
+allow evs_manager hal_graphics_allocator:fd use;
diff --git a/cpp/evs/sampleDriver/sepolicy/private/evs_driver.te b/cpp/evs/sampleDriver/sepolicy/private/evs_driver.te
index 5b847fa..ab0c251 100644
--- a/cpp/evs/sampleDriver/sepolicy/private/evs_driver.te
+++ b/cpp/evs/sampleDriver/sepolicy/private/evs_driver.te
@@ -14,7 +14,11 @@
 hal_client_domain(hal_evs_driver, hal_graphics_composer)
 hal_client_domain(hal_evs_driver, hal_configstore)
 
+# Allow the driver to access EGL
 allow hal_evs_driver gpu_device:chr_file rw_file_perms;
+allow hal_evs_driver gpu_device:dir search;
+
+# Allow the driver to use SurfaceFlinger
 binder_call(hal_evs_driver, surfaceflinger);
 allow hal_evs_driver surfaceflinger_service:service_manager find;
 allow hal_evs_driver ion_device:chr_file r_file_perms;
@@ -25,4 +29,4 @@
 # Allow the driver to use automotive display proxy service
 allow hal_evs_driver automotive_display_service_server:binder call;
 allow hal_evs_driver fwk_automotive_display_hwservice:hwservice_manager find;
-
+allow hal_evs_driver automotive_display_service:fd use;
diff --git a/cpp/evs/sampleDriver/sepolicy/private/surfaceflinger.te b/cpp/evs/sampleDriver/sepolicy/private/surfaceflinger.te
index ce51a0d..d7aba85 100644
--- a/cpp/evs/sampleDriver/sepolicy/private/surfaceflinger.te
+++ b/cpp/evs/sampleDriver/sepolicy/private/surfaceflinger.te
@@ -1,2 +1,5 @@
 # Allow surfaceflinger to perform binder IPC to hal_evs_driver
 binder_call(surfaceflinger, hal_evs_driver)
+
+# Allow surfaceflinger to perform binder IPC to automotive_display_service
+binder_call(surfaceflinger, automotive_display_service)
diff --git a/cpp/libsysfsmonitor/Android.bp b/cpp/libsysfsmonitor/Android.bp
new file mode 100644
index 0000000..545255b
--- /dev/null
+++ b/cpp/libsysfsmonitor/Android.bp
@@ -0,0 +1,60 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_defaults {
+    name: "libsysfsmonitor_defaults",
+    cflags: [
+        "-Wall",
+        "-Wno-missing-field-initializers",
+        "-Werror",
+        "-Wno-unused-variable",
+        "-Wunused-parameter",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "libutils",
+    ],
+}
+
+cc_library {
+    name: "libsysfsmonitor",
+    srcs: [
+        "src/SysfsMonitor.cpp",
+    ],
+    defaults: [
+        "libsysfsmonitor_defaults",
+    ],
+    export_include_dirs: [
+        "src",
+    ],
+}
+
+cc_test {
+    name: "libsysfsmonitor_test",
+    srcs: [
+        "tests/SysfsMonitorTest.cpp",
+    ],
+    defaults: [
+        "libsysfsmonitor_defaults",
+    ],
+    static_libs: [
+        "libgtest",
+        "libsysfsmonitor",
+    ],
+}
diff --git a/cpp/libsysfsmonitor/src/SysfsMonitor.cpp b/cpp/libsysfsmonitor/src/SysfsMonitor.cpp
new file mode 100644
index 0000000..ee89ec0
--- /dev/null
+++ b/cpp/libsysfsmonitor/src/SysfsMonitor.cpp
@@ -0,0 +1,135 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "libsysfsmonitor"
+#define DEBUG false
+
+#include "SysfsMonitor.h"
+
+#include <android-base/stringprintf.h>
+#include <log/log.h>
+
+#include <sys/epoll.h>
+
+namespace {
+
+using ::android::base::Error;
+using ::android::base::Result;
+using ::android::base::StringPrintf;
+
+// The maximum number of sysfs files to monitor.
+constexpr int32_t EPOLL_MAX_EVENTS = 10;
+
+}  // namespace
+
+namespace android {
+namespace automotive {
+
+Result<void> SysfsMonitor::init(CallbackFunc callback) {
+    if (mEpollFd >= 0) {
+        return Error() << "Epoll instance was already created";
+    }
+    if (mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC)); mEpollFd < 0) {
+        return Error() << "Cannot create epoll instance: errno = " << errno;
+    }
+    mCallback = callback;
+    return {};
+}
+
+Result<void> SysfsMonitor::release() {
+    if (mEpollFd < 0) {
+        return Error() << "Epoll instance wasn't created";
+    }
+    for (const int32_t fd : mMonitoringFds) {
+        if (epoll_ctl(mEpollFd, EPOLL_CTL_DEL, fd, /*event=*/nullptr)) {
+            ALOGW("Failed to deregister fd(%d) from epoll instance: errno = %d", fd, errno);
+        }
+    }
+    mMonitoringFds.clear();
+    mEpollFd.reset();
+    mCallback = nullptr;
+    return {};
+}
+
+Result<void> SysfsMonitor::registerFd(int32_t fd) {
+    if (fd < 0) {
+        return Error() << StringPrintf("fd(%d) is invalid", fd);
+    }
+    if (mMonitoringFds.count(fd) > 0) {
+        return Error() << StringPrintf("fd(%d) is already being monitored", fd);
+    }
+    if (mMonitoringFds.size() == EPOLL_MAX_EVENTS) {
+        return Error() << "Cannot monitor more than " << EPOLL_MAX_EVENTS << " sysfs files";
+    }
+    struct epoll_event eventItem = {};
+    eventItem.events = EPOLLIN | EPOLLPRI | EPOLLET;
+    eventItem.data.fd = fd;
+    if (int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem); result != 0) {
+        return Error() << StringPrintf("Failed to add fd(%d) to epoll instance: errno = %d", fd,
+                                       errno);
+    }
+    mMonitoringFds.insert(fd);
+    return {};
+}
+
+Result<void> SysfsMonitor::unregisterFd(int32_t fd) {
+    if (fd < 0) {
+        return Error() << StringPrintf("fd(%d) is invalid", fd);
+    }
+    if (mMonitoringFds.count(fd) == 0) {
+        return Error() << StringPrintf("fd(%d) is not being monitored", fd);
+    }
+    // Even when epoll_ctl() fails, we proceed to handle the request.
+    if (epoll_ctl(mEpollFd, EPOLL_CTL_DEL, fd, /*event=*/nullptr)) {
+        ALOGW("Failed to deregister fd(%d) from epoll instance: errno = %d", fd, errno);
+    }
+    mMonitoringFds.erase(fd);
+    return {};
+}
+
+Result<void> SysfsMonitor::observe() {
+    if (mEpollFd < 0) {
+        return Error() << "Epoll instance is not initialized";
+    }
+
+    struct epoll_event events[EPOLL_MAX_EVENTS];
+    while (true) {
+        int pollResult = epoll_wait(mEpollFd, events, EPOLL_MAX_EVENTS, /*timeout=*/-1);
+        if (pollResult < 0) {
+            ALOGW("Polling sysfs failed, but continue polling: errno = %d", errno);
+            continue;
+        }
+        std::vector<int32_t> fds;
+        for (int i = 0; i < pollResult; i++) {
+            int fd = events[i].data.fd;
+            if (mMonitoringFds.count(fd) == 0) {
+                continue;
+            }
+            if (events[i].events & EPOLLIN) {
+                fds.push_back(fd);
+            } else if (events[i].events & EPOLLERR) {
+                ALOGW("An error occurred when polling fd(%d)", fd);
+            }
+        }
+        if (mCallback && fds.size() > 0) {
+            mCallback(fds);
+        }
+    }
+    return {};
+}
+
+}  // namespace automotive
+}  // namespace android
diff --git a/cpp/libsysfsmonitor/src/SysfsMonitor.h b/cpp/libsysfsmonitor/src/SysfsMonitor.h
new file mode 100644
index 0000000..f941041
--- /dev/null
+++ b/cpp/libsysfsmonitor/src/SysfsMonitor.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 CPP_LIBSYSFSMONITOR_SRC_SYSFSMONITOR_H_
+#define CPP_LIBSYSFSMONITOR_SRC_SYSFSMONITOR_H_
+
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
+#include <utils/RefBase.h>
+
+#include <functional>
+#include <string>
+#include <unordered_set>
+
+namespace android {
+namespace automotive {
+
+using CallbackFunc = ::std::function<void(const std::vector<int32_t>&)>;
+
+/**
+ * SysfsMonitor monitors sysfs file changes and invokes the registered callback when there is a
+ * change at the sysfs files to monitor.
+ */
+class SysfsMonitor final : public RefBase {
+public:
+    // Initializes SysfsMonitor instance.
+    android::base::Result<void> init(CallbackFunc callback);
+    // Releases resources used for monitoring.
+    android::base::Result<void> release();
+    // Registers a sysfs file to monitor.
+    android::base::Result<void> registerFd(int32_t fd);
+    // Unregisters a sysfs file to monitor.
+    android::base::Result<void> unregisterFd(int32_t fd);
+    // Starts observing sysfs file changes.
+    android::base::Result<void> observe();
+
+private:
+    android::base::unique_fd mEpollFd;
+    std::unordered_set<int32_t> mMonitoringFds;
+    CallbackFunc mCallback;
+};
+
+}  // namespace automotive
+}  // namespace android
+
+#endif  // CPP_LIBSYSFSMONITOR_SRC_SYSFSMONITOR_H_
diff --git a/cpp/libsysfsmonitor/tests/SysfsMonitorTest.cpp b/cpp/libsysfsmonitor/tests/SysfsMonitorTest.cpp
new file mode 100644
index 0000000..f6dda6c
--- /dev/null
+++ b/cpp/libsysfsmonitor/tests/SysfsMonitorTest.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "SysfsMonitor.h"
+
+#include <gtest/gtest.h>
+#include <utils/StrongPointer.h>
+
+#include <sys/socket.h>
+
+#include <vector>
+
+namespace android {
+namespace automotive {
+
+class SysfsMonitorTest : public ::testing::Test {
+public:
+    void SetUp() override { mSysfsMonitor = sp<SysfsMonitor>::make(); }
+    void TearDown() override { mSysfsMonitor = nullptr; }
+
+protected:
+    sp<SysfsMonitor> mSysfsMonitor;
+};
+
+TEST_F(SysfsMonitorTest, TestDuplicateInitialize) {
+    auto ret = mSysfsMonitor->init([](const std::vector<int32_t>&) {});
+    ASSERT_TRUE(ret.ok()) << "First initialization should be successful: " << ret.error().message();
+    ASSERT_FALSE(mSysfsMonitor->init([](const std::vector<int32_t>&) {}).ok())
+            << "Initialization cannot be done twice";
+}
+
+TEST_F(SysfsMonitorTest, TestDuplicateRelease) {
+    auto ret = mSysfsMonitor->init([](const std::vector<int32_t>&) {});
+    ASSERT_TRUE(ret.ok()) << "First initialization should be successful: " << ret.error().message();
+    ret = mSysfsMonitor->release();
+    ASSERT_TRUE(ret.ok()) << "Releasing the initialized instance should be successful: "
+                          << ret.error().message();
+    ASSERT_FALSE(mSysfsMonitor->release().ok()) << "Released instance cannot be released";
+}
+
+TEST_F(SysfsMonitorTest, TestNoInitRelease) {
+    ASSERT_FALSE(mSysfsMonitor->release().ok()) << "Uninitailized instance cannot be released";
+}
+
+TEST_F(SysfsMonitorTest, TestUnregisterNotRegisteredFd) {
+    // A regular file cannot be registered to epoll instance. So, the test is using a socket file.
+    int32_t fd = socket(AF_UNIX, SOCK_DGRAM, /*protocol=*/0);
+    mSysfsMonitor->init([](const std::vector<int32_t>&) {});
+
+    ASSERT_FALSE(mSysfsMonitor->unregisterFd(fd).ok())
+            << "Unregistered file description cannot be unregistered";
+    close(fd);
+}
+
+TEST_F(SysfsMonitorTest, TestDuplicateRegister) {
+    int32_t fd = socket(AF_UNIX, SOCK_DGRAM, /*protocol=*/0);
+    mSysfsMonitor->init([](const std::vector<int32_t>&) {});
+
+    auto ret = mSysfsMonitor->registerFd(fd);
+    ASSERT_TRUE(ret.ok()) << "Registering a file descriptor first time should be successful: "
+                          << ret.error().message();
+    ASSERT_FALSE(mSysfsMonitor->registerFd(fd).ok())
+            << "Registering a file descriptor twice cannot be done";
+    close(fd);
+}
+
+TEST_F(SysfsMonitorTest, TestDuplicateUnregister) {
+    int32_t fd = socket(AF_UNIX, SOCK_DGRAM, /*protocol=*/0);
+    mSysfsMonitor->init([](const std::vector<int32_t>&) {});
+
+    auto ret = mSysfsMonitor->registerFd(fd);
+    ASSERT_TRUE(ret.ok()) << "Registering a file descriptor first time should be successful: "
+                          << ret.error().message();
+    ret = mSysfsMonitor->unregisterFd(fd);
+    ASSERT_TRUE(ret.ok()) << "The registered file descriptor should be unregistered: "
+                          << ret.error().message();
+    ASSERT_FALSE(mSysfsMonitor->unregisterFd(fd).ok())
+            << "Unregistering the unregistered file descriptor cannot be done";
+    close(fd);
+}
+
+TEST_F(SysfsMonitorTest, TestRegisterInvalidFd) {
+    const int32_t invalidFd = -1;
+    ASSERT_FALSE(mSysfsMonitor->registerFd(invalidFd).ok())
+            << "fd(-1) cannot be registered to SysfsMonitor";
+}
+
+TEST_F(SysfsMonitorTest, TestUnregisterInvalidFd) {
+    const int32_t invalidFd = -1;
+    ASSERT_FALSE(mSysfsMonitor->unregisterFd(invalidFd).ok())
+            << "fd(-1) cannot be given to unregisterFd";
+}
+
+TEST_F(SysfsMonitorTest, TestRegisterMultipleFds) {
+    const int32_t maxFdCount = 10;
+    int32_t fdsToMonitor[maxFdCount + 1];
+
+    mSysfsMonitor->init([](const std::vector<int32_t>&) {});
+    for (int i = 0; i < maxFdCount; i++) {
+        fdsToMonitor[i] = socket(AF_UNIX, SOCK_DGRAM, /*protocol=*/0);
+        auto ret = mSysfsMonitor->registerFd(fdsToMonitor[i]);
+        ASSERT_TRUE(ret.ok()) << "Registering a file descriptor first time should be successful: "
+                              << ret.error().message();
+    }
+    fdsToMonitor[maxFdCount] = socket(AF_UNIX, SOCK_DGRAM, /*protocol=*/0);
+    ASSERT_FALSE(mSysfsMonitor->registerFd(fdsToMonitor[maxFdCount]).ok())
+            << "Registering more than " << maxFdCount << " files cannot be done";
+
+    mSysfsMonitor->release();
+    for (int i = 0; i <= maxFdCount; i++) {
+        close(fdsToMonitor[i]);
+    }
+}
+
+TEST_F(SysfsMonitorTest, TestObserveWithoutInitialization) {
+    ASSERT_FALSE(mSysfsMonitor->observe().ok()) << "Uninitialized instance cannot observe";
+}
+
+}  // namespace automotive
+}  // namespace android
diff --git a/cpp/powerpolicy/server/Android.bp b/cpp/powerpolicy/server/Android.bp
index 094a3f5..2dd40bd 100644
--- a/cpp/powerpolicy/server/Android.bp
+++ b/cpp/powerpolicy/server/Android.bp
@@ -36,6 +36,9 @@
         "libtinyxml2",
         "libutils",
     ],
+    static_libs: [
+        "libsysfsmonitor",
+    ],
 }
 
 cc_library {
diff --git a/cpp/powerpolicy/server/src/SilentModeHandler.cpp b/cpp/powerpolicy/server/src/SilentModeHandler.cpp
index c57b6fa..79d78be 100644
--- a/cpp/powerpolicy/server/src/SilentModeHandler.cpp
+++ b/cpp/powerpolicy/server/src/SilentModeHandler.cpp
@@ -25,7 +25,7 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 
-#include <sys/inotify.h>
+#include <sys/epoll.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
@@ -35,6 +35,7 @@
 namespace powerpolicy {
 
 using ::android::Mutex;
+using ::android::automotive::SysfsMonitor;
 using ::android::base::Error;
 using ::android::base::GetProperty;
 using ::android::base::ReadFileToString;
@@ -49,12 +50,11 @@
 
 constexpr const char kPropertySystemBootReason[] = "sys.boot.reason";
 constexpr const char kSilentModeHwStateFilename[] = "/sys/power/pm_silentmode_hw_state";
-constexpr const char kKernelSilentModeFilename[] = "/sys/power/pm_silentmode_kernel";
+constexpr const char kKernelSilentModeFilename[] = "/sys/power/pm_silentmode_kernel_state";
 // To prevent boot animation from being started.
 constexpr const char kPropertyNoBootAnimation[] = "debug.sf.nobootanimation";
 // To stop boot animation while it is being played.
 constexpr const char kPropertyBootAnimationExit[] = "service.bootanim.exit";
-constexpr int kEventBufferSize = 512;
 
 bool fileExists(const char* filename) {
     struct stat buffer;
@@ -68,7 +68,7 @@
       mSilentModeHwStateFilename(kSilentModeHwStateFilename),
       mKernelSilentModeFilename(kKernelSilentModeFilename),
       mSilentModeChangeHandler(handler),
-      mFdInotify(-1) {
+      mSysfsMonitor(sp<SysfsMonitor>::make()) {
     mBootReason = GetProperty(kPropertySystemBootReason, "");
 }
 
@@ -83,7 +83,7 @@
     if (mForcedMode) {
         handleSilentModeChange(mSilentModeByHwState);
         mSilentModeChangeHandler->notifySilentModeChange(mSilentModeByHwState);
-        ALOGI("Now in forced mode: monitoring %s is disabled", kSilentModeHwStateFilename);
+        ALOGI("Now in forced mode: monitoring %s is disabled", mSilentModeHwStateFilename.c_str());
     } else {
         startMonitoringSilentModeHwState();
     }
@@ -101,13 +101,15 @@
 void SilentModeHandler::stopMonitoringSilentModeHwState(bool shouldWaitThread) {
     if (mIsMonitoring) {
         mIsMonitoring = false;
-        inotify_rm_watch(mFdInotify, mWdSilentModeHwState);
-        mWdSilentModeHwState = -1;
+        if (auto ret = mSysfsMonitor->unregisterFd(mFdSilentModeHwState.get()); !ret.ok()) {
+            ALOGW("Unregistering %s from SysfsMonitor failed", mSilentModeHwStateFilename.c_str());
+        }
         if (shouldWaitThread && mSilentModeMonitoringThread.joinable()) {
             mSilentModeMonitoringThread.join();
         }
     }
-    mFdInotify.reset(-1);
+    mFdSilentModeHwState.reset();
+    mSysfsMonitor->release();
 }
 
 Result<void> SilentModeHandler::dump(int fd, const Vector<String16>& /*args*/) {
@@ -132,56 +134,38 @@
         ALOGW("Silent Mode monitoring is already started");
         return;
     }
-    if (mFdInotify < 0) {
-        mFdInotify.reset(inotify_init1(IN_CLOEXEC));
-        if (mFdInotify < 0) {
-            ALOGE("Failed to start monitoring Silent Mode HW state: creating inotify instance "
-                  "failed (errno = %d)",
-                  errno);
-            return;
-        }
-    }
     const char* filename = mSilentModeHwStateFilename.c_str();
     if (!fileExists(filename)) {
         ALOGW("Failed to start monitoring Silent Mode HW state: %s doesn't exist", filename);
-        mFdInotify.reset(-1);
         return;
     }
-    // TODO(b/178843534): Additional masks might be needed to detect sysfs change.
-    const uint32_t masks = IN_MODIFY;
-    mWdSilentModeHwState = inotify_add_watch(mFdInotify, filename, masks);
+    if (mFdSilentModeHwState == -1) {
+        if (mFdSilentModeHwState.reset(open(filename, O_RDONLY | O_NONBLOCK | O_CLOEXEC));
+            mFdSilentModeHwState == -1) {
+            ALOGW("Failed to open %s for monitoring: errno = %d", filename, errno);
+            return;
+        }
+    }
+    auto ret = mSysfsMonitor->init([this](const std::vector<int32_t>& fileDescriptors) {
+        // Only one sysfs file is registered.
+        if (mFdSilentModeHwState != fileDescriptors[0]) {
+            return;
+        }
+        handleSilentModeHwStateChange();
+    });
+    if (!ret.ok()) {
+        ALOGW("Failed to initialize SysfsMonitor: %s", ret.error().message().c_str());
+        return;
+    }
+    if (auto ret = mSysfsMonitor->registerFd(mFdSilentModeHwState.get()); !ret.ok()) {
+        ALOGW("Failed to register %s to SysfsMonitor: %s", filename, ret.error().message().c_str());
+        return;
+    }
     mIsMonitoring = true;
-    mSilentModeMonitoringThread = std::thread([this]() {
-        char eventBuf[kEventBufferSize];
-        struct inotify_event* event;
-        constexpr size_t inotifyEventSize = sizeof(*event);
-        ALOGI("Monitoring %s started", mSilentModeHwStateFilename.c_str());
-        while (mIsMonitoring) {
-            int eventPos = 0;
-            int numBytes = read(mFdInotify, eventBuf, sizeof(eventBuf));
-            if (numBytes < static_cast<int>(inotifyEventSize)) {
-                if (errno == EINTR) {
-                    ALOGW("System call interrupted. Wait for inotify event again.");
-                    continue;
-                }
-                mIsMonitoring = false;
-                inotify_rm_watch(mFdInotify, mWdSilentModeHwState);
-                mWdSilentModeHwState = -1;
-                mFdInotify.reset(-1);
-                ALOGW("Failed to wait for change at %s (errno = %d)",
-                      mSilentModeHwStateFilename.c_str(), errno);
-                return;
-            }
-            while (numBytes >= static_cast<int>(inotifyEventSize)) {
-                int eventSize;
-                event = (struct inotify_event*)(eventBuf + eventPos);
-                if (event->wd == mWdSilentModeHwState && (event->mask & masks)) {
-                    handleSilentModeHwStateChange();
-                }
-                eventSize = inotifyEventSize + event->len;
-                numBytes -= eventSize;
-                eventPos += eventSize;
-            }
+    mSilentModeMonitoringThread = std::thread([this, filename]() {
+        if (auto ret = mSysfsMonitor->observe(); !ret.ok()) {
+            ALOGI("Failed to observe %s", filename);
+            return;
         }
         ALOGI("Monitoring %s ended", mSilentModeHwStateFilename.c_str());
     });
diff --git a/cpp/powerpolicy/server/src/SilentModeHandler.h b/cpp/powerpolicy/server/src/SilentModeHandler.h
index 9fae109..96074f8 100644
--- a/cpp/powerpolicy/server/src/SilentModeHandler.h
+++ b/cpp/powerpolicy/server/src/SilentModeHandler.h
@@ -21,8 +21,11 @@
 #include <android-base/unique_fd.h>
 #include <utils/Mutex.h>
 #include <utils/String16.h>
+#include <utils/StrongPointer.h>
 #include <utils/Vector.h>
 
+#include <SysfsMonitor.h>
+
 #include <atomic>
 #include <thread>  // NOLINT(build/c++11)
 
@@ -47,8 +50,8 @@
 
 /**
  * SilentModeHandler monitors {@code /sys/power/pm_silentmode_hw_state} in sysfs to detect Silent
- * Mode change by a vehicle processor. Also, it updates {@code /sys/power/pm_silentmode_kernel} in
- * sysfs to tell kernel the current Silent Mode.
+ * Mode change by a vehicle processor. Also, it updates
+ * {@code /sys/power/pm_silentmode_kernel_state} in sysfs to tell kernel the current Silent Mode.
  */
 class SilentModeHandler final {
 public:
@@ -80,9 +83,9 @@
     std::string mKernelSilentModeFilename;
     ISilentModeChangeHandler* mSilentModeChangeHandler;
     std::thread mSilentModeMonitoringThread;
-    android::base::unique_fd mFdInotify;
-    int mWdSilentModeHwState = -1;
     std::atomic_bool mIsMonitoring = false;
+    android::sp<android::automotive::SysfsMonitor> mSysfsMonitor;
+    android::base::unique_fd mFdSilentModeHwState;
 
     // For unit tests.
     friend class internal::SilentModeHandlerPeer;
diff --git a/cpp/powerpolicy/server/tests/SilentModeHandlerTest.cpp b/cpp/powerpolicy/server/tests/SilentModeHandlerTest.cpp
index 1eaec81..6548cf1 100644
--- a/cpp/powerpolicy/server/tests/SilentModeHandlerTest.cpp
+++ b/cpp/powerpolicy/server/tests/SilentModeHandlerTest.cpp
@@ -46,20 +46,6 @@
 constexpr int kMaxPollingAttempts = 5;
 constexpr std::chrono::microseconds kPollingDelayUs = 50ms;
 
-bool waitForSilentMode(SilentModeHandler* handler, bool expectedSilentMode) {
-    int count = 0;
-    while (true) {
-        if (handler->isSilentMode() == expectedSilentMode) {
-            return true;
-        }
-        if (count++; count == kMaxPollingAttempts) {
-            break;
-        }
-        usleep(kPollingDelayUs.count());
-    }
-    return false;
-}
-
 }  // namespace
 
 namespace internal {
@@ -124,23 +110,6 @@
     sp<MockCarPowerPolicyServer> carPowerPolicyServer;
 };
 
-TEST_F(SilentModeHandlerTest, TestSilentModeHwStateMonitoring) {
-    SilentModeHandler handler(carPowerPolicyServer.get());
-    internal::SilentModeHandlerPeer handlerPeer(&handler);
-    handlerPeer.injectBootReason(kBootReasonNormal);
-    handlerPeer.init();
-
-    handlerPeer.updateSilentModeHwState(/*isSilent=*/true);
-
-    ASSERT_TRUE(waitForSilentMode(&handler, /*expectedSilentMode=*/true))
-            << "It should be silent mode when HW state is on";
-
-    handlerPeer.updateSilentModeHwState(/*isSilent=*/false);
-
-    ASSERT_TRUE(waitForSilentMode(&handler, /*expectedSilentMode=*/false))
-            << "It should be non-silent mode when HW state is off";
-}
-
 TEST_F(SilentModeHandlerTest, TestRebootForForcedSilentMode) {
     SilentModeHandler handler(carPowerPolicyServer.get());
     internal::SilentModeHandlerPeer handlerPeer(&handler);
diff --git a/cpp/security/vehicle_binding_util/Android.bp b/cpp/security/vehicle_binding_util/Android.bp
index 3f235b1..bea8afa 100644
--- a/cpp/security/vehicle_binding_util/Android.bp
+++ b/cpp/security/vehicle_binding_util/Android.bp
@@ -52,23 +52,6 @@
     ],
 }
 
-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: [
diff --git a/cpp/security/vehicle_binding_util/tests/Android.bp b/cpp/security/vehicle_binding_util/tests/Android.bp
new file mode 100644
index 0000000..69a61cf
--- /dev/null
+++ b/cpp/security/vehicle_binding_util/tests/Android.bp
@@ -0,0 +1,67 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "libvehicle_binding_util_test",
+    defaults: [
+        "vehicle_binding_util_defaults",
+    ],
+    test_suites: ["general-tests"],
+    srcs: [
+        "VehicleBindingUtilTests.cpp",
+    ],
+    static_libs: [
+        "libbase",
+        "libgmock",
+        "libgtest",
+        "libvehicle_binding_util",
+    ],
+}
+
+cc_test {
+    name: "vehicle_binding_integration_test",
+    test_suites: [
+        "device-tests",
+    ],
+    require_root: true,
+    defaults: ["vehicle_binding_util_defaults"],
+    tidy: false,
+    srcs: [
+        "VehicleBindingIntegrationTedt.cpp"
+    ],
+    shared_libs: [
+        "android.hardware.automotive.vehicle@2.0",
+        "libbase",
+        "libbinder",
+        "libhidlbase",
+        "libutils",
+    ],
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+    sanitize: {
+        address: false,
+        recover: [ "all" ],
+    },
+}
diff --git a/cpp/security/vehicle_binding_util/tests/VehicleBindingIntegrationTedt.cpp b/cpp/security/vehicle_binding_util/tests/VehicleBindingIntegrationTedt.cpp
new file mode 100644
index 0000000..50f358e
--- /dev/null
+++ b/cpp/security/vehicle_binding_util/tests/VehicleBindingIntegrationTedt.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <android-base/properties.h>
+#include <android/hardware/automotive/vehicle/2.0/IVehicle.h>
+#include <gtest/gtest.h>
+
+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::VehiclePropConfig;
+using android::hardware::automotive::vehicle::V2_0::VehicleProperty;
+
+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;
+}
+
+// Verify that vold got the binding seed if VHAL reports a seed
+TEST(VehicleBindingIntegrationTedt, TestVehicleBindingSeedSet) {
+    std::string expected_value = "1";
+    if (!isSeedVhalPropertySupported(IVehicle::getService())) {
+        GTEST_LOG_(INFO) << "Device does not support vehicle binding seed "
+                            "(STORAGE_ENCRYPTION_BINDING_SEED).";
+        expected_value = "";
+    }
+
+    ASSERT_EQ(expected_value, android::base::GetProperty("vold.storage_seed_bound", ""));
+}
+
+}  // namespace
+}  // namespace security
+}  // namespace automotive
+}  // namespace android
diff --git a/cpp/watchdog/server/Android.bp b/cpp/watchdog/server/Android.bp
index 8ce4c04..c8d07b4 100644
--- a/cpp/watchdog/server/Android.bp
+++ b/cpp/watchdog/server/Android.bp
@@ -85,9 +85,10 @@
         "src/LooperWrapper.cpp",
         "src/OveruseConfigurationXmlHelper.cpp",
         "src/ProcDiskStats.cpp",
-        "src/ProcPidStat.cpp",
         "src/ProcStat.cpp",
-        "src/UidIoStats.cpp",
+        "src/UidIoStatsCollector.cpp",
+        "src/UidProcStatsCollector.cpp",
+        "src/UidStatsCollector.cpp",
     ],
     whole_static_libs: [
         "libwatchdog_properties",
@@ -120,11 +121,13 @@
         "tests/OveruseConfigurationTestUtils.cpp",
         "tests/OveruseConfigurationXmlHelperTest.cpp",
         "tests/PackageInfoResolverTest.cpp",
+        "tests/PackageInfoTestUtils.cpp",
         "tests/ProcDiskStatsTest.cpp",
         "tests/ProcPidDir.cpp",
-        "tests/ProcPidStatTest.cpp",
         "tests/ProcStatTest.cpp",
-        "tests/UidIoStatsTest.cpp",
+        "tests/UidIoStatsCollectorTest.cpp",
+        "tests/UidProcStatsCollectorTest.cpp",
+        "tests/UidStatsCollectorTest.cpp",
         "tests/WatchdogBinderMediatorTest.cpp",
         "tests/WatchdogInternalHandlerTest.cpp",
         "tests/WatchdogPerfServiceTest.cpp",
diff --git a/cpp/watchdog/server/src/IoOveruseConfigs.cpp b/cpp/watchdog/server/src/IoOveruseConfigs.cpp
index 2ff118a..0237e7d 100644
--- a/cpp/watchdog/server/src/IoOveruseConfigs.cpp
+++ b/cpp/watchdog/server/src/IoOveruseConfigs.cpp
@@ -580,7 +580,8 @@
     return {};
 }
 
-void IoOveruseConfigs::get(std::vector<ResourceOveruseConfiguration>* resourceOveruseConfigs) {
+void IoOveruseConfigs::get(
+        std::vector<ResourceOveruseConfiguration>* resourceOveruseConfigs) const {
     auto systemConfig = get(mSystemConfig, kSystemComponentUpdatableConfigs);
     if (systemConfig.has_value()) {
         systemConfig->componentType = ComponentType::SYSTEM;
@@ -601,7 +602,8 @@
 }
 
 std::optional<ResourceOveruseConfiguration> IoOveruseConfigs::get(
-        const ComponentSpecificConfig& componentSpecificConfig, const int32_t componentFilter) {
+        const ComponentSpecificConfig& componentSpecificConfig,
+        const int32_t componentFilter) const {
     if (componentSpecificConfig.mGeneric.name == kDefaultThresholdName) {
         return {};
     }
diff --git a/cpp/watchdog/server/src/IoOveruseConfigs.h b/cpp/watchdog/server/src/IoOveruseConfigs.h
index 53ee9dd..7ae758b 100644
--- a/cpp/watchdog/server/src/IoOveruseConfigs.h
+++ b/cpp/watchdog/server/src/IoOveruseConfigs.h
@@ -68,7 +68,7 @@
 
 }  // namespace internal
 
-/*
+/**
  * Defines the methods that the I/O overuse configs module should implement.
  */
 class IIoOveruseConfigs : public android::RefBase {
@@ -80,18 +80,18 @@
     // Returns the existing configurations.
     virtual void get(
             std::vector<android::automotive::watchdog::internal::ResourceOveruseConfiguration>*
-                    resourceOveruseConfigs) = 0;
+                    resourceOveruseConfigs) const = 0;
 
     // Writes the cached configs to disk.
     virtual android::base::Result<void> writeToDisk() = 0;
 
-    /*
+    /**
      * Returns the list of vendor package prefixes. Any pre-installed package matching one of these
      * prefixes should be classified as a vendor package.
      */
     virtual const std::unordered_set<std::string>& vendorPackagePrefixes() = 0;
 
-    /*
+    /**
      * Returns the package names to application category mappings.
      */
     virtual const std::unordered_map<
@@ -129,7 +129,7 @@
 
 class IoOveruseConfigs;
 
-/*
+/**
  * ComponentSpecificConfig represents the I/O overuse config defined per component.
  */
 class ComponentSpecificConfig final {
@@ -141,32 +141,32 @@
         mSafeToKillPackages.clear();
     }
 
-    /*
+    /**
      * Updates |mPerPackageThresholds|.
      */
     android::base::Result<void> updatePerPackageThresholds(
             const std::vector<android::automotive::watchdog::internal::PerStateIoOveruseThreshold>&
                     thresholds,
             const std::function<void(const std::string&)>& maybeAppendVendorPackagePrefixes);
-    /*
+    /**
      * Updates |mSafeToKillPackages|.
      */
     android::base::Result<void> updateSafeToKillPackages(
             const std::vector<std::string>& packages,
             const std::function<void(const std::string&)>& maybeAppendVendorPackagePrefixes);
 
-    /*
+    /**
      * I/O overuse configurations for all packages under the component that are not covered by
      * |mPerPackageThresholds| or |IoOveruseConfigs.mPerCategoryThresholds|.
      */
     android::automotive::watchdog::internal::PerStateIoOveruseThreshold mGeneric;
-    /*
+    /**
      * I/O overuse configurations for specific packages under the component.
      */
     std::unordered_map<std::string,
                        android::automotive::watchdog::internal::PerStateIoOveruseThreshold>
             mPerPackageThresholds;
-    /*
+    /**
      * List of safe to kill packages under the component in the event of I/O overuse.
      */
     std::unordered_set<std::string> mSafeToKillPackages;
@@ -175,7 +175,7 @@
     friend class IoOveruseConfigs;
 };
 
-/*
+/**
  * IoOveruseConfigs represents the I/O overuse configuration defined by system and vendor
  * applications. This class is not thread safe for performance purposes. The caller is responsible
  * for calling the methods in a thread safe manner.
@@ -194,7 +194,7 @@
                    configs) override;
 
     void get(std::vector<android::automotive::watchdog::internal::ResourceOveruseConfiguration>*
-                     resourceOveruseConfigs) override;
+                     resourceOveruseConfigs) const override;
 
     android::base::Result<void> writeToDisk();
 
@@ -245,7 +245,8 @@
                     thresholds);
 
     std::optional<android::automotive::watchdog::internal::ResourceOveruseConfiguration> get(
-            const ComponentSpecificConfig& componentSpecificConfig, const int32_t componentFilter);
+            const ComponentSpecificConfig& componentSpecificConfig,
+            const int32_t componentFilter) const;
 
     // System component specific configuration.
     ComponentSpecificConfig mSystemConfig;
diff --git a/cpp/watchdog/server/src/IoOveruseMonitor.cpp b/cpp/watchdog/server/src/IoOveruseMonitor.cpp
index b97dd98..23e14ee 100644
--- a/cpp/watchdog/server/src/IoOveruseMonitor.cpp
+++ b/cpp/watchdog/server/src/IoOveruseMonitor.cpp
@@ -170,10 +170,11 @@
 }
 
 Result<void> IoOveruseMonitor::onPeriodicCollection(
-        time_t time, SystemState systemState, const android::wp<UidIoStats>& uidIoStats,
-        [[maybe_unused]] const android::wp<ProcStat>& procStat,
-        [[maybe_unused]] const android::wp<ProcPidStat>& procPidStat) {
-    if (uidIoStats == nullptr) {
+        time_t time, SystemState systemState,
+        const android::wp<UidStatsCollectorInterface>& uidStatsCollector,
+        [[maybe_unused]] const android::wp<ProcStat>& procStat) {
+    android::sp<UidStatsCollectorInterface> uidStatsCollectorSp = uidStatsCollector.promote();
+    if (uidStatsCollectorSp == nullptr) {
         return Error() << "Per-UID I/O stats collector must not be null";
     }
 
@@ -191,36 +192,24 @@
     mLastUserPackageIoMonitorTime = time;
     const auto [startTime, durationInSeconds] = calculateStartAndDuration(curGmt);
 
-    auto perUidIoUsage = uidIoStats.promote()->deltaStats();
-    /*
-     * TODO(b/185849350): Maybe move the packageInfo fetching logic into UidIoStats module.
-     *  This will also help avoid fetching package names in IoPerfCollection module.
-     */
-    std::vector<uid_t> seenUids;
-    for (auto it = perUidIoUsage.begin(); it != perUidIoUsage.end();) {
-        /*
-         * UidIoStats::deltaStats returns entries with zero write bytes because other metrics
-         * in these entries are non-zero.
-         */
-        if (it->second.ios.sumWriteBytes() == 0) {
-            it = perUidIoUsage.erase(it);
-            continue;
-        }
-        seenUids.push_back(it->first);
-        ++it;
-    }
-    if (perUidIoUsage.empty()) {
+    auto uidStats = uidStatsCollectorSp->deltaStats();
+    if (uidStats.empty()) {
         return {};
     }
-    const auto packageInfosByUid = mPackageInfoResolver->getPackageInfosForUids(seenUids);
     std::unordered_map<uid_t, IoOveruseStats> overusingNativeStats;
     bool isGarageModeActive = systemState == SystemState::GARAGE_MODE;
-    for (const auto& [uid, uidIoStats] : perUidIoUsage) {
-        const auto& packageInfo = packageInfosByUid.find(uid);
-        if (packageInfo == packageInfosByUid.end()) {
+    for (const auto& curUidStats : uidStats) {
+        if (curUidStats.ioStats.sumWriteBytes() == 0 || !curUidStats.hasPackageInfo()) {
+            /* 1. Ignore UIDs with zero written bytes since the last collection because they are
+             * either already accounted for or no writes made since system start.
+             *
+             * 2. UID stats without package info is not useful because the stats isn't attributed to
+             * any package/service.
+             */
             continue;
         }
-        UserPackageIoUsage curUsage(packageInfo->second, uidIoStats.ios, isGarageModeActive);
+        UserPackageIoUsage curUsage(curUidStats.packageInfo, curUidStats.ioStats,
+                                    isGarageModeActive);
         UserPackageIoUsage* dailyIoUsage;
         if (auto cachedUsage = mUserPackageDailyIoUsageById.find(curUsage.id());
             cachedUsage != mUserPackageDailyIoUsageById.end()) {
@@ -235,7 +224,7 @@
         const auto threshold = mIoOveruseConfigs->fetchThreshold(dailyIoUsage->packageInfo);
 
         PackageIoOveruseStats stats;
-        stats.uid = uid;
+        stats.uid = curUidStats.packageInfo.packageIdentifier.uid;
         stats.shouldNotify = false;
         stats.ioOveruseStats.startTime = startTime;
         stats.ioOveruseStats.durationInSeconds = durationInSeconds;
@@ -272,7 +261,7 @@
              */
             stats.shouldNotify = true;
             if (dailyIoUsage->packageInfo.uidType == UidType::NATIVE) {
-                overusingNativeStats[uid] = stats.ioOveruseStats;
+                overusingNativeStats[stats.uid] = stats.ioOveruseStats;
             }
             shouldSyncWatchdogService = true;
         } else if (dailyIoUsage->packageInfo.uidType != UidType::NATIVE &&
@@ -320,10 +309,10 @@
 Result<void> IoOveruseMonitor::onCustomCollection(
         time_t time, SystemState systemState,
         [[maybe_unused]] const std::unordered_set<std::string>& filterPackages,
-        const android::wp<UidIoStats>& uidIoStats, const android::wp<ProcStat>& procStat,
-        const android::wp<ProcPidStat>& procPidStat) {
+        const android::wp<UidStatsCollectorInterface>& uidStatsCollector,
+        const android::wp<ProcStat>& procStat) {
     // Nothing special for custom collection.
-    return onPeriodicCollection(time, systemState, uidIoStats, procStat, procPidStat);
+    return onPeriodicCollection(time, systemState, uidStatsCollector, procStat);
 }
 
 Result<void> IoOveruseMonitor::onPeriodicMonitor(
@@ -380,13 +369,13 @@
     return {};
 }
 
-Result<void> IoOveruseMonitor::onDump([[maybe_unused]] int fd) {
+Result<void> IoOveruseMonitor::onDump([[maybe_unused]] int fd) const {
     // TODO(b/183436216): Dump the list of killed/disabled packages. Dump the list of packages that
     //  exceed xx% of their threshold.
     return {};
 }
 
-bool IoOveruseMonitor::dumpHelpText(int fd) {
+bool IoOveruseMonitor::dumpHelpText(int fd) const {
     return WriteStringToFd(StringPrintf(kHelpText, name().c_str(), kResetResourceOveruseStatsFlag),
                            fd);
 }
@@ -438,7 +427,7 @@
 }
 
 Result<void> IoOveruseMonitor::getResourceOveruseConfigurations(
-        std::vector<ResourceOveruseConfiguration>* configs) {
+        std::vector<ResourceOveruseConfiguration>* configs) const {
     std::shared_lock readLock(mRwMutex);
     if (!isInitializedLocked()) {
         return Error(Status::EX_ILLEGAL_STATE) << name() << " is not initialized";
@@ -496,7 +485,7 @@
     return {};
 }
 
-Result<void> IoOveruseMonitor::getIoOveruseStats(IoOveruseStats* ioOveruseStats) {
+Result<void> IoOveruseMonitor::getIoOveruseStats(IoOveruseStats* ioOveruseStats) const {
     if (!isInitialized()) {
         return Error(Status::EX_ILLEGAL_STATE) << "I/O overuse monitor is not initialized";
     }
@@ -580,14 +569,14 @@
 }
 
 IoOveruseMonitor::UserPackageIoUsage::UserPackageIoUsage(const PackageInfo& pkgInfo,
-                                                         const IoUsage& ioUsage,
+                                                         const UidIoStats& uidIoStats,
                                                          const bool isGarageModeActive) {
     packageInfo = pkgInfo;
     if (isGarageModeActive) {
-        writtenBytes.garageModeBytes = ioUsage.sumWriteBytes();
+        writtenBytes.garageModeBytes = uidIoStats.sumWriteBytes();
     } else {
-        writtenBytes.foregroundBytes = ioUsage.metrics[WRITE_BYTES][FOREGROUND];
-        writtenBytes.backgroundBytes = ioUsage.metrics[WRITE_BYTES][BACKGROUND];
+        writtenBytes.foregroundBytes = uidIoStats.metrics[WRITE_BYTES][FOREGROUND];
+        writtenBytes.backgroundBytes = uidIoStats.metrics[WRITE_BYTES][BACKGROUND];
     }
 }
 
diff --git a/cpp/watchdog/server/src/IoOveruseMonitor.h b/cpp/watchdog/server/src/IoOveruseMonitor.h
index 27e179f..df7ff60 100644
--- a/cpp/watchdog/server/src/IoOveruseMonitor.h
+++ b/cpp/watchdog/server/src/IoOveruseMonitor.h
@@ -19,9 +19,8 @@
 
 #include "IoOveruseConfigs.h"
 #include "PackageInfoResolver.h"
-#include "ProcPidStat.h"
 #include "ProcStat.h"
-#include "UidIoStats.h"
+#include "UidStatsCollector.h"
 #include "WatchdogPerfService.h"
 
 #include <android-base/result.h>
@@ -62,17 +61,17 @@
 // Used only in tests.
 std::tuple<int64_t, int64_t> calculateStartAndDuration(const time_t& currentTime);
 
-/*
+/**
  * IIoOveruseMonitor interface defines the methods that the I/O overuse monitoring module
  * should implement.
  */
 class IIoOveruseMonitor : virtual public IDataProcessorInterface {
 public:
     // Returns whether or not the monitor is initialized.
-    virtual bool isInitialized() = 0;
+    virtual bool isInitialized() const = 0;
 
     // Dumps the help text.
-    virtual bool dumpHelpText(int fd) = 0;
+    virtual bool dumpHelpText(int fd) const = 0;
 
     // Below API is from internal/ICarWatchdog.aidl. Please refer to the AIDL for description.
     virtual android::base::Result<void> updateResourceOveruseConfigurations(
@@ -81,7 +80,7 @@
                     configs) = 0;
     virtual android::base::Result<void> getResourceOveruseConfigurations(
             std::vector<android::automotive::watchdog::internal::ResourceOveruseConfiguration>*
-                    configs) = 0;
+                    configs) const = 0;
     virtual android::base::Result<void> actionTakenOnIoOveruse(
             const std::vector<
                     android::automotive::watchdog::internal::PackageResourceOveruseAction>&
@@ -94,7 +93,7 @@
     virtual android::base::Result<void> removeIoOveruseListener(
             const sp<IResourceOveruseListener>& listener) = 0;
 
-    virtual android::base::Result<void> getIoOveruseStats(IoOveruseStats* ioOveruseStats) = 0;
+    virtual android::base::Result<void> getIoOveruseStats(IoOveruseStats* ioOveruseStats) const = 0;
 
     virtual android::base::Result<void> resetIoOveruseStats(
             const std::vector<std::string>& packageNames) = 0;
@@ -106,42 +105,42 @@
 
     ~IoOveruseMonitor() { terminate(); }
 
-    bool isInitialized() {
+    bool isInitialized() const override {
         std::shared_lock readLock(mRwMutex);
         return isInitializedLocked();
     }
 
     // Below methods implement IDataProcessorInterface.
-    std::string name() { return "IoOveruseMonitor"; }
+    std::string name() const override { return "IoOveruseMonitor"; }
     friend std::ostream& operator<<(std::ostream& os, const IoOveruseMonitor& monitor);
     android::base::Result<void> onBoottimeCollection(
-            time_t /*time*/, const android::wp<UidIoStats>& /*uidIoStats*/,
-            const android::wp<ProcStat>& /*procStat*/,
-            const android::wp<ProcPidStat>& /*procPidStat*/) {
+            [[maybe_unused]] time_t time,
+            [[maybe_unused]] const android::wp<UidStatsCollectorInterface>& uidStatsCollector,
+            [[maybe_unused]] const android::wp<ProcStat>& procStat) override {
         // No I/O overuse monitoring during boot-time.
         return {};
     }
 
-    android::base::Result<void> onPeriodicCollection(time_t time, SystemState systemState,
-                                                     const android::wp<UidIoStats>& uidIoStats,
-                                                     const android::wp<ProcStat>& procStat,
-                                                     const android::wp<ProcPidStat>& procPidStat);
+    android::base::Result<void> onPeriodicCollection(
+            time_t time, SystemState systemState,
+            const android::wp<UidStatsCollectorInterface>& uidStatsCollector,
+            const android::wp<ProcStat>& procStat) override;
 
     android::base::Result<void> onCustomCollection(
             time_t time, SystemState systemState,
             const std::unordered_set<std::string>& filterPackages,
-            const android::wp<UidIoStats>& uidIoStats, const android::wp<ProcStat>& procStat,
-            const android::wp<ProcPidStat>& procPidStat);
+            const android::wp<UidStatsCollectorInterface>& uidStatsCollector,
+            const android::wp<ProcStat>& procStat) override;
 
     android::base::Result<void> onPeriodicMonitor(
             time_t time, const android::wp<IProcDiskStatsInterface>& procDiskStats,
-            const std::function<void()>& alertHandler);
+            const std::function<void()>& alertHandler) override;
 
-    android::base::Result<void> onDump(int fd);
+    android::base::Result<void> onDump(int fd) const override;
 
-    bool dumpHelpText(int fd);
+    bool dumpHelpText(int fd) const override;
 
-    android::base::Result<void> onCustomCollectionDump(int /*fd*/) {
+    android::base::Result<void> onCustomCollectionDump([[maybe_unused]] int fd) override {
         // No special processing for custom collection. Thus no custom collection dump.
         return {};
     }
@@ -149,26 +148,28 @@
     // Below methods implement AIDL interfaces.
     android::base::Result<void> updateResourceOveruseConfigurations(
             const std::vector<
-                    android::automotive::watchdog::internal::ResourceOveruseConfiguration>&
-                    configs);
+                    android::automotive::watchdog::internal::ResourceOveruseConfiguration>& configs)
+            override;
 
     android::base::Result<void> getResourceOveruseConfigurations(
             std::vector<android::automotive::watchdog::internal::ResourceOveruseConfiguration>*
-                    configs);
+                    configs) const override;
 
     android::base::Result<void> actionTakenOnIoOveruse(
             const std::vector<
-                    android::automotive::watchdog::internal::PackageResourceOveruseAction>&
-                    actions);
+                    android::automotive::watchdog::internal::PackageResourceOveruseAction>& actions)
+            override;
 
-    android::base::Result<void> addIoOveruseListener(const sp<IResourceOveruseListener>& listener);
+    android::base::Result<void> addIoOveruseListener(
+            const sp<IResourceOveruseListener>& listener) override;
 
     android::base::Result<void> removeIoOveruseListener(
-            const sp<IResourceOveruseListener>& listener);
+            const sp<IResourceOveruseListener>& listener) override;
 
-    android::base::Result<void> getIoOveruseStats(IoOveruseStats* ioOveruseStats);
+    android::base::Result<void> getIoOveruseStats(IoOveruseStats* ioOveruseStats) const override;
 
-    android::base::Result<void> resetIoOveruseStats(const std::vector<std::string>& packageName);
+    android::base::Result<void> resetIoOveruseStats(
+            const std::vector<std::string>& packageName) override;
 
 protected:
     android::base::Result<void> init();
@@ -183,7 +184,7 @@
 
     struct UserPackageIoUsage {
         UserPackageIoUsage(const android::automotive::watchdog::internal::PackageInfo& packageInfo,
-                           const IoUsage& IoUsage, const bool isGarageModeActive);
+                           const UidIoStats& uidIoStats, const bool isGarageModeActive);
         android::automotive::watchdog::internal::PackageInfo packageInfo = {};
         PerStateBytes writtenBytes = {};
         PerStateBytes forgivenWriteBytes = {};
@@ -211,7 +212,7 @@
     };
 
 private:
-    bool isInitializedLocked() { return mIoOveruseConfigs != nullptr; }
+    bool isInitializedLocked() const { return mIoOveruseConfigs != nullptr; }
 
     void notifyNativePackagesLocked(const std::unordered_map<uid_t, IoOveruseStats>& statsByUid);
 
@@ -221,7 +222,7 @@
     using Processor = std::function<void(ListenersByUidMap&, ListenersByUidMap::const_iterator)>;
     bool findListenerAndProcessLocked(const sp<IBinder>& binder, const Processor& processor);
 
-    /*
+    /**
      * Writes in-memory configs to disk asynchronously if configs are not written after latest
      * update.
      */
@@ -239,7 +240,7 @@
     // Summary of configs available for all the components and system-wide overuse alert thresholds.
     sp<IIoOveruseConfigs> mIoOveruseConfigs GUARDED_BY(mRwMutex);
 
-    /*
+    /**
      * Delta of system-wide written kib across all disks from the last |mPeriodicMonitorBufferSize|
      * polls along with the polling duration.
      */
diff --git a/cpp/watchdog/server/src/IoPerfCollection.cpp b/cpp/watchdog/server/src/IoPerfCollection.cpp
index 5537caf..d9f5e02 100644
--- a/cpp/watchdog/server/src/IoPerfCollection.cpp
+++ b/cpp/watchdog/server/src/IoPerfCollection.cpp
@@ -45,229 +45,281 @@
 
 namespace {
 
-const int32_t kDefaultTopNStatsPerCategory = 10;
-const int32_t kDefaultTopNStatsPerSubcategory = 5;
+constexpr int32_t kDefaultTopNStatsPerCategory = 10;
+constexpr int32_t kDefaultTopNStatsPerSubcategory = 5;
+constexpr const char kBootTimeCollectionTitle[] = "%s\nBoot-time I/O performance report:\n%s\n";
+constexpr const char kPeriodicCollectionTitle[] =
+        "%s\nLast N minutes I/O performance report:\n%s\n";
+constexpr const char kCustomCollectionTitle[] = "%s\nCustom I/O performance data report:\n%s\n";
+constexpr const char kCollectionTitle[] =
+        "Collection duration: %.f seconds\nNumber of collections: %zu\n";
+constexpr const char kRecordTitle[] = "\nCollection %zu: <%s>\n%s\n%s";
+constexpr const char kIoReadsTitle[] = "\nTop N Reads:\n%s\n";
+constexpr const char kIoWritesTitle[] = "\nTop N Writes:\n%s\n";
+constexpr const char kIoStatsHeader[] =
+        "Android User ID, Package Name, Foreground Bytes, Foreground Bytes %%, Foreground Fsync, "
+        "Foreground Fsync %%, Background Bytes, Background Bytes %%, Background Fsync, "
+        "Background Fsync %%\n";
+constexpr const char kIoBlockedTitle[] = "\nTop N I/O waiting UIDs:\n%s\n";
+constexpr const char kIoBlockedHeader[] =
+        "Android User ID, Package Name, Number of owned tasks waiting for I/O, Percentage of owned "
+        "tasks waiting for I/O\n\tCommand, Number of I/O waiting tasks, Percentage of UID's tasks "
+        "waiting for I/O\n";
+constexpr const char kMajorPageFaultsTitle[] = "\nTop N major page faults:\n%s\n";
+constexpr const char kMajorFaultsHeader[] =
+        "Android User ID, Package Name, Number of major page faults, Percentage of total major "
+        "page faults\n\tCommand, Number of major page faults, Percentage of UID's major page "
+        "faults\n";
+constexpr const char kMajorFaultsSummary[] =
+        "Number of major page faults since last collection: %" PRIu64 "\n"
+        "Percentage of change in major page faults since last collection: %.2f%%\n";
 
 double percentage(uint64_t numer, uint64_t denom) {
     return denom == 0 ? 0.0 : (static_cast<double>(numer) / static_cast<double>(denom)) * 100.0;
 }
 
-struct UidProcessStats {
-    struct ProcessInfo {
-        std::string comm = "";
-        uint64_t count = 0;
+void addUidIoStats(const int64_t entry[][UID_STATES], int64_t total[][UID_STATES]) {
+    const auto sum = [](int64_t lhs, int64_t rhs) -> int64_t {
+        return std::numeric_limits<int64_t>::max() - lhs > rhs
+                ? lhs + rhs
+                : std::numeric_limits<int64_t>::max();
     };
-    uint64_t uid = 0;
-    uint32_t ioBlockedTasksCnt = 0;
-    uint32_t totalTasksCnt = 0;
-    uint64_t majorFaults = 0;
-    std::vector<ProcessInfo> topNIoBlockedProcesses = {};
-    std::vector<ProcessInfo> topNMajorFaultProcesses = {};
-};
-
-std::unique_ptr<std::unordered_map<uid_t, UidProcessStats>> getUidProcessStats(
-        const std::vector<ProcessStats>& processStats, int topNStatsPerSubCategory) {
-    std::unique_ptr<std::unordered_map<uid_t, UidProcessStats>> uidProcessStats(
-            new std::unordered_map<uid_t, UidProcessStats>());
-    for (const auto& stats : processStats) {
-        if (stats.uid < 0) {
-            continue;
-        }
-        uid_t uid = static_cast<uid_t>(stats.uid);
-        if (uidProcessStats->find(uid) == uidProcessStats->end()) {
-            (*uidProcessStats)[uid] = UidProcessStats{
-                    .uid = uid,
-                    .topNIoBlockedProcesses = std::vector<
-                            UidProcessStats::ProcessInfo>(topNStatsPerSubCategory,
-                                                          UidProcessStats::ProcessInfo{}),
-                    .topNMajorFaultProcesses = std::vector<
-                            UidProcessStats::ProcessInfo>(topNStatsPerSubCategory,
-                                                          UidProcessStats::ProcessInfo{}),
-            };
-        }
-        auto& curUidProcessStats = (*uidProcessStats)[uid];
-        // Top-level process stats has the aggregated major page faults count and this should be
-        // persistent across thread creation/termination. Thus use the value from this field.
-        curUidProcessStats.majorFaults += stats.process.majorFaults;
-        curUidProcessStats.totalTasksCnt += stats.threads.size();
-        // The process state is the same as the main thread state. Thus to avoid double counting
-        // ignore the process state.
-        uint32_t ioBlockedTasksCnt = 0;
-        for (const auto& threadStat : stats.threads) {
-            ioBlockedTasksCnt += threadStat.second.state == "D" ? 1 : 0;
-        }
-        curUidProcessStats.ioBlockedTasksCnt += ioBlockedTasksCnt;
-        for (auto it = curUidProcessStats.topNIoBlockedProcesses.begin();
-             it != curUidProcessStats.topNIoBlockedProcesses.end(); ++it) {
-            if (it->count < ioBlockedTasksCnt) {
-                curUidProcessStats.topNIoBlockedProcesses
-                        .emplace(it,
-                                 UidProcessStats::ProcessInfo{
-                                         .comm = stats.process.comm,
-                                         .count = ioBlockedTasksCnt,
-                                 });
-                curUidProcessStats.topNIoBlockedProcesses.pop_back();
-                break;
-            }
-        }
-        for (auto it = curUidProcessStats.topNMajorFaultProcesses.begin();
-             it != curUidProcessStats.topNMajorFaultProcesses.end(); ++it) {
-            if (it->count < stats.process.majorFaults) {
-                curUidProcessStats.topNMajorFaultProcesses
-                        .emplace(it,
-                                 UidProcessStats::ProcessInfo{
-                                         .comm = stats.process.comm,
-                                         .count = stats.process.majorFaults,
-                                 });
-                curUidProcessStats.topNMajorFaultProcesses.pop_back();
-                break;
-            }
-        }
-    }
-    return uidProcessStats;
+    total[READ_BYTES][FOREGROUND] =
+            sum(total[READ_BYTES][FOREGROUND], entry[READ_BYTES][FOREGROUND]);
+    total[READ_BYTES][BACKGROUND] =
+            sum(total[READ_BYTES][BACKGROUND], entry[READ_BYTES][BACKGROUND]);
+    total[WRITE_BYTES][FOREGROUND] =
+            sum(total[WRITE_BYTES][FOREGROUND], entry[WRITE_BYTES][FOREGROUND]);
+    total[WRITE_BYTES][BACKGROUND] =
+            sum(total[WRITE_BYTES][BACKGROUND], entry[WRITE_BYTES][BACKGROUND]);
+    total[FSYNC_COUNT][FOREGROUND] =
+            sum(total[FSYNC_COUNT][FOREGROUND], entry[FSYNC_COUNT][FOREGROUND]);
+    total[FSYNC_COUNT][BACKGROUND] =
+            sum(total[FSYNC_COUNT][BACKGROUND], entry[FSYNC_COUNT][BACKGROUND]);
+    return;
 }
 
-Result<void> checkDataCollectors(const wp<UidIoStats>& uidIoStats, const wp<ProcStat>& procStat,
-                                 const wp<ProcPidStat>& procPidStat) {
-    if (uidIoStats != nullptr && procStat != nullptr && procPidStat != nullptr) {
+UserPackageStats toUserPackageStats(MetricType metricType, const UidStats& uidStats) {
+    const UidIoStats& ioStats = uidStats.ioStats;
+    return UserPackageStats{
+            .uid = uidStats.uid(),
+            .genericPackageName = uidStats.genericPackageName(),
+            .stats = UserPackageStats::
+                    IoStats{.bytes = {ioStats.metrics[metricType][UidState::FOREGROUND],
+                                      ioStats.metrics[metricType][UidState::BACKGROUND]},
+                            .fsync =
+                                    {ioStats.metrics[MetricType::FSYNC_COUNT][UidState::FOREGROUND],
+                                     ioStats.metrics[MetricType::FSYNC_COUNT]
+                                                    [UidState::BACKGROUND]}},
+    };
+}
+
+void cacheTopNIoStats(MetricType metricType, const UidStats& uidStats,
+                      std::vector<UserPackageStats>* topNIoStats) {
+    if (metricType != MetricType::READ_BYTES && metricType != MetricType::WRITE_BYTES) {
+        return;
+    }
+    int64_t totalBytes = metricType == MetricType::READ_BYTES ? uidStats.ioStats.sumReadBytes()
+                                                              : uidStats.ioStats.sumWriteBytes();
+    if (totalBytes == 0) {
+        return;
+    }
+    for (auto it = topNIoStats->begin(); it != topNIoStats->end(); ++it) {
+        if (const auto* ioStats = std::get_if<UserPackageStats::IoStats>(&it->stats);
+            ioStats == nullptr || totalBytes > ioStats->totalBytes()) {
+            topNIoStats->emplace(it, toUserPackageStats(metricType, uidStats));
+            topNIoStats->pop_back();
+            break;
+        }
+    }
+    return;
+}
+
+enum ProcStatType {
+    IO_BLOCKED_TASKS_COUNT = 0,
+    MAJOR_FAULTS,
+    PROC_STAT_TYPES,
+};
+
+bool cacheTopNProcessStats(ProcStatType procStatType, const ProcessStats& processStats,
+                           std::vector<UserPackageStats::ProcStats::ProcessCount>* topNProcesses) {
+    uint64_t count = procStatType == IO_BLOCKED_TASKS_COUNT ? processStats.ioBlockedTasksCount
+                                                            : processStats.totalMajorFaults;
+    if (count == 0) {
+        return false;
+    }
+    for (auto it = topNProcesses->begin(); it != topNProcesses->end(); ++it) {
+        if (count > it->count) {
+            topNProcesses->emplace(it,
+                                   UserPackageStats::ProcStats::ProcessCount{
+                                           .comm = processStats.comm,
+                                           .count = count,
+                                   });
+            topNProcesses->pop_back();
+            return true;
+        }
+    }
+    return false;
+}
+
+UserPackageStats toUserPackageStats(ProcStatType procStatType, const UidStats& uidStats,
+                                    int topNProcessCount) {
+    uint64_t count = procStatType == IO_BLOCKED_TASKS_COUNT ? uidStats.procStats.ioBlockedTasksCount
+                                                            : uidStats.procStats.totalMajorFaults;
+    UserPackageStats userPackageStats = {
+            .uid = uidStats.uid(),
+            .genericPackageName = uidStats.genericPackageName(),
+            .stats = UserPackageStats::ProcStats{.count = count},
+    };
+    auto& procStats = std::get<UserPackageStats::ProcStats>(userPackageStats.stats);
+    procStats.topNProcesses.resize(topNProcessCount);
+    int cachedProcessCount = 0;
+    for (const auto& [_, processStats] : uidStats.procStats.processStatsByPid) {
+        if (cacheTopNProcessStats(procStatType, processStats, &procStats.topNProcesses)) {
+            ++cachedProcessCount;
+        }
+    }
+    if (cachedProcessCount < topNProcessCount) {
+        procStats.topNProcesses.erase(procStats.topNProcesses.begin() + cachedProcessCount,
+                                      procStats.topNProcesses.end());
+    }
+    return userPackageStats;
+}
+
+bool cacheTopNProcStats(ProcStatType procStatType, const UidStats& uidStats, int topNProcessCount,
+                        std::vector<UserPackageStats>* topNProcStats) {
+    uint64_t count = procStatType == IO_BLOCKED_TASKS_COUNT ? uidStats.procStats.ioBlockedTasksCount
+                                                            : uidStats.procStats.totalMajorFaults;
+    if (count == 0) {
+        return false;
+    }
+    for (auto it = topNProcStats->begin(); it != topNProcStats->end(); ++it) {
+        if (const auto* procStats = std::get_if<UserPackageStats::ProcStats>(&it->stats);
+            procStats == nullptr || count > procStats->count) {
+            topNProcStats->emplace(it,
+                                   toUserPackageStats(procStatType, uidStats, topNProcessCount));
+            topNProcStats->pop_back();
+            return true;
+        }
+    }
+    return false;
+}
+
+Result<void> checkDataCollectors(const sp<UidStatsCollectorInterface>& uidStatsCollector,
+                                 const sp<ProcStat>& procStat) {
+    if (uidStatsCollector != nullptr && procStat != nullptr) {
         return {};
     }
     std::string error;
-    if (uidIoStats == nullptr) {
-        error = "Per-UID I/O stats collector must not be empty";
+    if (uidStatsCollector == nullptr) {
+        error = "Per-UID stats collector must not be null";
     }
     if (procStat == nullptr) {
         StringAppendF(&error, "%s%s", error.empty() ? "" : ", ",
-                      "Proc stats collector must not be empty");
+                      "Proc stats collector must not be null");
     }
-    if (procPidStat == nullptr) {
-        StringAppendF(&error, "%s%s", error.empty() ? "" : ", ",
-                      "Per-process stats collector must not be empty");
-    }
-
     return Error() << "Invalid data collectors: " << error;
 }
 
 }  // namespace
 
-std::string toString(const UidIoPerfData& data) {
+std::string UserPackageStats::toString(MetricType metricsType,
+                                       const int64_t totalIoStats[][UID_STATES]) const {
     std::string buffer;
-    if (data.topNReads.size() > 0) {
-        StringAppendF(&buffer, "\nTop N Reads:\n%s\n", std::string(12, '-').c_str());
-        StringAppendF(&buffer,
-                      "Android User ID, Package Name, Foreground Bytes, Foreground Bytes %%, "
-                      "Foreground Fsync, Foreground Fsync %%, Background Bytes, "
-                      "Background Bytes %%, Background Fsync, Background Fsync %%\n");
+    StringAppendF(&buffer, "%" PRIu32 ", %s", multiuser_get_user_id(uid),
+                  genericPackageName.c_str());
+    const auto& ioStats = std::get<UserPackageStats::IoStats>(stats);
+    for (int i = 0; i < UID_STATES; ++i) {
+        StringAppendF(&buffer, ", %" PRIi64 ", %.2f%%, %" PRIi64 ", %.2f%%", ioStats.bytes[i],
+                      percentage(ioStats.bytes[i], totalIoStats[metricsType][i]), ioStats.fsync[i],
+                      percentage(ioStats.fsync[i], totalIoStats[FSYNC_COUNT][i]));
     }
-    for (const auto& stat : data.topNReads) {
-        StringAppendF(&buffer, "%" PRIu32 ", %s", stat.userId, stat.packageName.c_str());
-        for (int i = 0; i < UID_STATES; ++i) {
-            StringAppendF(&buffer, ", %" PRIi64 ", %.2f%%, %" PRIi64 ", %.2f%%", stat.bytes[i],
-                          percentage(stat.bytes[i], data.total[READ_BYTES][i]), stat.fsync[i],
-                          percentage(stat.fsync[i], data.total[FSYNC_COUNT][i]));
-        }
-        StringAppendF(&buffer, "\n");
-    }
-    if (data.topNWrites.size() > 0) {
-        StringAppendF(&buffer, "\nTop N Writes:\n%s\n", std::string(13, '-').c_str());
-        StringAppendF(&buffer,
-                      "Android User ID, Package Name, Foreground Bytes, Foreground Bytes %%, "
-                      "Foreground Fsync, Foreground Fsync %%, Background Bytes, "
-                      "Background Bytes %%, Background Fsync, Background Fsync %%\n");
-    }
-    for (const auto& stat : data.topNWrites) {
-        StringAppendF(&buffer, "%" PRIu32 ", %s", stat.userId, stat.packageName.c_str());
-        for (int i = 0; i < UID_STATES; ++i) {
-            StringAppendF(&buffer, ", %" PRIi64 ", %.2f%%, %" PRIi64 ", %.2f%%", stat.bytes[i],
-                          percentage(stat.bytes[i], data.total[WRITE_BYTES][i]), stat.fsync[i],
-                          percentage(stat.fsync[i], data.total[FSYNC_COUNT][i]));
-        }
-        StringAppendF(&buffer, "\n");
+    StringAppendF(&buffer, "\n");
+    return buffer;
+}
+
+std::string UserPackageStats::toString(int64_t totalCount) const {
+    std::string buffer;
+    const auto& procStats = std::get<UserPackageStats::ProcStats>(stats);
+    StringAppendF(&buffer, "%" PRIu32 ", %s, %" PRIu64 ", %.2f%%\n", multiuser_get_user_id(uid),
+                  genericPackageName.c_str(), procStats.count,
+                  percentage(procStats.count, totalCount));
+    for (const auto& processCount : procStats.topNProcesses) {
+        StringAppendF(&buffer, "\t%s, %" PRIu64 ", %.2f%%\n", processCount.comm.c_str(),
+                      processCount.count, percentage(processCount.count, procStats.count));
     }
     return buffer;
 }
 
-std::string toString(const SystemIoPerfData& data) {
+std::string UserPackageSummaryStats::toString() const {
     std::string buffer;
-    StringAppendF(&buffer, "CPU I/O wait time/percent: %" PRIu64 " / %.2f%%\n", data.cpuIoWaitTime,
-                  percentage(data.cpuIoWaitTime, data.totalCpuTime));
+    if (!topNIoReads.empty()) {
+        StringAppendF(&buffer, kIoReadsTitle, std::string(12, '-').c_str());
+        StringAppendF(&buffer, kIoStatsHeader);
+        for (const auto& stats : topNIoReads) {
+            StringAppendF(&buffer, "%s",
+                          stats.toString(MetricType::READ_BYTES, totalIoStats).c_str());
+        }
+    }
+    if (!topNIoWrites.empty()) {
+        StringAppendF(&buffer, kIoWritesTitle, std::string(13, '-').c_str());
+        StringAppendF(&buffer, kIoStatsHeader);
+        for (const auto& stats : topNIoWrites) {
+            StringAppendF(&buffer, "%s",
+                          stats.toString(MetricType::WRITE_BYTES, totalIoStats).c_str());
+        }
+    }
+    if (!topNIoBlocked.empty()) {
+        StringAppendF(&buffer, kIoBlockedTitle, std::string(23, '-').c_str());
+        StringAppendF(&buffer, kIoBlockedHeader);
+        for (const auto& stats : topNIoBlocked) {
+            const auto it = taskCountByUid.find(stats.uid);
+            if (it == taskCountByUid.end()) {
+                continue;
+            }
+            StringAppendF(&buffer, "%s", stats.toString(it->second).c_str());
+        }
+    }
+    if (!topNMajorFaults.empty()) {
+        StringAppendF(&buffer, kMajorPageFaultsTitle, std::string(24, '-').c_str());
+        StringAppendF(&buffer, kMajorFaultsHeader);
+        for (const auto& stats : topNMajorFaults) {
+            StringAppendF(&buffer, "%s", stats.toString(totalMajorFaults).c_str());
+        }
+        StringAppendF(&buffer, kMajorFaultsSummary, totalMajorFaults, majorFaultsPercentChange);
+    }
+    return buffer;
+}
+
+std::string SystemSummaryStats::toString() const {
+    std::string buffer;
+    StringAppendF(&buffer, "CPU I/O wait time/percent: %" PRIu64 " / %.2f%%\n", cpuIoWaitTime,
+                  percentage(cpuIoWaitTime, totalCpuTime));
     StringAppendF(&buffer, "Number of I/O blocked processes/percent: %" PRIu32 " / %.2f%%\n",
-                  data.ioBlockedProcessesCnt,
-                  percentage(data.ioBlockedProcessesCnt, data.totalProcessesCnt));
+                  ioBlockedProcessCount, percentage(ioBlockedProcessCount, totalProcessCount));
     return buffer;
 }
 
-std::string toString(const ProcessIoPerfData& data) {
+std::string PerfStatsRecord::toString() const {
     std::string buffer;
-    StringAppendF(&buffer, "Number of major page faults since last collection: %" PRIu64 "\n",
-                  data.totalMajorFaults);
-    StringAppendF(&buffer,
-                  "Percentage of change in major page faults since last collection: %.2f%%\n",
-                  data.majorFaultsPercentChange);
-    if (data.topNMajorFaultUids.size() > 0) {
-        StringAppendF(&buffer, "\nTop N major page faults:\n%s\n", std::string(24, '-').c_str());
-        StringAppendF(&buffer,
-                      "Android User ID, Package Name, Number of major page faults, "
-                      "Percentage of total major page faults\n");
-        StringAppendF(&buffer,
-                      "\tCommand, Number of major page faults, Percentage of UID's major page "
-                      "faults\n");
-    }
-    for (const auto& uidStats : data.topNMajorFaultUids) {
-        StringAppendF(&buffer, "%" PRIu32 ", %s, %" PRIu64 ", %.2f%%\n", uidStats.userId,
-                      uidStats.packageName.c_str(), uidStats.count,
-                      percentage(uidStats.count, data.totalMajorFaults));
-        for (const auto& procStats : uidStats.topNProcesses) {
-            StringAppendF(&buffer, "\t%s, %" PRIu64 ", %.2f%%\n", procStats.comm.c_str(),
-                          procStats.count, percentage(procStats.count, uidStats.count));
-        }
-    }
-    if (data.topNIoBlockedUids.size() > 0) {
-        StringAppendF(&buffer, "\nTop N I/O waiting UIDs:\n%s\n", std::string(23, '-').c_str());
-        StringAppendF(&buffer,
-                      "Android User ID, Package Name, Number of owned tasks waiting for I/O, "
-                      "Percentage of owned tasks waiting for I/O\n");
-        StringAppendF(&buffer,
-                      "\tCommand, Number of I/O waiting tasks, Percentage of UID's tasks waiting "
-                      "for I/O\n");
-    }
-    for (size_t i = 0; i < data.topNIoBlockedUids.size(); ++i) {
-        const auto& uidStats = data.topNIoBlockedUids[i];
-        StringAppendF(&buffer, "%" PRIu32 ", %s, %" PRIu64 ", %.2f%%\n", uidStats.userId,
-                      uidStats.packageName.c_str(), uidStats.count,
-                      percentage(uidStats.count, data.topNIoBlockedUidsTotalTaskCnt[i]));
-        for (const auto& procStats : uidStats.topNProcesses) {
-            StringAppendF(&buffer, "\t%s, %" PRIu64 ", %.2f%%\n", procStats.comm.c_str(),
-                          procStats.count, percentage(procStats.count, uidStats.count));
-        }
-    }
+    StringAppendF(&buffer, "%s%s", systemSummaryStats.toString().c_str(),
+                  userPackageSummaryStats.toString().c_str());
     return buffer;
 }
 
-std::string toString(const IoPerfRecord& record) {
-    std::string buffer;
-    StringAppendF(&buffer, "%s%s%s", toString(record.systemIoPerfData).c_str(),
-                  toString(record.processIoPerfData).c_str(),
-                  toString(record.uidIoPerfData).c_str());
-    return buffer;
-}
-
-std::string toString(const CollectionInfo& collectionInfo) {
-    if (collectionInfo.records.empty()) {
+std::string CollectionInfo::toString() const {
+    if (records.empty()) {
         return kEmptyCollectionMessage;
     }
     std::string buffer;
-    double duration =
-            difftime(collectionInfo.records.back().time, collectionInfo.records.front().time);
-    StringAppendF(&buffer, "Collection duration: %.f seconds\nNumber of collections: %zu\n",
-                  duration, collectionInfo.records.size());
-
-    for (size_t i = 0; i < collectionInfo.records.size(); ++i) {
-        const auto& record = collectionInfo.records[i];
+    double duration = difftime(records.back().time, records.front().time);
+    StringAppendF(&buffer, kCollectionTitle, duration, records.size());
+    for (size_t i = 0; i < records.size(); ++i) {
+        const auto& record = records[i];
         std::stringstream timestamp;
         timestamp << std::put_time(std::localtime(&record.time), "%c %Z");
-        StringAppendF(&buffer, "\nCollection %zu: <%s>\n%s\n%s", i, timestamp.str().c_str(),
-                      std::string(45, '=').c_str(), toString(record).c_str());
+        StringAppendF(&buffer, kRecordTitle, i, timestamp.str().c_str(),
+                      std::string(45, '=').c_str(), record.toString().c_str());
     }
     return buffer;
 }
@@ -313,16 +365,16 @@
     mCustomCollection = {};
 }
 
-Result<void> IoPerfCollection::onDump(int fd) {
+Result<void> IoPerfCollection::onDump(int fd) const {
     Mutex::Autolock lock(mMutex);
-    if (!WriteStringToFd(StringPrintf("%s\nBoot-time I/O performance report:\n%s\n",
-                                      std::string(75, '-').c_str(), std::string(33, '=').c_str()),
+    if (!WriteStringToFd(StringPrintf(kBootTimeCollectionTitle, std::string(75, '-').c_str(),
+                                      std::string(33, '=').c_str()),
                          fd) ||
-        !WriteStringToFd(toString(mBoottimeCollection), fd) ||
-        !WriteStringToFd(StringPrintf("%s\nLast N minutes I/O performance report:\n%s\n",
-                                      std::string(75, '-').c_str(), std::string(38, '=').c_str()),
+        !WriteStringToFd(mBoottimeCollection.toString(), fd) ||
+        !WriteStringToFd(StringPrintf(kPeriodicCollectionTitle, std::string(75, '-').c_str(),
+                                      std::string(38, '=').c_str()),
                          fd) ||
-        !WriteStringToFd(toString(mPeriodicCollection), fd)) {
+        !WriteStringToFd(mPeriodicCollection.toString(), fd)) {
         return Error(FAILED_TRANSACTION)
                 << "Failed to dump the boot-time and periodic collection reports.";
     }
@@ -340,70 +392,70 @@
         return {};
     }
 
-    if (!WriteStringToFd(StringPrintf("%s\nCustom I/O performance data report:\n%s\n",
-                                      std::string(75, '-').c_str(), std::string(75, '-').c_str()),
+    if (!WriteStringToFd(StringPrintf(kCustomCollectionTitle, std::string(75, '-').c_str(),
+                                      std::string(75, '-').c_str()),
                          fd) ||
-        !WriteStringToFd(toString(mCustomCollection), fd)) {
+        !WriteStringToFd(mCustomCollection.toString(), fd)) {
         return Error(FAILED_TRANSACTION) << "Failed to write custom I/O collection report.";
     }
 
     return {};
 }
 
-Result<void> IoPerfCollection::onBoottimeCollection(time_t time, const wp<UidIoStats>& uidIoStats,
-                                                    const wp<ProcStat>& procStat,
-                                                    const wp<ProcPidStat>& procPidStat) {
-    auto result = checkDataCollectors(uidIoStats, procStat, procPidStat);
+Result<void> IoPerfCollection::onBoottimeCollection(
+        time_t time, const wp<UidStatsCollectorInterface>& uidStatsCollector,
+        const wp<ProcStat>& procStat) {
+    const sp<UidStatsCollectorInterface> uidStatsCollectorSp = uidStatsCollector.promote();
+    const sp<ProcStat> procStatSp = procStat.promote();
+    auto result = checkDataCollectors(uidStatsCollectorSp, procStatSp);
     if (!result.ok()) {
         return result;
     }
     Mutex::Autolock lock(mMutex);
-    return processLocked(time, std::unordered_set<std::string>(), uidIoStats, procStat, procPidStat,
+    return processLocked(time, std::unordered_set<std::string>(), uidStatsCollectorSp, procStatSp,
                          &mBoottimeCollection);
 }
 
-Result<void> IoPerfCollection::onPeriodicCollection(time_t time,
-                                                    [[maybe_unused]] SystemState systemState,
-                                                    const wp<UidIoStats>& uidIoStats,
-                                                    const wp<ProcStat>& procStat,
-                                                    const wp<ProcPidStat>& procPidStat) {
-    auto result = checkDataCollectors(uidIoStats, procStat, procPidStat);
+Result<void> IoPerfCollection::onPeriodicCollection(
+        time_t time, [[maybe_unused]] SystemState systemState,
+        const wp<UidStatsCollectorInterface>& uidStatsCollector, const wp<ProcStat>& procStat) {
+    const sp<UidStatsCollectorInterface> uidStatsCollectorSp = uidStatsCollector.promote();
+    const sp<ProcStat> procStatSp = procStat.promote();
+    auto result = checkDataCollectors(uidStatsCollectorSp, procStatSp);
     if (!result.ok()) {
         return result;
     }
     Mutex::Autolock lock(mMutex);
-    return processLocked(time, std::unordered_set<std::string>(), uidIoStats, procStat, procPidStat,
+    return processLocked(time, std::unordered_set<std::string>(), uidStatsCollectorSp, procStatSp,
                          &mPeriodicCollection);
 }
 
 Result<void> IoPerfCollection::onCustomCollection(
         time_t time, [[maybe_unused]] SystemState systemState,
-        const std::unordered_set<std::string>& filterPackages, const wp<UidIoStats>& uidIoStats,
-        const wp<ProcStat>& procStat, const wp<ProcPidStat>& procPidStat) {
-    auto result = checkDataCollectors(uidIoStats, procStat, procPidStat);
+        const std::unordered_set<std::string>& filterPackages,
+        const wp<UidStatsCollectorInterface>& uidStatsCollector, const wp<ProcStat>& procStat) {
+    const sp<UidStatsCollectorInterface> uidStatsCollectorSp = uidStatsCollector.promote();
+    const sp<ProcStat> procStatSp = procStat.promote();
+    auto result = checkDataCollectors(uidStatsCollectorSp, procStatSp);
     if (!result.ok()) {
         return result;
     }
     Mutex::Autolock lock(mMutex);
-    return processLocked(time, filterPackages, uidIoStats, procStat, procPidStat,
-                         &mCustomCollection);
+    return processLocked(time, filterPackages, uidStatsCollectorSp, procStatSp, &mCustomCollection);
 }
 
-Result<void> IoPerfCollection::processLocked(time_t time,
-                                             const std::unordered_set<std::string>& filterPackages,
-                                             const wp<UidIoStats>& uidIoStats,
-                                             const wp<ProcStat>& procStat,
-                                             const wp<ProcPidStat>& procPidStat,
-                                             CollectionInfo* collectionInfo) {
+Result<void> IoPerfCollection::processLocked(
+        time_t time, const std::unordered_set<std::string>& filterPackages,
+        const sp<UidStatsCollectorInterface>& uidStatsCollector, const sp<ProcStat>& procStat,
+        CollectionInfo* collectionInfo) {
     if (collectionInfo->maxCacheSize == 0) {
         return Error() << "Maximum cache size cannot be 0";
     }
-    IoPerfRecord record{
+    PerfStatsRecord record{
             .time = time,
     };
-    processSystemIoPerfData(procStat, &record.systemIoPerfData);
-    processProcessIoPerfDataLocked(filterPackages, procPidStat, &record.processIoPerfData);
-    processUidIoPerfData(filterPackages, uidIoStats, &record.uidIoPerfData);
+    processUidStatsLocked(filterPackages, uidStatsCollector, &record.userPackageSummaryStats);
+    processProcStatLocked(procStat, &record.systemSummaryStats);
     if (collectionInfo->records.size() > collectionInfo->maxCacheSize) {
         collectionInfo->records.erase(collectionInfo->records.begin());  // Erase the oldest record.
     }
@@ -411,220 +463,82 @@
     return {};
 }
 
-void IoPerfCollection::processUidIoPerfData(const std::unordered_set<std::string>& filterPackages,
-                                            const wp<UidIoStats>& uidIoStats,
-                                            UidIoPerfData* uidIoPerfData) const {
-    const std::unordered_map<uid_t, UidIoUsage>& usages = uidIoStats.promote()->deltaStats();
-
-    // Fetch only the top N reads and writes from the usage records.
-    UidIoUsage tempUsage = {};
-    std::vector<const UidIoUsage*> topNReads(mTopNStatsPerCategory, &tempUsage);
-    std::vector<const UidIoUsage*> topNWrites(mTopNStatsPerCategory, &tempUsage);
-    std::vector<uid_t> uids;
-
-    for (const auto& uIt : usages) {
-        const UidIoUsage& curUsage = uIt.second;
-        uids.push_back(curUsage.uid);
-        uidIoPerfData->total[READ_BYTES][FOREGROUND] +=
-                curUsage.ios.metrics[READ_BYTES][FOREGROUND];
-        uidIoPerfData->total[READ_BYTES][BACKGROUND] +=
-                curUsage.ios.metrics[READ_BYTES][BACKGROUND];
-        uidIoPerfData->total[WRITE_BYTES][FOREGROUND] +=
-                curUsage.ios.metrics[WRITE_BYTES][FOREGROUND];
-        uidIoPerfData->total[WRITE_BYTES][BACKGROUND] +=
-                curUsage.ios.metrics[WRITE_BYTES][BACKGROUND];
-        uidIoPerfData->total[FSYNC_COUNT][FOREGROUND] +=
-                curUsage.ios.metrics[FSYNC_COUNT][FOREGROUND];
-        uidIoPerfData->total[FSYNC_COUNT][BACKGROUND] +=
-                curUsage.ios.metrics[FSYNC_COUNT][BACKGROUND];
-
-        for (auto it = topNReads.begin(); it != topNReads.end(); ++it) {
-            const UidIoUsage* curRead = *it;
-            if (curRead->ios.sumReadBytes() < curUsage.ios.sumReadBytes()) {
-                topNReads.emplace(it, &curUsage);
-                if (filterPackages.empty()) {
-                    topNReads.pop_back();
-                }
-                break;
-            }
-        }
-        for (auto it = topNWrites.begin(); it != topNWrites.end(); ++it) {
-            const UidIoUsage* curWrite = *it;
-            if (curWrite->ios.sumWriteBytes() < curUsage.ios.sumWriteBytes()) {
-                topNWrites.emplace(it, &curUsage);
-                if (filterPackages.empty()) {
-                    topNWrites.pop_back();
-                }
-                break;
-            }
-        }
+void IoPerfCollection::processUidStatsLocked(
+        const std::unordered_set<std::string>& filterPackages,
+        const sp<UidStatsCollectorInterface>& uidStatsCollector,
+        UserPackageSummaryStats* userPackageSummaryStats) {
+    const std::vector<UidStats>& uidStats = uidStatsCollector->deltaStats();
+    if (uidStats.empty()) {
+        return;
     }
-
-    const auto& uidToPackageNameMapping = mPackageInfoResolver->getPackageNamesForUids(uids);
-
-    // Convert the top N I/O usage to UidIoPerfData.
-    for (const auto& usage : topNReads) {
-        if (usage->ios.isZero()) {
-            // End of non-zero usage records. This case occurs when the number of UIDs with active
-            // I/O operations is < |ro.carwatchdog.top_n_stats_per_category|.
-            break;
-        }
-        UidIoPerfData::Stats stats = {
-                .userId = multiuser_get_user_id(usage->uid),
-                .packageName = std::to_string(usage->uid),
-                .bytes = {usage->ios.metrics[READ_BYTES][FOREGROUND],
-                          usage->ios.metrics[READ_BYTES][BACKGROUND]},
-                .fsync = {usage->ios.metrics[FSYNC_COUNT][FOREGROUND],
-                          usage->ios.metrics[FSYNC_COUNT][BACKGROUND]},
-        };
-        if (uidToPackageNameMapping.find(usage->uid) != uidToPackageNameMapping.end()) {
-            stats.packageName = uidToPackageNameMapping.at(usage->uid);
-        }
-        if (!filterPackages.empty() &&
-            filterPackages.find(stats.packageName) == filterPackages.end()) {
+    if (filterPackages.empty()) {
+        userPackageSummaryStats->topNIoReads.resize(mTopNStatsPerCategory);
+        userPackageSummaryStats->topNIoWrites.resize(mTopNStatsPerCategory);
+        userPackageSummaryStats->topNIoBlocked.resize(mTopNStatsPerCategory);
+        userPackageSummaryStats->topNMajorFaults.resize(mTopNStatsPerCategory);
+    }
+    for (const auto& curUidStats : uidStats) {
+        uid_t uid = curUidStats.uid();
+        addUidIoStats(curUidStats.ioStats.metrics, userPackageSummaryStats->totalIoStats);
+        userPackageSummaryStats->totalMajorFaults += curUidStats.procStats.totalMajorFaults;
+        if (filterPackages.empty()) {
+            cacheTopNIoStats(MetricType::READ_BYTES, curUidStats,
+                             &userPackageSummaryStats->topNIoReads);
+            cacheTopNIoStats(MetricType::WRITE_BYTES, curUidStats,
+                             &userPackageSummaryStats->topNIoWrites);
+            if (cacheTopNProcStats(IO_BLOCKED_TASKS_COUNT, curUidStats, mTopNStatsPerSubcategory,
+                                   &userPackageSummaryStats->topNIoBlocked)) {
+                userPackageSummaryStats->taskCountByUid[uid] =
+                        curUidStats.procStats.totalTasksCount;
+            }
+            cacheTopNProcStats(MAJOR_FAULTS, curUidStats, mTopNStatsPerSubcategory,
+                               &userPackageSummaryStats->topNMajorFaults);
             continue;
         }
-        uidIoPerfData->topNReads.emplace_back(stats);
-    }
-
-    for (const auto& usage : topNWrites) {
-        if (usage->ios.isZero()) {
-            // End of non-zero usage records. This case occurs when the number of UIDs with active
-            // I/O operations is < |ro.carwatchdog.top_n_stats_per_category|.
-            break;
-        }
-        UidIoPerfData::Stats stats = {
-                .userId = multiuser_get_user_id(usage->uid),
-                .packageName = std::to_string(usage->uid),
-                .bytes = {usage->ios.metrics[WRITE_BYTES][FOREGROUND],
-                          usage->ios.metrics[WRITE_BYTES][BACKGROUND]},
-                .fsync = {usage->ios.metrics[FSYNC_COUNT][FOREGROUND],
-                          usage->ios.metrics[FSYNC_COUNT][BACKGROUND]},
-        };
-        if (uidToPackageNameMapping.find(usage->uid) != uidToPackageNameMapping.end()) {
-            stats.packageName = uidToPackageNameMapping.at(usage->uid);
-        }
-        if (!filterPackages.empty() &&
-            filterPackages.find(stats.packageName) == filterPackages.end()) {
-            continue;
-        }
-        uidIoPerfData->topNWrites.emplace_back(stats);
-    }
-}
-
-void IoPerfCollection::processSystemIoPerfData(const wp<ProcStat>& procStat,
-                                               SystemIoPerfData* systemIoPerfData) const {
-    const ProcStatInfo& procStatInfo = procStat.promote()->deltaStats();
-    systemIoPerfData->cpuIoWaitTime = procStatInfo.cpuStats.ioWaitTime;
-    systemIoPerfData->totalCpuTime = procStatInfo.totalCpuTime();
-    systemIoPerfData->ioBlockedProcessesCnt = procStatInfo.ioBlockedProcessesCnt;
-    systemIoPerfData->totalProcessesCnt = procStatInfo.totalProcessesCnt();
-}
-
-void IoPerfCollection::processProcessIoPerfDataLocked(
-        const std::unordered_set<std::string>& filterPackages, const wp<ProcPidStat>& procPidStat,
-        ProcessIoPerfData* processIoPerfData) {
-    const std::vector<ProcessStats>& processStats = procPidStat.promote()->deltaStats();
-
-    const auto& uidProcessStats = getUidProcessStats(processStats, mTopNStatsPerSubcategory);
-    std::vector<uid_t> uids;
-    // Fetch only the top N I/O blocked UIDs and UIDs with most major page faults.
-    UidProcessStats temp = {};
-    std::vector<const UidProcessStats*> topNIoBlockedUids(mTopNStatsPerCategory, &temp);
-    std::vector<const UidProcessStats*> topNMajorFaultUids(mTopNStatsPerCategory, &temp);
-    processIoPerfData->totalMajorFaults = 0;
-    for (const auto& it : *uidProcessStats) {
-        const UidProcessStats& curStats = it.second;
-        uids.push_back(curStats.uid);
-        processIoPerfData->totalMajorFaults += curStats.majorFaults;
-        for (auto it = topNIoBlockedUids.begin(); it != topNIoBlockedUids.end(); ++it) {
-            const UidProcessStats* topStats = *it;
-            if (topStats->ioBlockedTasksCnt < curStats.ioBlockedTasksCnt) {
-                topNIoBlockedUids.emplace(it, &curStats);
-                if (filterPackages.empty()) {
-                    topNIoBlockedUids.pop_back();
-                }
-                break;
-            }
-        }
-        for (auto it = topNMajorFaultUids.begin(); it != topNMajorFaultUids.end(); ++it) {
-            const UidProcessStats* topStats = *it;
-            if (topStats->majorFaults < curStats.majorFaults) {
-                topNMajorFaultUids.emplace(it, &curStats);
-                if (filterPackages.empty()) {
-                    topNMajorFaultUids.pop_back();
-                }
-                break;
-            }
+        if (filterPackages.count(curUidStats.genericPackageName()) != 0) {
+            userPackageSummaryStats->topNIoReads.emplace_back(
+                    toUserPackageStats(MetricType::READ_BYTES, curUidStats));
+            userPackageSummaryStats->topNIoWrites.emplace_back(
+                    toUserPackageStats(MetricType::WRITE_BYTES, curUidStats));
+            userPackageSummaryStats->topNIoBlocked.emplace_back(
+                    toUserPackageStats(IO_BLOCKED_TASKS_COUNT, curUidStats,
+                                       mTopNStatsPerSubcategory));
+            userPackageSummaryStats->topNMajorFaults.emplace_back(
+                    toUserPackageStats(MAJOR_FAULTS, curUidStats, mTopNStatsPerSubcategory));
+            userPackageSummaryStats->taskCountByUid[uid] = curUidStats.procStats.totalTasksCount;
         }
     }
-
-    const auto& uidToPackageNameMapping = mPackageInfoResolver->getPackageNamesForUids(uids);
-
-    // Convert the top N uid process stats to ProcessIoPerfData.
-    for (const auto& it : topNIoBlockedUids) {
-        if (it->ioBlockedTasksCnt == 0) {
-            // End of non-zero elements. This case occurs when the number of UIDs with I/O blocked
-            // processes is < |ro.carwatchdog.top_n_stats_per_category|.
-            break;
-        }
-        ProcessIoPerfData::UidStats stats = {
-                .userId = multiuser_get_user_id(it->uid),
-                .packageName = std::to_string(it->uid),
-                .count = it->ioBlockedTasksCnt,
-        };
-        if (uidToPackageNameMapping.find(it->uid) != uidToPackageNameMapping.end()) {
-            stats.packageName = uidToPackageNameMapping.at(it->uid);
-        }
-        if (!filterPackages.empty() &&
-            filterPackages.find(stats.packageName) == filterPackages.end()) {
-            continue;
-        }
-        for (const auto& pIt : it->topNIoBlockedProcesses) {
-            if (pIt.count == 0) {
-                break;
-            }
-            stats.topNProcesses.emplace_back(
-                    ProcessIoPerfData::UidStats::ProcessStats{pIt.comm, pIt.count});
-        }
-        processIoPerfData->topNIoBlockedUids.emplace_back(stats);
-        processIoPerfData->topNIoBlockedUidsTotalTaskCnt.emplace_back(it->totalTasksCnt);
-    }
-    for (const auto& it : topNMajorFaultUids) {
-        if (it->majorFaults == 0) {
-            // End of non-zero elements. This case occurs when the number of UIDs with major faults
-            // is < |ro.carwatchdog.top_n_stats_per_category|.
-            break;
-        }
-        ProcessIoPerfData::UidStats stats = {
-                .userId = multiuser_get_user_id(it->uid),
-                .packageName = std::to_string(it->uid),
-                .count = it->majorFaults,
-        };
-        if (uidToPackageNameMapping.find(it->uid) != uidToPackageNameMapping.end()) {
-            stats.packageName = uidToPackageNameMapping.at(it->uid);
-        }
-        if (!filterPackages.empty() &&
-            filterPackages.find(stats.packageName) == filterPackages.end()) {
-            continue;
-        }
-        for (const auto& pIt : it->topNMajorFaultProcesses) {
-            if (pIt.count == 0) {
-                break;
-            }
-            stats.topNProcesses.emplace_back(
-                    ProcessIoPerfData::UidStats::ProcessStats{pIt.comm, pIt.count});
-        }
-        processIoPerfData->topNMajorFaultUids.emplace_back(stats);
-    }
-    if (mLastMajorFaults == 0) {
-        processIoPerfData->majorFaultsPercentChange = 0;
-    } else {
-        int64_t increase = processIoPerfData->totalMajorFaults - mLastMajorFaults;
-        processIoPerfData->majorFaultsPercentChange =
+    if (mLastMajorFaults != 0) {
+        int64_t increase = userPackageSummaryStats->totalMajorFaults - mLastMajorFaults;
+        userPackageSummaryStats->majorFaultsPercentChange =
                 (static_cast<double>(increase) / static_cast<double>(mLastMajorFaults)) * 100.0;
     }
-    mLastMajorFaults = processIoPerfData->totalMajorFaults;
+    mLastMajorFaults = userPackageSummaryStats->totalMajorFaults;
+
+    const auto removeEmptyStats = [](std::vector<UserPackageStats>& userPackageStats) {
+        for (auto it = userPackageStats.begin(); it != userPackageStats.end(); ++it) {
+            /* std::monostate is the first alternative in the variant. When the variant is
+             * uninitialized, the index points to this alternative.
+             */
+            if (it->stats.index() == 0) {
+                userPackageStats.erase(it, userPackageStats.end());
+                break;
+            }
+        }
+    };
+    removeEmptyStats(userPackageSummaryStats->topNIoReads);
+    removeEmptyStats(userPackageSummaryStats->topNIoWrites);
+    removeEmptyStats(userPackageSummaryStats->topNIoBlocked);
+    removeEmptyStats(userPackageSummaryStats->topNMajorFaults);
+}
+
+void IoPerfCollection::processProcStatLocked(const sp<ProcStat>& procStat,
+                                             SystemSummaryStats* systemSummaryStats) const {
+    const ProcStatInfo& procStatInfo = procStat->deltaStats();
+    systemSummaryStats->cpuIoWaitTime = procStatInfo.cpuStats.ioWaitTime;
+    systemSummaryStats->totalCpuTime = procStatInfo.totalCpuTime();
+    systemSummaryStats->ioBlockedProcessCount = procStatInfo.ioBlockedProcessCount;
+    systemSummaryStats->totalProcessCount = procStatInfo.totalProcessCount();
 }
 
 }  // namespace watchdog
diff --git a/cpp/watchdog/server/src/IoPerfCollection.h b/cpp/watchdog/server/src/IoPerfCollection.h
index 2e976da..265cb55 100644
--- a/cpp/watchdog/server/src/IoPerfCollection.h
+++ b/cpp/watchdog/server/src/IoPerfCollection.h
@@ -17,11 +17,9 @@
 #ifndef CPP_WATCHDOG_SERVER_SRC_IOPERFCOLLECTION_H_
 #define CPP_WATCHDOG_SERVER_SRC_IOPERFCOLLECTION_H_
 
-#include "PackageInfoResolver.h"
 #include "ProcDiskStats.h"
-#include "ProcPidStat.h"
 #include "ProcStat.h"
-#include "UidIoStats.h"
+#include "UidStatsCollector.h"
 #include "WatchdogPerfService.h"
 
 #include <android-base/result.h>
@@ -34,79 +32,16 @@
 #include <ctime>
 #include <string>
 #include <unordered_set>
+#include <variant>
 #include <vector>
 
 namespace android {
 namespace automotive {
 namespace watchdog {
 
-// Number of periodic collection perf data snapshots to cache in memory.
-const int32_t kDefaultPeriodicCollectionBufferSize = 180;
-constexpr const char* kEmptyCollectionMessage = "No collection recorded\n";
-
-// Performance data collected from the `/proc/uid_io/stats` file.
-struct UidIoPerfData {
-    struct Stats {
-        userid_t userId = 0;
-        std::string packageName;
-        int64_t bytes[UID_STATES];
-        int64_t fsync[UID_STATES];
-    };
-    std::vector<Stats> topNReads = {};
-    std::vector<Stats> topNWrites = {};
-    int64_t total[METRIC_TYPES][UID_STATES] = {{0}};
-};
-
-std::string toString(const UidIoPerfData& perfData);
-
-// Performance data collected from the `/proc/stats` file.
-struct SystemIoPerfData {
-    uint64_t cpuIoWaitTime = 0;
-    uint64_t totalCpuTime = 0;
-    uint32_t ioBlockedProcessesCnt = 0;
-    uint32_t totalProcessesCnt = 0;
-};
-
-std::string toString(const SystemIoPerfData& perfData);
-
-// Performance data collected from the `/proc/[pid]/stat` and `/proc/[pid]/task/[tid]/stat` files.
-struct ProcessIoPerfData {
-    struct UidStats {
-        userid_t userId = 0;
-        std::string packageName;
-        uint64_t count = 0;
-        struct ProcessStats {
-            std::string comm = "";
-            uint64_t count = 0;
-        };
-        std::vector<ProcessStats> topNProcesses = {};
-    };
-    std::vector<UidStats> topNIoBlockedUids = {};
-    // Total # of tasks owned by each UID in |topNIoBlockedUids|.
-    std::vector<uint64_t> topNIoBlockedUidsTotalTaskCnt = {};
-    std::vector<UidStats> topNMajorFaultUids = {};
-    uint64_t totalMajorFaults = 0;
-    // Percentage of increase/decrease in the major page faults since last collection.
-    double majorFaultsPercentChange = 0.0;
-};
-
-std::string toString(const ProcessIoPerfData& data);
-
-struct IoPerfRecord {
-    time_t time;  // Collection time.
-    UidIoPerfData uidIoPerfData;
-    SystemIoPerfData systemIoPerfData;
-    ProcessIoPerfData processIoPerfData;
-};
-
-std::string toString(const IoPerfRecord& record);
-
-struct CollectionInfo {
-    size_t maxCacheSize = 0;            // Maximum cache size for the collection.
-    std::vector<IoPerfRecord> records;  // Cache of collected performance records.
-};
-
-std::string toString(const CollectionInfo& collectionInfo);
+// Number of periodic collection records to cache in memory.
+constexpr int32_t kDefaultPeriodicCollectionBufferSize = 180;
+constexpr const char kEmptyCollectionMessage[] = "No collection recorded\n";
 
 // Forward declaration for testing use only.
 namespace internal {
@@ -115,13 +50,84 @@
 
 }  // namespace internal
 
+// Below structs should be used only by the implementation and unit tests.
+/**
+ * Struct to represent user package performance stats.
+ */
+struct UserPackageStats {
+    struct IoStats {
+        int64_t bytes[UID_STATES] = {0};
+        int64_t fsync[UID_STATES] = {0};
+
+        int64_t totalBytes() const {
+            return std::numeric_limits<int64_t>::max() - bytes[UidState::FOREGROUND] >
+                            bytes[UidState::BACKGROUND]
+                    ? bytes[UidState::FOREGROUND] + bytes[UidState::BACKGROUND]
+                    : std::numeric_limits<int64_t>::max();
+        }
+    };
+    struct ProcStats {
+        uint64_t count = 0;
+        struct ProcessCount {
+            std::string comm = "";
+            uint64_t count = 0;
+        };
+        std::vector<ProcessCount> topNProcesses = {};
+    };
+    uid_t uid = 0;
+    std::string genericPackageName = "";
+    std::variant<std::monostate, IoStats, ProcStats> stats;
+    std::string toString(MetricType metricsType, const int64_t totalIoStats[][UID_STATES]) const;
+    std::string toString(int64_t count) const;
+};
+
+/**
+ * User package summary performance stats collected from the `/proc/uid_io/stats`,
+ * `/proc/[pid]/stat`, `/proc/[pid]/task/[tid]/stat`, and /proc/[pid]/status` files.
+ */
+struct UserPackageSummaryStats {
+    std::vector<UserPackageStats> topNIoReads = {};
+    std::vector<UserPackageStats> topNIoWrites = {};
+    std::vector<UserPackageStats> topNIoBlocked = {};
+    std::vector<UserPackageStats> topNMajorFaults = {};
+    int64_t totalIoStats[METRIC_TYPES][UID_STATES] = {{0}};
+    std::unordered_map<uid_t, uint64_t> taskCountByUid = {};
+    uint64_t totalMajorFaults = 0;
+    // Percentage of increase/decrease in the major page faults since last collection.
+    double majorFaultsPercentChange = 0.0;
+    std::string toString() const;
+};
+
+// System performance stats collected from the `/proc/stats` file.
+struct SystemSummaryStats {
+    uint64_t cpuIoWaitTime = 0;
+    uint64_t totalCpuTime = 0;
+    uint32_t ioBlockedProcessCount = 0;
+    uint32_t totalProcessCount = 0;
+    std::string toString() const;
+};
+
+// Performance record collected during a sampling/collection period.
+struct PerfStatsRecord {
+    time_t time;  // Collection time.
+    SystemSummaryStats systemSummaryStats;
+    UserPackageSummaryStats userPackageSummaryStats;
+    std::string toString() const;
+};
+
+// Group of performance records collected for a collection event.
+struct CollectionInfo {
+    size_t maxCacheSize = 0;               // Maximum cache size for the collection.
+    std::vector<PerfStatsRecord> records;  // Cache of collected performance records.
+    std::string toString() const;
+};
+
 // IoPerfCollection implements the I/O performance data collection module.
 class IoPerfCollection : public IDataProcessorInterface {
 public:
     IoPerfCollection() :
           mTopNStatsPerCategory(0),
           mTopNStatsPerSubcategory(0),
-          mPackageInfoResolver(PackageInfoResolver::getInstance()),
           mBoottimeCollection({}),
           mPeriodicCollection({}),
           mCustomCollection({}),
@@ -129,36 +135,35 @@
 
     ~IoPerfCollection() { terminate(); }
 
-    std::string name() { return "IoPerfCollection"; }
+    std::string name() const override { return "IoPerfCollection"; }
 
     // Implements IDataProcessorInterface.
-    android::base::Result<void> onBoottimeCollection(time_t time,
-                                                     const android::wp<UidIoStats>& uidIoStats,
-                                                     const android::wp<ProcStat>& procStat,
-                                                     const android::wp<ProcPidStat>& procPidStat);
+    android::base::Result<void> onBoottimeCollection(
+            time_t time, const android::wp<UidStatsCollectorInterface>& uidStatsCollector,
+            const android::wp<ProcStat>& procStat) override;
 
-    android::base::Result<void> onPeriodicCollection(time_t time, SystemState systemState,
-                                                     const android::wp<UidIoStats>& uidIoStats,
-                                                     const android::wp<ProcStat>& procStat,
-                                                     const android::wp<ProcPidStat>& procPidStat);
+    android::base::Result<void> onPeriodicCollection(
+            time_t time, SystemState systemState,
+            const android::wp<UidStatsCollectorInterface>& uidStatsCollector,
+            const android::wp<ProcStat>& procStat) override;
 
     android::base::Result<void> onCustomCollection(
             time_t time, SystemState systemState,
             const std::unordered_set<std::string>& filterPackages,
-            const android::wp<UidIoStats>& uidIoStats, const android::wp<ProcStat>& procStat,
-            const android::wp<ProcPidStat>& procPidStat);
+            const android::wp<UidStatsCollectorInterface>& uidStatsCollector,
+            const android::wp<ProcStat>& procStat) override;
 
     android::base::Result<void> onPeriodicMonitor(
             [[maybe_unused]] time_t time,
             [[maybe_unused]] const android::wp<IProcDiskStatsInterface>& procDiskStats,
-            [[maybe_unused]] const std::function<void()>& alertHandler) {
+            [[maybe_unused]] const std::function<void()>& alertHandler) override {
         // No monitoring done here as this DataProcessor only collects I/O performance records.
         return {};
     }
 
-    android::base::Result<void> onDump(int fd);
+    android::base::Result<void> onDump(int fd) const override;
 
-    android::base::Result<void> onCustomCollectionDump(int fd);
+    android::base::Result<void> onCustomCollectionDump(int fd) override;
 
 protected:
     android::base::Result<void> init();
@@ -168,27 +173,19 @@
 
 private:
     // Processes the collected data.
-    android::base::Result<void> processLocked(time_t time,
-                                              const std::unordered_set<std::string>& filterPackages,
-                                              const android::wp<UidIoStats>& uidIoStats,
-                                              const android::wp<ProcStat>& procStat,
-                                              const android::wp<ProcPidStat>& procPidStat,
-                                              CollectionInfo* collectionInfo);
+    android::base::Result<void> processLocked(
+            time_t time, const std::unordered_set<std::string>& filterPackages,
+            const android::sp<UidStatsCollectorInterface>& uidStatsCollector,
+            const android::sp<ProcStat>& procStat, CollectionInfo* collectionInfo);
 
-    // Processes performance data from the `/proc/uid_io/stats` file.
-    void processUidIoPerfData(const std::unordered_set<std::string>& filterPackages,
-                              const android::wp<UidIoStats>& uidIoStats,
-                              UidIoPerfData* uidIoPerfData) const;
+    // Processes per-UID performance data.
+    void processUidStatsLocked(const std::unordered_set<std::string>& filterPackages,
+                               const android::sp<UidStatsCollectorInterface>& uidStatsCollector,
+                               UserPackageSummaryStats* userPackageSummaryStats);
 
-    // Processes performance data from the `/proc/stats` file.
-    void processSystemIoPerfData(const android::wp<ProcStat>& procStat,
-                                 SystemIoPerfData* systemIoPerfData) const;
-
-    // Processes performance data from the `/proc/[pid]/stat` and `/proc/[pid]/task/[tid]/stat`
-    // files.
-    void processProcessIoPerfDataLocked(const std::unordered_set<std::string>& filterPackages,
-                                        const android::wp<ProcPidStat>& procPidStat,
-                                        ProcessIoPerfData* processIoPerfData);
+    // Processes system performance data from the `/proc/stats` file.
+    void processProcStatLocked(const android::sp<ProcStat>& procStat,
+                               SystemSummaryStats* systemSummaryStats) const;
 
     // Top N per-UID stats per category.
     int mTopNStatsPerCategory;
@@ -196,36 +193,34 @@
     // Top N per-process stats per subcategory.
     int mTopNStatsPerSubcategory;
 
-    // Local IPackageInfoResolver instance. Useful to mock in tests.
-    sp<IPackageInfoResolver> mPackageInfoResolver;
-
     // Makes sure only one collection is running at any given time.
-    Mutex mMutex;
+    mutable Mutex mMutex;
 
     // Info for the boot-time collection event. The cache is persisted until system shutdown/reboot.
     CollectionInfo mBoottimeCollection GUARDED_BY(mMutex);
 
-    // Info for the periodic collection event. The cache size is limited by
-    // |ro.carwatchdog.periodic_collection_buffer_size|.
+    /**
+     * Info for the periodic collection event. The cache size is limited by
+     * |ro.carwatchdog.periodic_collection_buffer_size|.
+     */
     CollectionInfo mPeriodicCollection GUARDED_BY(mMutex);
 
-    // Info for the custom collection event. The info is cleared at the end of every custom
-    // collection.
+    /**
+     * Info for the custom collection event. The info is cleared at the end of every custom
+     * collection.
+     */
     CollectionInfo mCustomCollection GUARDED_BY(mMutex);
 
-    // Major faults delta from last collection. Useful when calculating the percentage change in
-    // major faults since last collection.
+    /**
+     * Major faults delta from last collection. Useful when calculating the percentage change in
+     * major faults since last collection.
+     */
     uint64_t mLastMajorFaults GUARDED_BY(mMutex);
 
     friend class WatchdogPerfService;
 
     // For unit tests.
     friend class internal::IoPerfCollectionPeer;
-    FRIEND_TEST(IoPerfCollectionTest, TestUidIoStatsGreaterThanTopNStatsLimit);
-    FRIEND_TEST(IoPerfCollectionTest, TestUidIOStatsLessThanTopNStatsLimit);
-    FRIEND_TEST(IoPerfCollectionTest, TestProcessSystemIoPerfData);
-    FRIEND_TEST(IoPerfCollectionTest, TestProcPidContentsGreaterThanTopNStatsLimit);
-    FRIEND_TEST(IoPerfCollectionTest, TestProcPidContentsLessThanTopNStatsLimit);
 };
 
 }  // namespace watchdog
diff --git a/cpp/watchdog/server/src/ProcPidStat.cpp b/cpp/watchdog/server/src/ProcPidStat.cpp
deleted file mode 100644
index 8bf9cf0..0000000
--- a/cpp/watchdog/server/src/ProcPidStat.cpp
+++ /dev/null
@@ -1,348 +0,0 @@
-/**
- * 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.
- */
-
-#define LOG_TAG "carwatchdogd"
-
-#include "ProcPidStat.h"
-
-#include <android-base/file.h>
-#include <android-base/parseint.h>
-#include <android-base/strings.h>
-#include <dirent.h>
-#include <log/log.h>
-
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-namespace android {
-namespace automotive {
-namespace watchdog {
-
-using ::android::base::EndsWith;
-using ::android::base::Error;
-using ::android::base::ParseInt;
-using ::android::base::ParseUint;
-using ::android::base::ReadFileToString;
-using ::android::base::Result;
-using ::android::base::Split;
-using ::android::base::Trim;
-
-namespace {
-
-enum ReadError {
-    ERR_INVALID_FILE = 0,
-    ERR_FILE_OPEN_READ = 1,
-    NUM_ERRORS = 2,
-};
-
-// /proc/PID/stat or /proc/PID/task/TID/stat format:
-// <pid> <comm> <state> <ppid> <pgrp ID> <session ID> <tty_nr> <tpgid> <flags> <minor faults>
-// <children minor faults> <major faults> <children major faults> <user mode time>
-// <system mode time> <children user mode time> <children kernel mode time> <priority> <nice value>
-// <num threads> <start time since boot> <virtual memory size> <resident set size> <rss soft limit>
-// <start code addr> <end code addr> <start stack addr> <ESP value> <EIP> <bitmap of pending sigs>
-// <bitmap of blocked sigs> <bitmap of ignored sigs> <waiting channel> <num pages swapped>
-// <cumulative pages swapped> <exit signal> <processor #> <real-time prio> <agg block I/O delays>
-// <guest time> <children guest time> <start data addr> <end data addr> <start break addr>
-// <cmd line args start addr> <amd line args end addr> <env start addr> <env end addr> <exit code>
-// Example line: 1 (init) S 0 0 0 0 0 0 0 0 220 0 0 0 0 0 0 0 2 0 0 ...etc...
-bool parsePidStatLine(const std::string& line, PidStat* pidStat) {
-    std::vector<std::string> fields = Split(line, " ");
-
-    // Note: Regex parsing for the below logic increased the time taken to run the
-    // ProcPidStatTest#TestProcPidStatContentsFromDevice from 151.7ms to 1.3 seconds.
-
-    // Comm string is enclosed with ( ) brackets and may contain space(s). Thus calculate the
-    // commEndOffset based on the field that contains the closing bracket.
-    size_t commEndOffset = 0;
-    for (size_t i = 1; i < fields.size(); ++i) {
-        pidStat->comm += fields[i];
-        if (EndsWith(fields[i], ")")) {
-            commEndOffset = i - 1;
-            break;
-        }
-        pidStat->comm += " ";
-    }
-
-    if (pidStat->comm.front() != '(' || pidStat->comm.back() != ')') {
-        ALOGW("Comm string `%s` not enclosed in brackets", pidStat->comm.c_str());
-        return false;
-    }
-    pidStat->comm.erase(pidStat->comm.begin());
-    pidStat->comm.erase(pidStat->comm.end() - 1);
-
-    // The required data is in the first 22 + |commEndOffset| fields so make sure there are at least
-    // these many fields in the file.
-    if (fields.size() < 22 + commEndOffset || !ParseInt(fields[0], &pidStat->pid) ||
-        !ParseInt(fields[3 + commEndOffset], &pidStat->ppid) ||
-        !ParseUint(fields[11 + commEndOffset], &pidStat->majorFaults) ||
-        !ParseUint(fields[19 + commEndOffset], &pidStat->numThreads) ||
-        !ParseUint(fields[21 + commEndOffset], &pidStat->startTime)) {
-        ALOGW("Invalid proc pid stat contents: \"%s\"", line.c_str());
-        return false;
-    }
-    pidStat->state = fields[2 + commEndOffset];
-    return true;
-}
-
-Result<void> readPidStatFile(const std::string& path, PidStat* pidStat) {
-    std::string buffer;
-    if (!ReadFileToString(path, &buffer)) {
-        return Error(ERR_FILE_OPEN_READ) << "ReadFileToString failed for " << path;
-    }
-    std::vector<std::string> lines = Split(std::move(buffer), "\n");
-    if (lines.size() != 1 && (lines.size() != 2 || !lines[1].empty())) {
-        return Error(ERR_INVALID_FILE) << path << " contains " << lines.size() << " lines != 1";
-    }
-    if (!parsePidStatLine(std::move(lines[0]), pidStat)) {
-        return Error(ERR_INVALID_FILE) << "Failed to parse the contents of " << path;
-    }
-    return {};
-}
-
-Result<std::unordered_map<std::string, std::string>> readKeyValueFile(
-        const std::string& path, const std::string& delimiter) {
-    std::string buffer;
-    if (!ReadFileToString(path, &buffer)) {
-        return Error(ERR_FILE_OPEN_READ) << "ReadFileToString failed for " << path;
-    }
-    std::unordered_map<std::string, std::string> contents;
-    std::vector<std::string> lines = Split(std::move(buffer), "\n");
-    for (size_t i = 0; i < lines.size(); ++i) {
-        if (lines[i].empty()) {
-            continue;
-        }
-        std::vector<std::string> elements = Split(lines[i], delimiter);
-        if (elements.size() < 2) {
-            return Error(ERR_INVALID_FILE)
-                    << "Line \"" << lines[i] << "\" doesn't contain the delimiter \"" << delimiter
-                    << "\" in file " << path;
-        }
-        std::string key = elements[0];
-        std::string value = Trim(lines[i].substr(key.length() + delimiter.length()));
-        if (contents.find(key) != contents.end()) {
-            return Error(ERR_INVALID_FILE)
-                    << "Duplicate " << key << " line: \"" << lines[i] << "\" in file " << path;
-        }
-        contents[key] = value;
-    }
-    return contents;
-}
-
-// /proc/PID/status file format(*):
-// Tgid:    <Thread group ID of the process>
-// Uid:     <Read UID>   <Effective UID>   <Saved set UID>   <Filesystem UID>
-// VmPeak:  <Peak virtual memory size> kB
-// VmSize:  <Virtual memory size> kB
-// VmHWM:   <Peak resident set size> kB
-// VmRSS:   <Resident set size> kB
-//
-// (*) - Included only the fields that are parsed from the file.
-Result<void> readPidStatusFile(const std::string& path, ProcessStats* processStats) {
-    auto ret = readKeyValueFile(path, ":\t");
-    if (!ret.ok()) {
-        return Error(ret.error().code()) << ret.error();
-    }
-    auto contents = ret.value();
-    if (contents.empty()) {
-        return Error(ERR_INVALID_FILE) << "Empty file " << path;
-    }
-    if (contents.find("Uid") == contents.end() ||
-        !ParseInt(Split(contents["Uid"], "\t")[0], &processStats->uid)) {
-        return Error(ERR_INVALID_FILE) << "Failed to read 'UIDs' from file " << path;
-    }
-    if (contents.find("Tgid") == contents.end() ||
-        !ParseInt(contents["Tgid"], &processStats->tgid)) {
-        return Error(ERR_INVALID_FILE) << "Failed to read 'Tgid' from file " << path;
-    }
-    // Below Vm* fields may not be present for some processes so don't fail when they are missing.
-    if (contents.find("VmPeak") != contents.end() &&
-        !ParseUint(Split(contents["VmPeak"], " ")[0], &processStats->vmPeakKb)) {
-        return Error(ERR_INVALID_FILE) << "Failed to parse 'VmPeak' from file " << path;
-    }
-    if (contents.find("VmSize") != contents.end() &&
-        !ParseUint(Split(contents["VmSize"], " ")[0], &processStats->vmSizeKb)) {
-        return Error(ERR_INVALID_FILE) << "Failed to parse 'VmSize' from file " << path;
-    }
-    if (contents.find("VmHWM") != contents.end() &&
-        !ParseUint(Split(contents["VmHWM"], " ")[0], &processStats->vmHwmKb)) {
-        return Error(ERR_INVALID_FILE) << "Failed to parse 'VmHWM' from file " << path;
-    }
-    if (contents.find("VmRSS") != contents.end() &&
-        !ParseUint(Split(contents["VmRSS"], " ")[0], &processStats->vmRssKb)) {
-        return Error(ERR_INVALID_FILE) << "Failed to parse 'VmRSS' from file " << path;
-    }
-    return {};
-}
-
-}  // namespace
-
-Result<void> ProcPidStat::collect() {
-    if (!mEnabled) {
-        return Error() << "Can not access PID stat files under " << kProcDirPath;
-    }
-
-    Mutex::Autolock lock(mMutex);
-    const auto& processStats = getProcessStatsLocked();
-    if (!processStats.ok()) {
-        return Error() << processStats.error();
-    }
-
-    mDeltaProcessStats.clear();
-    for (const auto& it : *processStats) {
-        const ProcessStats& curStats = it.second;
-        const auto& cachedIt = mLatestProcessStats.find(it.first);
-        if (cachedIt == mLatestProcessStats.end() ||
-            cachedIt->second.process.startTime != curStats.process.startTime) {
-            // New/reused PID so don't calculate the delta.
-            mDeltaProcessStats.emplace_back(curStats);
-            continue;
-        }
-
-        ProcessStats deltaStats = curStats;
-        const ProcessStats& cachedStats = cachedIt->second;
-        deltaStats.process.majorFaults -= cachedStats.process.majorFaults;
-        for (auto& deltaThread : deltaStats.threads) {
-            const auto& cachedThread = cachedStats.threads.find(deltaThread.first);
-            if (cachedThread == cachedStats.threads.end() ||
-                cachedThread->second.startTime != deltaThread.second.startTime) {
-                // New TID or TID reused by the same PID so don't calculate the delta.
-                continue;
-            }
-            deltaThread.second.majorFaults -= cachedThread->second.majorFaults;
-        }
-        mDeltaProcessStats.emplace_back(deltaStats);
-    }
-    mLatestProcessStats = *processStats;
-    return {};
-}
-
-Result<std::unordered_map<pid_t, ProcessStats>> ProcPidStat::getProcessStatsLocked() const {
-    std::unordered_map<pid_t, ProcessStats> processStats;
-    auto procDirp = std::unique_ptr<DIR, int (*)(DIR*)>(opendir(mPath.c_str()), closedir);
-    if (!procDirp) {
-        return Error() << "Failed to open " << mPath << " directory";
-    }
-    dirent* pidDir = nullptr;
-    while ((pidDir = readdir(procDirp.get())) != nullptr) {
-        // 1. Read top-level pid stats.
-        pid_t pid = 0;
-        if (pidDir->d_type != DT_DIR || !ParseInt(pidDir->d_name, &pid)) {
-            continue;
-        }
-        ProcessStats curStats;
-        std::string path = StringPrintf((mPath + kStatFileFormat).c_str(), pid);
-        auto ret = readPidStatFile(path, &curStats.process);
-        if (!ret.ok()) {
-            // PID may disappear between scanning the directory and parsing the stat file.
-            // Thus treat ERR_FILE_OPEN_READ errors as soft errors.
-            if (ret.error().code() != ERR_FILE_OPEN_READ) {
-                return Error() << "Failed to read top-level per-process stat file: "
-                               << ret.error().message().c_str();
-            }
-            ALOGW("Failed to read top-level per-process stat file %s: %s", path.c_str(),
-                  ret.error().message().c_str());
-            continue;
-        }
-
-        // 2. Read aggregated process status.
-        path = StringPrintf((mPath + kStatusFileFormat).c_str(), curStats.process.pid);
-        ret = readPidStatusFile(path, &curStats);
-        if (!ret.ok()) {
-            if (ret.error().code() != ERR_FILE_OPEN_READ) {
-                return Error() << "Failed to read pid status for pid " << curStats.process.pid
-                               << ": " << ret.error().message().c_str();
-            }
-            ALOGW("Failed to read pid status for pid %" PRIu32 ": %s", curStats.process.pid,
-                  ret.error().message().c_str());
-        }
-
-        // 3. When failed to read tgid or uid, copy these from the previous collection.
-        if (curStats.tgid == -1 || curStats.uid == -1) {
-            const auto& it = mLatestProcessStats.find(curStats.process.pid);
-            if (it != mLatestProcessStats.end() &&
-                it->second.process.startTime == curStats.process.startTime) {
-                curStats.tgid = it->second.tgid;
-                curStats.uid = it->second.uid;
-            }
-        }
-
-        if (curStats.tgid != -1 && curStats.tgid != curStats.process.pid) {
-            ALOGW("Skipping non-process (i.e., Tgid != PID) entry for PID %" PRIu32,
-                  curStats.process.pid);
-            continue;
-        }
-
-        // 3. Fetch per-thread stats.
-        std::string taskDir = StringPrintf((mPath + kTaskDirFormat).c_str(), pid);
-        auto taskDirp = std::unique_ptr<DIR, int (*)(DIR*)>(opendir(taskDir.c_str()), closedir);
-        if (!taskDirp) {
-            // Treat this as a soft error so at least the process stats will be collected.
-            ALOGW("Failed to open %s directory", taskDir.c_str());
-        }
-        dirent* tidDir = nullptr;
-        bool didReadMainThread = false;
-        while (taskDirp != nullptr && (tidDir = readdir(taskDirp.get())) != nullptr) {
-            pid_t tid = 0;
-            if (tidDir->d_type != DT_DIR || !ParseInt(tidDir->d_name, &tid)) {
-                continue;
-            }
-            if (processStats.find(tid) != processStats.end()) {
-                return Error() << "Process stats already exists for TID " << tid
-                               << ". Stats will be double counted";
-            }
-
-            PidStat curThreadStat = {};
-            path = StringPrintf((taskDir + kStatFileFormat).c_str(), tid);
-            const auto& ret = readPidStatFile(path, &curThreadStat);
-            if (!ret.ok()) {
-                if (ret.error().code() != ERR_FILE_OPEN_READ) {
-                    return Error() << "Failed to read per-thread stat file: "
-                                   << ret.error().message().c_str();
-                }
-                // Maybe the thread terminated before reading the file so skip this thread and
-                // continue with scanning the next thread's stat.
-                ALOGW("Failed to read per-thread stat file %s: %s", path.c_str(),
-                      ret.error().message().c_str());
-                continue;
-            }
-            if (curThreadStat.pid == curStats.process.pid) {
-                didReadMainThread = true;
-            }
-            curStats.threads[curThreadStat.pid] = curThreadStat;
-        }
-        if (!didReadMainThread) {
-            // In the event of failure to read main-thread info (mostly because the process
-            // terminated during scanning/parsing), fill out the stat that are common between main
-            // thread and the process.
-            curStats.threads[curStats.process.pid] = PidStat{
-                    .pid = curStats.process.pid,
-                    .comm = curStats.process.comm,
-                    .state = curStats.process.state,
-                    .ppid = curStats.process.ppid,
-                    .numThreads = curStats.process.numThreads,
-                    .startTime = curStats.process.startTime,
-            };
-        }
-        processStats[curStats.process.pid] = curStats;
-    }
-    return processStats;
-}
-
-}  // namespace watchdog
-}  // namespace automotive
-}  // namespace android
diff --git a/cpp/watchdog/server/src/ProcPidStat.h b/cpp/watchdog/server/src/ProcPidStat.h
deleted file mode 100644
index de1988a..0000000
--- a/cpp/watchdog/server/src/ProcPidStat.h
+++ /dev/null
@@ -1,142 +0,0 @@
-/**
- * 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_WATCHDOG_SERVER_SRC_PROCPIDSTAT_H_
-#define CPP_WATCHDOG_SERVER_SRC_PROCPIDSTAT_H_
-
-#include <android-base/result.h>
-#include <android-base/stringprintf.h>
-#include <gtest/gtest_prod.h>
-#include <inttypes.h>
-#include <stdint.h>
-#include <utils/Mutex.h>
-#include <utils/RefBase.h>
-
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-namespace android {
-namespace automotive {
-namespace watchdog {
-
-using ::android::base::StringPrintf;
-
-#define PID_FOR_INIT 1
-
-constexpr const char* kProcDirPath = "/proc";
-constexpr const char* kStatFileFormat = "/%" PRIu32 "/stat";
-constexpr const char* kTaskDirFormat = "/%" PRIu32 "/task";
-constexpr const char* kStatusFileFormat = "/%" PRIu32 "/status";
-
-struct PidStat {
-    pid_t pid = 0;
-    std::string comm = "";
-    std::string state = "";
-    pid_t ppid = 0;
-    uint64_t majorFaults = 0;
-    uint32_t numThreads = 0;
-    uint64_t startTime = 0;  // Useful when identifying PID/TID reuse
-};
-
-struct ProcessStats {
-    int64_t tgid = -1;                              // -1 indicates a failure to read this value
-    int64_t uid = -1;                               // -1 indicates a failure to read this value
-    uint64_t vmPeakKb = 0;
-    uint64_t vmSizeKb = 0;
-    uint64_t vmHwmKb = 0;
-    uint64_t vmRssKb = 0;
-    PidStat process = {};                           // Aggregated stats across all the threads
-    std::unordered_map<pid_t, PidStat> threads;     // Per-thread stat including the main thread
-};
-
-// Collector/parser for `/proc/[pid]/stat`, `/proc/[pid]/task/[tid]/stat` and /proc/[pid]/status`
-// files.
-class ProcPidStat : public RefBase {
-public:
-    explicit ProcPidStat(const std::string& path = kProcDirPath) :
-          mLatestProcessStats({}),
-          mPath(path) {
-        std::string pidStatPath = StringPrintf((mPath + kStatFileFormat).c_str(), PID_FOR_INIT);
-        std::string tidStatPath = StringPrintf((mPath + kTaskDirFormat + kStatFileFormat).c_str(),
-                                               PID_FOR_INIT, PID_FOR_INIT);
-        std::string pidStatusPath = StringPrintf((mPath + kStatusFileFormat).c_str(), PID_FOR_INIT);
-
-        mEnabled = !access(pidStatPath.c_str(), R_OK) && !access(tidStatPath.c_str(), R_OK) &&
-                !access(pidStatusPath.c_str(), R_OK);
-    }
-
-    virtual ~ProcPidStat() {}
-
-    // Collects per-process stats.
-    virtual android::base::Result<void> collect();
-
-    // Returns the latest per-process stats collected.
-    virtual const std::unordered_map<pid_t, ProcessStats> latestStats() const {
-        Mutex::Autolock lock(mMutex);
-        return mLatestProcessStats;
-    }
-
-    // Returns the delta of per-process stats since the last before collection.
-    virtual const std::vector<ProcessStats> deltaStats() const {
-        Mutex::Autolock lock(mMutex);
-        return mDeltaProcessStats;
-    }
-
-    // Called by WatchdogPerfService and tests.
-    virtual bool enabled() { return mEnabled; }
-
-    virtual std::string dirPath() { return mPath; }
-
-private:
-    // Reads the contents of the below files:
-    // 1. Pid stat file at |mPath| + |kStatFileFormat|
-    // 2. Aggregated per-process status at |mPath| + |kStatusFileFormat|
-    // 3. Tid stat file at |mPath| + |kTaskDirFormat| + |kStatFileFormat|
-    android::base::Result<std::unordered_map<pid_t, ProcessStats>> getProcessStatsLocked() const;
-
-    // Makes sure only one collection is running at any given time.
-    mutable Mutex mMutex;
-
-    // Latest dump of per-process stats. Useful for calculating the delta and identifying PID/TID
-    // reuse.
-    std::unordered_map<pid_t, ProcessStats> mLatestProcessStats GUARDED_BY(mMutex);
-
-    // Latest delta of per-process stats.
-    std::vector<ProcessStats> mDeltaProcessStats GUARDED_BY(mMutex);
-
-    // True if the below files are accessible:
-    // 1. Pid stat file at |mPath| + |kTaskStatFileFormat|
-    // 2. Tid stat file at |mPath| + |kTaskDirFormat| + |kStatFileFormat|
-    // 3. Pid status file at |mPath| + |kStatusFileFormat|
-    // Otherwise, set to false.
-    bool mEnabled;
-
-    // Proc directory path. Default value is |kProcDirPath|.
-    // Updated by tests to point to a different location when needed.
-    std::string mPath;
-
-    FRIEND_TEST(IoPerfCollectionTest, TestValidProcPidContents);
-    FRIEND_TEST(ProcPidStatTest, TestValidStatFiles);
-    FRIEND_TEST(ProcPidStatTest, TestHandlesProcessTerminationBetweenScanningAndParsing);
-    FRIEND_TEST(ProcPidStatTest, TestHandlesPidTidReuse);
-};
-
-}  // namespace watchdog
-}  // namespace automotive
-}  // namespace android
-
-#endif  //  CPP_WATCHDOG_SERVER_SRC_PROCPIDSTAT_H_
diff --git a/cpp/watchdog/server/src/ProcStat.cpp b/cpp/watchdog/server/src/ProcStat.cpp
index 0a78dcd..7c5337b 100644
--- a/cpp/watchdog/server/src/ProcStat.cpp
+++ b/cpp/watchdog/server/src/ProcStat.cpp
@@ -42,8 +42,9 @@
 bool parseCpuStats(const std::string& data, CpuStats* cpuStats) {
     std::vector<std::string> fields = Split(data, " ");
     if (fields.size() == 12 && fields[1].empty()) {
-        // The first cpu line will have an extra space after the first word. This will generate an
-        // empty element when the line is split on " ". Erase the extra element.
+        /* The first cpu line will have an extra space after the first word. This will generate an
+         * empty element when the line is split on " ". Erase the extra element.
+         */
         fields.erase(fields.begin() + 1);
     }
     if (fields.size() != 11 || fields[0] != "cpu" || !ParseUint(fields[1], &cpuStats->userTime) ||
@@ -115,7 +116,7 @@
                 if (didReadProcsRunning) {
                     return Error() << "Duplicate `procs_running .*` line in " << kPath;
                 }
-                if (!parseProcsCount(std::move(lines[i]), &info.runnableProcessesCnt)) {
+                if (!parseProcsCount(std::move(lines[i]), &info.runnableProcessCount)) {
                     return Error() << "Failed to parse `procs_running .*` line in " << kPath;
                 }
                 didReadProcsRunning = true;
@@ -124,7 +125,7 @@
                 if (didReadProcsBlocked) {
                     return Error() << "Duplicate `procs_blocked .*` line in " << kPath;
                 }
-                if (!parseProcsCount(std::move(lines[i]), &info.ioBlockedProcessesCnt)) {
+                if (!parseProcsCount(std::move(lines[i]), &info.ioBlockedProcessCount)) {
                     return Error() << "Failed to parse `procs_blocked .*` line in " << kPath;
                 }
                 didReadProcsBlocked = true;
diff --git a/cpp/watchdog/server/src/ProcStat.h b/cpp/watchdog/server/src/ProcStat.h
index 2e660a1..3bb03b4 100644
--- a/cpp/watchdog/server/src/ProcStat.h
+++ b/cpp/watchdog/server/src/ProcStat.h
@@ -18,10 +18,11 @@
 #define CPP_WATCHDOG_SERVER_SRC_PROCSTAT_H_
 
 #include <android-base/result.h>
-#include <stdint.h>
 #include <utils/Mutex.h>
 #include <utils/RefBase.h>
 
+#include <stdint.h>
+
 namespace android {
 namespace automotive {
 namespace watchdog {
@@ -57,28 +58,31 @@
 
 class ProcStatInfo {
 public:
-    ProcStatInfo() : cpuStats({}), runnableProcessesCnt(0), ioBlockedProcessesCnt(0) {}
+    ProcStatInfo() : cpuStats({}), runnableProcessCount(0), ioBlockedProcessCount(0) {}
     ProcStatInfo(CpuStats stats, uint32_t runnableCnt, uint32_t ioBlockedCnt) :
-          cpuStats(stats), runnableProcessesCnt(runnableCnt), ioBlockedProcessesCnt(ioBlockedCnt) {}
+          cpuStats(stats),
+          runnableProcessCount(runnableCnt),
+          ioBlockedProcessCount(ioBlockedCnt) {}
     CpuStats cpuStats;
-    uint32_t runnableProcessesCnt;
-    uint32_t ioBlockedProcessesCnt;
+    uint32_t runnableProcessCount;
+    uint32_t ioBlockedProcessCount;
 
     uint64_t totalCpuTime() const {
         return cpuStats.userTime + cpuStats.niceTime + cpuStats.sysTime + cpuStats.idleTime +
                 cpuStats.ioWaitTime + cpuStats.irqTime + cpuStats.softIrqTime + cpuStats.stealTime +
                 cpuStats.guestTime + cpuStats.guestNiceTime;
     }
-    uint32_t totalProcessesCnt() const { return runnableProcessesCnt + ioBlockedProcessesCnt; }
+    uint32_t totalProcessCount() const { return runnableProcessCount + ioBlockedProcessCount; }
     bool operator==(const ProcStatInfo& info) const {
         return memcmp(&cpuStats, &info.cpuStats, sizeof(cpuStats)) == 0 &&
-                runnableProcessesCnt == info.runnableProcessesCnt &&
-                ioBlockedProcessesCnt == info.ioBlockedProcessesCnt;
+                runnableProcessCount == info.runnableProcessCount &&
+                ioBlockedProcessCount == info.ioBlockedProcessCount;
     }
     ProcStatInfo& operator-=(const ProcStatInfo& rhs) {
         cpuStats -= rhs.cpuStats;
-        // Don't diff *ProcessesCnt as they are real-time values unlike |cpuStats|, which are
-        // aggregated values since system startup.
+        /* Don't diff *ProcessCount as they are real-time values unlike |cpuStats|, which are
+         * aggregated values since system startup.
+         */
         return *this;
     }
 };
@@ -96,8 +100,9 @@
     // Collects proc stat delta since the last collection.
     virtual android::base::Result<void> collect();
 
-    // Returns true when the proc stat file is accessible. Otherwise, returns false.
-    // Called by WatchdogPerfService and tests.
+    /* Returns true when the proc stat file is accessible. Otherwise, returns false.
+     * Called by WatchdogPerfService and tests.
+     */
     virtual bool enabled() { return kEnabled; }
 
     virtual std::string filePath() { return kProcStatPath; }
diff --git a/cpp/watchdog/server/src/UidIoStats.cpp b/cpp/watchdog/server/src/UidIoStats.cpp
deleted file mode 100644
index e88693a..0000000
--- a/cpp/watchdog/server/src/UidIoStats.cpp
+++ /dev/null
@@ -1,157 +0,0 @@
-/**
- * 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.
- */
-
-#define LOG_TAG "carwatchdogd"
-
-#include "UidIoStats.h"
-
-#include <android-base/file.h>
-#include <android-base/parseint.h>
-#include <android-base/stringprintf.h>
-#include <android-base/strings.h>
-#include <log/log.h>
-
-#include <inttypes.h>
-
-#include <string>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-namespace android {
-namespace automotive {
-namespace watchdog {
-
-using ::android::base::Error;
-using ::android::base::ParseInt;
-using ::android::base::ParseUint;
-using ::android::base::ReadFileToString;
-using ::android::base::Result;
-using ::android::base::Split;
-using ::android::base::StringPrintf;
-
-namespace {
-
-bool parseUidIoStats(const std::string& data, UidIoUsage* usage) {
-    std::vector<std::string> fields = Split(data, " ");
-    if (fields.size() < 11 || !ParseUint(fields[0], &usage->uid) ||
-        !ParseInt(fields[3], &usage->ios.metrics[READ_BYTES][FOREGROUND]) ||
-        !ParseInt(fields[4], &usage->ios.metrics[WRITE_BYTES][FOREGROUND]) ||
-        !ParseInt(fields[7], &usage->ios.metrics[READ_BYTES][BACKGROUND]) ||
-        !ParseInt(fields[8], &usage->ios.metrics[WRITE_BYTES][BACKGROUND]) ||
-        !ParseInt(fields[9], &usage->ios.metrics[FSYNC_COUNT][FOREGROUND]) ||
-        !ParseInt(fields[10], &usage->ios.metrics[FSYNC_COUNT][BACKGROUND])) {
-        ALOGW("Invalid uid I/O stats: \"%s\"", data.c_str());
-        return false;
-    }
-    return true;
-}
-
-int64_t maybeDiff(int64_t lhs, int64_t rhs) {
-    return lhs > rhs ? lhs - rhs : 0;
-}
-
-}  // namespace
-
-IoUsage& IoUsage::operator-=(const IoUsage& rhs) {
-    metrics[READ_BYTES][FOREGROUND] =
-            maybeDiff(metrics[READ_BYTES][FOREGROUND], rhs.metrics[READ_BYTES][FOREGROUND]);
-    metrics[READ_BYTES][BACKGROUND] =
-            maybeDiff(metrics[READ_BYTES][BACKGROUND], rhs.metrics[READ_BYTES][BACKGROUND]);
-    metrics[WRITE_BYTES][FOREGROUND] =
-            maybeDiff(metrics[WRITE_BYTES][FOREGROUND], rhs.metrics[WRITE_BYTES][FOREGROUND]);
-    metrics[WRITE_BYTES][BACKGROUND] =
-            maybeDiff(metrics[WRITE_BYTES][BACKGROUND], rhs.metrics[WRITE_BYTES][BACKGROUND]);
-    metrics[FSYNC_COUNT][FOREGROUND] =
-            maybeDiff(metrics[FSYNC_COUNT][FOREGROUND], rhs.metrics[FSYNC_COUNT][FOREGROUND]);
-    metrics[FSYNC_COUNT][BACKGROUND] =
-            maybeDiff(metrics[FSYNC_COUNT][BACKGROUND], rhs.metrics[FSYNC_COUNT][BACKGROUND]);
-    return *this;
-}
-
-bool IoUsage::isZero() const {
-    for (int i = 0; i < METRIC_TYPES; i++) {
-        for (int j = 0; j < UID_STATES; j++) {
-            if (metrics[i][j]) {
-                return false;
-            }
-        }
-    }
-    return true;
-}
-
-std::string IoUsage::toString() const {
-    return StringPrintf("FgRdBytes:%" PRIi64 " BgRdBytes:%" PRIi64 " FgWrBytes:%" PRIi64
-                        " BgWrBytes:%" PRIi64 " FgFsync:%" PRIi64 " BgFsync:%" PRIi64,
-                        metrics[READ_BYTES][FOREGROUND], metrics[READ_BYTES][BACKGROUND],
-                        metrics[WRITE_BYTES][FOREGROUND], metrics[WRITE_BYTES][BACKGROUND],
-                        metrics[FSYNC_COUNT][FOREGROUND], metrics[FSYNC_COUNT][BACKGROUND]);
-}
-
-Result<void> UidIoStats::collect() {
-    if (!kEnabled) {
-        return Error() << "Can not access " << kPath;
-    }
-
-    Mutex::Autolock lock(mMutex);
-    const auto& uidIoUsages = getUidIoUsagesLocked();
-    if (!uidIoUsages.ok() || uidIoUsages->empty()) {
-        return Error() << "Failed to get UID IO stats: " << uidIoUsages.error();
-    }
-
-    mDeltaUidIoUsages.clear();
-    for (const auto& it : *uidIoUsages) {
-        UidIoUsage curUsage = it.second;
-        if (curUsage.ios.isZero()) {
-            continue;
-        }
-        if (mLatestUidIoUsages.find(it.first) != mLatestUidIoUsages.end()) {
-            if (curUsage -= mLatestUidIoUsages[it.first]; curUsage.ios.isZero()) {
-                continue;
-            }
-        }
-        mDeltaUidIoUsages[it.first] = curUsage;
-    }
-    mLatestUidIoUsages = *uidIoUsages;
-    return {};
-}
-
-Result<std::unordered_map<uid_t, UidIoUsage>> UidIoStats::getUidIoUsagesLocked() const {
-    std::string buffer;
-    if (!ReadFileToString(kPath, &buffer)) {
-        return Error() << "ReadFileToString failed for " << kPath;
-    }
-
-    std::vector<std::string> ioStats = Split(std::move(buffer), "\n");
-    std::unordered_map<uid_t, UidIoUsage> uidIoUsages;
-    UidIoUsage usage;
-    for (size_t i = 0; i < ioStats.size(); i++) {
-        if (ioStats[i].empty() || !ioStats[i].compare(0, 4, "task")) {
-            // Skip per-task stats as CONFIG_UID_SYS_STATS_DEBUG is not set in the kernel and
-            // the collected data is aggregated only per-UID.
-            continue;
-        }
-        if (!parseUidIoStats(std::move(ioStats[i]), &usage)) {
-            return Error() << "Failed to parse the contents of " << kPath;
-        }
-        uidIoUsages[usage.uid] = usage;
-    }
-    return uidIoUsages;
-}
-
-}  // namespace watchdog
-}  // namespace automotive
-}  // namespace android
diff --git a/cpp/watchdog/server/src/UidIoStats.h b/cpp/watchdog/server/src/UidIoStats.h
deleted file mode 100644
index b3257f8..0000000
--- a/cpp/watchdog/server/src/UidIoStats.h
+++ /dev/null
@@ -1,147 +0,0 @@
-/**
- * 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_WATCHDOG_SERVER_SRC_UIDIOSTATS_H_
-#define CPP_WATCHDOG_SERVER_SRC_UIDIOSTATS_H_
-
-#include <android-base/result.h>
-#include <android-base/stringprintf.h>
-#include <utils/Mutex.h>
-#include <utils/RefBase.h>
-
-#include <stdint.h>
-
-#include <string>
-#include <unordered_map>
-
-namespace android {
-namespace automotive {
-namespace watchdog {
-
-constexpr const char* kUidIoStatsPath = "/proc/uid_io/stats";
-
-enum UidState {
-    FOREGROUND = 0,
-    BACKGROUND,
-    UID_STATES,
-};
-
-enum MetricType {
-    READ_BYTES = 0,  // bytes read (from storage layer)
-    WRITE_BYTES,     // bytes written (to storage layer)
-    FSYNC_COUNT,     // number of fsync syscalls
-    METRIC_TYPES,
-};
-
-class IoUsage {
-public:
-    IoUsage() : metrics{{0}} {};
-    IoUsage(int64_t fgRdBytes, int64_t bgRdBytes, int64_t fgWrBytes, int64_t bgWrBytes,
-            int64_t fgFsync, int64_t bgFsync) {
-        metrics[READ_BYTES][FOREGROUND] = fgRdBytes;
-        metrics[READ_BYTES][BACKGROUND] = bgRdBytes;
-        metrics[WRITE_BYTES][FOREGROUND] = fgWrBytes;
-        metrics[WRITE_BYTES][BACKGROUND] = bgWrBytes;
-        metrics[FSYNC_COUNT][FOREGROUND] = fgFsync;
-        metrics[FSYNC_COUNT][BACKGROUND] = bgFsync;
-    }
-    IoUsage& operator-=(const IoUsage& rhs);
-    bool operator==(const IoUsage& usage) const {
-        return memcmp(&metrics, &usage.metrics, sizeof(metrics)) == 0;
-    }
-    int64_t sumReadBytes() const {
-        const auto& [fgBytes, bgBytes] =
-                std::tuple(metrics[READ_BYTES][FOREGROUND], metrics[READ_BYTES][BACKGROUND]);
-        return (std::numeric_limits<int64_t>::max() - fgBytes) > bgBytes
-                ? (fgBytes + bgBytes)
-                : std::numeric_limits<int64_t>::max();
-    }
-    int64_t sumWriteBytes() const {
-        const auto& [fgBytes, bgBytes] =
-                std::tuple(metrics[WRITE_BYTES][FOREGROUND], metrics[WRITE_BYTES][BACKGROUND]);
-        return (std::numeric_limits<int64_t>::max() - fgBytes) > bgBytes
-                ? (fgBytes + bgBytes)
-                : std::numeric_limits<int64_t>::max();
-    }
-    bool isZero() const;
-    std::string toString() const;
-    int64_t metrics[METRIC_TYPES][UID_STATES];
-};
-
-struct UidIoUsage {
-    uid_t uid = 0;  // Linux user id.
-    IoUsage ios = {};
-    UidIoUsage& operator-=(const UidIoUsage& rhs) {
-        ios -= rhs.ios;
-        return *this;
-    }
-    bool operator==(const UidIoUsage& rhs) const { return uid == rhs.uid && ios == rhs.ios; }
-    std::string toString() const {
-        return android::base::StringPrintf("Uid: %d, Usage: {%s}", uid, ios.toString().c_str());
-    }
-};
-
-class UidIoStats : public RefBase {
-public:
-    explicit UidIoStats(const std::string& path = kUidIoStatsPath) :
-          kEnabled(!access(path.c_str(), R_OK)), kPath(path) {}
-
-    virtual ~UidIoStats() {}
-
-    // Collects the per-UID I/O usage.
-    virtual android::base::Result<void> collect();
-
-    virtual const std::unordered_map<uid_t, UidIoUsage> latestStats() const {
-        Mutex::Autolock lock(mMutex);
-        return mLatestUidIoUsages;
-    }
-
-    virtual const std::unordered_map<uid_t, UidIoUsage> deltaStats() const {
-        Mutex::Autolock lock(mMutex);
-        return mDeltaUidIoUsages;
-    }
-
-    // Returns true when the uid_io stats file is accessible. Otherwise, returns false.
-    // Called by IoPerfCollection and tests.
-    virtual bool enabled() { return kEnabled; }
-
-    virtual std::string filePath() { return kPath; }
-
-private:
-    // Reads the contents of |kPath|.
-    android::base::Result<std::unordered_map<uid_t, UidIoUsage>> getUidIoUsagesLocked() const;
-
-    // Makes sure only one collection is running at any given time.
-    mutable Mutex mMutex;
-
-    // Latest dump from the file at |kPath|.
-    std::unordered_map<uid_t, UidIoUsage> mLatestUidIoUsages GUARDED_BY(mMutex);
-
-    // Delta of per-UID I/O usage since last before collection.
-    std::unordered_map<uid_t, UidIoUsage> mDeltaUidIoUsages GUARDED_BY(mMutex);
-
-    // True if kPath is accessible.
-    const bool kEnabled;
-
-    // Path to uid_io stats file. Default path is |kUidIoStatsPath|.
-    const std::string kPath;
-};
-
-}  // namespace watchdog
-}  // namespace automotive
-}  // namespace android
-
-#endif  //  CPP_WATCHDOG_SERVER_SRC_UIDIOSTATS_H_
diff --git a/cpp/watchdog/server/src/UidIoStatsCollector.cpp b/cpp/watchdog/server/src/UidIoStatsCollector.cpp
new file mode 100644
index 0000000..9e8bc32
--- /dev/null
+++ b/cpp/watchdog/server/src/UidIoStatsCollector.cpp
@@ -0,0 +1,155 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "carwatchdogd"
+
+#include "UidIoStatsCollector.h"
+
+#include <android-base/file.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <log/log.h>
+
+#include <inttypes.h>
+
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+using ::android::base::Error;
+using ::android::base::ParseInt;
+using ::android::base::ParseUint;
+using ::android::base::ReadFileToString;
+using ::android::base::Result;
+using ::android::base::Split;
+using ::android::base::StringPrintf;
+
+namespace {
+
+bool parseUidIoStats(const std::string& data, UidIoStats* stats, uid_t* uid) {
+    std::vector<std::string> fields = Split(data, " ");
+    if (fields.size() < 11 || !ParseUint(fields[0], uid) ||
+        !ParseInt(fields[3], &stats->metrics[READ_BYTES][FOREGROUND]) ||
+        !ParseInt(fields[4], &stats->metrics[WRITE_BYTES][FOREGROUND]) ||
+        !ParseInt(fields[7], &stats->metrics[READ_BYTES][BACKGROUND]) ||
+        !ParseInt(fields[8], &stats->metrics[WRITE_BYTES][BACKGROUND]) ||
+        !ParseInt(fields[9], &stats->metrics[FSYNC_COUNT][FOREGROUND]) ||
+        !ParseInt(fields[10], &stats->metrics[FSYNC_COUNT][BACKGROUND])) {
+        ALOGW("Invalid uid I/O stats: \"%s\"", data.c_str());
+        return false;
+    }
+    return true;
+}
+
+}  // namespace
+
+UidIoStats& UidIoStats::operator-=(const UidIoStats& rhs) {
+    const auto diff = [](int64_t lhs, int64_t rhs) -> int64_t { return lhs > rhs ? lhs - rhs : 0; };
+    metrics[READ_BYTES][FOREGROUND] =
+            diff(metrics[READ_BYTES][FOREGROUND], rhs.metrics[READ_BYTES][FOREGROUND]);
+    metrics[READ_BYTES][BACKGROUND] =
+            diff(metrics[READ_BYTES][BACKGROUND], rhs.metrics[READ_BYTES][BACKGROUND]);
+    metrics[WRITE_BYTES][FOREGROUND] =
+            diff(metrics[WRITE_BYTES][FOREGROUND], rhs.metrics[WRITE_BYTES][FOREGROUND]);
+    metrics[WRITE_BYTES][BACKGROUND] =
+            diff(metrics[WRITE_BYTES][BACKGROUND], rhs.metrics[WRITE_BYTES][BACKGROUND]);
+    metrics[FSYNC_COUNT][FOREGROUND] =
+            diff(metrics[FSYNC_COUNT][FOREGROUND], rhs.metrics[FSYNC_COUNT][FOREGROUND]);
+    metrics[FSYNC_COUNT][BACKGROUND] =
+            diff(metrics[FSYNC_COUNT][BACKGROUND], rhs.metrics[FSYNC_COUNT][BACKGROUND]);
+    return *this;
+}
+
+bool UidIoStats::isZero() const {
+    for (int i = 0; i < METRIC_TYPES; i++) {
+        for (int j = 0; j < UID_STATES; j++) {
+            if (metrics[i][j]) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+std::string UidIoStats::toString() const {
+    return StringPrintf("FgRdBytes:%" PRIi64 " BgRdBytes:%" PRIi64 " FgWrBytes:%" PRIi64
+                        " BgWrBytes:%" PRIi64 " FgFsync:%" PRIi64 " BgFsync:%" PRIi64,
+                        metrics[READ_BYTES][FOREGROUND], metrics[READ_BYTES][BACKGROUND],
+                        metrics[WRITE_BYTES][FOREGROUND], metrics[WRITE_BYTES][BACKGROUND],
+                        metrics[FSYNC_COUNT][FOREGROUND], metrics[FSYNC_COUNT][BACKGROUND]);
+}
+
+Result<void> UidIoStatsCollector::collect() {
+    if (!kEnabled) {
+        return Error() << "Can not access " << kPath;
+    }
+
+    Mutex::Autolock lock(mMutex);
+    const auto& uidIoStatsByUid = readUidIoStatsLocked();
+    if (!uidIoStatsByUid.ok() || uidIoStatsByUid->empty()) {
+        return Error() << "Failed to get UID IO stats: " << uidIoStatsByUid.error();
+    }
+
+    mDeltaStats.clear();
+    for (const auto& [uid, uidIoStats] : *uidIoStatsByUid) {
+        if (uidIoStats.isZero()) {
+            continue;
+        }
+        UidIoStats deltaStats = uidIoStats;
+        if (const auto it = mLatestStats.find(uid); it != mLatestStats.end()) {
+            if (deltaStats -= it->second; deltaStats.isZero()) {
+                continue;
+            }
+        }
+        mDeltaStats[uid] = deltaStats;
+    }
+    mLatestStats = *uidIoStatsByUid;
+    return {};
+}
+
+Result<std::unordered_map<uid_t, UidIoStats>> UidIoStatsCollector::readUidIoStatsLocked() const {
+    std::string buffer;
+    if (!ReadFileToString(kPath, &buffer)) {
+        return Error() << "ReadFileToString failed for " << kPath;
+    }
+    std::unordered_map<uid_t, UidIoStats> uidIoStatsByUid;
+    std::vector<std::string> lines = Split(std::move(buffer), "\n");
+    for (const auto& line : lines) {
+        if (line.empty() || !line.compare(0, 4, "task")) {
+            /* Skip per-task stats as CONFIG_UID_SYS_STATS_DEBUG is not set in the kernel and
+             * the collected data is aggregated only per-UID.
+             */
+            continue;
+        }
+        uid_t uid;
+        UidIoStats uidIoStats;
+        if (!parseUidIoStats(line, &uidIoStats, &uid)) {
+            return Error() << "Failed to parse the contents of " << kPath;
+        }
+        uidIoStatsByUid[uid] = uidIoStats;
+    }
+    return uidIoStatsByUid;
+}
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
diff --git a/cpp/watchdog/server/src/UidIoStatsCollector.h b/cpp/watchdog/server/src/UidIoStatsCollector.h
new file mode 100644
index 0000000..b5f4494
--- /dev/null
+++ b/cpp/watchdog/server/src/UidIoStatsCollector.h
@@ -0,0 +1,147 @@
+/*
+ * 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_WATCHDOG_SERVER_SRC_UIDIOSTATSCOLLECTOR_H_
+#define CPP_WATCHDOG_SERVER_SRC_UIDIOSTATSCOLLECTOR_H_
+
+#include <android-base/result.h>
+#include <android-base/stringprintf.h>
+#include <utils/Mutex.h>
+#include <utils/RefBase.h>
+
+#include <stdint.h>
+
+#include <string>
+#include <unordered_map>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+constexpr const char* kUidIoStatsPath = "/proc/uid_io/stats";
+
+enum UidState {
+    FOREGROUND = 0,
+    BACKGROUND,
+    UID_STATES,
+};
+
+enum MetricType {
+    READ_BYTES = 0,  // bytes read (from storage layer)
+    WRITE_BYTES,     // bytes written (to storage layer)
+    FSYNC_COUNT,     // number of fsync syscalls
+    METRIC_TYPES,
+};
+
+// Defines the per-UID I/O stats.
+class UidIoStats {
+public:
+    UidIoStats() : metrics{{0}} {};
+    UidIoStats(int64_t fgRdBytes, int64_t bgRdBytes, int64_t fgWrBytes, int64_t bgWrBytes,
+               int64_t fgFsync, int64_t bgFsync) {
+        metrics[READ_BYTES][FOREGROUND] = fgRdBytes;
+        metrics[READ_BYTES][BACKGROUND] = bgRdBytes;
+        metrics[WRITE_BYTES][FOREGROUND] = fgWrBytes;
+        metrics[WRITE_BYTES][BACKGROUND] = bgWrBytes;
+        metrics[FSYNC_COUNT][FOREGROUND] = fgFsync;
+        metrics[FSYNC_COUNT][BACKGROUND] = bgFsync;
+    }
+    UidIoStats& operator-=(const UidIoStats& rhs);
+    bool operator==(const UidIoStats& stats) const {
+        return memcmp(&metrics, &stats.metrics, sizeof(metrics)) == 0;
+    }
+    int64_t sumReadBytes() const {
+        const auto& [fgBytes, bgBytes] =
+                std::tuple(metrics[READ_BYTES][FOREGROUND], metrics[READ_BYTES][BACKGROUND]);
+        return (std::numeric_limits<int64_t>::max() - fgBytes) > bgBytes
+                ? (fgBytes + bgBytes)
+                : std::numeric_limits<int64_t>::max();
+    }
+    int64_t sumWriteBytes() const {
+        const auto& [fgBytes, bgBytes] =
+                std::tuple(metrics[WRITE_BYTES][FOREGROUND], metrics[WRITE_BYTES][BACKGROUND]);
+        return (std::numeric_limits<int64_t>::max() - fgBytes) > bgBytes
+                ? (fgBytes + bgBytes)
+                : std::numeric_limits<int64_t>::max();
+    }
+    bool isZero() const;
+    std::string toString() const;
+    int64_t metrics[METRIC_TYPES][UID_STATES];
+};
+
+// Collector/Parser for `/proc/uid_io/stats`.
+class UidIoStatsCollectorInterface : public RefBase {
+public:
+    // Collects the per-UID I/O stats.
+    virtual android::base::Result<void> collect() = 0;
+    // Returns the latest per-uid I/O stats.
+    virtual const std::unordered_map<uid_t, UidIoStats> latestStats() const = 0;
+    // Returns the delta of per-uid I/O stats since the last before collection.
+    virtual const std::unordered_map<uid_t, UidIoStats> deltaStats() const = 0;
+    // Returns true only when the per-UID I/O stats file is accessible.
+    virtual bool enabled() const = 0;
+    // Returns the path for the per-UID I/O stats file.
+    virtual const std::string filePath() const = 0;
+};
+
+class UidIoStatsCollector final : public UidIoStatsCollectorInterface {
+public:
+    explicit UidIoStatsCollector(const std::string& path = kUidIoStatsPath) :
+          kEnabled(!access(path.c_str(), R_OK)), kPath(path) {}
+
+    ~UidIoStatsCollector() {}
+
+    android::base::Result<void> collect() override;
+
+    const std::unordered_map<uid_t, UidIoStats> latestStats() const override {
+        Mutex::Autolock lock(mMutex);
+        return mLatestStats;
+    }
+
+    const std::unordered_map<uid_t, UidIoStats> deltaStats() const override {
+        Mutex::Autolock lock(mMutex);
+        return mDeltaStats;
+    }
+
+    bool enabled() const override { return kEnabled; }
+
+    const std::string filePath() const override { return kPath; }
+
+private:
+    // Reads the contents of |kPath|.
+    android::base::Result<std::unordered_map<uid_t, UidIoStats>> readUidIoStatsLocked() const;
+
+    // Makes sure only one collection is running at any given time.
+    mutable Mutex mMutex;
+
+    // Latest dump from the file at |kPath|.
+    std::unordered_map<uid_t, UidIoStats> mLatestStats GUARDED_BY(mMutex);
+
+    // Delta of per-UID I/O stats since last before collection.
+    std::unordered_map<uid_t, UidIoStats> mDeltaStats GUARDED_BY(mMutex);
+
+    // True if kPath is accessible.
+    const bool kEnabled;
+
+    // Path to uid_io stats file. Default path is |kUidIoStatsPath|.
+    const std::string kPath;
+};
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
+
+#endif  //  CPP_WATCHDOG_SERVER_SRC_UIDIOSTATSCOLLECTOR_H_
diff --git a/cpp/watchdog/server/src/UidProcStatsCollector.cpp b/cpp/watchdog/server/src/UidProcStatsCollector.cpp
new file mode 100644
index 0000000..da3bffc
--- /dev/null
+++ b/cpp/watchdog/server/src/UidProcStatsCollector.cpp
@@ -0,0 +1,368 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "carwatchdogd"
+#define DEBUG false  // STOPSHIP if true.
+
+#include "UidProcStatsCollector.h"
+
+#include <android-base/file.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <log/log.h>
+
+#include <dirent.h>
+
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+using ::android::base::EndsWith;
+using ::android::base::Error;
+using ::android::base::ParseInt;
+using ::android::base::ParseUint;
+using ::android::base::ReadFileToString;
+using ::android::base::Result;
+using ::android::base::Split;
+using ::android::base::StringAppendF;
+using ::android::base::Trim;
+
+namespace {
+
+enum ReadError {
+    ERR_INVALID_FILE = 0,
+    ERR_FILE_OPEN_READ = 1,
+    NUM_ERRORS = 2,
+};
+
+// Per-pid/tid stats.
+struct PidStat {
+    std::string comm = "";
+    std::string state = "";
+    uint64_t startTime = 0;
+    uint64_t majorFaults = 0;
+};
+
+/**
+ * /proc/PID/stat or /proc/PID/task/TID/stat format:
+ * <pid> <comm> <state> <ppid> <pgrp ID> <session ID> <tty_nr> <tpgid> <flags> <minor faults>
+ * <children minor faults> <major faults> <children major faults> <user mode time>
+ * <system mode time> <children user mode time> <children kernel mode time> <priority> <nice value>
+ * <num threads> <start time since boot> <virtual memory size> <resident set size> <rss soft limit>
+ * <start code addr> <end code addr> <start stack addr> <ESP value> <EIP> <bitmap of pending sigs>
+ * <bitmap of blocked sigs> <bitmap of ignored sigs> <waiting channel> <num pages swapped>
+ * <cumulative pages swapped> <exit signal> <processor #> <real-time prio> <agg block I/O delays>
+ * <guest time> <children guest time> <start data addr> <end data addr> <start break addr>
+ * <cmd line args start addr> <amd line args end addr> <env start addr> <env end addr> <exit code>
+ * Example line: 1 (init) S 0 0 0 0 0 0 0 0 220 0 0 0 0 0 0 0 2 0 0 ...etc...
+ */
+bool parsePidStatLine(const std::string& line, PidStat* pidStat) {
+    std::vector<std::string> fields = Split(line, " ");
+
+    /* Note: Regex parsing for the below logic increased the time taken to run the
+     * UidProcStatsCollectorTest#TestProcPidStatContentsFromDevice from 151.7ms to 1.3 seconds.
+     *
+     * Comm string is enclosed with ( ) brackets and may contain space(s). Thus calculate the
+     * commEndOffset based on the field that contains the closing bracket.
+     */
+    size_t commEndOffset = 0;
+    for (size_t i = 1; i < fields.size(); ++i) {
+        pidStat->comm += fields[i];
+        if (EndsWith(fields[i], ")")) {
+            commEndOffset = i - 1;
+            break;
+        }
+        pidStat->comm += " ";
+    }
+
+    if (pidStat->comm.front() != '(' || pidStat->comm.back() != ')') {
+        ALOGD("Comm string `%s` not enclosed in brackets", pidStat->comm.c_str());
+        return false;
+    }
+    pidStat->comm.erase(pidStat->comm.begin());
+    pidStat->comm.erase(pidStat->comm.end() - 1);
+
+    if (fields.size() < 22 + commEndOffset ||
+        !ParseUint(fields[11 + commEndOffset], &pidStat->majorFaults) ||
+        !ParseUint(fields[21 + commEndOffset], &pidStat->startTime)) {
+        ALOGD("Invalid proc pid stat contents: \"%s\"", line.c_str());
+        return false;
+    }
+    pidStat->state = fields[2 + commEndOffset];
+    return true;
+}
+
+Result<void> readPidStatFile(const std::string& path, PidStat* pidStat) {
+    std::string buffer;
+    if (!ReadFileToString(path, &buffer)) {
+        return Error(ERR_FILE_OPEN_READ) << "ReadFileToString failed for " << path;
+    }
+    std::vector<std::string> lines = Split(std::move(buffer), "\n");
+    if (lines.size() != 1 && (lines.size() != 2 || !lines[1].empty())) {
+        return Error(ERR_INVALID_FILE) << path << " contains " << lines.size() << " lines != 1";
+    }
+    if (!parsePidStatLine(std::move(lines[0]), pidStat)) {
+        return Error(ERR_INVALID_FILE) << "Failed to parse the contents of " << path;
+    }
+    return {};
+}
+
+Result<std::unordered_map<std::string, std::string>> readKeyValueFile(
+        const std::string& path, const std::string& delimiter) {
+    std::string buffer;
+    if (!ReadFileToString(path, &buffer)) {
+        return Error(ERR_FILE_OPEN_READ) << "ReadFileToString failed for " << path;
+    }
+    std::unordered_map<std::string, std::string> contents;
+    std::vector<std::string> lines = Split(std::move(buffer), "\n");
+    for (size_t i = 0; i < lines.size(); ++i) {
+        if (lines[i].empty()) {
+            continue;
+        }
+        std::vector<std::string> elements = Split(lines[i], delimiter);
+        if (elements.size() < 2) {
+            return Error(ERR_INVALID_FILE)
+                    << "Line \"" << lines[i] << "\" doesn't contain the delimiter \"" << delimiter
+                    << "\" in file " << path;
+        }
+        std::string key = elements[0];
+        std::string value = Trim(lines[i].substr(key.length() + delimiter.length()));
+        if (contents.find(key) != contents.end()) {
+            return Error(ERR_INVALID_FILE)
+                    << "Duplicate " << key << " line: \"" << lines[i] << "\" in file " << path;
+        }
+        contents[key] = value;
+    }
+    return contents;
+}
+
+/**
+ * /proc/PID/status file format:
+ * Tgid:    <Thread group ID of the process>
+ * Uid:     <Read UID>   <Effective UID>   <Saved set UID>   <Filesystem UID>
+ *
+ * Note: Included only the fields that are parsed from the file.
+ */
+Result<std::tuple<uid_t, pid_t>> readPidStatusFile(const std::string& path) {
+    auto result = readKeyValueFile(path, ":\t");
+    if (!result.ok()) {
+        return Error(result.error().code()) << result.error();
+    }
+    auto contents = result.value();
+    if (contents.empty()) {
+        return Error(ERR_INVALID_FILE) << "Empty file " << path;
+    }
+    int64_t uid = 0;
+    int64_t tgid = 0;
+    if (contents.find("Uid") == contents.end() ||
+        !ParseInt(Split(contents["Uid"], "\t")[0], &uid)) {
+        return Error(ERR_INVALID_FILE) << "Failed to read 'UID' from file " << path;
+    }
+    if (contents.find("Tgid") == contents.end() || !ParseInt(contents["Tgid"], &tgid)) {
+        return Error(ERR_INVALID_FILE) << "Failed to read 'Tgid' from file " << path;
+    }
+    return std::make_tuple(uid, tgid);
+}
+
+}  // namespace
+
+std::string ProcessStats::toString() const {
+    return StringPrintf("{comm: %s, startTime: %" PRIu64 ", totalMajorFaults: %" PRIu64
+                        ", totalTasksCount: %d, ioBlockedTasksCount: %d}",
+                        comm.c_str(), startTime, totalMajorFaults, totalTasksCount,
+                        ioBlockedTasksCount);
+}
+
+std::string UidProcStats::toString() const {
+    std::string buffer;
+    StringAppendF(&buffer,
+                  "UidProcStats{totalMajorFaults: %" PRIu64 ", totalTasksCount: %d,"
+                  "ioBlockedTasksCount: %d, processStatsByPid: {",
+                  totalMajorFaults, totalTasksCount, ioBlockedTasksCount);
+    for (const auto& [pid, processStats] : processStatsByPid) {
+        StringAppendF(&buffer, "{pid: %" PRIi32 ", processStats: %s},", pid,
+                      processStats.toString().c_str());
+    }
+    StringAppendF(&buffer, "}");
+    return buffer;
+}
+
+Result<void> UidProcStatsCollector::collect() {
+    if (!mEnabled) {
+        return Error() << "Can not access PID stat files under " << kProcDirPath;
+    }
+
+    Mutex::Autolock lock(mMutex);
+    auto uidProcStatsByUid = readUidProcStatsLocked();
+    if (!uidProcStatsByUid.ok()) {
+        return Error() << uidProcStatsByUid.error();
+    }
+
+    mDeltaStats.clear();
+    for (const auto& [uid, currStats] : *uidProcStatsByUid) {
+        if (const auto& it = mLatestStats.find(uid); it == mLatestStats.end()) {
+            mDeltaStats[uid] = currStats;
+            continue;
+        }
+        const auto& prevStats = mLatestStats[uid];
+        UidProcStats deltaStats = {
+                .totalTasksCount = currStats.totalTasksCount,
+                .ioBlockedTasksCount = currStats.ioBlockedTasksCount,
+        };
+        for (const auto& [pid, processStats] : currStats.processStatsByPid) {
+            ProcessStats deltaProcessStats = processStats;
+            if (const auto& it = prevStats.processStatsByPid.find(pid);
+                it != prevStats.processStatsByPid.end() &&
+                it->second.startTime == processStats.startTime &&
+                it->second.totalMajorFaults <= deltaProcessStats.totalMajorFaults) {
+                deltaProcessStats.totalMajorFaults =
+                        deltaProcessStats.totalMajorFaults - it->second.totalMajorFaults;
+            }
+            deltaStats.totalMajorFaults += deltaProcessStats.totalMajorFaults;
+            deltaStats.processStatsByPid[pid] = deltaProcessStats;
+        }
+        mDeltaStats[uid] = std::move(deltaStats);
+    }
+    mLatestStats = std::move(*uidProcStatsByUid);
+    return {};
+}
+
+Result<std::unordered_map<uid_t, UidProcStats>> UidProcStatsCollector::readUidProcStatsLocked()
+        const {
+    std::unordered_map<uid_t, UidProcStats> uidProcStatsByUid;
+    auto procDirp = std::unique_ptr<DIR, int (*)(DIR*)>(opendir(mPath.c_str()), closedir);
+    if (!procDirp) {
+        return Error() << "Failed to open " << mPath << " directory";
+    }
+    for (dirent* pidDir = nullptr; (pidDir = readdir(procDirp.get())) != nullptr;) {
+        pid_t pid = 0;
+        if (pidDir->d_type != DT_DIR || !ParseInt(pidDir->d_name, &pid)) {
+            continue;
+        }
+        auto result = readProcessStatsLocked(pid);
+        if (!result.ok()) {
+            if (result.error().code() != ERR_FILE_OPEN_READ) {
+                return Error() << result.error();
+            }
+            /* |ERR_FILE_OPEN_READ| is a soft-error because PID may disappear between scanning and
+             * reading directory/files.
+             */
+            if (DEBUG) {
+                ALOGD("%s", result.error().message().c_str());
+            }
+            continue;
+        }
+        uid_t uid = std::get<0>(*result);
+        ProcessStats processStats = std::get<ProcessStats>(*result);
+        if (uidProcStatsByUid.find(uid) == uidProcStatsByUid.end()) {
+            uidProcStatsByUid[uid] = {};
+        }
+        UidProcStats* uidProcStats = &uidProcStatsByUid[uid];
+        uidProcStats->totalMajorFaults += processStats.totalMajorFaults;
+        uidProcStats->totalTasksCount += processStats.totalTasksCount;
+        uidProcStats->ioBlockedTasksCount += processStats.ioBlockedTasksCount;
+        uidProcStats->processStatsByPid[pid] = std::move(processStats);
+    }
+    return uidProcStatsByUid;
+}
+
+Result<std::tuple<uid_t, ProcessStats>> UidProcStatsCollector::readProcessStatsLocked(
+        pid_t pid) const {
+    // 1. Read top-level pid stats.
+    PidStat pidStat = {};
+    std::string path = StringPrintf((mPath + kStatFileFormat).c_str(), pid);
+    if (auto result = readPidStatFile(path, &pidStat); !result.ok()) {
+        return Error(result.error().code())
+                << "Failed to read top-level per-process stat file '%s': %s"
+                << result.error().message().c_str();
+    }
+
+    // 2. Read aggregated process status.
+    pid_t tgid = -1;
+    uid_t uid = -1;
+    path = StringPrintf((mPath + kStatusFileFormat).c_str(), pid);
+    if (auto result = readPidStatusFile(path); !result.ok()) {
+        if (result.error().code() != ERR_FILE_OPEN_READ) {
+            return Error() << "Failed to read pid status for pid " << pid << ": "
+                           << result.error().message().c_str();
+        }
+        for (const auto& [curUid, uidProcStats] : mLatestStats) {
+            if (const auto it = uidProcStats.processStatsByPid.find(pid);
+                it != uidProcStats.processStatsByPid.end() &&
+                it->second.startTime == pidStat.startTime) {
+                tgid = pid;
+                uid = curUid;
+                break;
+            }
+        }
+    } else {
+        uid = std::get<0>(*result);
+        tgid = std::get<1>(*result);
+    }
+
+    if (uid == -1 || tgid != pid) {
+        return Error(ERR_FILE_OPEN_READ)
+                << "Skipping PID '" << pid << "' because either Tgid != PID or invalid UID";
+    }
+
+    ProcessStats processStats = {
+            .comm = std::move(pidStat.comm),
+            .startTime = pidStat.startTime,
+            .totalTasksCount = 1,
+            /* Top-level process stats has the aggregated major page faults count and this should be
+             * persistent across thread creation/termination. Thus use the value from this field.
+             */
+            .totalMajorFaults = pidStat.majorFaults,
+            .ioBlockedTasksCount = pidStat.state == "D" ? 1 : 0,
+    };
+
+    // 3. Read per-thread stats.
+    std::string taskDir = StringPrintf((mPath + kTaskDirFormat).c_str(), pid);
+    bool didReadMainThread = false;
+    auto taskDirp = std::unique_ptr<DIR, int (*)(DIR*)>(opendir(taskDir.c_str()), closedir);
+    for (dirent* tidDir = nullptr;
+         taskDirp != nullptr && (tidDir = readdir(taskDirp.get())) != nullptr;) {
+        pid_t tid = 0;
+        if (tidDir->d_type != DT_DIR || !ParseInt(tidDir->d_name, &tid) || tid == pid) {
+            continue;
+        }
+
+        PidStat tidStat = {};
+        path = StringPrintf((taskDir + kStatFileFormat).c_str(), tid);
+        if (const auto& result = readPidStatFile(path, &tidStat); !result.ok()) {
+            if (result.error().code() != ERR_FILE_OPEN_READ) {
+                return Error() << "Failed to read per-thread stat file: "
+                               << result.error().message().c_str();
+            }
+            /* Maybe the thread terminated before reading the file so skip this thread and
+             * continue with scanning the next thread's stat.
+             */
+            continue;
+        }
+        processStats.ioBlockedTasksCount += tidStat.state == "D" ? 1 : 0;
+        processStats.totalTasksCount += 1;
+    }
+    return std::make_tuple(uid, processStats);
+}
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
diff --git a/cpp/watchdog/server/src/UidProcStatsCollector.h b/cpp/watchdog/server/src/UidProcStatsCollector.h
new file mode 100644
index 0000000..d0ec3c0
--- /dev/null
+++ b/cpp/watchdog/server/src/UidProcStatsCollector.h
@@ -0,0 +1,160 @@
+/*
+ * 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_WATCHDOG_SERVER_SRC_UIDPROCSTATSCOLLECTOR_H_
+#define CPP_WATCHDOG_SERVER_SRC_UIDPROCSTATSCOLLECTOR_H_
+
+#include <android-base/result.h>
+#include <android-base/stringprintf.h>
+#include <gtest/gtest_prod.h>
+#include <utils/Mutex.h>
+#include <utils/RefBase.h>
+
+#include <inttypes.h>
+#include <stdint.h>
+
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+using ::android::base::StringPrintf;
+
+#define PID_FOR_INIT 1
+
+constexpr const char kProcDirPath[] = "/proc";
+constexpr const char kStatFileFormat[] = "/%" PRIu32 "/stat";
+constexpr const char kTaskDirFormat[] = "/%" PRIu32 "/task";
+constexpr const char kStatusFileFormat[] = "/%" PRIu32 "/status";
+
+// Per-process stats.
+struct ProcessStats {
+    std::string comm = "";
+    uint64_t startTime = 0;  // Useful when identifying PID reuse
+    uint64_t totalMajorFaults = 0;
+    int totalTasksCount = 0;
+    int ioBlockedTasksCount = 0;
+    std::string toString() const;
+};
+
+// Per-UID stats.
+struct UidProcStats {
+    uint64_t totalMajorFaults = 0;
+    int totalTasksCount = 0;
+    int ioBlockedTasksCount = 0;
+    std::unordered_map<pid_t, ProcessStats> processStatsByPid = {};
+    std::string toString() const;
+};
+
+/**
+ * Collector/parser for `/proc/[pid]/stat`, `/proc/[pid]/task/[tid]/stat` and /proc/[pid]/status`
+ * files.
+ */
+class UidProcStatsCollectorInterface : public RefBase {
+public:
+    // Collects the per-uid stats from /proc directory.
+    virtual android::base::Result<void> collect() = 0;
+    // Returns the latest per-uid process stats.
+    virtual const std::unordered_map<uid_t, UidProcStats> latestStats() const = 0;
+    // Returns the delta of per-uid process stats since the last before collection.
+    virtual const std::unordered_map<uid_t, UidProcStats> deltaStats() const = 0;
+    // Returns true only when the /proc files for the init process are accessible.
+    virtual bool enabled() const = 0;
+    // Returns the /proc files common ancestor directory path.
+    virtual const std::string dirPath() const = 0;
+};
+
+class UidProcStatsCollector final : public UidProcStatsCollectorInterface {
+public:
+    explicit UidProcStatsCollector(const std::string& path = kProcDirPath) :
+          mLatestStats({}),
+          mPath(path) {
+        std::string pidStatPath = StringPrintf((mPath + kStatFileFormat).c_str(), PID_FOR_INIT);
+        std::string tidStatPath = StringPrintf((mPath + kTaskDirFormat + kStatFileFormat).c_str(),
+                                               PID_FOR_INIT, PID_FOR_INIT);
+        std::string pidStatusPath = StringPrintf((mPath + kStatusFileFormat).c_str(), PID_FOR_INIT);
+
+        mEnabled = !access(pidStatPath.c_str(), R_OK) && !access(tidStatPath.c_str(), R_OK) &&
+                !access(pidStatusPath.c_str(), R_OK);
+    }
+
+    ~UidProcStatsCollector() {}
+
+    android::base::Result<void> collect() override;
+
+    const std::unordered_map<uid_t, UidProcStats> latestStats() const {
+        Mutex::Autolock lock(mMutex);
+        return mLatestStats;
+    }
+
+    const std::unordered_map<uid_t, UidProcStats> deltaStats() const {
+        Mutex::Autolock lock(mMutex);
+        return mDeltaStats;
+    }
+
+    bool enabled() const { return mEnabled; }
+
+    const std::string dirPath() const { return mPath; }
+
+private:
+    android::base::Result<std::unordered_map<uid_t, UidProcStats>> readUidProcStatsLocked() const;
+
+    /**
+     * Reads the contents of the below files:
+     * 1. Pid stat file at |mPath| + |kStatFileFormat|
+     * 2. Aggregated per-process status at |mPath| + |kStatusFileFormat|
+     * 3. Tid stat file at |mPath| + |kTaskDirFormat| + |kStatFileFormat|
+     */
+    android::base::Result<std::tuple<uid_t, ProcessStats>> readProcessStatsLocked(pid_t pid) const;
+
+    // Makes sure only one collection is running at any given time.
+    mutable Mutex mMutex;
+
+    // Latest dump of per-UID stats.
+    std::unordered_map<uid_t, UidProcStats> mLatestStats GUARDED_BY(mMutex);
+
+    // Latest delta of per-uid stats.
+    std::unordered_map<uid_t, UidProcStats> mDeltaStats GUARDED_BY(mMutex);
+
+    /**
+     * True if the below files are accessible:
+     * 1. Pid stat file at |mPath| + |kTaskStatFileFormat|
+     * 2. Tid stat file at |mPath| + |kTaskDirFormat| + |kStatFileFormat|
+     * 3. Pid status file at |mPath| + |kStatusFileFormat|
+     * Otherwise, set to false.
+     */
+    bool mEnabled;
+
+    /**
+     * Proc directory path. Default value is |kProcDirPath|.
+     * Updated by tests to point to a different location when needed.
+     */
+    std::string mPath;
+
+    FRIEND_TEST(IoPerfCollectionTest, TestValidProcPidContents);
+    FRIEND_TEST(UidProcStatsCollectorTest, TestValidStatFiles);
+    FRIEND_TEST(UidProcStatsCollectorTest, TestHandlesProcessTerminationBetweenScanningAndParsing);
+    FRIEND_TEST(UidProcStatsCollectorTest, TestHandlesPidTidReuse);
+};
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
+
+#endif  //  CPP_WATCHDOG_SERVER_SRC_UIDPROCSTATSCOLLECTOR_H_
diff --git a/cpp/watchdog/server/src/UidStatsCollector.cpp b/cpp/watchdog/server/src/UidStatsCollector.cpp
new file mode 100644
index 0000000..19d0fc9
--- /dev/null
+++ b/cpp/watchdog/server/src/UidStatsCollector.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "carwatchdogd"
+
+#include "UidStatsCollector.h"
+
+#include <algorithm>
+#include <unordered_map>
+#include <unordered_set>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+using ::android::sp;
+using ::android::base::Error;
+using ::android::base::Result;
+
+bool UidStats::hasPackageInfo() const {
+    return !packageInfo.packageIdentifier.name.empty();
+}
+
+uid_t UidStats::uid() const {
+    return static_cast<uid_t>(packageInfo.packageIdentifier.uid);
+}
+
+std::string UidStats::genericPackageName() const {
+    if (hasPackageInfo()) {
+        return packageInfo.packageIdentifier.name;
+    }
+    return std::to_string(packageInfo.packageIdentifier.uid);
+}
+
+Result<void> UidStatsCollector::collect() {
+    if (mUidProcStatsCollector->enabled()) {
+        if (const auto& result = mUidIoStatsCollector->collect(); !result.ok()) {
+            return Error() << "Failed to collect per-uid I/O stats: " << result.error();
+        }
+    }
+    if (mUidProcStatsCollector->enabled()) {
+        if (const auto& result = mUidProcStatsCollector->collect(); !result.ok()) {
+            return Error() << "Failed to collect per-uid process stats: " << result.error();
+        }
+    }
+    mLatestStats =
+            process(mUidIoStatsCollector->latestStats(), mUidProcStatsCollector->latestStats());
+    mDeltaStats = process(mUidIoStatsCollector->deltaStats(), mUidProcStatsCollector->deltaStats());
+    return {};
+}
+
+std::vector<UidStats> UidStatsCollector::process(
+        const std::unordered_map<uid_t, UidIoStats>& uidIoStatsByUid,
+        const std::unordered_map<uid_t, UidProcStats>& uidProcStatsByUid) const {
+    if (uidIoStatsByUid.empty() && uidProcStatsByUid.empty()) {
+        return std::vector<UidStats>();
+    }
+    std::unordered_set<uid_t> uidSet;
+    for (const auto& [uid, _] : uidIoStatsByUid) {
+        uidSet.insert(uid);
+    }
+    for (const auto& [uid, _] : uidProcStatsByUid) {
+        uidSet.insert(uid);
+    }
+    std::vector<uid_t> uids;
+    for (const auto& uid : uidSet) {
+        uids.push_back(uid);
+    }
+    const auto packageInfoByUid = mPackageInfoResolver->getPackageInfosForUids(uids);
+    std::vector<UidStats> uidStats;
+    for (const auto& uid : uids) {
+        UidStats curUidStats;
+        if (const auto it = packageInfoByUid.find(uid); it != packageInfoByUid.end()) {
+            curUidStats.packageInfo = it->second;
+        } else {
+            curUidStats.packageInfo.packageIdentifier.uid = uid;
+        }
+        if (const auto it = uidIoStatsByUid.find(uid); it != uidIoStatsByUid.end()) {
+            curUidStats.ioStats = it->second;
+        }
+        if (const auto it = uidProcStatsByUid.find(uid); it != uidProcStatsByUid.end()) {
+            curUidStats.procStats = it->second;
+        }
+        uidStats.emplace_back(std::move(curUidStats));
+    }
+    return uidStats;
+}
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
diff --git a/cpp/watchdog/server/src/UidStatsCollector.h b/cpp/watchdog/server/src/UidStatsCollector.h
new file mode 100644
index 0000000..9a81abe
--- /dev/null
+++ b/cpp/watchdog/server/src/UidStatsCollector.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_WATCHDOG_SERVER_SRC_UIDSTATSCOLLECTOR_H_
+#define CPP_WATCHDOG_SERVER_SRC_UIDSTATSCOLLECTOR_H_
+
+#include "PackageInfoResolver.h"
+#include "UidIoStatsCollector.h"
+#include "UidProcStatsCollector.h"
+
+#include <android-base/result.h>
+#include <android/automotive/watchdog/internal/PackageInfo.h>
+#include <utils/Mutex.h>
+#include <utils/RefBase.h>
+#include <utils/StrongPointer.h>
+
+#include <string>
+#include <vector>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+// Forward declaration for testing use only.
+namespace internal {
+
+class UidStatsCollectorPeer;
+
+}  // namespace internal
+
+struct UidStats {
+    android::automotive::watchdog::internal::PackageInfo packageInfo;
+    UidIoStats ioStats = {};
+    UidProcStats procStats = {};
+    // Returns true when package info is available.
+    bool hasPackageInfo() const;
+    // Returns package name if the |packageInfo| is available. Otherwise, returns the |uid|.
+    std::string genericPackageName() const;
+    // Returns the uid for the stats;
+    uid_t uid() const;
+};
+
+// Collector/Aggregator for per-UID I/O and proc stats.
+class UidStatsCollectorInterface : public RefBase {
+public:
+    // Collects the per-UID I/O and proc stats.
+    virtual android::base::Result<void> collect() = 0;
+    // Returns the latest per-uid I/O and proc stats.
+    virtual const std::vector<UidStats> latestStats() const = 0;
+    // Returns the delta of per-uid I/O and proc stats since the last before collection.
+    virtual const std::vector<UidStats> deltaStats() const = 0;
+    // Returns true only when the per-UID I/O or proc stats files are accessible.
+    virtual bool enabled() const = 0;
+};
+
+class UidStatsCollector final : public UidStatsCollectorInterface {
+public:
+    UidStatsCollector() :
+          mPackageInfoResolver(PackageInfoResolver::getInstance()),
+          mUidIoStatsCollector(android::sp<UidIoStatsCollector>::make()),
+          mUidProcStatsCollector(android::sp<UidProcStatsCollector>::make()) {}
+
+    android::base::Result<void> collect() override;
+
+    const std::vector<UidStats> latestStats() const override {
+        Mutex::Autolock lock(mMutex);
+        return mLatestStats;
+    }
+
+    const std::vector<UidStats> deltaStats() const override {
+        Mutex::Autolock lock(mMutex);
+        return mDeltaStats;
+    }
+
+    bool enabled() const override {
+        return mUidIoStatsCollector->enabled() || mUidProcStatsCollector->enabled();
+    }
+
+private:
+    std::vector<UidStats> process(
+            const std::unordered_map<uid_t, UidIoStats>& uidIoStatsByUid,
+            const std::unordered_map<uid_t, UidProcStats>& uidProcStatsByUid) const;
+    // Local IPackageInfoResolver instance. Useful to mock in tests.
+    sp<IPackageInfoResolver> mPackageInfoResolver;
+
+    mutable Mutex mMutex;
+
+    android::sp<UidIoStatsCollectorInterface> mUidIoStatsCollector;
+
+    android::sp<UidProcStatsCollectorInterface> mUidProcStatsCollector;
+
+    std::vector<UidStats> mLatestStats;
+
+    std::vector<UidStats> mDeltaStats;
+
+    // For unit tests.
+    friend class internal::UidStatsCollectorPeer;
+};
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
+
+#endif  //  CPP_WATCHDOG_SERVER_SRC_UIDSTATSCOLLECTOR_H_
diff --git a/cpp/watchdog/server/src/WatchdogPerfService.cpp b/cpp/watchdog/server/src/WatchdogPerfService.cpp
index bf7480f..2761b34 100644
--- a/cpp/watchdog/server/src/WatchdogPerfService.cpp
+++ b/cpp/watchdog/server/src/WatchdogPerfService.cpp
@@ -370,7 +370,7 @@
                             << kEndCustomCollectionFlag << " flags";
 }
 
-Result<void> WatchdogPerfService::onDump(int fd) {
+Result<void> WatchdogPerfService::onDump(int fd) const {
     Mutex::Autolock lock(mMutex);
     if (mCurrCollectionEvent == EventType::TERMINATED) {
         ALOGW("%s not active. Dumping cached data", kServiceName);
@@ -407,7 +407,7 @@
     return {};
 }
 
-bool WatchdogPerfService::dumpHelpText(int fd) {
+bool WatchdogPerfService::dumpHelpText(int fd) const {
     return WriteStringToFd(StringPrintf(kHelpText, kServiceName, kStartCustomCollectionFlag,
                                         kIntervalFlag,
                                         std::chrono::duration_cast<std::chrono::seconds>(
@@ -421,12 +421,11 @@
                            fd);
 }
 
-Result<void> WatchdogPerfService::dumpCollectorsStatusLocked(int fd) {
-    if (!mUidIoStats->enabled() &&
-        !WriteStringToFd(StringPrintf("UidIoStats collector failed to access the file %s",
-                                      mUidIoStats->filePath().c_str()),
+Result<void> WatchdogPerfService::dumpCollectorsStatusLocked(int fd) const {
+    if (!mUidStatsCollector->enabled() &&
+        !WriteStringToFd(StringPrintf("UidStatsCollector failed to access proc and I/O files"),
                          fd)) {
-        return Error() << "Failed to write UidIoStats collector status";
+        return Error() << "Failed to write UidStatsCollector status";
     }
     if (!mProcStat->enabled() &&
         !WriteStringToFd(StringPrintf("ProcStat collector failed to access the file %s",
@@ -434,12 +433,6 @@
                          fd)) {
         return Error() << "Failed to write ProcStat collector status";
     }
-    if (!mProcPidStat->enabled() &&
-        !WriteStringToFd(StringPrintf("ProcPidStat collector failed to access the directory %s",
-                                      mProcPidStat->dirPath().c_str()),
-                         fd)) {
-        return Error() << "Failed to write ProcPidStat collector status";
-    }
     return {};
 }
 
@@ -620,15 +613,15 @@
 }
 
 Result<void> WatchdogPerfService::collectLocked(WatchdogPerfService::EventMetadata* metadata) {
-    if (!mUidIoStats->enabled() && !mProcStat->enabled() && !mProcPidStat->enabled()) {
+    if (!mUidStatsCollector->enabled() && !mProcStat->enabled()) {
         return Error() << "No collectors enabled";
     }
 
     time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
 
-    if (mUidIoStats->enabled()) {
-        if (const auto result = mUidIoStats->collect(); !result.ok()) {
-            return Error() << "Failed to collect per-uid I/O usage: " << result.error();
+    if (mUidStatsCollector->enabled()) {
+        if (const auto result = mUidStatsCollector->collect(); !result.ok()) {
+            return Error() << "Failed to collect per-uid proc and I/O stats: " << result.error();
         }
     }
 
@@ -638,25 +631,19 @@
         }
     }
 
-    if (mProcPidStat->enabled()) {
-        if (const auto result = mProcPidStat->collect(); !result.ok()) {
-            return Error() << "Failed to collect process stats: " << result.error();
-        }
-    }
-
     for (const auto& processor : mDataProcessors) {
         Result<void> result;
         switch (mCurrCollectionEvent) {
             case EventType::BOOT_TIME_COLLECTION:
-                result = processor->onBoottimeCollection(now, mUidIoStats, mProcStat, mProcPidStat);
+                result = processor->onBoottimeCollection(now, mUidStatsCollector, mProcStat);
                 break;
             case EventType::PERIODIC_COLLECTION:
-                result = processor->onPeriodicCollection(now, mSystemState, mUidIoStats, mProcStat,
-                                                         mProcPidStat);
+                result = processor->onPeriodicCollection(now, mSystemState, mUidStatsCollector,
+                                                         mProcStat);
                 break;
             case EventType::CUSTOM_COLLECTION:
                 result = processor->onCustomCollection(now, mSystemState, metadata->filterPackages,
-                                                       mUidIoStats, mProcStat, mProcPidStat);
+                                                       mUidStatsCollector, mProcStat);
                 break;
             default:
                 result = Error() << "Invalid collection event " << toString(mCurrCollectionEvent);
diff --git a/cpp/watchdog/server/src/WatchdogPerfService.h b/cpp/watchdog/server/src/WatchdogPerfService.h
index 7ff9e56..8fdf303 100644
--- a/cpp/watchdog/server/src/WatchdogPerfService.h
+++ b/cpp/watchdog/server/src/WatchdogPerfService.h
@@ -19,9 +19,8 @@
 
 #include "LooperWrapper.h"
 #include "ProcDiskStats.h"
-#include "ProcPidStat.h"
 #include "ProcStat.h"
-#include "UidIoStats.h"
+#include "UidStatsCollector.h"
 
 #include <android-base/chrono_utils.h>
 #include <android-base/result.h>
@@ -64,7 +63,7 @@
     GARAGE_MODE = 1,
 };
 
-/*
+/**
  * DataProcessor defines methods that must be implemented in order to process the data collected
  * by |WatchdogPerfService|.
  */
@@ -73,29 +72,30 @@
     IDataProcessorInterface() {}
     virtual ~IDataProcessorInterface() {}
     // Returns the name of the data processor.
-    virtual std::string name() = 0;
+    virtual std::string name() const = 0;
     // Callback to initialize the data processor.
     virtual android::base::Result<void> init() = 0;
     // Callback to terminate the data processor.
     virtual void terminate() = 0;
     // Callback to process the data collected during boot-time.
     virtual android::base::Result<void> onBoottimeCollection(
-            time_t time, const android::wp<UidIoStats>& uidIoStats,
-            const android::wp<ProcStat>& procStat, const android::wp<ProcPidStat>& procPidStat) = 0;
+            time_t time, const android::wp<UidStatsCollectorInterface>& uidStatsCollector,
+            const android::wp<ProcStat>& procStat) = 0;
     // Callback to process the data collected periodically post boot complete.
     virtual android::base::Result<void> onPeriodicCollection(
-            time_t time, SystemState systemState, const android::wp<UidIoStats>& uidIoStats,
-            const android::wp<ProcStat>& procStat, const android::wp<ProcPidStat>& procPidStat) = 0;
-    /*
+            time_t time, SystemState systemState,
+            const android::wp<UidStatsCollectorInterface>& uidStatsCollector,
+            const android::wp<ProcStat>& procStat) = 0;
+    /**
      * Callback to process the data collected on custom collection and filter the results only to
      * the specified |filterPackages|.
      */
     virtual android::base::Result<void> onCustomCollection(
             time_t time, SystemState systemState,
             const std::unordered_set<std::string>& filterPackages,
-            const android::wp<UidIoStats>& uidIoStats, const android::wp<ProcStat>& procStat,
-            const android::wp<ProcPidStat>& procPidStat) = 0;
-    /*
+            const android::wp<UidStatsCollectorInterface>& uidStatsCollector,
+            const android::wp<ProcStat>& procStat) = 0;
+    /**
      * Callback to periodically monitor the collected data and trigger the given |alertHandler|
      * on detecting resource overuse.
      */
@@ -103,8 +103,8 @@
             time_t time, const android::wp<IProcDiskStatsInterface>& procDiskStats,
             const std::function<void()>& alertHandler) = 0;
     // Callback to dump the boot-time collected and periodically collected data.
-    virtual android::base::Result<void> onDump(int fd) = 0;
-    /*
+    virtual android::base::Result<void> onDump(int fd) const = 0;
+    /**
      * Callback to dump the custom collected data. When fd == -1, clear the custom collection cache.
      */
     virtual android::base::Result<void> onCustomCollectionDump(int fd) = 0;
@@ -127,20 +127,20 @@
 };
 
 enum SwitchMessage {
-    /*
+    /**
      * On receiving this message, collect the last boot-time record and start periodic collection
      * and monitor.
      */
     END_BOOTTIME_COLLECTION = EventType::LAST_EVENT + 1,
 
-    /*
+    /**
      * On receiving this message, ends custom collection, discard collected data and start periodic
      * collection and monitor.
      */
     END_CUSTOM_COLLECTION,
 };
 
-/*
+/**
  * WatchdogPerfServiceInterface collects performance data during boot-time and periodically post
  * boot complete. It exposes APIs that the main thread and binder service can call to start a
  * collection, switch the collection type, and generate collection dumps.
@@ -150,7 +150,7 @@
     // Register a data processor to process the data collected by |WatchdogPerfService|.
     virtual android::base::Result<void> registerDataProcessor(
             android::sp<IDataProcessorInterface> processor) = 0;
-    /*
+    /**
      * Starts the boot-time collection in the looper handler on a new thread and returns
      * immediately. Must be called only once. Otherwise, returns an error.
      */
@@ -161,7 +161,7 @@
     virtual void setSystemState(SystemState systemState) = 0;
     // Ends the boot-time collection by switching to periodic collection and returns immediately.
     virtual android::base::Result<void> onBootFinished() = 0;
-    /*
+    /**
      * Depending on the arguments, it either:
      * 1. Starts a custom collection.
      * 2. Or ends the current custom collection and dumps the collected data.
@@ -170,9 +170,9 @@
     virtual android::base::Result<void> onCustomCollection(
             int fd, const Vector<android::String16>& args) = 0;
     // Generates a dump from the boot-time and periodic collection events.
-    virtual android::base::Result<void> onDump(int fd) = 0;
+    virtual android::base::Result<void> onDump(int fd) const = 0;
     // Dumps the help text.
-    virtual bool dumpHelpText(int fd) = 0;
+    virtual bool dumpHelpText(int fd) const = 0;
 };
 
 class WatchdogPerfService final : public WatchdogPerfServiceInterface {
@@ -185,9 +185,8 @@
           mCustomCollection({}),
           mPeriodicMonitor({}),
           mCurrCollectionEvent(EventType::INIT),
-          mUidIoStats(android::sp<UidIoStats>::make()),
+          mUidStatsCollector(android::sp<UidStatsCollector>::make()),
           mProcStat(android::sp<ProcStat>::make()),
-          mProcPidStat(android::sp<ProcPidStat>::make()),
           mProcDiskStats(android::sp<ProcDiskStats>::make()),
           mDataProcessors({}) {}
 
@@ -207,9 +206,9 @@
     android::base::Result<void> onCustomCollection(int fd,
                                                    const Vector<android::String16>& args) override;
 
-    android::base::Result<void> onDump(int fd) override;
+    android::base::Result<void> onDump(int fd) const override;
 
-    bool dumpHelpText(int fd) override;
+    bool dumpHelpText(int fd) const override;
 
 private:
     struct EventMetadata {
@@ -226,9 +225,9 @@
     };
 
     // Dumps the collectors' status when they are disabled.
-    android::base::Result<void> dumpCollectorsStatusLocked(int fd);
+    android::base::Result<void> dumpCollectorsStatusLocked(int fd) const;
 
-    /*
+    /**
      * Starts a custom collection on the looper handler, temporarily stops the periodic collection
      * (won't discard the collected data), and returns immediately. Returns any error observed
      * during this process.
@@ -243,7 +242,7 @@
             std::chrono::nanoseconds interval, std::chrono::nanoseconds maxDuration,
             const std::unordered_set<std::string>& filterPackages);
 
-    /*
+    /**
      * Ends the current custom collection, generates a dump, sends a looper message to start the
      * periodic collection, and returns immediately. Returns an error when there is no custom
      * collection running or when a dump couldn't be generated from the custom collection.
@@ -262,7 +261,7 @@
     // Processes the monitor events received by |handleMessage|.
     android::base::Result<void> processMonitorEvent(EventMetadata* metadata);
 
-    /*
+    /**
      * Returns the metadata for the current collection based on |mCurrCollectionEvent|. Returns
      * nullptr on invalid collection event.
      */
@@ -272,7 +271,7 @@
     std::thread mCollectionThread;
 
     // Makes sure only one collection is running at any given time.
-    Mutex mMutex;
+    mutable Mutex mMutex;
 
     // Handler lopper to execute different collection events on the collection thread.
     android::sp<LooperWrapper> mHandlerLooper GUARDED_BY(mMutex);
@@ -295,21 +294,18 @@
     // Info for the |EventType::PERIODIC| monitor event.
     EventMetadata mPeriodicMonitor GUARDED_BY(mMutex);
 
-    /*
+    /**
      * Tracks either the WatchdogPerfService's state or current collection event. Updated on
      * |start|, |onBootComplete|, |startCustomCollection|, |endCustomCollection|, and |terminate|.
      */
     EventType mCurrCollectionEvent GUARDED_BY(mMutex);
 
-    // Collector/parser for `/proc/uid_io/stats`.
-    android::sp<UidIoStats> mUidIoStats GUARDED_BY(mMutex);
+    // Collector for UID process and I/O stats.
+    android::sp<UidStatsCollectorInterface> mUidStatsCollector GUARDED_BY(mMutex);
 
     // Collector/parser for `/proc/stat`.
     android::sp<ProcStat> mProcStat GUARDED_BY(mMutex);
 
-    // Collector/parser for `/proc/PID/*` stat files.
-    android::sp<ProcPidStat> mProcPidStat GUARDED_BY(mMutex);
-
     // Collector/parser for `/proc/diskstats` file.
     android::sp<IProcDiskStatsInterface> mProcDiskStats GUARDED_BY(mMutex);
 
diff --git a/cpp/watchdog/server/tests/IoOveruseConfigsTest.cpp b/cpp/watchdog/server/tests/IoOveruseConfigsTest.cpp
index c03ad85..8eefeb4 100644
--- a/cpp/watchdog/server/tests/IoOveruseConfigsTest.cpp
+++ b/cpp/watchdog/server/tests/IoOveruseConfigsTest.cpp
@@ -17,6 +17,7 @@
 #include "IoOveruseConfigs.h"
 #include "OveruseConfigurationTestUtils.h"
 #include "OveruseConfigurationXmlHelper.h"
+#include "PackageInfoTestUtils.h"
 
 #include <android-base/strings.h>
 #include <gmock/gmock.h>
@@ -73,19 +74,6 @@
     return mappings;
 }
 
-PackageInfo constructPackageInfo(
-        const char* packageName, const ComponentType componentType,
-        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;
-}
-
 std::string toString(std::vector<ResourceOveruseConfiguration> configs) {
     std::string buffer;
     StringAppendF(&buffer, "[");
@@ -99,9 +87,9 @@
     return buffer;
 }
 
-std::vector<Matcher<const ResourceOveruseConfiguration>> ResourceOveruseConfigurationsMatchers(
+std::vector<Matcher<const ResourceOveruseConfiguration&>> ResourceOveruseConfigurationsMatchers(
         const std::vector<ResourceOveruseConfiguration>& configs) {
-    std::vector<Matcher<const ResourceOveruseConfiguration>> matchers;
+    std::vector<Matcher<const ResourceOveruseConfiguration&>> matchers;
     for (const auto config : configs) {
         matchers.push_back(ResourceOveruseConfigurationMatcher(config));
     }
@@ -526,21 +514,21 @@
     PerStateBytes defaultPerStateBytes = defaultThreshold().perStateWriteBytes;
     IoOveruseConfigs ioOveruseConfigs;
 
-    auto packageInfo = constructPackageInfo("systemPackage", ComponentType::SYSTEM);
+    auto packageInfo = constructAppPackageInfo("systemPackage", ComponentType::SYSTEM);
     EXPECT_THAT(ioOveruseConfigs.fetchThreshold(packageInfo), defaultPerStateBytes)
             << "System package should have default threshold";
     EXPECT_FALSE(ioOveruseConfigs.isSafeToKill(packageInfo))
             << "System package shouldn't be killed by default";
 
-    packageInfo = constructPackageInfo("vendorPackage", ComponentType::VENDOR,
-                                       ApplicationCategoryType::MEDIA);
+    packageInfo = constructAppPackageInfo("vendorPackage", ComponentType::VENDOR,
+                                          ApplicationCategoryType::MEDIA);
     EXPECT_THAT(ioOveruseConfigs.fetchThreshold(packageInfo), defaultPerStateBytes)
             << "Vendor package should have default threshold";
     EXPECT_FALSE(ioOveruseConfigs.isSafeToKill(packageInfo))
             << "Vendor package shouldn't be killed by default";
 
-    packageInfo = constructPackageInfo("3pPackage", ComponentType::THIRD_PARTY,
-                                       ApplicationCategoryType::MAPS);
+    packageInfo = constructAppPackageInfo("3pPackage", ComponentType::THIRD_PARTY,
+                                          ApplicationCategoryType::MAPS);
     EXPECT_THAT(ioOveruseConfigs.fetchThreshold(packageInfo), defaultPerStateBytes)
             << "Third-party package should have default threshold";
     EXPECT_TRUE(ioOveruseConfigs.isSafeToKill(packageInfo))
@@ -810,25 +798,25 @@
     const auto ioOveruseConfigs = sampleIoOveruseConfigs();
 
     auto actual = ioOveruseConfigs->fetchThreshold(
-            constructPackageInfo("systemPackageGeneric", ComponentType::SYSTEM));
+            constructAppPackageInfo("systemPackageGeneric", ComponentType::SYSTEM));
 
     EXPECT_THAT(actual, SYSTEM_COMPONENT_LEVEL_THRESHOLDS);
 
     actual = ioOveruseConfigs->fetchThreshold(
-            constructPackageInfo("systemPackageA", ComponentType::SYSTEM));
+            constructAppPackageInfo("systemPackageA", ComponentType::SYSTEM));
 
     EXPECT_THAT(actual, SYSTEM_PACKAGE_A_THRESHOLDS);
 
-    actual = ioOveruseConfigs->fetchThreshold(constructPackageInfo("systemPackageB",
-                                                                   ComponentType::SYSTEM,
-                                                                   ApplicationCategoryType::MEDIA));
+    actual = ioOveruseConfigs->fetchThreshold(
+            constructAppPackageInfo("systemPackageB", ComponentType::SYSTEM,
+                                    ApplicationCategoryType::MEDIA));
 
     // Package specific thresholds get priority over media category thresholds.
     EXPECT_THAT(actual, SYSTEM_PACKAGE_B_THRESHOLDS);
 
-    actual = ioOveruseConfigs->fetchThreshold(constructPackageInfo("systemPackageC",
-                                                                   ComponentType::SYSTEM,
-                                                                   ApplicationCategoryType::MEDIA));
+    actual = ioOveruseConfigs->fetchThreshold(
+            constructAppPackageInfo("systemPackageC", ComponentType::SYSTEM,
+                                    ApplicationCategoryType::MEDIA));
 
     // Media category thresholds as there is no package specific thresholds.
     EXPECT_THAT(actual, MEDIA_THRESHOLDS);
@@ -846,12 +834,12 @@
     ioOveruseConfigs->update({sampleSystemConfig});
 
     auto actual = ioOveruseConfigs->fetchThreshold(
-            constructPackageInfo("shared:systemSharedPackage", ComponentType::SYSTEM));
+            constructAppPackageInfo("shared:systemSharedPackage", ComponentType::SYSTEM));
 
     EXPECT_THAT(actual, toPerStateBytes(100, 200, 300));
 
     actual = ioOveruseConfigs->fetchThreshold(
-            constructPackageInfo("systemSharedPackage", ComponentType::SYSTEM));
+            constructAppPackageInfo("systemSharedPackage", ComponentType::SYSTEM));
 
     EXPECT_THAT(actual, SYSTEM_COMPONENT_LEVEL_THRESHOLDS);
 }
@@ -860,18 +848,18 @@
     const auto ioOveruseConfigs = sampleIoOveruseConfigs();
 
     auto actual = ioOveruseConfigs->fetchThreshold(
-            constructPackageInfo("vendorPackageGeneric", ComponentType::VENDOR));
+            constructAppPackageInfo("vendorPackageGeneric", ComponentType::VENDOR));
 
     EXPECT_THAT(actual, VENDOR_COMPONENT_LEVEL_THRESHOLDS);
 
     actual = ioOveruseConfigs->fetchThreshold(
-            constructPackageInfo("vendorPkgB", ComponentType::VENDOR));
+            constructAppPackageInfo("vendorPkgB", ComponentType::VENDOR));
 
     EXPECT_THAT(actual, VENDOR_PKG_B_THRESHOLDS);
 
-    actual = ioOveruseConfigs->fetchThreshold(constructPackageInfo("vendorPackageC",
-                                                                   ComponentType::VENDOR,
-                                                                   ApplicationCategoryType::MAPS));
+    actual = ioOveruseConfigs->fetchThreshold(
+            constructAppPackageInfo("vendorPackageC", ComponentType::VENDOR,
+                                    ApplicationCategoryType::MAPS));
 
     // Maps category thresholds as there is no package specific thresholds.
     EXPECT_THAT(actual, MAPS_THRESHOLDS);
@@ -889,12 +877,12 @@
     ioOveruseConfigs->update({sampleVendorConfig});
 
     auto actual = ioOveruseConfigs->fetchThreshold(
-            constructPackageInfo("shared:vendorSharedPackage", ComponentType::VENDOR));
+            constructAppPackageInfo("shared:vendorSharedPackage", ComponentType::VENDOR));
 
     EXPECT_THAT(actual, toPerStateBytes(100, 200, 300));
 
     actual = ioOveruseConfigs->fetchThreshold(
-            constructPackageInfo("vendorSharedPackage", ComponentType::VENDOR));
+            constructAppPackageInfo("vendorSharedPackage", ComponentType::VENDOR));
 
     EXPECT_THAT(actual, VENDOR_COMPONENT_LEVEL_THRESHOLDS);
 }
@@ -903,19 +891,19 @@
     const auto ioOveruseConfigs = sampleIoOveruseConfigs();
 
     auto actual = ioOveruseConfigs->fetchThreshold(
-            constructPackageInfo("vendorPackageGenericImpostor", ComponentType::THIRD_PARTY));
+            constructAppPackageInfo("vendorPackageGenericImpostor", ComponentType::THIRD_PARTY));
 
     EXPECT_THAT(actual, THIRD_PARTY_COMPONENT_LEVEL_THRESHOLDS);
 
-    actual = ioOveruseConfigs->fetchThreshold(constructPackageInfo("3pMapsPackage",
-                                                                   ComponentType::THIRD_PARTY,
-                                                                   ApplicationCategoryType::MAPS));
+    actual = ioOveruseConfigs->fetchThreshold(
+            constructAppPackageInfo("3pMapsPackage", ComponentType::THIRD_PARTY,
+                                    ApplicationCategoryType::MAPS));
 
     EXPECT_THAT(actual, MAPS_THRESHOLDS);
 
-    actual = ioOveruseConfigs->fetchThreshold(constructPackageInfo("3pMediaPackage",
-                                                                   ComponentType::THIRD_PARTY,
-                                                                   ApplicationCategoryType::MEDIA));
+    actual = ioOveruseConfigs->fetchThreshold(
+            constructAppPackageInfo("3pMediaPackage", ComponentType::THIRD_PARTY,
+                                    ApplicationCategoryType::MEDIA));
 
     EXPECT_THAT(actual, MEDIA_THRESHOLDS);
 }
@@ -923,10 +911,10 @@
 TEST_F(IoOveruseConfigsTest, TestIsSafeToKillSystemPackages) {
     const auto ioOveruseConfigs = sampleIoOveruseConfigs();
     EXPECT_FALSE(ioOveruseConfigs->isSafeToKill(
-            constructPackageInfo("systemPackageGeneric", ComponentType::SYSTEM)));
+            constructAppPackageInfo("systemPackageGeneric", ComponentType::SYSTEM)));
 
     EXPECT_TRUE(ioOveruseConfigs->isSafeToKill(
-            constructPackageInfo("systemPackageA", ComponentType::SYSTEM)));
+            constructAppPackageInfo("systemPackageA", ComponentType::SYSTEM)));
 }
 
 TEST_F(IoOveruseConfigsTest, TestIsSafeToKillSharedSystemPackages) {
@@ -938,23 +926,23 @@
     EXPECT_RESULT_OK(ioOveruseConfigs->update({sampleSystemConfig}));
 
     PackageInfo packageInfo =
-            constructPackageInfo("systemSharedPackage", ComponentType::SYSTEM,
-                                 ApplicationCategoryType::OTHERS,
-                                 {"sharedUidSystemPackageA", "sharedUidSystemPackageB",
-                                  "sharedUidSystemPackageC"});
+            constructAppPackageInfo("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"});
+            constructAppPackageInfo("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"});
+            constructAppPackageInfo("systemSharedPackageD", ComponentType::SYSTEM,
+                                    ApplicationCategoryType::OTHERS, {"sharedUidSystemPackageA"});
     EXPECT_FALSE(ioOveruseConfigs->isSafeToKill(packageInfo))
             << "Shouldn't be safe-to-kill when the 'shared:' prefix is missing";
 }
@@ -962,10 +950,10 @@
 TEST_F(IoOveruseConfigsTest, TestIsSafeToKillVendorPackages) {
     const auto ioOveruseConfigs = sampleIoOveruseConfigs();
     EXPECT_FALSE(ioOveruseConfigs->isSafeToKill(
-            constructPackageInfo("vendorPackageGeneric", ComponentType::VENDOR)));
+            constructAppPackageInfo("vendorPackageGeneric", ComponentType::VENDOR)));
 
     EXPECT_TRUE(ioOveruseConfigs->isSafeToKill(
-            constructPackageInfo("vendorPackageA", ComponentType::VENDOR)));
+            constructAppPackageInfo("vendorPackageA", ComponentType::VENDOR)));
 }
 
 TEST_F(IoOveruseConfigsTest, TestIsSafeToKillSharedVendorPackages) {
@@ -981,23 +969,23 @@
     EXPECT_RESULT_OK(ioOveruseConfigs->update({sampleSystemConfig, sampleVendorConfig}));
 
     PackageInfo packageInfo =
-            constructPackageInfo("vendorSharedPackage", ComponentType::VENDOR,
-                                 ApplicationCategoryType::OTHERS,
-                                 {"sharedUidVendorPackageA", "sharedUidVendorPackageB",
-                                  "sharedUidVendorPackageC"});
+            constructAppPackageInfo("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"});
+            constructAppPackageInfo("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"});
+            constructAppPackageInfo("shared:vendorSharedPackageE", ComponentType::VENDOR,
+                                    ApplicationCategoryType::OTHERS, {"sharedUidVendorPackageA"});
     EXPECT_FALSE(ioOveruseConfigs->isSafeToKill(packageInfo))
             << "Shouldn't be safe-to-kill when the 'shared:' prefix is missing";
 }
@@ -1005,11 +993,11 @@
 TEST_F(IoOveruseConfigsTest, TestIsSafeToKillThirdPartyPackages) {
     const auto ioOveruseConfigs = sampleIoOveruseConfigs();
     EXPECT_TRUE(ioOveruseConfigs->isSafeToKill(
-            constructPackageInfo("vendorPackageGenericImpostor", ComponentType::THIRD_PARTY)));
+            constructAppPackageInfo("vendorPackageGenericImpostor", ComponentType::THIRD_PARTY)));
 
     EXPECT_TRUE(ioOveruseConfigs->isSafeToKill(
-            constructPackageInfo("3pMapsPackage", ComponentType::THIRD_PARTY,
-                                 ApplicationCategoryType::MAPS)));
+            constructAppPackageInfo("3pMapsPackage", ComponentType::THIRD_PARTY,
+                                    ApplicationCategoryType::MAPS)));
 }
 
 TEST_F(IoOveruseConfigsTest, TestIsSafeToKillNativePackages) {
diff --git a/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp b/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp
index 7dcc2d4..76f3cbb 100644
--- a/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp
+++ b/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp
@@ -19,14 +19,17 @@
 #include "MockPackageInfoResolver.h"
 #include "MockProcDiskStats.h"
 #include "MockResourceOveruseListener.h"
-#include "MockUidIoStats.h"
+#include "MockUidStatsCollector.h"
 #include "MockWatchdogServiceHelper.h"
+#include "PackageInfoTestUtils.h"
 
 #include <binder/IPCThreadState.h>
 #include <binder/Status.h>
 #include <utils/RefBase.h>
 
 #include <functional>
+#include <tuple>
+#include <unordered_map>
 
 namespace android {
 namespace automotive {
@@ -47,6 +50,7 @@
 using ::android::binder::Status;
 using ::testing::_;
 using ::testing::DoAll;
+using ::testing::Eq;
 using ::testing::Return;
 using ::testing::ReturnRef;
 using ::testing::SaveArg;
@@ -68,20 +72,11 @@
     return threshold;
 }
 
-PackageIdentifier constructPackageIdentifier(const char* packageName, const int32_t uid) {
-    PackageIdentifier packageIdentifier;
-    packageIdentifier.name = packageName;
-    packageIdentifier.uid = uid;
-    return packageIdentifier;
-}
-
-PackageInfo constructPackageInfo(const char* packageName, const int32_t uid,
-                                 const UidType uidType) {
+struct PackageWrittenBytes {
     PackageInfo packageInfo;
-    packageInfo.packageIdentifier = constructPackageIdentifier(packageName, uid);
-    packageInfo.uidType = uidType;
-    return packageInfo;
-}
+    int32_t foregroundBytes;
+    int32_t backgroundBytes;
+};
 
 PerStateBytes constructPerStateBytes(const int64_t fgBytes, const int64_t bgBytes,
                                      const int64_t gmBytes) {
@@ -196,42 +191,25 @@
         mMockWatchdogServiceHelper = sp<MockWatchdogServiceHelper>::make();
         mMockIoOveruseConfigs = sp<MockIoOveruseConfigs>::make();
         mMockPackageInfoResolver = sp<MockPackageInfoResolver>::make();
+        mMockUidStatsCollector = sp<MockUidStatsCollector>::make();
         mIoOveruseMonitor = sp<IoOveruseMonitor>::make(mMockWatchdogServiceHelper);
         mIoOveruseMonitorPeer = sp<internal::IoOveruseMonitorPeer>::make(mIoOveruseMonitor);
         mIoOveruseMonitorPeer->init(mMockIoOveruseConfigs, mMockPackageInfoResolver);
+        setUpPackagesAndConfigurations();
     }
 
     virtual void TearDown() {
         mMockWatchdogServiceHelper.clear();
         mMockIoOveruseConfigs.clear();
         mMockPackageInfoResolver.clear();
+        mMockUidStatsCollector.clear();
         mIoOveruseMonitor.clear();
         mIoOveruseMonitorPeer.clear();
     }
 
     void setUpPackagesAndConfigurations() {
-        std::unordered_map<uid_t, PackageInfo> packageInfoMapping =
-                {{1001000,
-                  constructPackageInfo(
-                          /*packageName=*/"system.daemon", /*uid=*/1001000, UidType::NATIVE)},
-                 {1112345,
-                  constructPackageInfo(
-                          /*packageName=*/"com.android.google.package", /*uid=*/1112345,
-                          UidType::APPLICATION)},
-                 {1113999,
-                  constructPackageInfo(
-                          /*packageName=*/"com.android.google.package", /*uid=*/1113999,
-                          UidType::APPLICATION)},
-                 {1212345,
-                  constructPackageInfo(
-                          /*packageName=*/"com.android.google.package", /*uid=*/1212345,
-                          UidType::APPLICATION)},
-                 {1312345,
-                  constructPackageInfo(
-                          /*packageName=*/"com.android.google.package", /*uid=*/1312345,
-                          UidType::APPLICATION)}};
         ON_CALL(*mMockPackageInfoResolver, getPackageInfosForUids(_))
-                .WillByDefault(Return(packageInfoMapping));
+                .WillByDefault(Return(kPackageInfosByUid));
         mMockIoOveruseConfigs->injectPackageConfigs({
                 {"system.daemon",
                  {constructPerStateBytes(/*fgBytes=*/80'000, /*bgBytes=*/40'000,
@@ -244,6 +222,24 @@
         });
     }
 
+    std::vector<UidStats> constructUidStats(
+            std::unordered_map<uid_t, std::tuple<int32_t, int32_t>> writtenBytesByUid) {
+        std::vector<UidStats> uidStats;
+        for (const auto& [uid, writtenBytes] : writtenBytesByUid) {
+            PackageInfo packageInfo;
+            if (kPackageInfosByUid.find(uid) != kPackageInfosByUid.end()) {
+                packageInfo = kPackageInfosByUid.at(uid);
+            }
+            uidStats.push_back(UidStats{.packageInfo = packageInfo,
+                                        .ioStats = {/*fgRdBytes=*/989'000,
+                                                    /*bgRdBytes=*/678'000,
+                                                    /*fgWrBytes=*/std::get<0>(writtenBytes),
+                                                    /*bgWrBytes=*/std::get<1>(writtenBytes),
+                                                    /*fgFsync=*/10'000, /*bgFsync=*/50'000}});
+        }
+        return uidStats;
+    }
+
     void executeAsUid(uid_t uid, std::function<void()> func) {
         sp<ScopedChangeCallingUid> scopedChangeCallingUid = sp<ScopedChangeCallingUid>::make(uid);
         ASSERT_NO_FATAL_FAILURE(func());
@@ -252,12 +248,36 @@
     sp<MockWatchdogServiceHelper> mMockWatchdogServiceHelper;
     sp<MockIoOveruseConfigs> mMockIoOveruseConfigs;
     sp<MockPackageInfoResolver> mMockPackageInfoResolver;
+    sp<MockUidStatsCollector> mMockUidStatsCollector;
     sp<IoOveruseMonitor> mIoOveruseMonitor;
     sp<internal::IoOveruseMonitorPeer> mIoOveruseMonitorPeer;
+
+    static const std::unordered_map<uid_t, PackageInfo> kPackageInfosByUid;
 };
 
+const std::unordered_map<uid_t, PackageInfo> IoOveruseMonitorTest::kPackageInfosByUid =
+        {{1001000,
+          constructPackageInfo(
+                  /*packageName=*/"system.daemon",
+                  /*uid=*/1001000, UidType::NATIVE)},
+         {1112345,
+          constructPackageInfo(
+                  /*packageName=*/"com.android.google.package",
+                  /*uid=*/1112345, UidType::APPLICATION)},
+         {1113999,
+          constructPackageInfo(
+                  /*packageName=*/"com.android.google.package",
+                  /*uid=*/1113999, UidType::APPLICATION)},
+         {1212345,
+          constructPackageInfo(
+                  /*packageName=*/"com.android.google.package",
+                  /*uid=*/1212345, UidType::APPLICATION)},
+         {1312345,
+          constructPackageInfo(
+                  /*packageName=*/"com.android.google.package",
+                  /*uid=*/1312345, UidType::APPLICATION)}};
+
 TEST_F(IoOveruseMonitorTest, TestOnPeriodicCollection) {
-    setUpPackagesAndConfigurations();
     sp<MockResourceOveruseListener> mockResourceOveruseListener =
             sp<MockResourceOveruseListener>::make();
     ASSERT_NO_FATAL_FAILURE(executeAsUid(1001000, [&]() {
@@ -268,11 +288,11 @@
      * Package "system.daemon" (UID: 1001000) exceeds warn threshold percentage of 80% but no
      * warning is issued as it is a native UID.
      */
-    sp<MockUidIoStats> mockUidIoStats = sp<MockUidIoStats>::make();
-    mockUidIoStats->expectDeltaStats(
-            {{1001000, IoUsage(0, 0, /*fgWrBytes=*/70'000, /*bgWrBytes=*/20'000, 0, 0)},
-             {1112345, IoUsage(0, 0, /*fgWrBytes=*/35'000, /*bgWrBytes=*/15'000, 0, 0)},
-             {1212345, IoUsage(0, 0, /*fgWrBytes=*/70'000, /*bgWrBytes=*/20'000, 0, 0)}});
+    EXPECT_CALL(*mMockUidStatsCollector, deltaStats())
+            .WillOnce(Return(
+                    constructUidStats({{1001000, {/*fgWrBytes=*/70'000, /*bgWrBytes=*/20'000}},
+                                       {1112345, {/*fgWrBytes=*/35'000, /*bgWrBytes=*/15'000}},
+                                       {1212345, {/*fgWrBytes=*/70'000, /*bgWrBytes=*/20'000}}})));
 
     std::vector<PackageIoOveruseStats> actualIoOveruseStats;
     EXPECT_CALL(*mMockWatchdogServiceHelper, latestIoOveruseStats(_))
@@ -282,7 +302,7 @@
     const auto [startTime, durationInSeconds] = calculateStartAndDuration(currentTime);
 
     ASSERT_RESULT_OK(mIoOveruseMonitor->onPeriodicCollection(currentTime, SystemState::NORMAL_MODE,
-                                                             mockUidIoStats, nullptr, nullptr));
+                                                             mMockUidStatsCollector, nullptr));
 
     std::vector<PackageIoOveruseStats> expectedIoOveruseStats =
             {constructPackageIoOveruseStats(/*uid*=*/1001000, /*shouldNotify=*/false,
@@ -308,10 +328,11 @@
 
     ResourceOveruseStats actualOverusingNativeStats;
     // Package "com.android.google.package" for user 11 changed uid from 1112345 to 1113999.
-    mockUidIoStats->expectDeltaStats(
-            {{1001000, IoUsage(0, 0, /*fgWrBytes=*/30'000, /*bgWrBytes=*/0, 0, 0)},
-             {1113999, IoUsage(0, 0, /*fgWrBytes=*/25'000, /*bgWrBytes=*/10'000, 0, 0)},
-             {1212345, IoUsage(0, 0, /*fgWrBytes=*/20'000, /*bgWrBytes=*/30'000, 0, 0)}});
+    EXPECT_CALL(*mMockUidStatsCollector, deltaStats())
+            .WillOnce(Return(
+                    constructUidStats({{1001000, {/*fgWrBytes=*/30'000, /*bgWrBytes=*/0}},
+                                       {1113999, {/*fgWrBytes=*/25'000, /*bgWrBytes=*/10'000}},
+                                       {1212345, {/*fgWrBytes=*/20'000, /*bgWrBytes=*/30'000}}})));
     actualIoOveruseStats.clear();
     EXPECT_CALL(*mockResourceOveruseListener, onOveruse(_))
             .WillOnce(DoAll(SaveArg<0>(&actualOverusingNativeStats), Return(Status::ok())));
@@ -319,14 +340,14 @@
             .WillOnce(DoAll(SaveArg<0>(&actualIoOveruseStats), Return(Status::ok())));
 
     ASSERT_RESULT_OK(mIoOveruseMonitor->onPeriodicCollection(currentTime, SystemState::NORMAL_MODE,
-                                                             mockUidIoStats, nullptr, nullptr));
+                                                             mMockUidStatsCollector, nullptr));
 
     const auto expectedOverusingNativeStats = constructResourceOveruseStats(
             constructIoOveruseStats(/*isKillable=*/false,
                                     /*remaining=*/constructPerStateBytes(0, 20'000, 100'000),
                                     /*written=*/constructPerStateBytes(100'000, 20'000, 0),
                                     /*totalOveruses=*/1, startTime, durationInSeconds));
-    EXPECT_THAT(actualOverusingNativeStats, expectedOverusingNativeStats)
+    EXPECT_THAT(actualOverusingNativeStats, Eq(expectedOverusingNativeStats))
             << "Expected: " << expectedOverusingNativeStats.toString()
             << "\nActual: " << actualOverusingNativeStats.toString();
 
@@ -360,17 +381,18 @@
      * Current date changed so the daily I/O usage stats should be reset and the latest I/O overuse
      * stats should not aggregate with the previous day's stats.
      */
-    mockUidIoStats->expectDeltaStats(
-            {{1001000, IoUsage(0, 0, /*fgWrBytes=*/78'000, /*bgWrBytes=*/38'000, 0, 0)},
-             {1113999, IoUsage(0, 0, /*fgWrBytes=*/55'000, /*bgWrBytes=*/23'000, 0, 0)},
-             {1212345, IoUsage(0, 0, /*fgWrBytes=*/55'000, /*bgWrBytes=*/23'000, 0, 0)}});
+    EXPECT_CALL(*mMockUidStatsCollector, deltaStats())
+            .WillOnce(Return(
+                    constructUidStats({{1001000, {/*fgWrBytes=*/78'000, /*bgWrBytes=*/38'000}},
+                                       {1113999, {/*fgWrBytes=*/55'000, /*bgWrBytes=*/23'000}},
+                                       {1212345, {/*fgWrBytes=*/55'000, /*bgWrBytes=*/23'000}}})));
     actualIoOveruseStats.clear();
     EXPECT_CALL(*mMockWatchdogServiceHelper, latestIoOveruseStats(_))
             .WillOnce(DoAll(SaveArg<0>(&actualIoOveruseStats), Return(Status::ok())));
 
     currentTime += (24 * 60 * 60);  // Change collection time to next day.
     ASSERT_RESULT_OK(mIoOveruseMonitor->onPeriodicCollection(currentTime, SystemState::NORMAL_MODE,
-                                                             mockUidIoStats, nullptr, nullptr));
+                                                             mMockUidStatsCollector, nullptr));
 
     const auto [nextDayStartTime, nextDayDuration] = calculateStartAndDuration(currentTime);
     expectedIoOveruseStats =
@@ -397,7 +419,6 @@
 }
 
 TEST_F(IoOveruseMonitorTest, TestOnPeriodicCollectionWithGarageMode) {
-    setUpPackagesAndConfigurations();
     sp<MockResourceOveruseListener> mockResourceOveruseListener =
             sp<MockResourceOveruseListener>::make();
     ASSERT_NO_FATAL_FAILURE(executeAsUid(1001000, [&]() {
@@ -408,11 +429,11 @@
      * Package "system.daemon" (UID: 1001000) exceeds warn threshold percentage of 80% but no
      * warning is issued as it is a native UID.
      */
-    sp<MockUidIoStats> mockUidIoStats = sp<MockUidIoStats>::make();
-    mockUidIoStats->expectDeltaStats(
-            {{1001000, IoUsage(0, 0, /*fgWrBytes=*/70'000, /*bgWrBytes=*/60'000, 0, 0)},
-             {1112345, IoUsage(0, 0, /*fgWrBytes=*/35'000, /*bgWrBytes=*/15'000, 0, 0)},
-             {1212345, IoUsage(0, 0, /*fgWrBytes=*/90'000, /*bgWrBytes=*/20'000, 0, 0)}});
+    EXPECT_CALL(*mMockUidStatsCollector, deltaStats())
+            .WillOnce(Return(
+                    constructUidStats({{1001000, {/*fgWrBytes=*/70'000, /*bgWrBytes=*/60'000}},
+                                       {1112345, {/*fgWrBytes=*/35'000, /*bgWrBytes=*/15'000}},
+                                       {1212345, {/*fgWrBytes=*/90'000, /*bgWrBytes=*/20'000}}})));
 
     ResourceOveruseStats actualOverusingNativeStats;
     EXPECT_CALL(*mockResourceOveruseListener, onOveruse(_))
@@ -425,14 +446,14 @@
     const auto [startTime, durationInSeconds] = calculateStartAndDuration(currentTime);
 
     ASSERT_RESULT_OK(mIoOveruseMonitor->onPeriodicCollection(currentTime, SystemState::GARAGE_MODE,
-                                                             mockUidIoStats, nullptr, nullptr));
+                                                             mMockUidStatsCollector, nullptr));
 
     const auto expectedOverusingNativeStats = constructResourceOveruseStats(
             constructIoOveruseStats(/*isKillable=*/false,
                                     /*remaining=*/constructPerStateBytes(80'000, 40'000, 0),
                                     /*written=*/constructPerStateBytes(0, 0, 130'000),
                                     /*totalOveruses=*/1, startTime, durationInSeconds));
-    EXPECT_THAT(actualOverusingNativeStats, expectedOverusingNativeStats)
+    EXPECT_THAT(actualOverusingNativeStats, Eq(expectedOverusingNativeStats))
             << "Expected: " << expectedOverusingNativeStats.toString()
             << "\nActual: " << actualOverusingNativeStats.toString();
 
@@ -460,11 +481,10 @@
 }
 
 TEST_F(IoOveruseMonitorTest, TestOnPeriodicCollectionWithZeroWriteBytes) {
-    sp<MockUidIoStats> mockUidIoStats = sp<MockUidIoStats>::make();
-    mockUidIoStats->expectDeltaStats(
-            {{1001000, IoUsage(10, 0, /*fgWrBytes=*/0, /*bgWrBytes=*/0, 1, 0)},
-             {1112345, IoUsage(0, 20, /*fgWrBytes=*/0, /*bgWrBytes=*/0, 0, 0)},
-             {1212345, IoUsage(0, 00, /*fgWrBytes=*/0, /*bgWrBytes=*/0, 0, 1)}});
+    EXPECT_CALL(*mMockUidStatsCollector, deltaStats())
+            .WillOnce(Return(constructUidStats({{1001000, {/*fgWrBytes=*/0, /*bgWrBytes=*/0}},
+                                                {1112345, {/*fgWrBytes=*/0, /*bgWrBytes=*/0}},
+                                                {1212345, {/*fgWrBytes=*/0, /*bgWrBytes=*/0}}})));
 
     EXPECT_CALL(*mMockPackageInfoResolver, getPackageInfosForUids(_)).Times(0);
     EXPECT_CALL(*mMockIoOveruseConfigs, fetchThreshold(_)).Times(0);
@@ -474,22 +494,21 @@
     ASSERT_RESULT_OK(
             mIoOveruseMonitor->onPeriodicCollection(std::chrono::system_clock::to_time_t(
                                                             std::chrono::system_clock::now()),
-                                                    SystemState::NORMAL_MODE, mockUidIoStats,
-                                                    nullptr, nullptr));
+                                                    SystemState::NORMAL_MODE,
+                                                    mMockUidStatsCollector, nullptr));
 }
 
 TEST_F(IoOveruseMonitorTest, TestOnPeriodicCollectionWithSmallWrittenBytes) {
-    setUpPackagesAndConfigurations();
-    sp<MockUidIoStats> mockUidIoStats = sp<MockUidIoStats>::make();
     /*
      * UID 1212345 current written bytes < |KTestMinSyncWrittenBytes| so the UID's stats are not
      * synced.
      */
-    mockUidIoStats->expectDeltaStats(
-            {{1001000, IoUsage(10, 0, /*fgWrBytes=*/59'200, /*bgWrBytes=*/0, 1, 0)},
-             {1112345, IoUsage(0, 20, /*fgWrBytes=*/0, /*bgWrBytes=*/25'200, 0, 0)},
-             {1212345, IoUsage(0, 00, /*fgWrBytes=*/300, /*bgWrBytes=*/600, 0, 1)},
-             {1312345, IoUsage(0, 00, /*fgWrBytes=*/51'200, /*bgWrBytes=*/0, 0, 1)}});
+    EXPECT_CALL(*mMockUidStatsCollector, deltaStats())
+            .WillOnce(Return(
+                    constructUidStats({{1001000, {/*fgWrBytes=*/59'200, /*bgWrBytes=*/0}},
+                                       {1112345, {/*fgWrBytes=*/0, /*bgWrBytes=*/25'200}},
+                                       {1212345, {/*fgWrBytes=*/300, /*bgWrBytes=*/600}},
+                                       {1312345, {/*fgWrBytes=*/51'200, /*bgWrBytes=*/0}}})));
 
     std::vector<PackageIoOveruseStats> actualIoOveruseStats;
     EXPECT_CALL(*mMockWatchdogServiceHelper, latestIoOveruseStats(_))
@@ -499,7 +518,7 @@
     const auto [startTime, durationInSeconds] = calculateStartAndDuration(currentTime);
 
     ASSERT_RESULT_OK(mIoOveruseMonitor->onPeriodicCollection(currentTime, SystemState::NORMAL_MODE,
-                                                             mockUidIoStats, nullptr, nullptr));
+                                                             mMockUidStatsCollector, nullptr));
 
     std::vector<PackageIoOveruseStats> expectedIoOveruseStats =
             {constructPackageIoOveruseStats(/*uid*=*/1001000, /*shouldNotify=*/false,
@@ -537,19 +556,15 @@
      * UID 1312345 current written bytes is < |kTestMinSyncWrittenBytes| but exceeds warn threshold
      * and killable so the UID's stat are synced.
      */
-    mockUidIoStats->expectDeltaStats(
-            {{1001000,
-              IoUsage(10, 0, /*fgWrBytes=*/KTestMinSyncWrittenBytes - 100, /*bgWrBytes=*/0, 1, 0)},
-             {1112345,
-              IoUsage(0, 20, /*fgWrBytes=*/0, /*bgWrBytes=*/KTestMinSyncWrittenBytes - 100, 0, 0)},
-             {1212345,
-              IoUsage(0, 00, /*fgWrBytes=*/KTestMinSyncWrittenBytes - 300, /*bgWrBytes=*/0, 0, 1)},
-             {1312345,
-              IoUsage(0, 00, /*fgWrBytes=*/KTestMinSyncWrittenBytes - 100, /*bgWrBytes=*/0, 0,
-                      1)}});
+    EXPECT_CALL(*mMockUidStatsCollector, deltaStats())
+            .WillOnce(Return(constructUidStats(
+                    {{1001000, {/*fgWrBytes=*/KTestMinSyncWrittenBytes - 100, /*bgWrBytes=*/0}},
+                     {1112345, {/*fgWrBytes=*/0, /*bgWrBytes=*/KTestMinSyncWrittenBytes - 100}},
+                     {1212345, {/*fgWrBytes=*/KTestMinSyncWrittenBytes - 300, /*bgWrBytes=*/0}},
+                     {1312345, {/*fgWrBytes=*/KTestMinSyncWrittenBytes - 100, /*bgWrBytes=*/0}}})));
 
     ASSERT_RESULT_OK(mIoOveruseMonitor->onPeriodicCollection(currentTime, SystemState::NORMAL_MODE,
-                                                             mockUidIoStats, nullptr, nullptr));
+                                                             mMockUidStatsCollector, nullptr));
 
     expectedIoOveruseStats =
             {constructPackageIoOveruseStats(/*uid*=*/1112345, /*shouldNotify=*/true,
@@ -573,14 +588,11 @@
 }
 
 TEST_F(IoOveruseMonitorTest, TestOnPeriodicCollectionWithNoPackageInfo) {
-    sp<MockUidIoStats> mockUidIoStats = sp<MockUidIoStats>::make();
-    mockUidIoStats->expectDeltaStats(
-            {{1001000, IoUsage(0, 0, /*fgWrBytes=*/70'000, /*bgWrBytes=*/20'000, 0, 0)},
-             {1112345, IoUsage(0, 0, /*fgWrBytes=*/35'000, /*bgWrBytes=*/15'000, 0, 0)},
-             {1212345, IoUsage(0, 0, /*fgWrBytes=*/70'000, /*bgWrBytes=*/20'000, 0, 0)}});
-
-    ON_CALL(*mMockPackageInfoResolver, getPackageInfosForUids(_))
-            .WillByDefault(Return(std::unordered_map<uid_t, PackageInfo>{}));
+    EXPECT_CALL(*mMockUidStatsCollector, deltaStats())
+            .WillOnce(Return(
+                    constructUidStats({{2301000, {/*fgWrBytes=*/70'000, /*bgWrBytes=*/20'000}},
+                                       {2412345, {/*fgWrBytes=*/35'000, /*bgWrBytes=*/15'000}},
+                                       {2512345, {/*fgWrBytes=*/70'000, /*bgWrBytes=*/20'000}}})));
 
     EXPECT_CALL(*mMockIoOveruseConfigs, fetchThreshold(_)).Times(0);
     EXPECT_CALL(*mMockIoOveruseConfigs, isSafeToKill(_)).Times(0);
@@ -589,8 +601,8 @@
     ASSERT_RESULT_OK(
             mIoOveruseMonitor->onPeriodicCollection(std::chrono::system_clock::to_time_t(
                                                             std::chrono::system_clock::now()),
-                                                    SystemState::NORMAL_MODE, mockUidIoStats,
-                                                    nullptr, nullptr));
+                                                    SystemState::NORMAL_MODE,
+                                                    mMockUidStatsCollector, nullptr));
 }
 
 TEST_F(IoOveruseMonitorTest, TestOnPeriodicMonitor) {
@@ -712,16 +724,15 @@
 }
 
 TEST_F(IoOveruseMonitorTest, TestGetIoOveruseStats) {
-    setUpPackagesAndConfigurations();
-    sp<MockUidIoStats> mockUidIoStats = sp<MockUidIoStats>::make();
-    mockUidIoStats->expectDeltaStats(
-            {{1001000, IoUsage(0, 0, /*fgWrBytes=*/90'000, /*bgWrBytes=*/20'000, 0, 0)}});
+    EXPECT_CALL(*mMockUidStatsCollector, deltaStats())
+            .WillOnce(Return(
+                    constructUidStats({{1001000, {/*fgWrBytes=*/90'000, /*bgWrBytes=*/20'000}}})));
 
     time_t currentTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
     const auto [startTime, durationInSeconds] = calculateStartAndDuration(currentTime);
 
     ASSERT_RESULT_OK(mIoOveruseMonitor->onPeriodicCollection(currentTime, SystemState::NORMAL_MODE,
-                                                             mockUidIoStats, nullptr, nullptr));
+                                                             mMockUidStatsCollector, nullptr));
 
     const auto expected =
             constructIoOveruseStats(/*isKillable=*/false,
@@ -739,16 +750,15 @@
 }
 
 TEST_F(IoOveruseMonitorTest, TestResetIoOveruseStats) {
-    setUpPackagesAndConfigurations();
-    sp<MockUidIoStats> mockUidIoStats = sp<MockUidIoStats>::make();
-    mockUidIoStats->expectDeltaStats(
-            {{1001000, IoUsage(0, 0, /*fgWrBytes=*/90'000, /*bgWrBytes=*/20'000, 0, 0)}});
+    EXPECT_CALL(*mMockUidStatsCollector, deltaStats())
+            .WillOnce(Return(
+                    constructUidStats({{1001000, {/*fgWrBytes=*/90'000, /*bgWrBytes=*/20'000}}})));
 
     ASSERT_RESULT_OK(
             mIoOveruseMonitor->onPeriodicCollection(std::chrono::system_clock::to_time_t(
                                                             std::chrono::system_clock::now()),
-                                                    SystemState::NORMAL_MODE, mockUidIoStats,
-                                                    nullptr, nullptr));
+                                                    SystemState::NORMAL_MODE,
+                                                    mMockUidStatsCollector, nullptr));
 
     IoOveruseStats actual;
     ASSERT_NO_FATAL_FAILURE(executeAsUid(1001000, [&]() {
diff --git a/cpp/watchdog/server/tests/IoPerfCollectionTest.cpp b/cpp/watchdog/server/tests/IoPerfCollectionTest.cpp
index 85e049d..8da3b6f 100644
--- a/cpp/watchdog/server/tests/IoPerfCollectionTest.cpp
+++ b/cpp/watchdog/server/tests/IoPerfCollectionTest.cpp
@@ -15,101 +15,182 @@
  */
 
 #include "IoPerfCollection.h"
-#include "MockPackageInfoResolver.h"
-#include "MockProcPidStat.h"
 #include "MockProcStat.h"
-#include "MockUidIoStats.h"
+#include "MockUidStatsCollector.h"
 #include "MockWatchdogServiceHelper.h"
-#include "PackageInfoResolver.h"
+#include "PackageInfoTestUtils.h"
 
 #include <WatchdogProperties.sysprop.h>
 #include <android-base/file.h>
 #include <gmock/gmock.h>
+#include <utils/RefBase.h>
 
 #include <sys/types.h>
 #include <unistd.h>
 
 #include <string>
+#include <type_traits>
 #include <vector>
 
 namespace android {
 namespace automotive {
 namespace watchdog {
 
+using ::android::RefBase;
 using ::android::sp;
-using ::android::base::Error;
+using ::android::automotive::watchdog::internal::PackageInfo;
 using ::android::base::ReadFdToString;
 using ::android::base::Result;
 using ::testing::_;
+using ::testing::AllOf;
+using ::testing::ElementsAreArray;
+using ::testing::Eq;
+using ::testing::ExplainMatchResult;
+using ::testing::Field;
+using ::testing::IsSubsetOf;
+using ::testing::Matcher;
 using ::testing::Return;
+using ::testing::Test;
+using ::testing::UnorderedElementsAreArray;
+using ::testing::VariantWith;
 
 namespace {
 
-bool isEqual(const UidIoPerfData& lhs, const UidIoPerfData& rhs) {
-    if (lhs.topNReads.size() != rhs.topNReads.size() ||
-        lhs.topNWrites.size() != rhs.topNWrites.size()) {
-        return false;
+MATCHER_P(IoStatsEq, expected, "") {
+    return ExplainMatchResult(AllOf(Field("bytes", &UserPackageStats::IoStats::bytes,
+                                          ElementsAreArray(expected.bytes)),
+                                    Field("fsync", &UserPackageStats::IoStats::fsync,
+                                          ElementsAreArray(expected.fsync))),
+                              arg, result_listener);
+}
+
+MATCHER_P(ProcessCountEq, expected, "") {
+    return ExplainMatchResult(AllOf(Field("comm", &UserPackageStats::ProcStats::ProcessCount::comm,
+                                          Eq(expected.comm)),
+                                    Field("count",
+                                          &UserPackageStats::ProcStats::ProcessCount::count,
+                                          Eq(expected.count))),
+                              arg, result_listener);
+}
+
+MATCHER_P(ProcStatsEq, expected, "") {
+    std::vector<Matcher<const UserPackageStats::ProcStats::ProcessCount&>> processCountMatchers;
+    for (const auto& processCount : expected.topNProcesses) {
+        processCountMatchers.push_back(ProcessCountEq(processCount));
     }
-    for (int i = 0; i < METRIC_TYPES; ++i) {
-        for (int j = 0; j < UID_STATES; ++j) {
-            if (lhs.total[i][j] != rhs.total[i][j]) {
+    return ExplainMatchResult(AllOf(Field("count", &UserPackageStats::ProcStats::count,
+                                          Eq(expected.count)),
+                                    Field("topNProcesses",
+                                          &UserPackageStats::ProcStats::topNProcesses,
+                                          ElementsAreArray(processCountMatchers))),
+                              arg, result_listener);
+}
+
+MATCHER_P(UserPackageStatsEq, expected, "") {
+    const auto uidMatcher = Field("uid", &UserPackageStats::uid, Eq(expected.uid));
+    const auto packageNameMatcher =
+            Field("genericPackageName", &UserPackageStats::genericPackageName,
+                  Eq(expected.genericPackageName));
+    return std::visit(
+            [&](const auto& stats) -> bool {
+                using T = std::decay_t<decltype(stats)>;
+                if constexpr (std::is_same_v<T, UserPackageStats::IoStats>) {
+                    return ExplainMatchResult(AllOf(uidMatcher, packageNameMatcher,
+                                                    Field("stats:IoStats", &UserPackageStats::stats,
+                                                          VariantWith<UserPackageStats::IoStats>(
+                                                                  IoStatsEq(stats)))),
+                                              arg, result_listener);
+                } else if constexpr (std::is_same_v<T, UserPackageStats::ProcStats>) {
+                    return ExplainMatchResult(AllOf(uidMatcher, packageNameMatcher,
+                                                    Field("stats:ProcStats",
+                                                          &UserPackageStats::stats,
+                                                          VariantWith<UserPackageStats::ProcStats>(
+                                                                  ProcStatsEq(stats)))),
+                                              arg, result_listener);
+                }
+                *result_listener << "Unexpected variant in UserPackageStats::stats";
                 return false;
-            }
+            },
+            expected.stats);
+}
+
+MATCHER_P(UserPackageSummaryStatsEq, expected, "") {
+    const auto& userPackageStatsMatchers = [&](const std::vector<UserPackageStats>& stats) {
+        std::vector<Matcher<const UserPackageStats&>> matchers;
+        for (const auto& curStats : stats) {
+            matchers.push_back(UserPackageStatsEq(curStats));
         }
-    }
-    auto comp = [&](const UidIoPerfData::Stats& l, const UidIoPerfData::Stats& r) -> bool {
-        bool isEqual = l.userId == r.userId && l.packageName == r.packageName;
-        for (int i = 0; i < UID_STATES; ++i) {
-            isEqual &= l.bytes[i] == r.bytes[i] && l.fsync[i] == r.fsync[i];
+        return ElementsAreArray(matchers);
+    };
+    const auto& totalIoStatsArrayMatcher = [&](const int64_t expected[][UID_STATES]) {
+        std::vector<Matcher<const int64_t[UID_STATES]>> matchers;
+        for (int i = 0; i < METRIC_TYPES; ++i) {
+            matchers.push_back(ElementsAreArray(expected[i], UID_STATES));
         }
-        return isEqual;
+        return ElementsAreArray(matchers);
     };
-    return lhs.topNReads.size() == rhs.topNReads.size() &&
-            std::equal(lhs.topNReads.begin(), lhs.topNReads.end(), rhs.topNReads.begin(), comp) &&
-            lhs.topNWrites.size() == rhs.topNWrites.size() &&
-            std::equal(lhs.topNWrites.begin(), lhs.topNWrites.end(), rhs.topNWrites.begin(), comp);
+    return ExplainMatchResult(AllOf(Field("topNIoReads", &UserPackageSummaryStats::topNIoReads,
+                                          userPackageStatsMatchers(expected.topNIoReads)),
+                                    Field("topNIoWrites", &UserPackageSummaryStats::topNIoWrites,
+                                          userPackageStatsMatchers(expected.topNIoWrites)),
+                                    Field("topNIoBlocked", &UserPackageSummaryStats::topNIoBlocked,
+                                          userPackageStatsMatchers(expected.topNIoBlocked)),
+                                    Field("topNMajorFaults",
+                                          &UserPackageSummaryStats::topNMajorFaults,
+                                          userPackageStatsMatchers(expected.topNMajorFaults)),
+                                    Field("totalIoStats", &UserPackageSummaryStats::totalIoStats,
+                                          totalIoStatsArrayMatcher(expected.totalIoStats)),
+                                    Field("taskCountByUid",
+                                          &UserPackageSummaryStats::taskCountByUid,
+                                          IsSubsetOf(expected.taskCountByUid)),
+                                    Field("totalMajorFaults",
+                                          &UserPackageSummaryStats::totalMajorFaults,
+                                          Eq(expected.totalMajorFaults)),
+                                    Field("majorFaultsPercentChange",
+                                          &UserPackageSummaryStats::majorFaultsPercentChange,
+                                          Eq(expected.majorFaultsPercentChange))),
+                              arg, result_listener);
 }
 
-bool isEqual(const SystemIoPerfData& lhs, const SystemIoPerfData& rhs) {
-    return lhs.cpuIoWaitTime == rhs.cpuIoWaitTime && lhs.totalCpuTime == rhs.totalCpuTime &&
-            lhs.ioBlockedProcessesCnt == rhs.ioBlockedProcessesCnt &&
-            lhs.totalProcessesCnt == rhs.totalProcessesCnt;
+MATCHER_P(SystemSummaryStatsEq, expected, "") {
+    return ExplainMatchResult(AllOf(Field("cpuIoWaitTime", &SystemSummaryStats::cpuIoWaitTime,
+                                          Eq(expected.cpuIoWaitTime)),
+                                    Field("totalCpuTime", &SystemSummaryStats::totalCpuTime,
+                                          Eq(expected.totalCpuTime)),
+                                    Field("ioBlockedProcessCount",
+                                          &SystemSummaryStats::ioBlockedProcessCount,
+                                          Eq(expected.ioBlockedProcessCount)),
+                                    Field("totalProcessCount",
+                                          &SystemSummaryStats::totalProcessCount,
+                                          Eq(expected.totalProcessCount))),
+                              arg, result_listener);
 }
 
-bool isEqual(const ProcessIoPerfData& lhs, const ProcessIoPerfData& rhs) {
-    if (lhs.topNIoBlockedUids.size() != rhs.topNIoBlockedUids.size() ||
-        lhs.topNMajorFaultUids.size() != rhs.topNMajorFaultUids.size() ||
-        lhs.totalMajorFaults != rhs.totalMajorFaults ||
-        lhs.majorFaultsPercentChange != rhs.majorFaultsPercentChange) {
-        return false;
+MATCHER_P(PerfStatsRecordEq, expected, "") {
+    return ExplainMatchResult(AllOf(Field(&PerfStatsRecord::systemSummaryStats,
+                                          SystemSummaryStatsEq(expected.systemSummaryStats)),
+                                    Field(&PerfStatsRecord::userPackageSummaryStats,
+                                          UserPackageSummaryStatsEq(
+                                                  expected.userPackageSummaryStats))),
+                              arg, result_listener);
+}
+
+const std::vector<Matcher<const PerfStatsRecord&>> constructPerfStatsRecordMatchers(
+        const std::vector<PerfStatsRecord>& records) {
+    std::vector<Matcher<const PerfStatsRecord&>> matchers;
+    for (const auto& record : records) {
+        matchers.push_back(PerfStatsRecordEq(record));
     }
-    auto comp = [&](const ProcessIoPerfData::UidStats& l,
-                    const ProcessIoPerfData::UidStats& r) -> bool {
-        auto comp = [&](const ProcessIoPerfData::UidStats::ProcessStats& l,
-                        const ProcessIoPerfData::UidStats::ProcessStats& r) -> bool {
-            return l.comm == r.comm && l.count == r.count;
-        };
-        return l.userId == r.userId && l.packageName == r.packageName && l.count == r.count &&
-                l.topNProcesses.size() == r.topNProcesses.size() &&
-                std::equal(l.topNProcesses.begin(), l.topNProcesses.end(), r.topNProcesses.begin(),
-                           comp);
-    };
-    return lhs.topNIoBlockedUids.size() == lhs.topNIoBlockedUids.size() &&
-            std::equal(lhs.topNIoBlockedUids.begin(), lhs.topNIoBlockedUids.end(),
-                       rhs.topNIoBlockedUids.begin(), comp) &&
-            lhs.topNIoBlockedUidsTotalTaskCnt.size() == rhs.topNIoBlockedUidsTotalTaskCnt.size() &&
-            std::equal(lhs.topNIoBlockedUidsTotalTaskCnt.begin(),
-                       lhs.topNIoBlockedUidsTotalTaskCnt.end(),
-                       rhs.topNIoBlockedUidsTotalTaskCnt.begin()) &&
-            lhs.topNMajorFaultUids.size() == rhs.topNMajorFaultUids.size() &&
-            std::equal(lhs.topNMajorFaultUids.begin(), lhs.topNMajorFaultUids.end(),
-                       rhs.topNMajorFaultUids.begin(), comp);
+    return matchers;
 }
 
-bool isEqual(const IoPerfRecord& lhs, const IoPerfRecord& rhs) {
-    return isEqual(lhs.uidIoPerfData, rhs.uidIoPerfData) &&
-            isEqual(lhs.systemIoPerfData, rhs.systemIoPerfData) &&
-            isEqual(lhs.processIoPerfData, rhs.processIoPerfData);
+MATCHER_P(CollectionInfoEq, expected, "") {
+    return ExplainMatchResult(AllOf(Field("maxCacheSize", &CollectionInfo::maxCacheSize,
+                                          Eq(expected.maxCacheSize)),
+                                    Field("records", &CollectionInfo::records,
+                                          ElementsAreArray(constructPerfStatsRecordMatchers(
+                                                  expected.records)))),
+                              arg, result_listener);
 }
 
 int countOccurrences(std::string str, std::string subStr) {
@@ -122,23 +203,167 @@
     return occurrences;
 }
 
+std::tuple<std::vector<UidStats>, UserPackageSummaryStats> sampleUidStats(int multiplier = 1) {
+    /* The number of returned sample stats are less that the top N stats per category/sub-category.
+     * The top N stats per category/sub-category is set to % during test setup. Thus, the default
+     * testing behavior is # reported stats < top N stats.
+     */
+    const auto int64Multiplier = [&](int64_t bytes) -> int64_t {
+        return static_cast<int64_t>(bytes * multiplier);
+    };
+    const auto uint64Multiplier = [&](uint64_t count) -> uint64_t {
+        return static_cast<uint64_t>(count * multiplier);
+    };
+    std::vector<UidStats>
+            uidStats{{.packageInfo = constructPackageInfo("mount", 1009),
+                      .ioStats = {/*fgRdBytes=*/0,
+                                  /*bgRdBytes=*/int64Multiplier(14'000),
+                                  /*fgWrBytes=*/0,
+                                  /*bgWrBytes=*/int64Multiplier(16'000),
+                                  /*fgFsync=*/0, /*bgFsync=*/int64Multiplier(100)},
+                      .procStats = {.totalMajorFaults = uint64Multiplier(11'000),
+                                    .totalTasksCount = 1,
+                                    .ioBlockedTasksCount = 1,
+                                    .processStatsByPid =
+                                            {{/*pid=*/100,
+                                              {/*comm=*/"disk I/O", /*startTime=*/234,
+                                               /*totalMajorFaults=*/uint64Multiplier(11'000),
+                                               /*totalTasksCount=*/1,
+                                               /*ioBlockedTasksCount=*/1}}}}},
+                     {.packageInfo =
+                              constructPackageInfo("com.google.android.car.kitchensink", 1002001),
+                      .ioStats = {/*fgRdBytes=*/0,
+                                  /*bgRdBytes=*/int64Multiplier(3'400),
+                                  /*fgWrBytes=*/0,
+                                  /*bgWrBytes=*/int64Multiplier(6'700),
+                                  /*fgFsync=*/0,
+                                  /*bgFsync=*/int64Multiplier(200)},
+                      .procStats = {.totalMajorFaults = uint64Multiplier(22'445),
+                                    .totalTasksCount = 5,
+                                    .ioBlockedTasksCount = 3,
+                                    .processStatsByPid =
+                                            {{/*pid=*/1000,
+                                              {/*comm=*/"KitchenSinkApp", /*startTime=*/467,
+                                               /*totalMajorFaults=*/uint64Multiplier(12'345),
+                                               /*totalTasksCount=*/2,
+                                               /*ioBlockedTasksCount=*/1}},
+                                             {/*pid=*/1001,
+                                              {/*comm=*/"CTS", /*startTime=*/789,
+                                               /*totalMajorFaults=*/uint64Multiplier(10'100),
+                                               /*totalTasksCount=*/3,
+                                               /*ioBlockedTasksCount=*/2}}}}},
+                     {.packageInfo = constructPackageInfo("", 1012345),
+                      .ioStats = {/*fgRdBytes=*/int64Multiplier(1'000),
+                                  /*bgRdBytes=*/int64Multiplier(4'200),
+                                  /*fgWrBytes=*/int64Multiplier(300),
+                                  /*bgWrBytes=*/int64Multiplier(5'600),
+                                  /*fgFsync=*/int64Multiplier(600),
+                                  /*bgFsync=*/int64Multiplier(300)},
+                      .procStats = {.totalMajorFaults = uint64Multiplier(50'900),
+                                    .totalTasksCount = 4,
+                                    .ioBlockedTasksCount = 2,
+                                    .processStatsByPid =
+                                            {{/*pid=*/2345,
+                                              {/*comm=*/"MapsApp", /*startTime=*/6789,
+                                               /*totalMajorFaults=*/uint64Multiplier(50'900),
+                                               /*totalTasksCount=*/4,
+                                               /*ioBlockedTasksCount=*/2}}}}},
+                     {.packageInfo = constructPackageInfo("com.google.radio", 1015678),
+                      .ioStats = {/*fgRdBytes=*/0,
+                                  /*bgRdBytes=*/0,
+                                  /*fgWrBytes=*/0,
+                                  /*bgWrBytes=*/0,
+                                  /*fgFsync=*/0, /*bgFsync=*/0},
+                      .procStats = {.totalMajorFaults = 0,
+                                    .totalTasksCount = 4,
+                                    .ioBlockedTasksCount = 0,
+                                    .processStatsByPid = {
+                                            {/*pid=*/2345,
+                                             {/*comm=*/"RadioApp", /*startTime=*/19789,
+                                              /*totalMajorFaults=*/0,
+                                              /*totalTasksCount=*/4,
+                                              /*ioBlockedTasksCount=*/0}}}}}};
+
+    UserPackageSummaryStats userPackageSummaryStats{
+            .topNIoReads =
+                    {{1009, "mount",
+                      UserPackageStats::IoStats{{0, int64Multiplier(14'000)},
+                                                {0, int64Multiplier(100)}}},
+                     {1012345, "1012345",
+                      UserPackageStats::IoStats{{int64Multiplier(1'000), int64Multiplier(4'200)},
+                                                {int64Multiplier(600), int64Multiplier(300)}}},
+                     {1002001, "com.google.android.car.kitchensink",
+                      UserPackageStats::IoStats{{0, int64Multiplier(3'400)},
+                                                {0, int64Multiplier(200)}}}},
+            .topNIoWrites =
+                    {{1009, "mount",
+                      UserPackageStats::IoStats{{0, int64Multiplier(16'000)},
+                                                {0, int64Multiplier(100)}}},
+                     {1002001, "com.google.android.car.kitchensink",
+                      UserPackageStats::IoStats{{0, int64Multiplier(6'700)},
+                                                {0, int64Multiplier(200)}}},
+                     {1012345, "1012345",
+                      UserPackageStats::IoStats{{int64Multiplier(300), int64Multiplier(5'600)},
+                                                {int64Multiplier(600), int64Multiplier(300)}}}},
+            .topNIoBlocked = {{1002001, "com.google.android.car.kitchensink",
+                               UserPackageStats::ProcStats{3, {{"CTS", 2}, {"KitchenSinkApp", 1}}}},
+                              {1012345, "1012345",
+                               UserPackageStats::ProcStats{2, {{"MapsApp", 2}}}},
+                              {1009, "mount", UserPackageStats::ProcStats{1, {{"disk I/O", 1}}}}},
+            .topNMajorFaults =
+                    {{1012345, "1012345",
+                      UserPackageStats::ProcStats{uint64Multiplier(50'900),
+                                                  {{"MapsApp", uint64Multiplier(50'900)}}}},
+                     {1002001, "com.google.android.car.kitchensink",
+                      UserPackageStats::ProcStats{uint64Multiplier(22'445),
+                                                  {{"KitchenSinkApp", uint64Multiplier(12'345)},
+                                                   {"CTS", uint64Multiplier(10'100)}}}},
+                     {1009, "mount",
+                      UserPackageStats::ProcStats{uint64Multiplier(11'000),
+                                                  {{"disk I/O", uint64Multiplier(11'000)}}}}},
+            .totalIoStats = {{int64Multiplier(1'000), int64Multiplier(21'600)},
+                             {int64Multiplier(300), int64Multiplier(28'300)},
+                             {int64Multiplier(600), int64Multiplier(600)}},
+            .taskCountByUid = {{1009, 1}, {1002001, 5}, {1012345, 4}},
+            .totalMajorFaults = uint64Multiplier(84'345),
+            .majorFaultsPercentChange = 0.0,
+    };
+    return std::make_tuple(uidStats, userPackageSummaryStats);
+}
+
+std::tuple<ProcStatInfo, SystemSummaryStats> sampleProcStat(int multiplier = 1) {
+    const auto uint64Multiplier = [&](uint64_t bytes) -> uint64_t {
+        return static_cast<uint64_t>(bytes * multiplier);
+    };
+    const auto uint32Multiplier = [&](uint32_t bytes) -> uint32_t {
+        return static_cast<uint32_t>(bytes * multiplier);
+    };
+    ProcStatInfo procStatInfo{/*cpuStats=*/{uint64Multiplier(2'900), uint64Multiplier(7'900),
+                                            uint64Multiplier(4'900), uint64Multiplier(8'900),
+                                            /*ioWaitTime=*/uint64Multiplier(5'900),
+                                            uint64Multiplier(6'966), uint64Multiplier(7'980), 0, 0,
+                                            uint64Multiplier(2'930)},
+                              /*runnableProcessCount=*/uint32Multiplier(100),
+                              /*ioBlockedProcessCount=*/uint32Multiplier(57)};
+    SystemSummaryStats systemSummaryStats{/*cpuIoWaitTime=*/uint64Multiplier(5'900),
+                                          /*totalCpuTime=*/uint64Multiplier(48'376),
+                                          /*ioBlockedProcessCount=*/uint32Multiplier(57),
+                                          /*totalProcessCount=*/uint32Multiplier(157)};
+    return std::make_tuple(procStatInfo, systemSummaryStats);
+}
+
 }  // namespace
 
 namespace internal {
 
-class IoPerfCollectionPeer {
+class IoPerfCollectionPeer : public RefBase {
 public:
-    explicit IoPerfCollectionPeer(sp<IoPerfCollection> collector) :
-          mCollector(collector),
-          mMockPackageInfoResolver(new MockPackageInfoResolver()) {
-        mCollector->mPackageInfoResolver = mMockPackageInfoResolver;
-    }
+    explicit IoPerfCollectionPeer(sp<IoPerfCollection> collector) : mCollector(collector) {}
 
     IoPerfCollectionPeer() = delete;
     ~IoPerfCollectionPeer() {
         mCollector->terminate();
         mCollector.clear();
-        mMockPackageInfoResolver.clear();
     }
 
     Result<void> init() { return mCollector->init(); }
@@ -147,11 +372,6 @@
 
     void setTopNStatsPerSubcategory(int value) { mCollector->mTopNStatsPerSubcategory = value; }
 
-    void injectUidToPackageNameMapping(std::unordered_map<uid_t, std::string> mapping) {
-        EXPECT_CALL(*mMockPackageInfoResolver, getPackageNamesForUids(_))
-                .WillRepeatedly(Return(mapping));
-    }
-
     const CollectionInfo& getBoottimeCollectionInfo() {
         Mutex::Autolock lock(mCollector->mMutex);
         return mCollector->mBoottimeCollection;
@@ -169,548 +389,319 @@
 
 private:
     sp<IoPerfCollection> mCollector;
-    sp<MockPackageInfoResolver> mMockPackageInfoResolver;
 };
 
 }  // namespace internal
 
-TEST(IoPerfCollectionTest, TestBoottimeCollection) {
-    sp<MockUidIoStats> mockUidIoStats = new MockUidIoStats();
-    sp<MockProcStat> mockProcStat = new MockProcStat();
-    sp<MockProcPidStat> mockProcPidStat = new MockProcPidStat();
+class IoPerfCollectionTest : public Test {
+protected:
+    void SetUp() override {
+        mMockUidStatsCollector = sp<MockUidStatsCollector>::make();
+        mMockProcStat = sp<MockProcStat>::make();
+        mCollector = sp<IoPerfCollection>::make();
+        mCollectorPeer = sp<internal::IoPerfCollectionPeer>::make(mCollector);
+        ASSERT_RESULT_OK(mCollectorPeer->init());
+        mCollectorPeer->setTopNStatsPerCategory(5);
+        mCollectorPeer->setTopNStatsPerSubcategory(5);
+    }
 
-    sp<IoPerfCollection> collector = new IoPerfCollection();
-    internal::IoPerfCollectionPeer collectorPeer(collector);
+    void TearDown() override {
+        mMockUidStatsCollector.clear();
+        mMockProcStat.clear();
+        mCollector.clear();
+        mCollectorPeer.clear();
+    }
 
-    ASSERT_RESULT_OK(collectorPeer.init());
+    void checkDumpContents(int wantedEmptyCollectionInstances) {
+        TemporaryFile dump;
+        ASSERT_RESULT_OK(mCollector->onDump(dump.fd));
 
-    const std::unordered_map<uid_t, UidIoUsage> uidIoUsages({
-            {1009, {.uid = 1009, .ios = {0, 14000, 0, 16000, 0, 100}}},
-    });
-    const ProcStatInfo procStatInfo{
-            /*stats=*/{2900, 7900, 4900, 8900, /*ioWaitTime=*/5900, 6966, 7980, 0, 0, 2930},
-            /*runnableCnt=*/100,
-            /*ioBlockedCnt=*/57,
-    };
-    const std::vector<ProcessStats> processStats({
-            {.tgid = 100,
-             .uid = 1009,
-             .process = {100, "disk I/O", "D", 1, 11000, 1, 234},
-             .threads = {{100, {100, "mount", "D", 1, 11000, 1, 234}}}},
-    });
+        checkDumpFd(wantedEmptyCollectionInstances, dump.fd);
+    }
 
-    EXPECT_CALL(*mockUidIoStats, deltaStats()).WillOnce(Return(uidIoUsages));
-    EXPECT_CALL(*mockProcStat, deltaStats()).WillOnce(Return(procStatInfo));
-    EXPECT_CALL(*mockProcPidStat, deltaStats()).WillOnce(Return(processStats));
+    void checkCustomDumpContents() {
+        TemporaryFile dump;
+        ASSERT_RESULT_OK(mCollector->onCustomCollectionDump(dump.fd));
 
-    const IoPerfRecord expected = {
-            .uidIoPerfData = {.topNReads = {{0, "mount", {0, 14000}, {0, 100}}},
-                              .topNWrites = {{0, "mount", {0, 16000}, {0, 100}}},
-                              .total = {{0, 14000}, {0, 16000}, {0, 100}}},
-            .systemIoPerfData = {5900, 48376, 57, 157},
-            .processIoPerfData =
-                    {.topNIoBlockedUids = {{0, "mount", 1, {{"disk I/O", 1}}}},
-                     .topNIoBlockedUidsTotalTaskCnt = {1},
-                     .topNMajorFaultUids = {{0, "mount", 11000, {{"disk I/O", 11000}}}},
-                     .totalMajorFaults = 11000,
-                     .majorFaultsPercentChange = 0},
-    };
-    collectorPeer.injectUidToPackageNameMapping({{1009, "mount"}});
+        checkDumpFd(/*wantedEmptyCollectionInstances=*/0, dump.fd);
+    }
+
+private:
+    void checkDumpFd(int wantedEmptyCollectionInstances, int fd) {
+        lseek(fd, 0, SEEK_SET);
+        std::string dumpContents;
+        ASSERT_TRUE(ReadFdToString(fd, &dumpContents));
+        ASSERT_FALSE(dumpContents.empty());
+
+        ASSERT_EQ(countOccurrences(dumpContents, kEmptyCollectionMessage),
+                  wantedEmptyCollectionInstances)
+                << "Dump contents: " << dumpContents;
+    }
+
+protected:
+    sp<MockUidStatsCollector> mMockUidStatsCollector;
+    sp<MockProcStat> mMockProcStat;
+    sp<IoPerfCollection> mCollector;
+    sp<internal::IoPerfCollectionPeer> mCollectorPeer;
+};
+
+TEST_F(IoPerfCollectionTest, TestOnBoottimeCollection) {
+    const auto [uidStats, userPackageSummaryStats] = sampleUidStats();
+    const auto [procStatInfo, systemSummaryStats] = sampleProcStat();
+
+    EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(uidStats));
+    EXPECT_CALL(*mMockProcStat, deltaStats()).WillOnce(Return(procStatInfo));
 
     time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
-    ASSERT_RESULT_OK(
-            collector->onBoottimeCollection(now, mockUidIoStats, mockProcStat, mockProcPidStat));
+    ASSERT_RESULT_OK(mCollector->onBoottimeCollection(now, mMockUidStatsCollector, mMockProcStat));
 
-    const CollectionInfo& collectionInfo = collectorPeer.getBoottimeCollectionInfo();
+    const auto actual = mCollectorPeer->getBoottimeCollectionInfo();
 
-    ASSERT_EQ(collectionInfo.maxCacheSize, std::numeric_limits<std::size_t>::max());
-    ASSERT_EQ(collectionInfo.records.size(), 1);
-    ASSERT_TRUE(isEqual(collectionInfo.records[0], expected))
-            << "Boottime collection record doesn't match.\nExpected:\n"
-            << toString(expected) << "\nActual:\n"
-            << toString(collectionInfo.records[0]);
+    const CollectionInfo expected{
+            .maxCacheSize = std::numeric_limits<std::size_t>::max(),
+            .records = {{
+                    .systemSummaryStats = systemSummaryStats,
+                    .userPackageSummaryStats = userPackageSummaryStats,
+            }},
+    };
 
-    TemporaryFile dump;
-    ASSERT_RESULT_OK(collector->onDump(dump.fd));
+    EXPECT_THAT(actual, CollectionInfoEq(expected))
+            << "Boottime collection info doesn't match.\nExpected:\n"
+            << expected.toString() << "\nActual:\n"
+            << actual.toString();
 
-    lseek(dump.fd, 0, SEEK_SET);
-    std::string dumpContents;
-    ASSERT_TRUE(ReadFdToString(dump.fd, &dumpContents));
-    ASSERT_FALSE(dumpContents.empty());
-
-    ASSERT_EQ(countOccurrences(dumpContents, kEmptyCollectionMessage), 1)
-            << "Only periodic collection should be not collected. Dump contents: " << dumpContents;
+    ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/1))
+            << "Periodic collection shouldn't be reported";
 }
 
-TEST(IoPerfCollectionTest, TestPeriodicCollection) {
-    sp<MockUidIoStats> mockUidIoStats = new MockUidIoStats();
-    sp<MockProcStat> mockProcStat = new MockProcStat();
-    sp<MockProcPidStat> mockProcPidStat = new MockProcPidStat();
+TEST_F(IoPerfCollectionTest, TestOnPeriodicCollection) {
+    const auto [uidStats, userPackageSummaryStats] = sampleUidStats();
+    const auto [procStatInfo, systemSummaryStats] = sampleProcStat();
 
-    sp<IoPerfCollection> collector = new IoPerfCollection();
-    internal::IoPerfCollectionPeer collectorPeer(collector);
-
-    ASSERT_RESULT_OK(collectorPeer.init());
-
-    const std::unordered_map<uid_t, UidIoUsage> uidIoUsages({
-            {1009, {.uid = 1009, .ios = {0, 14000, 0, 16000, 0, 100}}},
-    });
-    const ProcStatInfo procStatInfo{
-            /*stats=*/{2900, 7900, 4900, 8900, /*ioWaitTime=*/5900, 6966, 7980, 0, 0, 2930},
-            /*runnableCnt=*/100,
-            /*ioBlockedCnt=*/57,
-    };
-    const std::vector<ProcessStats> processStats({
-            {.tgid = 100,
-             .uid = 1009,
-             .process = {100, "disk I/O", "D", 1, 11000, 1, 234},
-             .threads = {{100, {100, "mount", "D", 1, 11000, 1, 234}}}},
-    });
-
-    EXPECT_CALL(*mockUidIoStats, deltaStats()).WillOnce(Return(uidIoUsages));
-    EXPECT_CALL(*mockProcStat, deltaStats()).WillOnce(Return(procStatInfo));
-    EXPECT_CALL(*mockProcPidStat, deltaStats()).WillOnce(Return(processStats));
-
-    const IoPerfRecord expected = {
-            .uidIoPerfData = {.topNReads = {{0, "mount", {0, 14000}, {0, 100}}},
-                              .topNWrites = {{0, "mount", {0, 16000}, {0, 100}}},
-                              .total = {{0, 14000}, {0, 16000}, {0, 100}}},
-            .systemIoPerfData = {5900, 48376, 57, 157},
-            .processIoPerfData =
-                    {.topNIoBlockedUids = {{0, "mount", 1, {{"disk I/O", 1}}}},
-                     .topNIoBlockedUidsTotalTaskCnt = {1},
-                     .topNMajorFaultUids = {{0, "mount", 11000, {{"disk I/O", 11000}}}},
-                     .totalMajorFaults = 11000,
-                     .majorFaultsPercentChange = 0},
-    };
-
-    collectorPeer.injectUidToPackageNameMapping({{1009, "mount"}});
+    EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(uidStats));
+    EXPECT_CALL(*mMockProcStat, deltaStats()).WillOnce(Return(procStatInfo));
 
     time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
-    ASSERT_RESULT_OK(collector->onPeriodicCollection(now, SystemState::NORMAL_MODE, mockUidIoStats,
-                                                     mockProcStat, mockProcPidStat));
+    ASSERT_RESULT_OK(mCollector->onPeriodicCollection(now, SystemState::NORMAL_MODE,
+                                                      mMockUidStatsCollector, mMockProcStat));
 
-    const CollectionInfo& collectionInfo = collectorPeer.getPeriodicCollectionInfo();
+    const auto actual = mCollectorPeer->getPeriodicCollectionInfo();
 
-    ASSERT_EQ(collectionInfo.maxCacheSize,
-              static_cast<size_t>(sysprop::periodicCollectionBufferSize().value_or(
-                      kDefaultPeriodicCollectionBufferSize)));
-    ASSERT_EQ(collectionInfo.records.size(), 1);
-    ASSERT_TRUE(isEqual(collectionInfo.records[0], expected))
-            << "Periodic collection record doesn't match.\nExpected:\n"
-            << toString(expected) << "\nActual:\n"
-            << toString(collectionInfo.records[0]);
+    const CollectionInfo expected{
+            .maxCacheSize = static_cast<size_t>(sysprop::periodicCollectionBufferSize().value_or(
+                    kDefaultPeriodicCollectionBufferSize)),
+            .records = {{
+                    .systemSummaryStats = systemSummaryStats,
+                    .userPackageSummaryStats = userPackageSummaryStats,
+            }},
+    };
 
-    TemporaryFile dump;
-    ASSERT_RESULT_OK(collector->onDump(dump.fd));
+    EXPECT_THAT(actual, CollectionInfoEq(expected))
+            << "Periodic collection info doesn't match.\nExpected:\n"
+            << expected.toString() << "\nActual:\n"
+            << actual.toString();
 
-    lseek(dump.fd, 0, SEEK_SET);
-    std::string dumpContents;
-    ASSERT_TRUE(ReadFdToString(dump.fd, &dumpContents));
-    ASSERT_FALSE(dumpContents.empty());
-
-    ASSERT_EQ(countOccurrences(dumpContents, kEmptyCollectionMessage), 1)
-            << "Only boot-time collection should be not collected. Dump contents: " << dumpContents;
+    ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/1))
+            << "Boot-time collection shouldn't be reported";
 }
 
-TEST(IoPerfCollectionTest, TestCustomCollection) {
-    sp<MockUidIoStats> mockUidIoStats = new MockUidIoStats();
-    sp<MockProcStat> mockProcStat = new MockProcStat();
-    sp<MockProcPidStat> mockProcPidStat = new MockProcPidStat();
+TEST_F(IoPerfCollectionTest, TestOnCustomCollectionWithoutPackageFilter) {
+    const auto [uidStats, userPackageSummaryStats] = sampleUidStats();
+    const auto [procStatInfo, systemSummaryStats] = sampleProcStat();
 
-    sp<IoPerfCollection> collector = new IoPerfCollection();
-    internal::IoPerfCollectionPeer collectorPeer(collector);
-
-    ASSERT_RESULT_OK(collectorPeer.init());
-
-    // Filter by package name should ignore this limit.
-    collectorPeer.setTopNStatsPerCategory(1);
-
-    const std::unordered_map<uid_t, UidIoUsage> uidIoUsages({
-            {1009, {.uid = 1009, .ios = {0, 14000, 0, 16000, 0, 100}}},
-            {2001, {.uid = 2001, .ios = {0, 3400, 0, 6700, 0, 200}}},
-            {3456, {.uid = 3456, .ios = {0, 4200, 0, 5600, 0, 300}}},
-    });
-    const ProcStatInfo procStatInfo{
-            /*stats=*/{2900, 7900, 4900, 8900, /*ioWaitTime=*/5900, 6966, 7980, 0, 0, 2930},
-            /*runnableCnt=*/100,
-            /*ioBlockedCnt=*/57,
-    };
-    const std::vector<ProcessStats> processStats({
-            {.tgid = 100,
-             .uid = 1009,
-             .process = {100, "cts_test", "D", 1, 50900, 2, 234},
-             .threads = {{100, {100, "cts_test", "D", 1, 50900, 1, 234}},
-                         {200, {200, "cts_test_2", "D", 1, 0, 1, 290}}}},
-            {.tgid = 1000,
-             .uid = 2001,
-             .process = {1000, "system_server", "D", 1, 1234, 1, 345},
-             .threads = {{1000, {1000, "system_server", "D", 1, 1234, 1, 345}}}},
-            {.tgid = 4000,
-             .uid = 3456,
-             .process = {4000, "random_process", "D", 1, 3456, 1, 890},
-             .threads = {{4000, {4000, "random_process", "D", 1, 50900, 1, 890}}}},
-    });
-
-    EXPECT_CALL(*mockUidIoStats, deltaStats()).WillOnce(Return(uidIoUsages));
-    EXPECT_CALL(*mockProcStat, deltaStats()).WillOnce(Return(procStatInfo));
-    EXPECT_CALL(*mockProcPidStat, deltaStats()).WillOnce(Return(processStats));
-    const IoPerfRecord expected = {
-            .uidIoPerfData = {.topNReads = {{.userId = 0,
-                                             .packageName = "android.car.cts",
-                                             .bytes = {0, 14000},
-                                             .fsync = {0, 100}},
-                                            {.userId = 0,
-                                             .packageName = "system_server",
-                                             .bytes = {0, 3400},
-                                             .fsync = {0, 200}}},
-                              .topNWrites = {{.userId = 0,
-                                              .packageName = "android.car.cts",
-                                              .bytes = {0, 16000},
-                                              .fsync = {0, 100}},
-                                             {.userId = 0,
-                                              .packageName = "system_server",
-                                              .bytes = {0, 6700},
-                                              .fsync = {0, 200}}},
-                              .total = {{0, 21600}, {0, 28300}, {0, 600}}},
-            .systemIoPerfData = {.cpuIoWaitTime = 5900,
-                                 .totalCpuTime = 48376,
-                                 .ioBlockedProcessesCnt = 57,
-                                 .totalProcessesCnt = 157},
-            .processIoPerfData =
-                    {.topNIoBlockedUids = {{0, "android.car.cts", 2, {{"cts_test", 2}}},
-                                           {0, "system_server", 1, {{"system_server", 1}}}},
-                     .topNIoBlockedUidsTotalTaskCnt = {2, 1},
-                     .topNMajorFaultUids = {{0, "android.car.cts", 50900, {{"cts_test", 50900}}},
-                                            {0, "system_server", 1234, {{"system_server", 1234}}}},
-                     .totalMajorFaults = 55590,
-                     .majorFaultsPercentChange = 0},
-    };
-    collectorPeer.injectUidToPackageNameMapping({
-            {1009, "android.car.cts"},
-            {2001, "system_server"},
-            {3456, "random_process"},
-    });
+    EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(uidStats));
+    EXPECT_CALL(*mMockProcStat, deltaStats()).WillOnce(Return(procStatInfo));
 
     time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
-    ASSERT_RESULT_OK(collector->onCustomCollection(now, SystemState::NORMAL_MODE,
-                                                   {"android.car.cts", "system_server"},
-                                                   mockUidIoStats, mockProcStat, mockProcPidStat));
+    ASSERT_RESULT_OK(mCollector->onCustomCollection(now, SystemState::NORMAL_MODE, {},
+                                                    mMockUidStatsCollector, mMockProcStat));
 
-    const CollectionInfo& collectionInfo = collectorPeer.getCustomCollectionInfo();
+    const auto actual = mCollectorPeer->getCustomCollectionInfo();
 
-    EXPECT_EQ(collectionInfo.maxCacheSize, std::numeric_limits<std::size_t>::max());
-    ASSERT_EQ(collectionInfo.records.size(), 1);
-    ASSERT_TRUE(isEqual(collectionInfo.records[0], expected))
-            << "Custom collection record doesn't match.\nExpected:\n"
-            << toString(expected) << "\nActual:\n"
-            << toString(collectionInfo.records[0]);
+    CollectionInfo expected{
+            .maxCacheSize = std::numeric_limits<std::size_t>::max(),
+            .records = {{
+                    .systemSummaryStats = systemSummaryStats,
+                    .userPackageSummaryStats = userPackageSummaryStats,
+            }},
+    };
+
+    EXPECT_THAT(actual, CollectionInfoEq(expected))
+            << "Custom collection info doesn't match.\nExpected:\n"
+            << expected.toString() << "\nActual:\n"
+            << actual.toString();
+
+    ASSERT_NO_FATAL_FAILURE(checkCustomDumpContents()) << "Custom collection should be reported";
 
     TemporaryFile customDump;
-    ASSERT_RESULT_OK(collector->onCustomCollectionDump(customDump.fd));
-
-    lseek(customDump.fd, 0, SEEK_SET);
-    std::string customDumpContents;
-    ASSERT_TRUE(ReadFdToString(customDump.fd, &customDumpContents));
-    ASSERT_FALSE(customDumpContents.empty());
-    ASSERT_EQ(countOccurrences(customDumpContents, kEmptyCollectionMessage), 0)
-            << "Custom collection should be reported. Dump contents: " << customDumpContents;
+    ASSERT_RESULT_OK(mCollector->onCustomCollectionDump(customDump.fd));
 
     // Should clear the cache.
-    ASSERT_RESULT_OK(collector->onCustomCollectionDump(-1));
+    ASSERT_RESULT_OK(mCollector->onCustomCollectionDump(-1));
 
-    const CollectionInfo& emptyCollectionInfo = collectorPeer.getCustomCollectionInfo();
-    EXPECT_TRUE(emptyCollectionInfo.records.empty());
-    EXPECT_EQ(emptyCollectionInfo.maxCacheSize, std::numeric_limits<std::size_t>::max());
+    expected.records.clear();
+    const CollectionInfo& emptyCollectionInfo = mCollectorPeer->getCustomCollectionInfo();
+    EXPECT_THAT(emptyCollectionInfo, CollectionInfoEq(expected))
+            << "Custom collection should be cleared.";
 }
 
-TEST(IoPerfCollectionTest, TestUidIoStatsGreaterThanTopNStatsLimit) {
-    std::unordered_map<uid_t, UidIoUsage> uidIoUsages({
-            {1001234, {.uid = 1001234, .ios = {3000, 0, 500, 0, 20, 0}}},
-            {1005678, {.uid = 1005678, .ios = {30, 100, 50, 200, 45, 60}}},
-            {1009, {.uid = 1009, .ios = {0, 20000, 0, 30000, 0, 300}}},
-            {1001000, {.uid = 1001000, .ios = {2000, 200, 1000, 100, 50, 10}}},
-    });
-    sp<MockUidIoStats> mockUidIoStats = new MockUidIoStats();
-    EXPECT_CALL(*mockUidIoStats, deltaStats()).WillOnce(Return(uidIoUsages));
+TEST_F(IoPerfCollectionTest, TestOnCustomCollectionWithPackageFilter) {
+    // Filter by package name should ignore this limit with package filter.
+    mCollectorPeer->setTopNStatsPerCategory(1);
 
-    struct UidIoPerfData expectedUidIoPerfData = {
-            .topNReads = {{.userId = 0,  // uid: 1009
-                           .packageName = "mount",
-                           .bytes = {0, 20000},
-                           .fsync = {0, 300}},
-                          {.userId = 10,  // uid: 1001234
-                           .packageName = "1001234",
-                           .bytes = {3000, 0},
-                           .fsync = {20, 0}}},
-            .topNWrites = {{.userId = 0,  // uid: 1009
-                            .packageName = "mount",
-                            .bytes = {0, 30000},
-                            .fsync = {0, 300}},
-                           {.userId = 10,  // uid: 1001000
-                            .packageName = "shared:android.uid.system",
-                            .bytes = {1000, 100},
-                            .fsync = {50, 10}}},
-            .total = {{5030, 20300}, {1550, 30300}, {115, 370}},
-    };
+    const auto [uidStats, _] = sampleUidStats();
+    const auto [procStatInfo, systemSummaryStats] = sampleProcStat();
 
-    IoPerfCollection collector;
-    collector.mTopNStatsPerCategory = 2;
+    EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(uidStats));
+    EXPECT_CALL(*mMockProcStat, deltaStats()).WillOnce(Return(procStatInfo));
 
-    sp<MockPackageInfoResolver> mockPackageInfoResolver = new MockPackageInfoResolver();
-    collector.mPackageInfoResolver = mockPackageInfoResolver;
-    EXPECT_CALL(*mockPackageInfoResolver, getPackageNamesForUids(_))
-            .WillRepeatedly(Return<std::unordered_map<uid_t, std::string>>(
-                    {{1009, "mount"}, {1001000, "shared:android.uid.system"}}));
+    time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
+    ASSERT_RESULT_OK(mCollector->onCustomCollection(now, SystemState::NORMAL_MODE,
+                                                    {"mount", "com.google.android.car.kitchensink"},
+                                                    mMockUidStatsCollector, mMockProcStat));
 
-    struct UidIoPerfData actualUidIoPerfData = {};
-    collector.processUidIoPerfData({}, mockUidIoStats, &actualUidIoPerfData);
+    const auto actual = mCollectorPeer->getCustomCollectionInfo();
 
-    EXPECT_TRUE(isEqual(expectedUidIoPerfData, actualUidIoPerfData))
-        << "First snapshot doesn't match.\nExpected:\n"
-        << toString(expectedUidIoPerfData) << "\nActual:\n"
-        << toString(actualUidIoPerfData);
-
-    uidIoUsages = {
-            {1001234, {.uid = 1001234, .ios = {4000, 0, 450, 0, 25, 0}}},
-            {1005678, {.uid = 1005678, .ios = {10, 900, 0, 400, 5, 10}}},
-            {1003456, {.uid = 1003456, .ios = {200, 0, 300, 0, 50, 0}}},
-            {1001000, {.uid = 1001000, .ios = {0, 0, 0, 0, 0, 0}}},
-    };
-    EXPECT_CALL(*mockUidIoStats, deltaStats()).WillOnce(Return(uidIoUsages));
-
-    expectedUidIoPerfData = {
-            .topNReads = {{.userId = 10,  // uid: 1001234
-                           .packageName = "1001234",
-                           .bytes = {4000, 0},
-                           .fsync = {25, 0}},
-                          {.userId = 10,  // uid: 1005678
-                           .packageName = "1005678",
-                           .bytes = {10, 900},
-                           .fsync = {5, 10}}},
-            .topNWrites = {{.userId = 10,  // uid: 1001234
-                            .packageName = "1001234",
-                            .bytes = {450, 0},
-                            .fsync = {25, 0}},
-                           {.userId = 10,  // uid: 1005678
-                            .packageName = "1005678",
-                            .bytes = {0, 400},
-                            .fsync = {5, 10}}},
-            .total = {{4210, 900}, {750, 400}, {80, 10}},
-    };
-    actualUidIoPerfData = {};
-    collector.processUidIoPerfData({}, mockUidIoStats, &actualUidIoPerfData);
-
-    EXPECT_TRUE(isEqual(expectedUidIoPerfData, actualUidIoPerfData))
-        << "Second snapshot doesn't match.\nExpected:\n"
-        << toString(expectedUidIoPerfData) << "\nActual:\n"
-        << toString(actualUidIoPerfData);
-}
-
-TEST(IoPerfCollectionTest, TestUidIOStatsLessThanTopNStatsLimit) {
-    const std::unordered_map<uid_t, UidIoUsage> uidIoUsages(
-            {{1001234, {.uid = 1001234, .ios = {3000, 0, 500, 0, 20, 0}}}});
-
-    const struct UidIoPerfData expectedUidIoPerfData = {
-            .topNReads = {{.userId = 10,
-                           .packageName = "1001234",
-                           .bytes = {3000, 0},
-                           .fsync = {20, 0}}},
-            .topNWrites =
-                    {{.userId = 10, .packageName = "1001234", .bytes = {500, 0}, .fsync = {20, 0}}},
-            .total = {{3000, 0}, {500, 0}, {20, 0}},
-    };
-
-    sp<MockUidIoStats> mockUidIoStats = new MockUidIoStats();
-    EXPECT_CALL(*mockUidIoStats, deltaStats()).WillOnce(Return(uidIoUsages));
-
-    IoPerfCollection collector;
-    collector.mTopNStatsPerCategory = 10;
-
-    struct UidIoPerfData actualUidIoPerfData = {};
-    collector.processUidIoPerfData({}, mockUidIoStats, &actualUidIoPerfData);
-
-    EXPECT_TRUE(isEqual(expectedUidIoPerfData, actualUidIoPerfData))
-        << "Collected data doesn't match.\nExpected:\n"
-        << toString(expectedUidIoPerfData) << "\nActual:\n"
-        << toString(actualUidIoPerfData);
-}
-
-TEST(IoPerfCollectionTest, TestProcessSystemIoPerfData) {
-    const ProcStatInfo procStatInfo(
-            /*stats=*/{6200, 5700, 1700, 3100, 1100, 5200, 3900, 0, 0, 0},
-            /*runnableCnt=*/17,
-            /*ioBlockedCnt=*/5);
-    struct SystemIoPerfData expectedSystemIoPerfData = {
-            .cpuIoWaitTime = 1100,
-            .totalCpuTime = 26900,
-            .ioBlockedProcessesCnt = 5,
-            .totalProcessesCnt = 22,
-    };
-
-    sp<MockProcStat> mockProcStat = new MockProcStat();
-    EXPECT_CALL(*mockProcStat, deltaStats()).WillOnce(Return(procStatInfo));
-
-    IoPerfCollection collector;
-    struct SystemIoPerfData actualSystemIoPerfData = {};
-    collector.processSystemIoPerfData(mockProcStat, &actualSystemIoPerfData);
-
-    EXPECT_TRUE(isEqual(expectedSystemIoPerfData, actualSystemIoPerfData))
-            << "Expected:\n"
-            << toString(expectedSystemIoPerfData) << "\nActual:\n"
-            << toString(actualSystemIoPerfData);
-}
-
-TEST(IoPerfCollectionTest, TestProcPidContentsGreaterThanTopNStatsLimit) {
-    const std::vector<ProcessStats> firstProcessStats({
-            {.tgid = 1,
-             .uid = 0,
-             .process = {1, "init", "S", 0, 220, 2, 0},
-             .threads = {{1, {1, "init", "S", 0, 200, 2, 0}},
-                         {453, {453, "init", "S", 0, 20, 2, 275}}}},
-            {.tgid = 2456,
-             .uid = 1001000,
-             .process = {2456, "system_server", "R", 1, 6000, 3, 1000},
-             .threads = {{2456, {2456, "system_server", "R", 1, 1000, 3, 1000}},
-                         {3456, {3456, "system_server", "S", 1, 3000, 3, 2300}},
-                         {4789, {4789, "system_server", "D", 1, 2000, 3, 4500}}}},
-            {.tgid = 7890,
-             .uid = 1001000,
-             .process = {7890, "logd", "D", 1, 15000, 3, 2345},
-             .threads = {{7890, {7890, "logd", "D", 1, 10000, 3, 2345}},
-                         {8978, {8978, "logd", "D", 1, 1000, 3, 2500}},
-                         {12890, {12890, "logd", "D", 1, 500, 3, 2900}}}},
-            {.tgid = 18902,
-             .uid = 1009,
-             .process = {18902, "disk I/O", "D", 1, 45678, 3, 897654},
-             .threads = {{18902, {18902, "disk I/O", "D", 1, 30000, 3, 897654}},
-                         {21345, {21345, "disk I/O", "D", 1, 15000, 3, 904000}},
-                         {32452, {32452, "disk I/O", "D", 1, 678, 3, 1007000}}}},
-            {.tgid = 28900,
-             .uid = 1001234,
-             .process = {28900, "tombstoned", "D", 1, 89765, 1, 2345671},
-             .threads = {{28900, {28900, "tombstoned", "D", 1, 89765, 1, 2345671}}}},
-    });
-    sp<MockProcPidStat> mockProcPidStat = new MockProcPidStat();
-    EXPECT_CALL(*mockProcPidStat, deltaStats()).WillOnce(Return(firstProcessStats));
-
-    struct ProcessIoPerfData expectedProcessIoPerfData = {
-            .topNIoBlockedUids = {{.userId = 10,  // uid: 1001000
-                                   .packageName = "shared:android.uid.system",
-                                   .count = 4,
-                                   .topNProcesses = {{"logd", 3}, {"system_server", 1}}},
-                                  {.userId = 0,
-                                   .packageName = "mount",
-                                   .count = 3,
-                                   .topNProcesses = {{"disk I/O", 3}}}},
-            .topNIoBlockedUidsTotalTaskCnt = {6, 3},
-            .topNMajorFaultUids = {{.userId = 10,  // uid: 1001234
-                                    .packageName = "1001234",
-                                    .count = 89765,
-                                    .topNProcesses = {{"tombstoned", 89765}}},
-                                   {.userId = 0,  // uid: 1009
-                                    .packageName = "mount",
-                                    .count = 45678,
-                                    .topNProcesses = {{"disk I/O", 45678}}}},
-            .totalMajorFaults = 156663,
+    UserPackageSummaryStats userPackageSummaryStats{
+            .topNIoReads = {{1009, "mount", UserPackageStats::IoStats{{0, 14'000}, {0, 100}}},
+                            {1002001, "com.google.android.car.kitchensink",
+                             UserPackageStats::IoStats{{0, 3'400}, {0, 200}}}},
+            .topNIoWrites = {{1009, "mount", UserPackageStats::IoStats{{0, 16'000}, {0, 100}}},
+                             {1002001, "com.google.android.car.kitchensink",
+                              UserPackageStats::IoStats{{0, 6'700}, {0, 200}}}},
+            .topNIoBlocked = {{1009, "mount", UserPackageStats::ProcStats{1, {{"disk I/O", 1}}}},
+                              {1002001, "com.google.android.car.kitchensink",
+                               UserPackageStats::ProcStats{3,
+                                                           {{"CTS", 2}, {"KitchenSinkApp", 1}}}}},
+            .topNMajorFaults =
+                    {{1009, "mount", UserPackageStats::ProcStats{11'000, {{"disk I/O", 11'000}}}},
+                     {1002001, "com.google.android.car.kitchensink",
+                      UserPackageStats::ProcStats{22'445,
+                                                  {{"KitchenSinkApp", 12'345}, {"CTS", 10'100}}}}},
+            .totalIoStats = {{1000, 21'600}, {300, 28'300}, {600, 600}},
+            .taskCountByUid = {{1009, 1}, {1002001, 5}},
+            .totalMajorFaults = 84'345,
             .majorFaultsPercentChange = 0.0,
     };
 
-    IoPerfCollection collector;
-    collector.mTopNStatsPerCategory = 2;
-    collector.mTopNStatsPerSubcategory = 2;
-
-    sp<MockPackageInfoResolver> mockPackageInfoResolver = new MockPackageInfoResolver();
-    collector.mPackageInfoResolver = mockPackageInfoResolver;
-    EXPECT_CALL(*mockPackageInfoResolver, getPackageNamesForUids(_))
-            .WillRepeatedly(Return<std::unordered_map<uid_t, std::string>>(
-                    {{0, "root"}, {1009, "mount"}, {1001000, "shared:android.uid.system"}}));
-
-    struct ProcessIoPerfData actualProcessIoPerfData = {};
-    collector.processProcessIoPerfDataLocked({}, mockProcPidStat, &actualProcessIoPerfData);
-
-    EXPECT_TRUE(isEqual(expectedProcessIoPerfData, actualProcessIoPerfData))
-            << "First snapshot doesn't match.\nExpected:\n"
-            << toString(expectedProcessIoPerfData) << "\nActual:\n"
-            << toString(actualProcessIoPerfData);
-
-    const std::vector<ProcessStats> secondProcessStats({
-            {.tgid = 1,
-             .uid = 0,
-             .process = {1, "init", "S", 0, 660, 2, 0},
-             .threads = {{1, {1, "init", "S", 0, 600, 2, 0}},
-                         {453, {453, "init", "S", 0, 60, 2, 275}}}},
-            {.tgid = 2546,
-             .uid = 1001000,
-             .process = {2546, "system_server", "R", 1, 12000, 3, 1000},
-             .threads = {{2456, {2456, "system_server", "R", 1, 2000, 3, 1000}},
-                         {3456, {3456, "system_server", "S", 1, 6000, 3, 2300}},
-                         {4789, {4789, "system_server", "D", 1, 4000, 3, 4500}}}},
-    });
-    EXPECT_CALL(*mockProcPidStat, deltaStats()).WillOnce(Return(secondProcessStats));
-    expectedProcessIoPerfData = {
-            .topNIoBlockedUids = {{.userId = 10,  // uid: 1001000
-                                   .packageName = "shared:android.uid.system",
-                                   .count = 1,
-                                   .topNProcesses = {{"system_server", 1}}}},
-            .topNIoBlockedUidsTotalTaskCnt = {3},
-            .topNMajorFaultUids = {{.userId = 10,  // uid: 1001000
-                                    .packageName = "shared:android.uid.system",
-                                    .count = 12000,
-                                    .topNProcesses = {{"system_server", 12000}}},
-                                   {.userId = 0,  // uid: 0
-                                    .packageName = "root",
-                                    .count = 660,
-                                    .topNProcesses = {{"init", 660}}}},
-            .totalMajorFaults = 12660,
-            .majorFaultsPercentChange = ((12660.0 - 156663.0) / 156663.0) * 100,
+    CollectionInfo expected{
+            .maxCacheSize = std::numeric_limits<std::size_t>::max(),
+            .records = {{
+                    .systemSummaryStats = systemSummaryStats,
+                    .userPackageSummaryStats = userPackageSummaryStats,
+            }},
     };
 
-    actualProcessIoPerfData = {};
-    collector.processProcessIoPerfDataLocked({}, mockProcPidStat, &actualProcessIoPerfData);
+    EXPECT_THAT(actual, CollectionInfoEq(expected))
+            << "Custom collection info doesn't match.\nExpected:\n"
+            << expected.toString() << "\nActual:\n"
+            << actual.toString();
 
-    EXPECT_TRUE(isEqual(expectedProcessIoPerfData, actualProcessIoPerfData))
-            << "Second snapshot doesn't match.\nExpected:\n"
-            << toString(expectedProcessIoPerfData) << "\nActual:\n"
-            << toString(actualProcessIoPerfData);
+    ASSERT_NO_FATAL_FAILURE(checkCustomDumpContents()) << "Custom collection should be reported";
+
+    TemporaryFile customDump;
+    ASSERT_RESULT_OK(mCollector->onCustomCollectionDump(customDump.fd));
+
+    // Should clear the cache.
+    ASSERT_RESULT_OK(mCollector->onCustomCollectionDump(-1));
+
+    expected.records.clear();
+    const CollectionInfo& emptyCollectionInfo = mCollectorPeer->getCustomCollectionInfo();
+    EXPECT_THAT(emptyCollectionInfo, CollectionInfoEq(expected))
+            << "Custom collection should be cleared.";
 }
 
-TEST(IoPerfCollectionTest, TestProcPidContentsLessThanTopNStatsLimit) {
-    const std::vector<ProcessStats> processStats({
-            {.tgid = 1,
-             .uid = 0,
-             .process = {1, "init", "S", 0, 880, 2, 0},
-             .threads = {{1, {1, "init", "S", 0, 800, 2, 0}},
-                         {453, {453, "init", "S", 0, 80, 2, 275}}}},
-    });
-    sp<MockProcPidStat> mockProcPidStat = new MockProcPidStat();
-    EXPECT_CALL(*mockProcPidStat, deltaStats()).WillOnce(Return(processStats));
+TEST_F(IoPerfCollectionTest, TestOnPeriodicCollectionWithTrimmingStatsAfterTopN) {
+    mCollectorPeer->setTopNStatsPerCategory(1);
+    mCollectorPeer->setTopNStatsPerSubcategory(1);
 
-    struct ProcessIoPerfData expectedProcessIoPerfData = {
-            .topNMajorFaultUids = {{.userId = 0,  // uid: 0
-                                    .packageName = "root",
-                                    .count = 880,
-                                    .topNProcesses = {{"init", 880}}}},
-            .totalMajorFaults = 880,
+    const auto [uidStats, _] = sampleUidStats();
+    const auto [procStatInfo, systemSummaryStats] = sampleProcStat();
+
+    EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(uidStats));
+    EXPECT_CALL(*mMockProcStat, deltaStats()).WillOnce(Return(procStatInfo));
+
+    time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
+    ASSERT_RESULT_OK(mCollector->onPeriodicCollection(now, SystemState::NORMAL_MODE,
+                                                      mMockUidStatsCollector, mMockProcStat));
+
+    const auto actual = mCollectorPeer->getPeriodicCollectionInfo();
+
+    UserPackageSummaryStats userPackageSummaryStats{
+            .topNIoReads = {{1009, "mount", UserPackageStats::IoStats{{0, 14'000}, {0, 100}}}},
+            .topNIoWrites = {{1009, "mount", UserPackageStats::IoStats{{0, 16'000}, {0, 100}}}},
+            .topNIoBlocked = {{1002001, "com.google.android.car.kitchensink",
+                               UserPackageStats::ProcStats{3, {{"CTS", 2}}}}},
+            .topNMajorFaults = {{1012345, "1012345",
+                                 UserPackageStats::ProcStats{50'900, {{"MapsApp", 50'900}}}}},
+            .totalIoStats = {{1000, 21'600}, {300, 28'300}, {600, 600}},
+            .taskCountByUid = {{1009, 1}, {1002001, 5}, {1012345, 4}},
+            .totalMajorFaults = 84'345,
             .majorFaultsPercentChange = 0.0,
     };
 
-    IoPerfCollection collector;
-    collector.mTopNStatsPerCategory = 5;
-    collector.mTopNStatsPerSubcategory = 3;
+    const CollectionInfo expected{
+            .maxCacheSize = static_cast<size_t>(sysprop::periodicCollectionBufferSize().value_or(
+                    kDefaultPeriodicCollectionBufferSize)),
+            .records = {{
+                    .systemSummaryStats = systemSummaryStats,
+                    .userPackageSummaryStats = userPackageSummaryStats,
+            }},
+    };
 
-    sp<MockPackageInfoResolver> mockPackageInfoResolver = new MockPackageInfoResolver();
-    collector.mPackageInfoResolver = mockPackageInfoResolver;
-    EXPECT_CALL(*mockPackageInfoResolver, getPackageNamesForUids(_))
-            .WillRepeatedly(Return<std::unordered_map<uid_t, std::string>>({{0, "root"}}));
+    EXPECT_THAT(actual, CollectionInfoEq(expected))
+            << "Periodic collection info doesn't match.\nExpected:\n"
+            << expected.toString() << "\nActual:\n"
+            << actual.toString();
 
-    struct ProcessIoPerfData actualProcessIoPerfData = {};
-    collector.processProcessIoPerfDataLocked({}, mockProcPidStat, &actualProcessIoPerfData);
+    ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/1))
+            << "Boot-time collection shouldn't be reported";
+}
 
-    EXPECT_TRUE(isEqual(expectedProcessIoPerfData, actualProcessIoPerfData))
-            << "proc pid contents don't match.\nExpected:\n"
-            << toString(expectedProcessIoPerfData) << "\nActual:\n"
-            << toString(actualProcessIoPerfData);
+TEST_F(IoPerfCollectionTest, TestConsecutiveOnPeriodicCollection) {
+    const auto [firstUidStats, firstUserPackageSummaryStats] = sampleUidStats();
+    const auto [firstProcStatInfo, firstSystemSummaryStats] = sampleProcStat();
+
+    EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(firstUidStats));
+    EXPECT_CALL(*mMockProcStat, deltaStats()).WillOnce(Return(firstProcStatInfo));
+
+    time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
+    ASSERT_RESULT_OK(mCollector->onPeriodicCollection(now, SystemState::NORMAL_MODE,
+                                                      mMockUidStatsCollector, mMockProcStat));
+
+    auto [secondUidStats, secondUserPackageSummaryStats] = sampleUidStats(/*multiplier=*/2);
+    const auto [secondProcStatInfo, secondSystemSummaryStats] = sampleProcStat(/*multiplier=*/2);
+
+    secondUserPackageSummaryStats.majorFaultsPercentChange =
+            (static_cast<double>(secondUserPackageSummaryStats.totalMajorFaults -
+                                 firstUserPackageSummaryStats.totalMajorFaults) /
+             static_cast<double>(firstUserPackageSummaryStats.totalMajorFaults)) *
+            100.0;
+
+    EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(secondUidStats));
+    EXPECT_CALL(*mMockProcStat, deltaStats()).WillOnce(Return(secondProcStatInfo));
+
+    ASSERT_RESULT_OK(mCollector->onPeriodicCollection(now, SystemState::NORMAL_MODE,
+                                                      mMockUidStatsCollector, mMockProcStat));
+
+    const auto actual = mCollectorPeer->getPeriodicCollectionInfo();
+
+    const CollectionInfo expected{
+            .maxCacheSize = static_cast<size_t>(sysprop::periodicCollectionBufferSize().value_or(
+                    kDefaultPeriodicCollectionBufferSize)),
+            .records = {{.systemSummaryStats = firstSystemSummaryStats,
+                         .userPackageSummaryStats = firstUserPackageSummaryStats},
+                        {.systemSummaryStats = secondSystemSummaryStats,
+                         .userPackageSummaryStats = secondUserPackageSummaryStats}},
+    };
+
+    EXPECT_THAT(actual, CollectionInfoEq(expected))
+            << "Periodic collection info doesn't match.\nExpected:\n"
+            << expected.toString() << "\nActual:\n"
+            << actual.toString();
+
+    ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/1))
+            << "Boot-time collection shouldn't be reported";
 }
 
 }  // namespace watchdog
diff --git a/cpp/watchdog/server/tests/MockDataProcessor.h b/cpp/watchdog/server/tests/MockDataProcessor.h
index d9a2600..b9ea48c 100644
--- a/cpp/watchdog/server/tests/MockDataProcessor.h
+++ b/cpp/watchdog/server/tests/MockDataProcessor.h
@@ -30,24 +30,22 @@
     MockDataProcessor() {
         EXPECT_CALL(*this, name()).WillRepeatedly(::testing::Return("MockedDataProcessor"));
     }
-    MOCK_METHOD(std::string, name, (), (override));
+    MOCK_METHOD(std::string, name, (), (const, override));
     MOCK_METHOD(android::base::Result<void>, init, (), (override));
     MOCK_METHOD(void, terminate, (), (override));
     MOCK_METHOD(android::base::Result<void>, onBoottimeCollection,
-                (time_t, const wp<UidIoStats>&, const wp<ProcStat>&, const wp<ProcPidStat>&),
-                (override));
+                (time_t, const wp<UidStatsCollectorInterface>&, const wp<ProcStat>&), (override));
     MOCK_METHOD(android::base::Result<void>, onPeriodicCollection,
-                (time_t, SystemState, const wp<UidIoStats>&, const wp<ProcStat>&,
-                 const wp<ProcPidStat>&),
+                (time_t, SystemState, const wp<UidStatsCollectorInterface>&, const wp<ProcStat>&),
                 (override));
     MOCK_METHOD(android::base::Result<void>, onCustomCollection,
-                (time_t, SystemState, const std::unordered_set<std::string>&, const wp<UidIoStats>&,
-                 const wp<ProcStat>&, const wp<ProcPidStat>&),
+                (time_t, SystemState, const std::unordered_set<std::string>&,
+                 const wp<UidStatsCollectorInterface>&, const wp<ProcStat>&),
                 (override));
     MOCK_METHOD(android::base::Result<void>, onPeriodicMonitor,
                 (time_t, const android::wp<IProcDiskStatsInterface>&, const std::function<void()>&),
                 (override));
-    MOCK_METHOD(android::base::Result<void>, onDump, (int), (override));
+    MOCK_METHOD(android::base::Result<void>, onDump, (int), (const, override));
     MOCK_METHOD(android::base::Result<void>, onCustomCollectionDump, (int), (override));
 };
 
diff --git a/cpp/watchdog/server/tests/MockIoOveruseConfigs.h b/cpp/watchdog/server/tests/MockIoOveruseConfigs.h
index 22c23f7..6859e18 100644
--- a/cpp/watchdog/server/tests/MockIoOveruseConfigs.h
+++ b/cpp/watchdog/server/tests/MockIoOveruseConfigs.h
@@ -43,7 +43,7 @@
     MOCK_METHOD(
             void, get,
             (std::vector<android::automotive::watchdog::internal::ResourceOveruseConfiguration>*),
-            (override));
+            (const, override));
 
     MOCK_METHOD(android::base::Result<void>, writeToDisk, (), (override));
 
diff --git a/cpp/watchdog/server/tests/MockIoOveruseMonitor.h b/cpp/watchdog/server/tests/MockIoOveruseMonitor.h
index 79ba932..a9d677e 100644
--- a/cpp/watchdog/server/tests/MockIoOveruseMonitor.h
+++ b/cpp/watchdog/server/tests/MockIoOveruseMonitor.h
@@ -35,8 +35,8 @@
         ON_CALL(*this, name()).WillByDefault(::testing::Return("MockIoOveruseMonitor"));
     }
     ~MockIoOveruseMonitor() {}
-    MOCK_METHOD(bool, isInitialized, (), (override));
-    MOCK_METHOD(bool, dumpHelpText, (int), (override));
+    MOCK_METHOD(bool, isInitialized, (), (const, override));
+    MOCK_METHOD(bool, dumpHelpText, (int), (const, override));
     MOCK_METHOD(android::base::Result<void>, updateResourceOveruseConfigurations,
                 (const std::vector<
                         android::automotive::watchdog::internal::ResourceOveruseConfiguration>&),
@@ -44,7 +44,7 @@
     MOCK_METHOD(
             android::base::Result<void>, getResourceOveruseConfigurations,
             (std::vector<android::automotive::watchdog::internal::ResourceOveruseConfiguration>*),
-            (override));
+            (const, override));
     MOCK_METHOD(android::base::Result<void>, actionTakenOnIoOveruse,
                 (const std::vector<
                         android::automotive::watchdog::internal::PackageResourceOveruseAction>&
@@ -54,7 +54,8 @@
                 (const sp<IResourceOveruseListener>&), (override));
     MOCK_METHOD(android::base::Result<void>, removeIoOveruseListener,
                 (const sp<IResourceOveruseListener>&), (override));
-    MOCK_METHOD(android::base::Result<void>, getIoOveruseStats, (IoOveruseStats*), (override));
+    MOCK_METHOD(android::base::Result<void>, getIoOveruseStats, (IoOveruseStats*),
+                (const, override));
     MOCK_METHOD(android::base::Result<void>, resetIoOveruseStats, (const std::vector<std::string>&),
                 (override));
 };
diff --git a/cpp/watchdog/server/tests/MockProcPidStat.h b/cpp/watchdog/server/tests/MockProcPidStat.h
deleted file mode 100644
index acc8b0c..0000000
--- a/cpp/watchdog/server/tests/MockProcPidStat.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- * 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_WATCHDOG_SERVER_TESTS_MOCKPROCPIDSTAT_H_
-#define CPP_WATCHDOG_SERVER_TESTS_MOCKPROCPIDSTAT_H_
-
-#include "ProcPidStat.h"
-
-#include <android-base/result.h>
-#include <gmock/gmock.h>
-
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-namespace android {
-namespace automotive {
-namespace watchdog {
-
-class MockProcPidStat : public ProcPidStat {
-public:
-    MockProcPidStat() { ON_CALL(*this, enabled()).WillByDefault(::testing::Return(true)); }
-    MOCK_METHOD(bool, enabled, (), (override));
-    MOCK_METHOD(android::base::Result<void>, collect, (), (override));
-    MOCK_METHOD((const std::unordered_map<pid_t, ProcessStats>), latestStats, (),
-                (const, override));
-    MOCK_METHOD(const std::vector<ProcessStats>, deltaStats, (), (const, override));
-    MOCK_METHOD(std::string, dirPath, (), (override));
-};
-
-}  // namespace watchdog
-}  // namespace automotive
-}  // namespace android
-
-#endif  //  CPP_WATCHDOG_SERVER_TESTS_MOCKPROCPIDSTAT_H_
diff --git a/cpp/watchdog/server/tests/MockUidIoStats.h b/cpp/watchdog/server/tests/MockUidIoStats.h
deleted file mode 100644
index cf30d9f..0000000
--- a/cpp/watchdog/server/tests/MockUidIoStats.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/**
- * 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_WATCHDOG_SERVER_TESTS_MOCKUIDIOSTATS_H_
-#define CPP_WATCHDOG_SERVER_TESTS_MOCKUIDIOSTATS_H_
-
-#include "UidIoStats.h"
-
-#include <android-base/result.h>
-#include <gmock/gmock.h>
-
-#include <string>
-#include <unordered_map>
-
-namespace android {
-namespace automotive {
-namespace watchdog {
-
-class MockUidIoStats : public UidIoStats {
-public:
-    MockUidIoStats() { ON_CALL(*this, enabled()).WillByDefault(::testing::Return(true)); }
-    MOCK_METHOD(bool, enabled, (), (override));
-    MOCK_METHOD(android::base::Result<void>, collect, (), (override));
-    MOCK_METHOD((const std::unordered_map<uid_t, UidIoUsage>), latestStats, (), (const, override));
-    MOCK_METHOD((const std::unordered_map<uid_t, UidIoUsage>), deltaStats, (), (const, override));
-    MOCK_METHOD(std::string, filePath, (), (override));
-
-    void expectDeltaStats(const std::unordered_map<uid_t, IoUsage>& deltaStats) {
-        std::unordered_map<uid_t, UidIoUsage> stats;
-        for (const auto& [uid, ios] : deltaStats) {
-            stats[uid] = UidIoUsage{.uid = uid, .ios = ios};
-        }
-        EXPECT_CALL(*this, deltaStats()).WillOnce(::testing::Return(stats));
-    }
-};
-
-}  // namespace watchdog
-}  // namespace automotive
-}  // namespace android
-
-#endif  //  CPP_WATCHDOG_SERVER_TESTS_MOCKUIDIOSTATS_H_
diff --git a/cpp/watchdog/server/tests/MockUidIoStatsCollector.h b/cpp/watchdog/server/tests/MockUidIoStatsCollector.h
new file mode 100644
index 0000000..7fe8fc9
--- /dev/null
+++ b/cpp/watchdog/server/tests/MockUidIoStatsCollector.h
@@ -0,0 +1,46 @@
+/*
+ * 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_WATCHDOG_SERVER_TESTS_MOCKUIDIOSTATSCOLLECTOR_H_
+#define CPP_WATCHDOG_SERVER_TESTS_MOCKUIDIOSTATSCOLLECTOR_H_
+
+#include "UidIoStatsCollector.h"
+
+#include <android-base/result.h>
+#include <gmock/gmock.h>
+
+#include <string>
+#include <unordered_map>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+class MockUidIoStatsCollector : public UidIoStatsCollectorInterface {
+public:
+    MockUidIoStatsCollector() { ON_CALL(*this, enabled()).WillByDefault(::testing::Return(true)); }
+    MOCK_METHOD(android::base::Result<void>, collect, (), (override));
+    MOCK_METHOD((const std::unordered_map<uid_t, UidIoStats>), latestStats, (), (const, override));
+    MOCK_METHOD((const std::unordered_map<uid_t, UidIoStats>), deltaStats, (), (const, override));
+    MOCK_METHOD(bool, enabled, (), (const, override));
+    MOCK_METHOD(const std::string, filePath, (), (const, override));
+};
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
+
+#endif  //  CPP_WATCHDOG_SERVER_TESTS_MOCKUIDIOSTATSCOLLECTOR_H_
diff --git a/cpp/watchdog/server/tests/MockUidProcStatsCollector.h b/cpp/watchdog/server/tests/MockUidProcStatsCollector.h
new file mode 100644
index 0000000..a16fa37
--- /dev/null
+++ b/cpp/watchdog/server/tests/MockUidProcStatsCollector.h
@@ -0,0 +1,49 @@
+/*
+ * 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_WATCHDOG_SERVER_TESTS_MOCKUIDPROCSTATSCOLLECTOR_H_
+#define CPP_WATCHDOG_SERVER_TESTS_MOCKUIDPROCSTATSCOLLECTOR_H_
+
+#include "UidProcStatsCollector.h"
+
+#include <android-base/result.h>
+#include <gmock/gmock.h>
+
+#include <string>
+#include <unordered_map>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+class MockUidProcStatsCollector : public UidProcStatsCollectorInterface {
+public:
+    MockUidProcStatsCollector() {
+        ON_CALL(*this, enabled()).WillByDefault(::testing::Return(true));
+    }
+    MOCK_METHOD(android::base::Result<void>, collect, (), (override));
+    MOCK_METHOD((const std::unordered_map<uid_t, UidProcStats>), latestStats, (),
+                (const, override));
+    MOCK_METHOD((const std::unordered_map<uid_t, UidProcStats>), deltaStats, (), (const, override));
+    MOCK_METHOD(bool, enabled, (), (const, override));
+    MOCK_METHOD(const std::string, dirPath, (), (const, override));
+};
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
+
+#endif  //  CPP_WATCHDOG_SERVER_TESTS_MOCKUIDPROCSTATSCOLLECTOR_H_
diff --git a/cpp/watchdog/server/tests/MockUidStatsCollector.h b/cpp/watchdog/server/tests/MockUidStatsCollector.h
new file mode 100644
index 0000000..45671c1
--- /dev/null
+++ b/cpp/watchdog/server/tests/MockUidStatsCollector.h
@@ -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.
+ */
+
+#ifndef CPP_WATCHDOG_SERVER_TESTS_MOCKUIDSTATSCOLLECTOR_H_
+#define CPP_WATCHDOG_SERVER_TESTS_MOCKUIDSTATSCOLLECTOR_H_
+
+#include "UidIoStatsCollector.h"
+
+#include <android-base/result.h>
+#include <gmock/gmock.h>
+
+#include <string>
+#include <unordered_map>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+class MockUidStatsCollector : public UidStatsCollectorInterface {
+public:
+    MockUidStatsCollector() { ON_CALL(*this, enabled()).WillByDefault(::testing::Return(true)); }
+    MOCK_METHOD(android::base::Result<void>, collect, (), (override));
+    MOCK_METHOD((const std::vector<UidStats>), latestStats, (), (const, override));
+    MOCK_METHOD((const std::vector<UidStats>), deltaStats, (), (const, override));
+    MOCK_METHOD(bool, enabled, (), (const, override));
+};
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
+
+#endif  //  CPP_WATCHDOG_SERVER_TESTS_MOCKUIDSTATSCOLLECTOR_H_
diff --git a/cpp/watchdog/server/tests/MockWatchdogPerfService.h b/cpp/watchdog/server/tests/MockWatchdogPerfService.h
index 6f0fb51..114c308 100644
--- a/cpp/watchdog/server/tests/MockWatchdogPerfService.h
+++ b/cpp/watchdog/server/tests/MockWatchdogPerfService.h
@@ -40,8 +40,8 @@
     MOCK_METHOD(android::base::Result<void>, onBootFinished, (), (override));
     MOCK_METHOD(android::base::Result<void>, onCustomCollection,
                 (int fd, const Vector<android::String16>& args), (override));
-    MOCK_METHOD(android::base::Result<void>, onDump, (int fd), (override));
-    MOCK_METHOD(bool, dumpHelpText, (int fd), (override));
+    MOCK_METHOD(android::base::Result<void>, onDump, (int fd), (const, override));
+    MOCK_METHOD(bool, dumpHelpText, (int fd), (const, override));
     MOCK_METHOD(void, handleMessage, (const Message&), (override));
 };
 
diff --git a/cpp/watchdog/server/tests/OveruseConfigurationTestUtils.cpp b/cpp/watchdog/server/tests/OveruseConfigurationTestUtils.cpp
index 1bf270d..9b67caf 100644
--- a/cpp/watchdog/server/tests/OveruseConfigurationTestUtils.cpp
+++ b/cpp/watchdog/server/tests/OveruseConfigurationTestUtils.cpp
@@ -154,15 +154,15 @@
     return threshold;
 }
 
-Matcher<const ResourceOveruseConfiguration> ResourceOveruseConfigurationMatcher(
+Matcher<const ResourceOveruseConfiguration&> ResourceOveruseConfigurationMatcher(
         const ResourceOveruseConfiguration& config) {
-    std::vector<Matcher<const ResourceSpecificConfiguration>> resourceSpecificConfigMatchers;
+    std::vector<Matcher<const ResourceSpecificConfiguration&>> resourceSpecificConfigMatchers;
     for (const auto& resourceSpecificConfig : config.resourceSpecificConfigurations) {
         resourceSpecificConfigMatchers.push_back(
                 IsResourceSpecificConfiguration(resourceSpecificConfig));
     }
 
-    std::vector<Matcher<const PackageMetadata>> metadataMatchers;
+    std::vector<Matcher<const PackageMetadata&>> metadataMatchers;
     for (const auto& metadata : config.packageMetadata) {
         metadataMatchers.push_back(IsPackageMetadata(metadata));
     }
diff --git a/cpp/watchdog/server/tests/OveruseConfigurationTestUtils.h b/cpp/watchdog/server/tests/OveruseConfigurationTestUtils.h
index 84c970a..a8991c7 100644
--- a/cpp/watchdog/server/tests/OveruseConfigurationTestUtils.h
+++ b/cpp/watchdog/server/tests/OveruseConfigurationTestUtils.h
@@ -77,7 +77,7 @@
 android::automotive::watchdog::internal::IoOveruseAlertThreshold toIoOveruseAlertThreshold(
         const int64_t durationInSeconds, const int64_t writtenBytesPerSecond);
 
-testing::Matcher<const android::automotive::watchdog::internal::ResourceOveruseConfiguration>
+testing::Matcher<const android::automotive::watchdog::internal::ResourceOveruseConfiguration&>
 ResourceOveruseConfigurationMatcher(
         const android::automotive::watchdog::internal::ResourceOveruseConfiguration& config);
 
diff --git a/cpp/watchdog/server/tests/PackageInfoResolverTest.cpp b/cpp/watchdog/server/tests/PackageInfoResolverTest.cpp
index cf282be..cf68dda 100644
--- a/cpp/watchdog/server/tests/PackageInfoResolverTest.cpp
+++ b/cpp/watchdog/server/tests/PackageInfoResolverTest.cpp
@@ -16,6 +16,7 @@
 
 #include "MockWatchdogServiceHelper.h"
 #include "PackageInfoResolver.h"
+#include "PackageInfoTestUtils.h"
 
 #include <android-base/stringprintf.h>
 #include <android/automotive/watchdog/internal/ApplicationCategoryType.h>
@@ -48,20 +49,6 @@
         std::unordered_map<std::string,
                            android::automotive::watchdog::internal::ApplicationCategoryType>;
 
-PackageInfo constructPackageInfo(const char* packageName, int32_t uid, UidType uidType,
-                                 ComponentType componentType,
-                                 ApplicationCategoryType appCategoryType,
-                                 std::vector<std::string> sharedUidPackages = {}) {
-    PackageInfo packageInfo;
-    packageInfo.packageIdentifier.name = packageName;
-    packageInfo.packageIdentifier.uid = uid;
-    packageInfo.uidType = uidType;
-    packageInfo.componentType = componentType;
-    packageInfo.appCategoryType = appCategoryType;
-    packageInfo.sharedUidPackages = sharedUidPackages;
-    return packageInfo;
-}
-
 std::string toString(const std::unordered_map<uid_t, PackageInfo>& mappings) {
     std::string buffer = "{";
     for (const auto& [uid, info] : mappings) {
diff --git a/cpp/watchdog/server/tests/PackageInfoTestUtils.cpp b/cpp/watchdog/server/tests/PackageInfoTestUtils.cpp
new file mode 100644
index 0000000..0834b2c
--- /dev/null
+++ b/cpp/watchdog/server/tests/PackageInfoTestUtils.cpp
@@ -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.
+ */
+
+#include "PackageInfoTestUtils.h"
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+using ::android::automotive::watchdog::internal::ApplicationCategoryType;
+using ::android::automotive::watchdog::internal::ComponentType;
+using ::android::automotive::watchdog::internal::PackageInfo;
+using ::android::automotive::watchdog::internal::UidType;
+
+PackageInfo constructPackageInfo(const char* packageName, int32_t uid, UidType uidType,
+                                 ComponentType componentType,
+                                 ApplicationCategoryType appCategoryType,
+                                 std::vector<std::string> sharedUidPackages) {
+    PackageInfo packageInfo;
+    packageInfo.packageIdentifier.name = packageName;
+    packageInfo.packageIdentifier.uid = uid;
+    packageInfo.uidType = uidType;
+    packageInfo.componentType = componentType;
+    packageInfo.appCategoryType = appCategoryType;
+    packageInfo.sharedUidPackages = sharedUidPackages;
+    return packageInfo;
+}
+
+PackageInfo constructAppPackageInfo(const char* packageName, const ComponentType componentType,
+                                    const ApplicationCategoryType appCategoryType,
+                                    const std::vector<std::string>& sharedUidPackages) {
+    return constructPackageInfo(packageName, 0, UidType::APPLICATION, componentType,
+                                appCategoryType, sharedUidPackages);
+}
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
diff --git a/cpp/watchdog/server/tests/PackageInfoTestUtils.h b/cpp/watchdog/server/tests/PackageInfoTestUtils.h
new file mode 100644
index 0000000..fc38434
--- /dev/null
+++ b/cpp/watchdog/server/tests/PackageInfoTestUtils.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_WATCHDOG_SERVER_TESTS_PACKAGEINFOTESTUTILS_H_
+#define CPP_WATCHDOG_SERVER_TESTS_PACKAGEINFOTESTUTILS_H_
+
+#include <android/automotive/watchdog/internal/ApplicationCategoryType.h>
+#include <android/automotive/watchdog/internal/ComponentType.h>
+#include <android/automotive/watchdog/internal/PackageInfo.h>
+#include <android/automotive/watchdog/internal/UidType.h>
+#include <gmock/gmock.h>
+
+#include <string>
+#include <vector>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+android::automotive::watchdog::internal::PackageInfo constructPackageInfo(
+        const char* packageName, int32_t uid,
+        android::automotive::watchdog::internal::UidType uidType =
+                android::automotive::watchdog::internal::UidType::NATIVE,
+        android::automotive::watchdog::internal::ComponentType componentType =
+                android::automotive::watchdog::internal::ComponentType::UNKNOWN,
+        android::automotive::watchdog::internal::ApplicationCategoryType appCategoryType =
+                android::automotive::watchdog::internal::ApplicationCategoryType::OTHERS,
+        std::vector<std::string> sharedUidPackages = {});
+
+android::automotive::watchdog::internal::PackageInfo constructAppPackageInfo(
+        const char* packageName,
+        const android::automotive::watchdog::internal::ComponentType componentType,
+        const android::automotive::watchdog::internal::ApplicationCategoryType appCategoryType =
+                android::automotive::watchdog::internal::ApplicationCategoryType::OTHERS,
+        const std::vector<std::string>& sharedUidPackages = {});
+
+MATCHER_P(PackageIdentifierEq, expected, "") {
+    const auto& actual = arg;
+    return ::testing::Value(actual.name, ::testing::Eq(expected.name)) &&
+            ::testing::Value(actual.uid, ::testing::Eq(expected.uid));
+}
+
+MATCHER_P(PackageInfoEq, expected, "") {
+    const auto& actual = arg;
+    return ::testing::Value(actual.packageIdentifier,
+                            PackageIdentifierEq(expected.packageIdentifier)) &&
+            ::testing::Value(actual.uidType, ::testing::Eq(expected.uidType)) &&
+            ::testing::Value(actual.sharedUidPackages,
+                             ::testing::UnorderedElementsAreArray(expected.sharedUidPackages)) &&
+            ::testing::Value(actual.componentType, ::testing::Eq(expected.componentType)) &&
+            ::testing::Value(actual.appCategoryType, ::testing::Eq(expected.appCategoryType));
+}
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
+
+#endif  // CPP_WATCHDOG_SERVER_TESTS_PACKAGEINFOTESTUTILS_H_
diff --git a/cpp/watchdog/server/tests/ProcPidDir.cpp b/cpp/watchdog/server/tests/ProcPidDir.cpp
index 134d231..b6662d7 100644
--- a/cpp/watchdog/server/tests/ProcPidDir.cpp
+++ b/cpp/watchdog/server/tests/ProcPidDir.cpp
@@ -16,10 +16,11 @@
 
 #include "ProcPidDir.h"
 
-#include "ProcPidStat.h"
+#include "UidProcStatsCollector.h"
 
 #include <android-base/file.h>
 #include <android-base/result.h>
+
 #include <errno.h>
 
 namespace android {
diff --git a/cpp/watchdog/server/tests/ProcPidStatTest.cpp b/cpp/watchdog/server/tests/ProcPidStatTest.cpp
deleted file mode 100644
index 8c3afbc..0000000
--- a/cpp/watchdog/server/tests/ProcPidStatTest.cpp
+++ /dev/null
@@ -1,553 +0,0 @@
-/*
- * Copyright 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.
- */
-
-#include "ProcPidStat.h"
-
-#include "ProcPidDir.h"
-
-#include <android-base/file.h>
-#include <android-base/stringprintf.h>
-#include <gmock/gmock.h>
-#include <inttypes.h>
-
-#include <algorithm>
-#include <string>
-
-namespace android {
-namespace automotive {
-namespace watchdog {
-
-using ::android::automotive::watchdog::testing::populateProcPidDir;
-using ::android::base::StringAppendF;
-using ::android::base::StringPrintf;
-
-namespace {
-
-std::string toString(const PidStat& stat) {
-    return StringPrintf("PID: %" PRIu32 ", PPID: %" PRIu32 ", Comm: %s, State: %s, "
-                        "Major page faults: %" PRIu64 ", Num threads: %" PRIu32
-                        ", Start time: %" PRIu64,
-                        stat.pid, stat.ppid, stat.comm.c_str(), stat.state.c_str(),
-                        stat.majorFaults, stat.numThreads, stat.startTime);
-}
-
-std::string toString(const ProcessStats& stats) {
-    std::string buffer;
-    StringAppendF(&buffer,
-                  "Tgid: %" PRIi64 ", UID: %" PRIi64 ", VmPeak: %" PRIu64 ", VmSize: %" PRIu64
-                  ", VmHWM: %" PRIu64 ", VmRSS: %" PRIu64 ", %s\n",
-                  stats.tgid, stats.uid, stats.vmPeakKb, stats.vmSizeKb, stats.vmHwmKb,
-                  stats.vmRssKb, toString(stats.process).c_str());
-    StringAppendF(&buffer, "\tThread stats:\n");
-    for (const auto& it : stats.threads) {
-        StringAppendF(&buffer, "\t\t%s\n", toString(it.second).c_str());
-    }
-    StringAppendF(&buffer, "\n");
-    return buffer;
-}
-
-std::string toString(const std::vector<ProcessStats>& stats) {
-    std::string buffer;
-    StringAppendF(&buffer, "Number of processes: %d\n", static_cast<int>(stats.size()));
-    for (const auto& it : stats) {
-        StringAppendF(&buffer, "%s", toString(it).c_str());
-    }
-    return buffer;
-}
-
-bool isEqual(const PidStat& lhs, const PidStat& rhs) {
-    return lhs.pid == rhs.pid && lhs.comm == rhs.comm && lhs.state == rhs.state &&
-            lhs.ppid == rhs.ppid && lhs.majorFaults == rhs.majorFaults &&
-            lhs.numThreads == rhs.numThreads && lhs.startTime == rhs.startTime;
-}
-
-bool isEqual(std::vector<ProcessStats>* lhs, std::vector<ProcessStats>* rhs) {
-    if (lhs->size() != rhs->size()) {
-        return false;
-    }
-    std::sort(lhs->begin(), lhs->end(), [&](const ProcessStats& l, const ProcessStats& r) -> bool {
-        return l.process.pid < r.process.pid;
-    });
-    std::sort(rhs->begin(), rhs->end(), [&](const ProcessStats& l, const ProcessStats& r) -> bool {
-        return l.process.pid < r.process.pid;
-    });
-    return std::equal(lhs->begin(), lhs->end(), rhs->begin(),
-                      [&](const ProcessStats& l, const ProcessStats& r) -> bool {
-                          if (l.tgid != r.tgid || l.uid != r.uid || l.vmPeakKb != r.vmPeakKb ||
-                              l.vmSizeKb != r.vmSizeKb || l.vmHwmKb != r.vmHwmKb ||
-                              l.vmRssKb != r.vmRssKb || !isEqual(l.process, r.process) ||
-                              l.threads.size() != r.threads.size()) {
-                              return false;
-                          }
-                          for (const auto& lIt : l.threads) {
-                              const auto& rIt = r.threads.find(lIt.first);
-                              if (rIt == r.threads.end()) {
-                                  return false;
-                              }
-                              if (!isEqual(lIt.second, rIt->second)) {
-                                  return false;
-                              }
-                          }
-                          return true;
-                      });
-}
-
-std::string pidStatusStr(pid_t pid, uid_t uid) {
-    return StringPrintf("Pid:\t%" PRIu32 "\nTgid:\t%" PRIu32 "\nUid:\t%" PRIu32 "\n", pid, pid,
-                        uid);
-}
-
-std::string pidStatusStr(pid_t pid, uid_t uid, uint64_t vmPeakKb, uint64_t vmSizeKb,
-                         uint64_t vmHwmKb, uint64_t vmRssKb) {
-    return StringPrintf("%sVmPeak:\t%" PRIu64 "\nVmSize:\t%" PRIu64 "\nVmHWM:\t%" PRIu64
-                        "\nVmRSS:\t%" PRIu64 "\n",
-                        pidStatusStr(pid, uid).c_str(), vmPeakKb, vmSizeKb, vmHwmKb, vmRssKb);
-}
-
-}  // namespace
-
-TEST(ProcPidStatTest, TestValidStatFiles) {
-    std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
-            {1, {1, 453}},
-            {1000, {1000, 1100}},
-    };
-
-    std::unordered_map<pid_t, std::string> perProcessStat = {
-            {1, "1 (init) S 0 0 0 0 0 0 0 0 220 0 0 0 0 0 0 0 2 0 0\n"},
-            {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 600 0 0 0 0 0 0 0 2 0 1000\n"},
-    };
-
-    std::unordered_map<pid_t, std::string> perProcessStatus = {
-            {1, pidStatusStr(1, 0, 123, 456, 789, 345)},
-            {1000, pidStatusStr(1000, 10001234, 234, 567, 890, 123)},
-    };
-
-    std::unordered_map<pid_t, std::string> perThreadStat = {
-            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 0\n"},
-            {453, "453 (init) S 0 0 0 0 0 0 0 0 20 0 0 0 0 0 0 0 2 0 275\n"},
-            {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 250 0 0 0 0 0 0 0 2 0 1000\n"},
-            {1100, "1100 (system_server) S 1 0 0 0 0 0 0 0 350 0 0 0 0 0 0 0 2 0 1200\n"},
-    };
-
-    std::vector<ProcessStats> expected = {
-            {.tgid = 1,
-             .uid = 0,
-             .vmPeakKb = 123,
-             .vmSizeKb = 456,
-             .vmHwmKb = 789,
-             .vmRssKb = 345,
-             .process = {1, "init", "S", 0, 220, 2, 0},
-             .threads = {{1, {1, "init", "S", 0, 200, 2, 0}},
-                         {453, {453, "init", "S", 0, 20, 2, 275}}}},
-            {.tgid = 1000,
-             .uid = 10001234,
-             .vmPeakKb = 234,
-             .vmSizeKb = 567,
-             .vmHwmKb = 890,
-             .vmRssKb = 123,
-             .process = {1000, "system_server", "R", 1, 600, 2, 1000},
-             .threads = {{1000, {1000, "system_server", "R", 1, 250, 2, 1000}},
-                         {1100, {1100, "system_server", "S", 1, 350, 2, 1200}}}},
-    };
-
-    TemporaryDir firstSnapshot;
-    ASSERT_RESULT_OK(populateProcPidDir(firstSnapshot.path, pidToTids, perProcessStat,
-                                        perProcessStatus, perThreadStat));
-
-    ProcPidStat procPidStat(firstSnapshot.path);
-    ASSERT_TRUE(procPidStat.enabled())
-            << "Files under the path `" << firstSnapshot.path << "` are inaccessible";
-    ASSERT_RESULT_OK(procPidStat.collect());
-
-    auto actual = std::vector<ProcessStats>(procPidStat.deltaStats());
-    EXPECT_TRUE(isEqual(&expected, &actual)) << "First snapshot doesn't match.\nExpected:\n"
-                                             << toString(expected) << "\nActual:\n"
-                                             << toString(actual);
-    pidToTids = {
-            {1, {1, 453}}, {1000, {1000, 1400}},  // TID 1100 terminated and 1400 instantiated.
-    };
-
-    perProcessStat = {
-            {1, "1 (init) S 0 0 0 0 0 0 0 0 920 0 0 0 0 0 0 0 2 0 0\n"},
-            {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 1550 0 0 0 0 0 0 0 2 0 1000\n"},
-    };
-
-    perThreadStat = {
-            {1, "1 (init) S 0 0 0 0 0 0 0 0 600 0 0 0 0 0 0 0 2 0 0\n"},
-            {453, "453 (init) S 0 0 0 0 0 0 0 0 320 0 0 0 0 0 0 0 2 0 275\n"},
-            {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 600 0 0 0 0 0 0 0 2 0 1000\n"},
-            // TID 1100 hits +400 major page faults before terminating. This is counted against
-            // PID 1000's perProcessStat.
-            {1400, "1400 (system_server) S 1 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 8977476\n"},
-    };
-
-    expected = {
-            {.tgid = 1,
-             .uid = 0,
-             .vmPeakKb = 123,
-             .vmSizeKb = 456,
-             .vmHwmKb = 789,
-             .vmRssKb = 345,
-             .process = {1, "init", "S", 0, 700, 2, 0},
-             .threads = {{1, {1, "init", "S", 0, 400, 2, 0}},
-                         {453, {453, "init", "S", 0, 300, 2, 275}}}},
-            {.tgid = 1000,
-             .uid = 10001234,
-             .vmPeakKb = 234,
-             .vmSizeKb = 567,
-             .vmHwmKb = 890,
-             .vmRssKb = 123,
-             .process = {1000, "system_server", "R", 1, 950, 2, 1000},
-             .threads = {{1000, {1000, "system_server", "R", 1, 350, 2, 1000}},
-                         {1400, {1400, "system_server", "S", 1, 200, 2, 8977476}}}},
-    };
-
-    TemporaryDir secondSnapshot;
-    ASSERT_RESULT_OK(populateProcPidDir(secondSnapshot.path, pidToTids, perProcessStat,
-                                        perProcessStatus, perThreadStat));
-
-    procPidStat.mPath = secondSnapshot.path;
-    ASSERT_TRUE(procPidStat.enabled())
-            << "Files under the path `" << secondSnapshot.path << "` are inaccessible";
-    ASSERT_RESULT_OK(procPidStat.collect());
-
-    actual = std::vector<ProcessStats>(procPidStat.deltaStats());
-    EXPECT_TRUE(isEqual(&expected, &actual)) << "Second snapshot doesn't match.\nExpected:\n"
-                                             << toString(expected) << "\nActual:\n"
-                                             << toString(actual);
-}
-
-TEST(ProcPidStatTest, TestHandlesProcessTerminationBetweenScanningAndParsing) {
-    std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
-            {1, {1}},
-            {100, {100}},          // Process terminates after scanning PID directory.
-            {1000, {1000}},        // Process terminates after reading stat file.
-            {2000, {2000}},        // Process terminates after scanning task directory.
-            {3000, {3000, 3300}},  // TID 3300 terminates after scanning task directory.
-    };
-
-    std::unordered_map<pid_t, std::string> perProcessStat = {
-            {1, "1 (init) S 0 0 0 0 0 0 0 0 220 0 0 0 0 0 0 0 1 0 0\n"},
-            // Process 100 terminated.
-            {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 600 0 0 0 0 0 0 0 1 0 1000\n"},
-            {2000, "2000 (logd) R 1 0 0 0 0 0 0 0 1200 0 0 0 0 0 0 0 1 0 4567\n"},
-            {3000, "3000 (disk I/O) R 1 0 0 0 0 0 0 0 10300 0 0 0 0 0 0 0 2 0 67890\n"},
-    };
-
-    std::unordered_map<pid_t, std::string> perProcessStatus = {
-            {1, "Pid:\t1\nTgid:\t1\nUid:\t0\t0\t0\t0\n"},
-            // Process 1000 terminated.
-            {2000, pidStatusStr(2000, 10001234)},
-            {3000, pidStatusStr(3000, 10001234)},
-    };
-
-    std::unordered_map<pid_t, std::string> perThreadStat = {
-            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
-            // Process 2000 terminated.
-            {3000, "3000 (disk I/O) R 1 0 0 0 0 0 0 0 2400 0 0 0 0 0 0 0 2 0 67890\n"},
-            // TID 3300 terminated.
-    };
-
-    std::vector<ProcessStats> expected = {
-            {.tgid = 1,
-             .uid = 0,
-             .process = {1, "init", "S", 0, 220, 1, 0},
-             .threads = {{1, {1, "init", "S", 0, 200, 1, 0}}}},
-            {.tgid = -1,
-             .uid = -1,
-             .process = {1000, "system_server", "R", 1, 600, 1, 1000},
-             // Stats common between process and main-thread are copied when
-             // main-thread stats are not available.
-             .threads = {{1000, {1000, "system_server", "R", 1, 0, 1, 1000}}}},
-            {.tgid = 2000,
-             .uid = 10001234,
-             .process = {2000, "logd", "R", 1, 1200, 1, 4567},
-             .threads = {{2000, {2000, "logd", "R", 1, 0, 1, 4567}}}},
-            {.tgid = 3000,
-             .uid = 10001234,
-             .process = {3000, "disk I/O", "R", 1, 10300, 2, 67890},
-             .threads = {{3000, {3000, "disk I/O", "R", 1, 2400, 2, 67890}}}},
-    };
-
-    TemporaryDir procDir;
-    ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
-                                        perThreadStat));
-
-    ProcPidStat procPidStat(procDir.path);
-    ASSERT_TRUE(procPidStat.enabled())
-            << "Files under the path `" << procDir.path << "` are inaccessible";
-    ASSERT_RESULT_OK(procPidStat.collect());
-
-    auto actual = std::vector<ProcessStats>(procPidStat.deltaStats());
-    EXPECT_TRUE(isEqual(&expected, &actual)) << "Proc pid contents doesn't match.\nExpected:\n"
-                                             << toString(expected) << "\nActual:\n"
-                                             << toString(actual);
-}
-
-TEST(ProcPidStatTest, TestHandlesPidTidReuse) {
-    std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
-            {1, {1, 367, 453, 589}},
-            {1000, {1000}},
-            {2345, {2345}},
-    };
-
-    std::unordered_map<pid_t, std::string> perProcessStat = {
-            {1, "1 (init) S 0 0 0 0 0 0 0 0 1200 0 0 0 0 0 0 0 4 0 0\n"},
-            {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 250 0 0 0 0 0 0 0 1 0 1000\n"},
-            {2345, "2345 (logd) R 1 0 0 0 0 0 0 0 54354 0 0 0 0 0 0 0 1 0 456\n"},
-    };
-
-    std::unordered_map<pid_t, std::string> perProcessStatus = {
-            {1, pidStatusStr(1, 0)},
-            {1000, pidStatusStr(1000, 10001234)},
-            {2345, pidStatusStr(2345, 10001234)},
-    };
-
-    std::unordered_map<pid_t, std::string> perThreadStat = {
-            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 4 0 0\n"},
-            {367, "367 (init) S 0 0 0 0 0 0 0 0 400 0 0 0 0 0 0 0 4 0 100\n"},
-            {453, "453 (init) S 0 0 0 0 0 0 0 0 100 0 0 0 0 0 0 0 4 0 275\n"},
-            {589, "589 (init) S 0 0 0 0 0 0 0 0 500 0 0 0 0 0 0 0 4 0 600\n"},
-            {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 250 0 0 0 0 0 0 0 1 0 1000\n"},
-            {2345, "2345 (logd) R 1 0 0 0 0 0 0 0 54354 0 0 0 0 0 0 0 1 0 456\n"},
-    };
-
-    std::vector<ProcessStats> expected = {
-            {.tgid = 1,
-             .uid = 0,
-             .process = {1, "init", "S", 0, 1200, 4, 0},
-             .threads = {{1, {1, "init", "S", 0, 200, 4, 0}},
-                         {367, {367, "init", "S", 0, 400, 4, 100}},
-                         {453, {453, "init", "S", 0, 100, 4, 275}},
-                         {589, {589, "init", "S", 0, 500, 4, 600}}}},
-            {.tgid = 1000,
-             .uid = 10001234,
-             .process = {1000, "system_server", "R", 1, 250, 1, 1000},
-             .threads = {{1000, {1000, "system_server", "R", 1, 250, 1, 1000}}}},
-            {.tgid = 2345,
-             .uid = 10001234,
-             .process = {2345, "logd", "R", 1, 54354, 1, 456},
-             .threads = {{2345, {2345, "logd", "R", 1, 54354, 1, 456}}}},
-    };
-
-    TemporaryDir firstSnapshot;
-    ASSERT_RESULT_OK(populateProcPidDir(firstSnapshot.path, pidToTids, perProcessStat,
-                                        perProcessStatus, perThreadStat));
-
-    ProcPidStat procPidStat(firstSnapshot.path);
-    ASSERT_TRUE(procPidStat.enabled())
-            << "Files under the path `" << firstSnapshot.path << "` are inaccessible";
-    ASSERT_RESULT_OK(procPidStat.collect());
-
-    auto actual = std::vector<ProcessStats>(procPidStat.deltaStats());
-    EXPECT_TRUE(isEqual(&expected, &actual)) << "First snapshot doesn't match.\nExpected:\n"
-                                             << toString(expected) << "\nActual:\n"
-                                             << toString(actual);
-
-    pidToTids = {
-            {1, {1, 589}},       // TID 589 reused by the same process.
-            {367, {367, 2000}},  // TID 367 reused as a PID. PID 2000 reused as a TID.
-            // PID 1000 reused as a new PID. TID 453 reused by a different PID.
-            {1000, {1000, 453}},
-    };
-
-    perProcessStat = {
-            {1, "1 (init) S 0 0 0 0 0 0 0 0 1800 0 0 0 0 0 0 0 2 0 0\n"},
-            {367, "367 (system_server) R 1 0 0 0 0 0 0 0 100 0 0 0 0 0 0 0 2 0 3450\n"},
-            {1000, "1000 (logd) R 1 0 0 0 0 0 0 0 2000 0 0 0 0 0 0 0 2 0 4650\n"},
-    };
-
-    perProcessStatus = {
-            {1, pidStatusStr(1, 0)},
-            {367, pidStatusStr(367, 10001234)},
-            {1000, pidStatusStr(1000, 10001234)},
-    };
-
-    perThreadStat = {
-            {1, "1 (init) S 0 0 0 0 0 0 0 0 500 0 0 0 0 0 0 0 2 0 0\n"},
-            {589, "589 (init) S 0 0 0 0 0 0 0 0 300 0 0 0 0 0 0 0 2 0 2345\n"},
-            {367, "367 (system_server) R 1 0 0 0 0 0 0 0 50 0 0 0 0 0 0 0 2 0 3450\n"},
-            {2000, "2000 (system_server) R 1 0 0 0 0 0 0 0 50 0 0 0 0 0 0 0 2 0 3670\n"},
-            {1000, "1000 (logd) R 1 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 4650\n"},
-            {453, "453 (logd) D 1 0 0 0 0 0 0 0 1800 0 0 0 0 0 0 0 2 0 4770\n"},
-    };
-
-    expected = {
-            {.tgid = 1,
-             .uid = 0,
-             .process = {1, "init", "S", 0, 600, 2, 0},
-             .threads = {{1, {1, "init", "S", 0, 300, 2, 0}},
-                         {589, {589, "init", "S", 0, 300, 2, 2345}}}},
-            {.tgid = 367,
-             .uid = 10001234,
-             .process = {367, "system_server", "R", 1, 100, 2, 3450},
-             .threads = {{367, {367, "system_server", "R", 1, 50, 2, 3450}},
-                         {2000, {2000, "system_server", "R", 1, 50, 2, 3670}}}},
-            {.tgid = 1000,
-             .uid = 10001234,
-             .process = {1000, "logd", "R", 1, 2000, 2, 4650},
-             .threads = {{1000, {1000, "logd", "R", 1, 200, 2, 4650}},
-                         {453, {453, "logd", "D", 1, 1800, 2, 4770}}}},
-    };
-
-    TemporaryDir secondSnapshot;
-    ASSERT_RESULT_OK(populateProcPidDir(secondSnapshot.path, pidToTids, perProcessStat,
-                                        perProcessStatus, perThreadStat));
-
-    procPidStat.mPath = secondSnapshot.path;
-    ASSERT_TRUE(procPidStat.enabled())
-            << "Files under the path `" << secondSnapshot.path << "` are inaccessible";
-    ASSERT_RESULT_OK(procPidStat.collect());
-
-    actual = std::vector<ProcessStats>(procPidStat.deltaStats());
-    EXPECT_TRUE(isEqual(&expected, &actual)) << "Second snapshot doesn't match.\nExpected:\n"
-                                             << toString(expected) << "\nActual:\n"
-                                             << toString(actual);
-}
-
-TEST(ProcPidStatTest, TestErrorOnCorruptedProcessStatFile) {
-    std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
-            {1, {1}},
-    };
-
-    std::unordered_map<pid_t, std::string> perProcessStat = {
-            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 CORRUPTED DATA\n"},
-    };
-
-    std::unordered_map<pid_t, std::string> perProcessStatus = {
-            {1, pidStatusStr(1, 0)},
-    };
-
-    std::unordered_map<pid_t, std::string> perThreadStat = {
-            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
-    };
-
-    TemporaryDir procDir;
-    ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
-                                        perThreadStat));
-
-    ProcPidStat procPidStat(procDir.path);
-    ASSERT_TRUE(procPidStat.enabled())
-            << "Files under the path `" << procDir.path << "` are inaccessible";
-    ASSERT_FALSE(procPidStat.collect().ok()) << "No error returned for invalid process stat file";
-}
-
-TEST(ProcPidStatTest, TestErrorOnCorruptedProcessStatusFile) {
-    std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
-            {1, {1}},
-    };
-
-    std::unordered_map<pid_t, std::string> perProcessStat = {
-            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
-    };
-
-    std::unordered_map<pid_t, std::string> perProcessStatus = {
-            {1, "Pid:\t1\nTgid:\t1\nCORRUPTED DATA\n"},
-    };
-
-    std::unordered_map<pid_t, std::string> perThreadStat = {
-            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
-    };
-
-    TemporaryDir procDir;
-    ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
-                                        perThreadStat));
-
-    ProcPidStat procPidStat(procDir.path);
-    ASSERT_TRUE(procPidStat.enabled())
-            << "Files under the path `" << procDir.path << "` are inaccessible";
-    ASSERT_FALSE(procPidStat.collect().ok()) << "No error returned for invalid process status file";
-}
-
-TEST(ProcPidStatTest, TestErrorOnCorruptedThreadStatFile) {
-    std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
-            {1, {1}},
-    };
-
-    std::unordered_map<pid_t, std::string> perProcessStat = {
-            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
-    };
-
-    std::unordered_map<pid_t, std::string> perProcessStatus = {
-            {1, pidStatusStr(1, 0)},
-    };
-
-    std::unordered_map<pid_t, std::string> perThreadStat = {
-            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 CORRUPTED DATA\n"},
-    };
-
-    TemporaryDir procDir;
-    ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
-                                        perThreadStat));
-
-    ProcPidStat procPidStat(procDir.path);
-    ASSERT_TRUE(procPidStat.enabled())
-            << "Files under the path `" << procDir.path << "` are inaccessible";
-    ASSERT_FALSE(procPidStat.collect().ok()) << "No error returned for invalid thread stat file";
-}
-
-TEST(ProcPidStatTest, TestHandlesSpaceInCommName) {
-    std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
-            {1, {1}},
-    };
-
-    std::unordered_map<pid_t, std::string> perProcessStat = {
-            {1, "1 (random process name with space) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
-    };
-
-    std::unordered_map<pid_t, std::string> perProcessStatus = {
-            {1, pidStatusStr(1, 0)},
-    };
-
-    std::unordered_map<pid_t, std::string> perThreadStat = {
-            {1, "1 (random process name with space) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
-    };
-
-    std::vector<ProcessStats> expected = {
-            {.tgid = 1,
-             .uid = 0,
-             .process = {1, "random process name with space", "S", 0, 200, 1, 0},
-             .threads = {{1, {1, "random process name with space", "S", 0, 200, 1, 0}}}},
-    };
-
-    TemporaryDir procDir;
-    ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
-                                        perThreadStat));
-
-    ProcPidStat procPidStat(procDir.path);
-    ASSERT_TRUE(procPidStat.enabled())
-            << "Files under the path `" << procDir.path << "` are inaccessible";
-    ASSERT_RESULT_OK(procPidStat.collect());
-
-    auto actual = std::vector<ProcessStats>(procPidStat.deltaStats());
-    EXPECT_TRUE(isEqual(&expected, &actual)) << "Proc pid contents doesn't match.\nExpected:\n"
-                                             << toString(expected) << "\nActual:\n"
-                                             << toString(actual);
-}
-
-TEST(ProcPidStatTest, TestProcPidStatContentsFromDevice) {
-    ProcPidStat procPidStat;
-    ASSERT_TRUE(procPidStat.enabled()) << "/proc/[pid]/.* files are inaccessible";
-    ASSERT_RESULT_OK(procPidStat.collect());
-
-    const auto& processStats = procPidStat.deltaStats();
-    // The below check should pass because there should be at least one process.
-    EXPECT_GT(processStats.size(), 0);
-}
-
-}  // namespace watchdog
-}  // namespace automotive
-}  // namespace android
diff --git a/cpp/watchdog/server/tests/ProcStatTest.cpp b/cpp/watchdog/server/tests/ProcStatTest.cpp
index c886310..ee33162 100644
--- a/cpp/watchdog/server/tests/ProcStatTest.cpp
+++ b/cpp/watchdog/server/tests/ProcStatTest.cpp
@@ -42,7 +42,7 @@
                         cpuStats.userTime, cpuStats.niceTime, cpuStats.sysTime, cpuStats.idleTime,
                         cpuStats.ioWaitTime, cpuStats.irqTime, cpuStats.softIrqTime,
                         cpuStats.stealTime, cpuStats.guestTime, cpuStats.guestNiceTime,
-                        info.runnableProcessesCnt, info.ioBlockedProcessesCnt);
+                        info.runnableProcessCount, info.ioBlockedProcessCount);
 }
 
 }  // namespace
@@ -56,8 +56,9 @@
             "cpu3 1000 20 190 650 109 130 140 0 0 0\n"
             "intr 694351583 0 0 0 297062868 0 5922464 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "
             "0 0\n"
-            // Skipped most of the intr line as it is not important for testing the ProcStat parsing
-            // logic.
+            /* Skipped most of the intr line as it is not important for testing the ProcStat parsing
+             * logic.
+             */
             "ctxt 579020168\n"
             "btime 1579718450\n"
             "processes 113804\n"
@@ -77,8 +78,8 @@
             .guestTime = 0,
             .guestNiceTime = 0,
     };
-    expectedFirstDelta.runnableProcessesCnt = 17;
-    expectedFirstDelta.ioBlockedProcessesCnt = 5;
+    expectedFirstDelta.runnableProcessCount = 17;
+    expectedFirstDelta.ioBlockedProcessCount = 5;
 
     TemporaryFile tf;
     ASSERT_NE(tf.fd, -1);
@@ -120,8 +121,8 @@
             .guestTime = 0,
             .guestNiceTime = 0,
     };
-    expectedSecondDelta.runnableProcessesCnt = 10;
-    expectedSecondDelta.ioBlockedProcessesCnt = 2;
+    expectedSecondDelta.runnableProcessCount = 10;
+    expectedSecondDelta.ioBlockedProcessCount = 2;
 
     ASSERT_TRUE(WriteStringToFile(secondSnapshot, tf.path));
     ASSERT_RESULT_OK(procStat.collect());
@@ -257,10 +258,11 @@
     ASSERT_RESULT_OK(procStat.collect());
 
     const auto& info = procStat.deltaStats();
-    // The below checks should pass because the /proc/stats file should have the CPU time spent
-    // since bootup and there should be at least one running process.
+    /* The below checks should pass because the /proc/stats file should have the CPU time spent
+     * since bootup and there should be at least one running process.
+     */
     EXPECT_GT(info.totalCpuTime(), 0);
-    EXPECT_GT(info.totalProcessesCnt(), 0);
+    EXPECT_GT(info.totalProcessCount(), 0);
 }
 
 }  // namespace watchdog
diff --git a/cpp/watchdog/server/tests/UidIoStatsCollectorTest.cpp b/cpp/watchdog/server/tests/UidIoStatsCollectorTest.cpp
new file mode 100644
index 0000000..fe30844
--- /dev/null
+++ b/cpp/watchdog/server/tests/UidIoStatsCollectorTest.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright 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.
+ */
+
+#include "UidIoStatsCollector.h"
+
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <gmock/gmock.h>
+
+#include <unordered_map>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+using ::android::base::StringAppendF;
+using ::android::base::WriteStringToFile;
+using ::testing::UnorderedElementsAreArray;
+
+namespace {
+
+std::string toString(std::unordered_map<uid_t, UidIoStats> uidIoStatsByUid) {
+    std::string buffer;
+    for (const auto& [uid, stats] : uidIoStatsByUid) {
+        StringAppendF(&buffer, "{%d: %s}\n", uid, stats.toString().c_str());
+    }
+    return buffer;
+}
+
+}  // namespace
+
+TEST(UidIoStatsCollectorTest, TestValidStatFile) {
+    // Format: uid fgRdChar fgWrChar fgRdBytes fgWrBytes bgRdChar bgWrChar bgRdBytes bgWrBytes
+    // fgFsync bgFsync
+    constexpr char firstSnapshot[] = "1001234 5000 1000 3000 500 0 0 0 0 20 0\n"
+                                     "1005678 500 100 30 50 300 400 100 200 45 60\n"
+                                     "1009 0 0 0 0 40000 50000 20000 30000 0 300\n"
+                                     "1001000 4000 3000 2000 1000 400 300 200 100 50 10\n";
+    std::unordered_map<uid_t, UidIoStats> expectedFirstUsage =
+            {{1001234,
+              UidIoStats{/*fgRdBytes=*/3'000, /*bgRdBytes=*/0, /*fgWrBytes=*/500,
+                         /*bgWrBytes=*/0, /*fgFsync=*/20, /*bgFsync=*/0}},
+             {1005678,
+              UidIoStats{/*fgRdBytes=*/30, /*bgRdBytes=*/100, /*fgWrBytes=*/50, /*bgWrBytes=*/200,
+                         /*fgFsync=*/45, /*bgFsync=*/60}},
+             {1009,
+              UidIoStats{/*fgRdBytes=*/0, /*bgRdBytes=*/20'000, /*fgWrBytes=*/0,
+                         /*bgWrBytes=*/30'000,
+                         /*fgFsync=*/0, /*bgFsync=*/300}},
+             {1001000,
+              UidIoStats{/*fgRdBytes=*/2'000, /*bgRdBytes=*/200, /*fgWrBytes=*/1'000,
+                         /*bgWrBytes=*/100, /*fgFsync=*/50, /*bgFsync=*/10}}};
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1);
+    ASSERT_TRUE(WriteStringToFile(firstSnapshot, tf.path));
+
+    UidIoStatsCollector collector(tf.path);
+    ASSERT_TRUE(collector.enabled()) << "Temporary file is inaccessible";
+    ASSERT_RESULT_OK(collector.collect());
+
+    const auto& actualFirstUsage = collector.deltaStats();
+    EXPECT_THAT(actualFirstUsage, UnorderedElementsAreArray(expectedFirstUsage))
+            << "Expected: " << toString(expectedFirstUsage)
+            << "Actual: " << toString(actualFirstUsage);
+
+    constexpr char secondSnapshot[] = "1001234 10000 2000 7000 950 0 0 0 0 45 0\n"
+                                      "1005678 600 100 40 50 1000 1000 1000 600 50 70\n"
+                                      "1003456 300 500 200 300 0 0 0 0 50 0\n"
+                                      "1001000 400 300 200 100 40 30 20 10 5 1\n";
+    std::unordered_map<uid_t, UidIoStats> expectedSecondUsage =
+            {{1001234,
+              UidIoStats{/*fgRdBytes=*/4'000, /*bgRdBytes=*/0,
+                         /*fgWrBytes=*/450, /*bgWrBytes=*/0, /*fgFsync=*/25,
+                         /*bgFsync=*/0}},
+             {1005678,
+              UidIoStats{/*fgRdBytes=*/10, /*bgRdBytes=*/900, /*fgWrBytes=*/0, /*bgWrBytes=*/400,
+                         /*fgFsync=*/5, /*bgFsync=*/10}},
+             {1003456,
+              UidIoStats{/*fgRdBytes=*/200, /*bgRdBytes=*/0, /*fgWrBytes=*/300, /*bgWrBytes=*/0,
+                         /*fgFsync=*/50, /*bgFsync=*/0}}};
+    ASSERT_TRUE(WriteStringToFile(secondSnapshot, tf.path));
+    ASSERT_RESULT_OK(collector.collect());
+
+    const auto& actualSecondUsage = collector.deltaStats();
+    EXPECT_THAT(actualSecondUsage, UnorderedElementsAreArray(expectedSecondUsage))
+            << "Expected: " << toString(expectedSecondUsage)
+            << "Actual: " << toString(actualSecondUsage);
+}
+
+TEST(UidIoStatsCollectorTest, TestErrorOnInvalidStatFile) {
+    // Format: uid fgRdChar fgWrChar fgRdBytes fgWrBytes bgRdChar bgWrChar bgRdBytes bgWrBytes
+    // fgFsync bgFsync
+    constexpr char contents[] = "1001234 5000 1000 3000 500 0 0 0 0 20 0\n"
+                                "1005678 500 100 30 50 300 400 100 200 45 60\n"
+                                "1009012 0 0 0 0 40000 50000 20000 30000 0 300\n"
+                                "1001000 4000 3000 2000 1000 CORRUPTED DATA\n";
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1);
+    ASSERT_TRUE(WriteStringToFile(contents, tf.path));
+
+    UidIoStatsCollector collector(tf.path);
+    ASSERT_TRUE(collector.enabled()) << "Temporary file is inaccessible";
+    EXPECT_FALSE(collector.collect().ok()) << "No error returned for invalid file";
+}
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
diff --git a/cpp/watchdog/server/tests/UidIoStatsTest.cpp b/cpp/watchdog/server/tests/UidIoStatsTest.cpp
deleted file mode 100644
index 6e88ed5..0000000
--- a/cpp/watchdog/server/tests/UidIoStatsTest.cpp
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright 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.
- */
-
-#include "UidIoStats.h"
-
-#include <android-base/file.h>
-#include <android-base/stringprintf.h>
-#include <gmock/gmock.h>
-
-#include <unordered_map>
-
-namespace android {
-namespace automotive {
-namespace watchdog {
-
-using ::android::base::StringAppendF;
-using ::android::base::WriteStringToFile;
-using ::testing::UnorderedElementsAreArray;
-
-namespace {
-
-std::string toString(std::unordered_map<uid_t, UidIoUsage> usages) {
-    std::string buffer;
-    for (const auto& [uid, usage] : usages) {
-        StringAppendF(&buffer, "{%s}\n", usage.toString().c_str());
-    }
-    return buffer;
-}
-
-}  // namespace
-
-TEST(UidIoStatsTest, TestValidStatFile) {
-    // Format: uid fgRdChar fgWrChar fgRdBytes fgWrBytes bgRdChar bgWrChar bgRdBytes bgWrBytes
-    // fgFsync bgFsync
-    constexpr char firstSnapshot[] = "1001234 5000 1000 3000 500 0 0 0 0 20 0\n"
-                                     "1005678 500 100 30 50 300 400 100 200 45 60\n"
-                                     "1009 0 0 0 0 40000 50000 20000 30000 0 300\n"
-                                     "1001000 4000 3000 2000 1000 400 300 200 100 50 10\n";
-    std::unordered_map<uid_t, UidIoUsage> expectedFirstUsage =
-            {{1001234,
-              {.uid = 1001234,
-               .ios = {/*fgRdBytes=*/3000, /*bgRdBytes=*/0, /*fgWrBytes=*/500,
-                       /*bgWrBytes=*/0, /*fgFsync=*/20, /*bgFsync=*/0}}},
-             {1005678, {.uid = 1005678, .ios = {30, 100, 50, 200, 45, 60}}},
-             {1009, {.uid = 1009, .ios = {0, 20000, 0, 30000, 0, 300}}},
-             {1001000, {.uid = 1001000, .ios = {2000, 200, 1000, 100, 50, 10}}}};
-    TemporaryFile tf;
-    ASSERT_NE(tf.fd, -1);
-    ASSERT_TRUE(WriteStringToFile(firstSnapshot, tf.path));
-
-    UidIoStats uidIoStats(tf.path);
-    ASSERT_TRUE(uidIoStats.enabled()) << "Temporary file is inaccessible";
-    ASSERT_RESULT_OK(uidIoStats.collect());
-
-    const auto& actualFirstUsage = uidIoStats.deltaStats();
-    EXPECT_THAT(actualFirstUsage, UnorderedElementsAreArray(expectedFirstUsage))
-            << "Expected: " << toString(expectedFirstUsage)
-            << "Actual: " << toString(actualFirstUsage);
-
-    constexpr char secondSnapshot[] = "1001234 10000 2000 7000 950 0 0 0 0 45 0\n"
-                                      "1005678 600 100 40 50 1000 1000 1000 600 50 70\n"
-                                      "1003456 300 500 200 300 0 0 0 0 50 0\n"
-                                      "1001000 400 300 200 100 40 30 20 10 5 1\n";
-    std::unordered_map<uid_t, UidIoUsage> expectedSecondUsage =
-            {{1001234,
-              {.uid = 1001234,
-               .ios = {/*fgRdBytes=*/4000, /*bgRdBytes=*/0,
-                       /*fgWrBytes=*/450, /*bgWrBytes=*/0, /*fgFsync=*/25,
-                       /*bgFsync=*/0}}},
-             {1005678, {.uid = 1005678, .ios = {10, 900, 0, 400, 5, 10}}},
-             {1003456, {.uid = 1003456, .ios = {200, 0, 300, 0, 50, 0}}}};
-    ASSERT_TRUE(WriteStringToFile(secondSnapshot, tf.path));
-    ASSERT_RESULT_OK(uidIoStats.collect());
-
-    const auto& actualSecondUsage = uidIoStats.deltaStats();
-    EXPECT_THAT(actualSecondUsage, UnorderedElementsAreArray(expectedSecondUsage))
-            << "Expected: " << toString(expectedSecondUsage)
-            << "Actual: " << toString(actualSecondUsage);
-}
-
-TEST(UidIoStatsTest, TestErrorOnInvalidStatFile) {
-    // Format: uid fgRdChar fgWrChar fgRdBytes fgWrBytes bgRdChar bgWrChar bgRdBytes bgWrBytes
-    // fgFsync bgFsync
-    constexpr char contents[] = "1001234 5000 1000 3000 500 0 0 0 0 20 0\n"
-                                "1005678 500 100 30 50 300 400 100 200 45 60\n"
-                                "1009012 0 0 0 0 40000 50000 20000 30000 0 300\n"
-                                "1001000 4000 3000 2000 1000 CORRUPTED DATA\n";
-    TemporaryFile tf;
-    ASSERT_NE(tf.fd, -1);
-    ASSERT_TRUE(WriteStringToFile(contents, tf.path));
-
-    UidIoStats uidIoStats(tf.path);
-    ASSERT_TRUE(uidIoStats.enabled()) << "Temporary file is inaccessible";
-    EXPECT_FALSE(uidIoStats.collect().ok()) << "No error returned for invalid file";
-}
-
-}  // namespace watchdog
-}  // namespace automotive
-}  // namespace android
diff --git a/cpp/watchdog/server/tests/UidProcStatsCollectorTest.cpp b/cpp/watchdog/server/tests/UidProcStatsCollectorTest.cpp
new file mode 100644
index 0000000..c9eb6d4
--- /dev/null
+++ b/cpp/watchdog/server/tests/UidProcStatsCollectorTest.cpp
@@ -0,0 +1,480 @@
+/*
+ * Copyright 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.
+ */
+
+#include "ProcPidDir.h"
+#include "UidProcStatsCollector.h"
+#include "UidProcStatsCollectorTestUtils.h"
+
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <gmock/gmock.h>
+
+#include <inttypes.h>
+
+#include <algorithm>
+#include <string>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+using ::android::automotive::watchdog::testing::populateProcPidDir;
+using ::android::base::StringAppendF;
+using ::android::base::StringPrintf;
+using ::testing::UnorderedPointwise;
+
+namespace {
+
+MATCHER(UidProcStatsByUidEq, "") {
+    const auto& actual = std::get<0>(arg);
+    const auto& expected = std::get<1>(arg);
+    return actual.first == expected.first &&
+            ExplainMatchResult(UidProcStatsEq(expected.second), actual.second, result_listener);
+}
+
+std::string pidStatusStr(pid_t pid, uid_t uid) {
+    return StringPrintf("Pid:\t%" PRIu32 "\nTgid:\t%" PRIu32 "\nUid:\t%" PRIu32 "\n", pid, pid,
+                        uid);
+}
+
+std::string toString(const std::unordered_map<uid_t, UidProcStats>& uidProcStatsByUid) {
+    std::string buffer;
+    StringAppendF(&buffer, "Number of UIDs: %" PRIi32 "\n",
+                  static_cast<int>(uidProcStatsByUid.size()));
+    for (const auto& [uid, stats] : uidProcStatsByUid) {
+        StringAppendF(&buffer, "{UID: %d, %s}", uid, stats.toString().c_str());
+    }
+    return buffer;
+}
+
+}  // namespace
+
+TEST(UidProcStatsCollectorTest, TestValidStatFiles) {
+    std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
+            {1, {1, 453}},
+            {1000, {1000, 1100}},
+    };
+
+    std::unordered_map<pid_t, std::string> perProcessStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 220 0 0 0 0 0 0 0 2 0 0\n"},
+            {1000, "1000 (system_server) D 1 0 0 0 0 0 0 0 600 0 0 0 0 0 0 0 2 0 13400\n"},
+    };
+
+    std::unordered_map<pid_t, std::string> perProcessStatus = {
+            {1, pidStatusStr(1, 0)},
+            {1000, pidStatusStr(1000, 10001234)},
+    };
+
+    std::unordered_map<pid_t, std::string> perThreadStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 0\n"},
+            {453, "453 (init) D 0 0 0 0 0 0 0 0 20 0 0 0 0 0 0 0 2 0 275\n"},
+            {1000, "1000 (system_server) D 1 0 0 0 0 0 0 0 250 0 0 0 0 0 0 0 2 0 13400\n"},
+            {1100, "1100 (system_server) D 1 0 0 0 0 0 0 0 350 0 0 0 0 0 0 0 2 0 13900\n"},
+    };
+
+    std::unordered_map<uid_t, UidProcStats> expected =
+            {{0,
+              UidProcStats{.totalMajorFaults = 220,
+                           .totalTasksCount = 2,
+                           .ioBlockedTasksCount = 1,
+                           .processStatsByPid = {{1, {"init", 0, 220, 2, 1}}}}},
+             {10001234,
+              UidProcStats{.totalMajorFaults = 600,
+                           .totalTasksCount = 2,
+                           .ioBlockedTasksCount = 2,
+                           .processStatsByPid = {{1000, {"system_server", 13'400, 600, 2, 2}}}}}};
+
+    TemporaryDir firstSnapshot;
+    ASSERT_RESULT_OK(populateProcPidDir(firstSnapshot.path, pidToTids, perProcessStat,
+                                        perProcessStatus, perThreadStat));
+
+    UidProcStatsCollector collector(firstSnapshot.path);
+
+    ASSERT_TRUE(collector.enabled())
+            << "Files under the path `" << firstSnapshot.path << "` are inaccessible";
+    ASSERT_RESULT_OK(collector.collect());
+
+    auto actual = collector.deltaStats();
+
+    EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
+            << "First snapshot doesn't match.\nExpected:\n"
+            << toString(expected) << "\nActual:\n"
+            << toString(actual);
+    pidToTids = {
+            {1, {1, 453}}, {1000, {1000, 1400}},  // TID 1100 terminated and 1400 instantiated.
+    };
+
+    perProcessStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 920 0 0 0 0 0 0 0 2 0 0\n"},
+            {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 1550 0 0 0 0 0 0 0 2 0 13400\n"},
+    };
+
+    perThreadStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 600 0 0 0 0 0 0 0 2 0 0\n"},
+            {453, "453 (init) S 0 0 0 0 0 0 0 0 320 0 0 0 0 0 0 0 2 0 275\n"},
+            {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 600 0 0 0 0 0 0 0 2 0 13400\n"},
+            // TID 1100 hits +400 major page faults before terminating. This is counted against
+            // PID 1000's perProcessStat.
+            {1400, "1400 (system_server) S 1 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 8977476\n"},
+    };
+
+    expected = {{0,
+                 {.totalMajorFaults = 700,
+                  .totalTasksCount = 2,
+                  .ioBlockedTasksCount = 0,
+                  .processStatsByPid = {{1, {"init", 0, 700, 2, 0}}}}},
+                {10001234,
+                 {.totalMajorFaults = 950,
+                  .totalTasksCount = 2,
+                  .ioBlockedTasksCount = 0,
+                  .processStatsByPid = {{1000, {"system_server", 13'400, 950, 2, 0}}}}}};
+
+    TemporaryDir secondSnapshot;
+    ASSERT_RESULT_OK(populateProcPidDir(secondSnapshot.path, pidToTids, perProcessStat,
+                                        perProcessStatus, perThreadStat));
+
+    collector.mPath = secondSnapshot.path;
+
+    ASSERT_TRUE(collector.enabled())
+            << "Files under the path `" << secondSnapshot.path << "` are inaccessible";
+    ASSERT_RESULT_OK(collector.collect());
+
+    actual = collector.deltaStats();
+    EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
+            << "Second snapshot doesn't match.\nExpected:\n"
+            << toString(expected) << "\nActual:\n"
+            << toString(actual);
+}
+
+TEST(UidProcStatsCollectorTest, TestHandlesProcessTerminationBetweenScanningAndParsing) {
+    std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
+            {1, {1}},
+            {100, {100}},          // Process terminates after scanning PID directory.
+            {1000, {1000}},        // Process terminates after reading stat file.
+            {2000, {2000}},        // Process terminates after scanning task directory.
+            {3000, {3000, 3300}},  // TID 3300 terminates after scanning task directory.
+    };
+
+    std::unordered_map<pid_t, std::string> perProcessStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 220 0 0 0 0 0 0 0 1 0 0\n"},
+            // Process 100 terminated.
+            {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 600 0 0 0 0 0 0 0 1 0 1000\n"},
+            {2000, "2000 (logd) R 1 0 0 0 0 0 0 0 1200 0 0 0 0 0 0 0 1 0 4567\n"},
+            {3000, "3000 (disk I/O) R 1 0 0 0 0 0 0 0 10300 0 0 0 0 0 0 0 2 0 67890\n"},
+    };
+
+    std::unordered_map<pid_t, std::string> perProcessStatus = {
+            {1, pidStatusStr(1, 0)},
+            // Process 1000 terminated.
+            {2000, pidStatusStr(2000, 10001234)},
+            {3000, pidStatusStr(3000, 10001234)},
+    };
+
+    std::unordered_map<pid_t, std::string> perThreadStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
+            // Process 2000 terminated.
+            {3000, "3000 (disk I/O) R 1 0 0 0 0 0 0 0 2400 0 0 0 0 0 0 0 2 0 67890\n"},
+            // TID 3300 terminated.
+    };
+
+    std::unordered_map<uid_t, UidProcStats> expected =
+            {{0,
+              UidProcStats{.totalMajorFaults = 220,
+                           .totalTasksCount = 1,
+                           .ioBlockedTasksCount = 0,
+                           .processStatsByPid = {{1, {"init", 0, 220, 1, 0}}}}},
+             {10001234,
+              UidProcStats{.totalMajorFaults = 11500,
+                           .totalTasksCount = 2,
+                           .ioBlockedTasksCount = 0,
+                           .processStatsByPid = {{2000, {"logd", 4567, 1200, 1, 0}},
+                                                 {3000, {"disk I/O", 67890, 10'300, 1, 0}}}}}};
+
+    TemporaryDir procDir;
+    ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
+                                        perThreadStat));
+
+    UidProcStatsCollector collector(procDir.path);
+
+    ASSERT_TRUE(collector.enabled())
+            << "Files under the path `" << procDir.path << "` are inaccessible";
+    ASSERT_RESULT_OK(collector.collect());
+
+    auto actual = collector.deltaStats();
+    EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
+            << "Proc pid contents doesn't match.\nExpected:\n"
+            << toString(expected) << "\nActual:\n"
+            << toString(actual);
+}
+
+TEST(UidProcStatsCollectorTest, TestHandlesPidTidReuse) {
+    std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
+            {1, {1, 367, 453, 589}},
+            {1000, {1000}},
+            {2345, {2345}},
+    };
+
+    std::unordered_map<pid_t, std::string> perProcessStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 1200 0 0 0 0 0 0 0 4 0 0\n"},
+            {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 250 0 0 0 0 0 0 0 1 0 1000\n"},
+            {2345, "2345 (logd) R 1 0 0 0 0 0 0 0 54354 0 0 0 0 0 0 0 1 0 456\n"},
+    };
+
+    std::unordered_map<pid_t, std::string> perProcessStatus = {
+            {1, pidStatusStr(1, 0)},
+            {1000, pidStatusStr(1000, 10001234)},
+            {2345, pidStatusStr(2345, 10001234)},
+    };
+
+    std::unordered_map<pid_t, std::string> perThreadStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 4 0 0\n"},
+            {367, "367 (init) S 0 0 0 0 0 0 0 0 400 0 0 0 0 0 0 0 4 0 100\n"},
+            {453, "453 (init) S 0 0 0 0 0 0 0 0 100 0 0 0 0 0 0 0 4 0 275\n"},
+            {589, "589 (init) D 0 0 0 0 0 0 0 0 500 0 0 0 0 0 0 0 4 0 600\n"},
+            {1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 250 0 0 0 0 0 0 0 1 0 1000\n"},
+            {2345, "2345 (logd) R 1 0 0 0 0 0 0 0 54354 0 0 0 0 0 0 0 1 0 456\n"},
+    };
+
+    std::unordered_map<uid_t, UidProcStats> expected =
+            {{0,
+              UidProcStats{.totalMajorFaults = 1200,
+                           .totalTasksCount = 4,
+                           .ioBlockedTasksCount = 1,
+                           .processStatsByPid = {{1, {"init", 0, 1200, 4, 1}}}}},
+             {10001234,
+              UidProcStats{.totalMajorFaults = 54'604,
+                           .totalTasksCount = 2,
+                           .ioBlockedTasksCount = 0,
+                           .processStatsByPid = {{1000, {"system_server", 1000, 250, 1, 0}},
+                                                 {2345, {"logd", 456, 54'354, 1, 0}}}}}};
+
+    TemporaryDir firstSnapshot;
+    ASSERT_RESULT_OK(populateProcPidDir(firstSnapshot.path, pidToTids, perProcessStat,
+                                        perProcessStatus, perThreadStat));
+
+    UidProcStatsCollector collector(firstSnapshot.path);
+
+    ASSERT_TRUE(collector.enabled())
+            << "Files under the path `" << firstSnapshot.path << "` are inaccessible";
+    ASSERT_RESULT_OK(collector.collect());
+
+    auto actual = collector.deltaStats();
+
+    EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
+            << "First snapshot doesn't match.\nExpected:\n"
+            << toString(expected) << "\nActual:\n"
+            << toString(actual);
+
+    pidToTids = {
+            {1, {1, 589}},       // TID 589 reused by the same process.
+            {367, {367, 2000}},  // TID 367 reused as a PID. PID 2000 reused as a TID.
+            // PID 1000 reused as a new PID. TID 453 reused by a different PID.
+            {1000, {1000, 453}},
+    };
+
+    perProcessStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 1800 0 0 0 0 0 0 0 2 0 0\n"},
+            {367, "367 (system_server) R 1 0 0 0 0 0 0 0 100 0 0 0 0 0 0 0 2 0 3450\n"},
+            {1000, "1000 (logd) R 1 0 0 0 0 0 0 0 2000 0 0 0 0 0 0 0 2 0 4650\n"},
+    };
+
+    perProcessStatus = {
+            {1, pidStatusStr(1, 0)},
+            {367, pidStatusStr(367, 10001234)},
+            {1000, pidStatusStr(1000, 10001234)},
+    };
+
+    perThreadStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 500 0 0 0 0 0 0 0 2 0 0\n"},
+            {589, "589 (init) S 0 0 0 0 0 0 0 0 300 0 0 0 0 0 0 0 2 0 2345\n"},
+            {367, "367 (system_server) R 1 0 0 0 0 0 0 0 50 0 0 0 0 0 0 0 2 0 3450\n"},
+            {2000, "2000 (system_server) R 1 0 0 0 0 0 0 0 50 0 0 0 0 0 0 0 2 0 3670\n"},
+            {1000, "1000 (logd) R 1 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 4650\n"},
+            {453, "453 (logd) D 1 0 0 0 0 0 0 0 1800 0 0 0 0 0 0 0 2 0 4770\n"},
+    };
+
+    expected = {{0,
+                 UidProcStats{.totalMajorFaults = 600,
+                              .totalTasksCount = 2,
+                              .ioBlockedTasksCount = 0,
+                              .processStatsByPid = {{1, {"init", 0, 600, 2, 0}}}}},
+                {10001234,
+                 UidProcStats{.totalMajorFaults = 2100,
+                              .totalTasksCount = 4,
+                              .ioBlockedTasksCount = 1,
+                              .processStatsByPid = {{367, {"system_server", 3450, 100, 2, 0}},
+                                                    {1000, {"logd", 4650, 2000, 2, 1}}}}}};
+
+    TemporaryDir secondSnapshot;
+    ASSERT_RESULT_OK(populateProcPidDir(secondSnapshot.path, pidToTids, perProcessStat,
+                                        perProcessStatus, perThreadStat));
+
+    collector.mPath = secondSnapshot.path;
+
+    ASSERT_TRUE(collector.enabled())
+            << "Files under the path `" << secondSnapshot.path << "` are inaccessible";
+    ASSERT_RESULT_OK(collector.collect());
+
+    actual = collector.deltaStats();
+
+    EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
+            << "Second snapshot doesn't match.\nExpected:\n"
+            << toString(expected) << "\nActual:\n"
+            << toString(actual);
+}
+
+TEST(UidProcStatsCollectorTest, TestErrorOnCorruptedProcessStatFile) {
+    std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
+            {1, {1}},
+    };
+
+    std::unordered_map<pid_t, std::string> perProcessStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 CORRUPTED DATA\n"},
+    };
+
+    std::unordered_map<pid_t, std::string> perProcessStatus = {
+            {1, pidStatusStr(1, 0)},
+    };
+
+    std::unordered_map<pid_t, std::string> perThreadStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
+    };
+
+    TemporaryDir procDir;
+    ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
+                                        perThreadStat));
+
+    UidProcStatsCollector collector(procDir.path);
+
+    ASSERT_TRUE(collector.enabled())
+            << "Files under the path `" << procDir.path << "` are inaccessible";
+    ASSERT_FALSE(collector.collect().ok()) << "No error returned for invalid process stat file";
+}
+
+TEST(UidProcStatsCollectorTest, TestErrorOnCorruptedProcessStatusFile) {
+    std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
+            {1, {1}},
+    };
+
+    std::unordered_map<pid_t, std::string> perProcessStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
+    };
+
+    std::unordered_map<pid_t, std::string> perProcessStatus = {
+            {1, "Pid:\t1\nTgid:\t1\nCORRUPTED DATA\n"},
+    };
+
+    std::unordered_map<pid_t, std::string> perThreadStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
+    };
+
+    TemporaryDir procDir;
+    ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
+                                        perThreadStat));
+
+    UidProcStatsCollector collector(procDir.path);
+
+    ASSERT_TRUE(collector.enabled())
+            << "Files under the path `" << procDir.path << "` are inaccessible";
+    ASSERT_FALSE(collector.collect().ok()) << "No error returned for invalid process status file";
+}
+
+TEST(UidProcStatsCollectorTest, TestErrorOnCorruptedThreadStatFile) {
+    std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
+            {1, {1, 234}},
+    };
+
+    std::unordered_map<pid_t, std::string> perProcessStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 678\n"},
+    };
+
+    std::unordered_map<pid_t, std::string> perProcessStatus = {
+            {1, pidStatusStr(1, 0)},
+    };
+
+    std::unordered_map<pid_t, std::string> perThreadStat = {
+            {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 678\n"},
+            {234, "234 (init) D 0 0 0 0 0 0 0 0 200 0 0 0 CORRUPTED DATA\n"},
+    };
+
+    TemporaryDir procDir;
+    ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
+                                        perThreadStat));
+
+    UidProcStatsCollector collector(procDir.path);
+
+    ASSERT_TRUE(collector.enabled())
+            << "Files under the path `" << procDir.path << "` are inaccessible";
+    ASSERT_FALSE(collector.collect().ok()) << "No error returned for invalid thread stat file";
+}
+
+TEST(UidProcStatsCollectorTest, TestHandlesSpaceInCommName) {
+    std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
+            {1, {1}},
+    };
+
+    std::unordered_map<pid_t, std::string> perProcessStat = {
+            {1, "1 (random process name with space) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
+    };
+
+    std::unordered_map<pid_t, std::string> perProcessStatus = {
+            {1, pidStatusStr(1, 0)},
+    };
+
+    std::unordered_map<pid_t, std::string> perThreadStat = {
+            {1, "1 (random process name with space) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 0\n"},
+    };
+
+    std::unordered_map<uid_t, UidProcStats> expected = {
+            {0,
+             UidProcStats{.totalMajorFaults = 200,
+                          .totalTasksCount = 1,
+                          .ioBlockedTasksCount = 0,
+                          .processStatsByPid = {
+                                  {1, {"random process name with space", 0, 200, 1, 0}}}}}};
+
+    TemporaryDir procDir;
+    ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
+                                        perThreadStat));
+
+    UidProcStatsCollector collector(procDir.path);
+
+    ASSERT_TRUE(collector.enabled())
+            << "Files under the path `" << procDir.path << "` are inaccessible";
+    ASSERT_RESULT_OK(collector.collect());
+
+    auto actual = collector.deltaStats();
+
+    EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
+            << "Proc pid contents doesn't match.\nExpected:\n"
+            << toString(expected) << "\nActual:\n"
+            << toString(actual);
+}
+
+TEST(UidProcStatsCollectorTest, TestUidProcStatsCollectorContentsFromDevice) {
+    UidProcStatsCollector collector;
+    ASSERT_TRUE(collector.enabled()) << "/proc/[pid]/.* files are inaccessible";
+    ASSERT_RESULT_OK(collector.collect());
+
+    const auto& processStats = collector.deltaStats();
+
+    // The below check should pass because there should be at least one process.
+    EXPECT_GT(processStats.size(), 0);
+}
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
diff --git a/cpp/watchdog/server/tests/UidProcStatsCollectorTestUtils.h b/cpp/watchdog/server/tests/UidProcStatsCollectorTestUtils.h
new file mode 100644
index 0000000..6e5a409
--- /dev/null
+++ b/cpp/watchdog/server/tests/UidProcStatsCollectorTestUtils.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CPP_WATCHDOG_SERVER_TESTS_UIDPROCSTATSCOLLECTORTESTUTILS_H_
+#define CPP_WATCHDOG_SERVER_TESTS_UIDPROCSTATSCOLLECTORTESTUTILS_H_
+
+#include "UidProcStatsCollector.h"
+
+#include <gmock/gmock.h>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+MATCHER_P(ProcessStatsEq, expected, "") {
+    const auto& actual = arg;
+    return ::testing::Value(actual.comm, ::testing::Eq(expected.comm)) &&
+            ::testing::Value(actual.startTime, ::testing::Eq(expected.startTime)) &&
+            ::testing::Value(actual.totalMajorFaults, ::testing::Eq(expected.totalMajorFaults)) &&
+            ::testing::Value(actual.totalTasksCount, ::testing::Eq(expected.totalTasksCount)) &&
+            ::testing::Value(actual.ioBlockedTasksCount,
+                             ::testing::Eq(expected.ioBlockedTasksCount));
+}
+
+MATCHER(ProcessStatsByPidEq, "") {
+    const auto& actual = std::get<0>(arg);
+    const auto& expected = std::get<1>(arg);
+    return actual.first == expected.first &&
+            ::testing::Value(actual.second, ProcessStatsEq(expected.second));
+}
+
+MATCHER_P(UidProcStatsEq, expected, "") {
+    const auto& actual = arg;
+    return ::testing::Value(actual.totalMajorFaults, ::testing::Eq(expected.totalMajorFaults)) &&
+            ::testing::Value(actual.totalTasksCount, ::testing::Eq(expected.totalTasksCount)) &&
+            ::testing::Value(actual.ioBlockedTasksCount,
+                             ::testing::Eq(expected.ioBlockedTasksCount)) &&
+            ::testing::Value(actual.processStatsByPid,
+                             ::testing::UnorderedPointwise(ProcessStatsByPidEq(),
+                                                           expected.processStatsByPid));
+}
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
+
+#endif  // CPP_WATCHDOG_SERVER_TESTS_UIDPROCSTATSCOLLECTORTESTUTILS_H_
diff --git a/cpp/watchdog/server/tests/UidStatsCollectorTest.cpp b/cpp/watchdog/server/tests/UidStatsCollectorTest.cpp
new file mode 100644
index 0000000..7d3bcb1
--- /dev/null
+++ b/cpp/watchdog/server/tests/UidStatsCollectorTest.cpp
@@ -0,0 +1,440 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "MockPackageInfoResolver.h"
+#include "MockUidIoStatsCollector.h"
+#include "MockUidProcStatsCollector.h"
+#include "PackageInfoTestUtils.h"
+#include "UidIoStatsCollector.h"
+#include "UidProcStatsCollector.h"
+#include "UidProcStatsCollectorTestUtils.h"
+#include "UidStatsCollector.h"
+
+#include <android-base/stringprintf.h>
+#include <gmock/gmock.h>
+#include <utils/RefBase.h>
+
+#include <string>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+using ::android::automotive::watchdog::internal::PackageInfo;
+using ::android::automotive::watchdog::internal::UidType;
+using ::android::base::Error;
+using ::android::base::Result;
+using ::android::base::StringAppendF;
+using ::android::base::StringPrintf;
+using ::testing::AllOf;
+using ::testing::Eq;
+using ::testing::ExplainMatchResult;
+using ::testing::Field;
+using ::testing::IsEmpty;
+using ::testing::Matcher;
+using ::testing::Return;
+using ::testing::UnorderedElementsAre;
+using ::testing::UnorderedElementsAreArray;
+
+namespace {
+
+std::string toString(const UidStats& uidStats) {
+    return StringPrintf("UidStats{%s, %s, %s}", uidStats.packageInfo.toString().c_str(),
+                        uidStats.ioStats.toString().c_str(), uidStats.procStats.toString().c_str());
+}
+
+std::string toString(const std::vector<UidStats>& uidStats) {
+    std::string buffer;
+    StringAppendF(&buffer, "{");
+    for (const auto& stats : uidStats) {
+        StringAppendF(&buffer, "%s\n", toString(stats).c_str());
+    }
+    StringAppendF(&buffer, "}");
+    return buffer;
+}
+
+MATCHER_P(UidStatsEq, expected, "") {
+    return ExplainMatchResult(AllOf(Field("packageInfo", &UidStats::packageInfo,
+                                          PackageInfoEq(expected.packageInfo)),
+                                    Field("ioStats", &UidStats::ioStats, Eq(expected.ioStats)),
+                                    Field("procStats", &UidStats::procStats,
+                                          UidProcStatsEq(expected.procStats))),
+                              arg, result_listener);
+}
+
+std::vector<Matcher<const UidStats&>> UidStatsMatchers(const std::vector<UidStats>& uidStats) {
+    std::vector<Matcher<const UidStats&>> matchers;
+    for (const auto& stats : uidStats) {
+        matchers.push_back(UidStatsEq(stats));
+    }
+    return matchers;
+}
+
+std::unordered_map<uid_t, PackageInfo> samplePackageInfoByUid() {
+    return {{1001234, constructPackageInfo("system.daemon", 1001234, UidType::NATIVE)},
+            {1005678, constructPackageInfo("kitchensink.app", 1005678, UidType::APPLICATION)}};
+}
+
+std::unordered_map<uid_t, UidIoStats> sampleUidIoStatsByUid() {
+    return {{1001234,
+             UidIoStats{/*fgRdBytes=*/3'000, /*bgRdBytes=*/0,
+                        /*fgWrBytes=*/500,
+                        /*bgWrBytes=*/0, /*fgFsync=*/20,
+                        /*bgFsync=*/0}},
+            {1005678,
+             UidIoStats{/*fgRdBytes=*/30, /*bgRdBytes=*/100,
+                        /*fgWrBytes=*/50, /*bgWrBytes=*/200,
+                        /*fgFsync=*/45, /*bgFsync=*/60}}};
+}
+
+std::unordered_map<uid_t, UidProcStats> sampleUidProcStatsByUid() {
+    return {{1001234,
+             UidProcStats{.totalMajorFaults = 220,
+                          .totalTasksCount = 2,
+                          .ioBlockedTasksCount = 1,
+                          .processStatsByPid = {{1, {"init", 0, 220, 2, 1}}}}},
+            {1005678,
+             UidProcStats{.totalMajorFaults = 600,
+                          .totalTasksCount = 2,
+                          .ioBlockedTasksCount = 2,
+                          .processStatsByPid = {{1000, {"system_server", 13'400, 600, 2, 2}}}}}};
+}
+
+std::vector<UidStats> sampleUidStats() {
+    return {{.packageInfo = constructPackageInfo("system.daemon", 1001234, UidType::NATIVE),
+             .ioStats = UidIoStats{/*fgRdBytes=*/3'000, /*bgRdBytes=*/0, /*fgWrBytes=*/500,
+                                   /*bgWrBytes=*/0, /*fgFsync=*/20, /*bgFsync=*/0},
+             .procStats = UidProcStats{.totalMajorFaults = 220,
+                                       .totalTasksCount = 2,
+                                       .ioBlockedTasksCount = 1,
+                                       .processStatsByPid = {{1, {"init", 0, 220, 2, 1}}}}},
+            {.packageInfo = constructPackageInfo("kitchensink.app", 1005678, UidType::APPLICATION),
+             .ioStats = UidIoStats{/*fgRdBytes=*/30, /*bgRdBytes=*/100, /*fgWrBytes=*/50,
+                                   /*bgWrBytes=*/200,
+                                   /*fgFsync=*/45, /*bgFsync=*/60},
+             .procStats = UidProcStats{.totalMajorFaults = 600,
+                                       .totalTasksCount = 2,
+                                       .ioBlockedTasksCount = 2,
+                                       .processStatsByPid = {
+                                               {1000, {"system_server", 13'400, 600, 2, 2}}}}}};
+}
+
+}  // namespace
+
+namespace internal {
+
+class UidStatsCollectorPeer : public RefBase {
+public:
+    explicit UidStatsCollectorPeer(sp<UidStatsCollector> collector) : mCollector(collector) {}
+
+    void setPackageInfoResolver(sp<IPackageInfoResolver> packageInfoResolver) {
+        mCollector->mPackageInfoResolver = packageInfoResolver;
+    }
+
+    void setUidIoStatsCollector(sp<UidIoStatsCollectorInterface> uidIoStatsCollector) {
+        mCollector->mUidIoStatsCollector = uidIoStatsCollector;
+    }
+
+    void setUidProcStatsCollector(sp<UidProcStatsCollectorInterface> uidProcStatsCollector) {
+        mCollector->mUidProcStatsCollector = uidProcStatsCollector;
+    }
+
+private:
+    sp<UidStatsCollector> mCollector;
+};
+
+}  // namespace internal
+
+class UidStatsCollectorTest : public ::testing::Test {
+protected:
+    virtual void SetUp() {
+        mUidStatsCollector = sp<UidStatsCollector>::make();
+        mUidStatsCollectorPeer = sp<internal::UidStatsCollectorPeer>::make(mUidStatsCollector);
+        mMockPackageInfoResolver = sp<MockPackageInfoResolver>::make();
+        mMockUidIoStatsCollector = sp<MockUidIoStatsCollector>::make();
+        mMockUidProcStatsCollector = sp<MockUidProcStatsCollector>::make();
+        mUidStatsCollectorPeer->setPackageInfoResolver(mMockPackageInfoResolver);
+        mUidStatsCollectorPeer->setUidIoStatsCollector(mMockUidIoStatsCollector);
+        mUidStatsCollectorPeer->setUidProcStatsCollector(mMockUidProcStatsCollector);
+    }
+
+    virtual void TearDown() {
+        mUidStatsCollector.clear();
+        mUidStatsCollectorPeer.clear();
+        mMockPackageInfoResolver.clear();
+        mMockUidIoStatsCollector.clear();
+        mMockUidProcStatsCollector.clear();
+    }
+
+    sp<UidStatsCollector> mUidStatsCollector;
+    sp<internal::UidStatsCollectorPeer> mUidStatsCollectorPeer;
+    sp<MockPackageInfoResolver> mMockPackageInfoResolver;
+    sp<MockUidIoStatsCollector> mMockUidIoStatsCollector;
+    sp<MockUidProcStatsCollector> mMockUidProcStatsCollector;
+};
+
+TEST_F(UidStatsCollectorTest, TestCollect) {
+    EXPECT_CALL(*mMockUidIoStatsCollector, collect()).WillOnce(Return(Result<void>()));
+    EXPECT_CALL(*mMockUidProcStatsCollector, collect()).WillOnce(Return(Result<void>()));
+
+    EXPECT_CALL(*mMockUidIoStatsCollector, latestStats())
+            .WillOnce(Return(std::unordered_map<uid_t, UidIoStats>()));
+    EXPECT_CALL(*mMockUidProcStatsCollector, latestStats())
+            .WillOnce(Return(std::unordered_map<uid_t, UidProcStats>()));
+
+    EXPECT_CALL(*mMockUidIoStatsCollector, deltaStats())
+            .WillOnce(Return(std::unordered_map<uid_t, UidIoStats>()));
+    EXPECT_CALL(*mMockUidProcStatsCollector, deltaStats())
+            .WillOnce(Return(std::unordered_map<uid_t, UidProcStats>()));
+
+    ASSERT_RESULT_OK(mUidStatsCollector->collect());
+}
+
+TEST_F(UidStatsCollectorTest, TestFailsCollectOnUidIoStatsCollectorError) {
+    Result<void> errorResult = Error() << "Failed to collect per-UID I/O stats";
+    EXPECT_CALL(*mMockUidIoStatsCollector, collect()).WillOnce(Return(errorResult));
+
+    ASSERT_FALSE(mUidStatsCollector->collect().ok())
+            << "Must fail to collect when per-UID I/O stats collector fails";
+}
+
+TEST_F(UidStatsCollectorTest, TestFailsCollectOnUidProcStatsCollectorError) {
+    Result<void> errorResult = Error() << "Failed to collect per-UID proc stats";
+    EXPECT_CALL(*mMockUidProcStatsCollector, collect()).WillOnce(Return(errorResult));
+
+    ASSERT_FALSE(mUidStatsCollector->collect().ok())
+            << "Must fail to collect when per-UID proc stats collector fails";
+}
+
+TEST_F(UidStatsCollectorTest, TestCollectLatestStats) {
+    const std::unordered_map<uid_t, PackageInfo> packageInfoByUid = samplePackageInfoByUid();
+    const std::unordered_map<uid_t, UidIoStats> uidIoStatsByUid = sampleUidIoStatsByUid();
+    const std::unordered_map<uid_t, UidProcStats> uidProcStatsByUid = sampleUidProcStatsByUid();
+
+    EXPECT_CALL(*mMockPackageInfoResolver,
+                getPackageInfosForUids(UnorderedElementsAre(1001234, 1005678)))
+            .WillOnce(Return(packageInfoByUid));
+    EXPECT_CALL(*mMockUidIoStatsCollector, latestStats()).WillOnce(Return(uidIoStatsByUid));
+    EXPECT_CALL(*mMockUidProcStatsCollector, latestStats()).WillOnce(Return(uidProcStatsByUid));
+
+    ASSERT_RESULT_OK(mUidStatsCollector->collect());
+
+    const std::vector<UidStats> expected = sampleUidStats();
+
+    auto actual = mUidStatsCollector->latestStats();
+
+    EXPECT_THAT(actual, UnorderedElementsAreArray(UidStatsMatchers(expected)))
+            << "Latest UID stats doesn't match.\nExpected: " << toString(expected)
+            << "\nActual: " << toString(actual);
+
+    actual = mUidStatsCollector->deltaStats();
+
+    EXPECT_THAT(actual, IsEmpty()) << "Delta UID stats isn't empty.\nActual: " << toString(actual);
+}
+
+TEST_F(UidStatsCollectorTest, TestCollectDeltaStats) {
+    const std::unordered_map<uid_t, PackageInfo> packageInfoByUid = samplePackageInfoByUid();
+    const std::unordered_map<uid_t, UidIoStats> uidIoStatsByUid = sampleUidIoStatsByUid();
+    const std::unordered_map<uid_t, UidProcStats> uidProcStatsByUid = sampleUidProcStatsByUid();
+
+    EXPECT_CALL(*mMockPackageInfoResolver,
+                getPackageInfosForUids(UnorderedElementsAre(1001234, 1005678)))
+            .WillOnce(Return(packageInfoByUid));
+    EXPECT_CALL(*mMockUidIoStatsCollector, deltaStats()).WillOnce(Return(uidIoStatsByUid));
+    EXPECT_CALL(*mMockUidProcStatsCollector, deltaStats()).WillOnce(Return(uidProcStatsByUid));
+
+    ASSERT_RESULT_OK(mUidStatsCollector->collect());
+
+    const std::vector<UidStats> expected = sampleUidStats();
+
+    auto actual = mUidStatsCollector->deltaStats();
+
+    EXPECT_THAT(actual, UnorderedElementsAreArray(UidStatsMatchers(expected)))
+            << "Delta UID stats doesn't match.\nExpected: " << toString(expected)
+            << "\nActual: " << toString(actual);
+
+    actual = mUidStatsCollector->latestStats();
+
+    EXPECT_THAT(actual, IsEmpty()) << "Latest UID stats isn't empty.\nActual: " << toString(actual);
+}
+
+TEST_F(UidStatsCollectorTest, TestCollectDeltaStatsWithMissingUidIoStats) {
+    const std::unordered_map<uid_t, PackageInfo> packageInfoByUid = samplePackageInfoByUid();
+    std::unordered_map<uid_t, UidIoStats> uidIoStatsByUid = sampleUidIoStatsByUid();
+    uidIoStatsByUid.erase(1001234);
+    const std::unordered_map<uid_t, UidProcStats> uidProcStatsByUid = sampleUidProcStatsByUid();
+
+    EXPECT_CALL(*mMockPackageInfoResolver,
+                getPackageInfosForUids(UnorderedElementsAre(1001234, 1005678)))
+            .WillOnce(Return(packageInfoByUid));
+    EXPECT_CALL(*mMockUidIoStatsCollector, deltaStats()).WillOnce(Return(uidIoStatsByUid));
+    EXPECT_CALL(*mMockUidProcStatsCollector, deltaStats()).WillOnce(Return(uidProcStatsByUid));
+
+    ASSERT_RESULT_OK(mUidStatsCollector->collect());
+
+    std::vector<UidStats> expected = sampleUidStats();
+    expected[0].ioStats = {};
+
+    auto actual = mUidStatsCollector->deltaStats();
+
+    EXPECT_THAT(actual, UnorderedElementsAreArray(UidStatsMatchers(expected)))
+            << "Delta UID stats doesn't match.\nExpected: " << toString(expected)
+            << "\nActual: " << toString(actual);
+
+    actual = mUidStatsCollector->latestStats();
+
+    EXPECT_THAT(actual, IsEmpty()) << "Latest UID stats isn't empty.\nActual: " << toString(actual);
+}
+
+TEST_F(UidStatsCollectorTest, TestCollectDeltaStatsWithMissingUidProcStats) {
+    const std::unordered_map<uid_t, PackageInfo> packageInfoByUid = samplePackageInfoByUid();
+    const std::unordered_map<uid_t, UidIoStats> uidIoStatsByUid = sampleUidIoStatsByUid();
+    std::unordered_map<uid_t, UidProcStats> uidProcStatsByUid = sampleUidProcStatsByUid();
+    uidProcStatsByUid.erase(1001234);
+
+    EXPECT_CALL(*mMockPackageInfoResolver,
+                getPackageInfosForUids(UnorderedElementsAre(1001234, 1005678)))
+            .WillOnce(Return(packageInfoByUid));
+    EXPECT_CALL(*mMockUidIoStatsCollector, deltaStats()).WillOnce(Return(uidIoStatsByUid));
+    EXPECT_CALL(*mMockUidProcStatsCollector, deltaStats()).WillOnce(Return(uidProcStatsByUid));
+
+    ASSERT_RESULT_OK(mUidStatsCollector->collect());
+
+    std::vector<UidStats> expected = sampleUidStats();
+    expected[0].procStats = {};
+
+    auto actual = mUidStatsCollector->deltaStats();
+
+    EXPECT_THAT(actual, UnorderedElementsAreArray(UidStatsMatchers(expected)))
+            << "Delta UID stats doesn't match.\nExpected: " << toString(expected)
+            << "\nActual: " << toString(actual);
+
+    actual = mUidStatsCollector->latestStats();
+
+    EXPECT_THAT(actual, IsEmpty()) << "Latest UID stats isn't empty.\nActual: " << toString(actual);
+}
+
+TEST_F(UidStatsCollectorTest, TestCollectDeltaStatsWithMissingPackageInfo) {
+    std::unordered_map<uid_t, PackageInfo> packageInfoByUid = samplePackageInfoByUid();
+    packageInfoByUid.erase(1001234);
+    const std::unordered_map<uid_t, UidIoStats> uidIoStatsByUid = sampleUidIoStatsByUid();
+    const std::unordered_map<uid_t, UidProcStats> uidProcStatsByUid = sampleUidProcStatsByUid();
+
+    EXPECT_CALL(*mMockPackageInfoResolver,
+                getPackageInfosForUids(UnorderedElementsAre(1001234, 1005678)))
+            .WillOnce(Return(packageInfoByUid));
+    EXPECT_CALL(*mMockUidIoStatsCollector, deltaStats()).WillOnce(Return(uidIoStatsByUid));
+    EXPECT_CALL(*mMockUidProcStatsCollector, deltaStats()).WillOnce(Return(uidProcStatsByUid));
+
+    ASSERT_RESULT_OK(mUidStatsCollector->collect());
+
+    std::vector<UidStats> expected = sampleUidStats();
+    expected[0].packageInfo = constructPackageInfo("", 1001234);
+
+    auto actual = mUidStatsCollector->deltaStats();
+
+    EXPECT_THAT(actual, UnorderedElementsAreArray(UidStatsMatchers(expected)))
+            << "Delta UID stats doesn't match.\nExpected: " << toString(expected)
+            << "\nActual: " << toString(actual);
+
+    actual = mUidStatsCollector->latestStats();
+
+    EXPECT_THAT(actual, IsEmpty()) << "Latest UID stats isn't empty.\nActual: " << toString(actual);
+}
+
+TEST_F(UidStatsCollectorTest, TestUidStatsHasPackageInfo) {
+    std::unordered_map<uid_t, PackageInfo> packageInfoByUid = samplePackageInfoByUid();
+    packageInfoByUid.erase(1001234);
+    const std::unordered_map<uid_t, UidIoStats> uidIoStatsByUid = sampleUidIoStatsByUid();
+    const std::unordered_map<uid_t, UidProcStats> uidProcStatsByUid = sampleUidProcStatsByUid();
+
+    EXPECT_CALL(*mMockPackageInfoResolver,
+                getPackageInfosForUids(UnorderedElementsAre(1001234, 1005678)))
+            .WillOnce(Return(packageInfoByUid));
+    EXPECT_CALL(*mMockUidIoStatsCollector, deltaStats()).WillOnce(Return(uidIoStatsByUid));
+    EXPECT_CALL(*mMockUidProcStatsCollector, deltaStats()).WillOnce(Return(uidProcStatsByUid));
+
+    ASSERT_RESULT_OK(mUidStatsCollector->collect());
+
+    const auto actual = mUidStatsCollector->deltaStats();
+
+    EXPECT_EQ(actual.size(), 2);
+    for (const auto stats : actual) {
+        if (stats.packageInfo.packageIdentifier.uid == 1001234) {
+            EXPECT_FALSE(stats.hasPackageInfo())
+                    << "Stats without package info should return false";
+        } else if (stats.packageInfo.packageIdentifier.uid == 1005678) {
+            EXPECT_TRUE(stats.hasPackageInfo()) << "Stats without package info should return true";
+        } else {
+            FAIL() << "Unexpected uid " << stats.packageInfo.packageIdentifier.uid;
+        }
+    }
+}
+
+TEST_F(UidStatsCollectorTest, TestUidStatsGenericPackageName) {
+    std::unordered_map<uid_t, PackageInfo> packageInfoByUid = samplePackageInfoByUid();
+    packageInfoByUid.erase(1001234);
+    const std::unordered_map<uid_t, UidIoStats> uidIoStatsByUid = sampleUidIoStatsByUid();
+    const std::unordered_map<uid_t, UidProcStats> uidProcStatsByUid = sampleUidProcStatsByUid();
+
+    EXPECT_CALL(*mMockPackageInfoResolver,
+                getPackageInfosForUids(UnorderedElementsAre(1001234, 1005678)))
+            .WillOnce(Return(packageInfoByUid));
+    EXPECT_CALL(*mMockUidIoStatsCollector, deltaStats()).WillOnce(Return(uidIoStatsByUid));
+    EXPECT_CALL(*mMockUidProcStatsCollector, deltaStats()).WillOnce(Return(uidProcStatsByUid));
+
+    ASSERT_RESULT_OK(mUidStatsCollector->collect());
+
+    const auto actual = mUidStatsCollector->deltaStats();
+
+    EXPECT_EQ(actual.size(), 2);
+    for (const auto stats : actual) {
+        if (stats.packageInfo.packageIdentifier.uid == 1001234) {
+            EXPECT_EQ(stats.genericPackageName(), "1001234")
+                    << "Stats without package info should return UID as package name";
+        } else if (stats.packageInfo.packageIdentifier.uid == 1005678) {
+            EXPECT_EQ(stats.genericPackageName(), "kitchensink.app")
+                    << "Stats with package info should return corresponding package name";
+        } else {
+            FAIL() << "Unexpected uid " << stats.packageInfo.packageIdentifier.uid;
+        }
+    }
+}
+
+TEST_F(UidStatsCollectorTest, TestUidStatsUid) {
+    std::unordered_map<uid_t, PackageInfo> packageInfoByUid = samplePackageInfoByUid();
+    packageInfoByUid.erase(1001234);
+    const std::unordered_map<uid_t, UidIoStats> uidIoStatsByUid = sampleUidIoStatsByUid();
+    const std::unordered_map<uid_t, UidProcStats> uidProcStatsByUid = sampleUidProcStatsByUid();
+
+    EXPECT_CALL(*mMockPackageInfoResolver,
+                getPackageInfosForUids(UnorderedElementsAre(1001234, 1005678)))
+            .WillOnce(Return(packageInfoByUid));
+    EXPECT_CALL(*mMockUidIoStatsCollector, deltaStats()).WillOnce(Return(uidIoStatsByUid));
+    EXPECT_CALL(*mMockUidProcStatsCollector, deltaStats()).WillOnce(Return(uidProcStatsByUid));
+
+    ASSERT_RESULT_OK(mUidStatsCollector->collect());
+
+    const auto actual = mUidStatsCollector->deltaStats();
+
+    for (const auto stats : actual) {
+        EXPECT_EQ(stats.uid(), stats.packageInfo.packageIdentifier.uid);
+    }
+}
+
+}  // namespace watchdog
+}  // namespace automotive
+}  // namespace android
diff --git a/cpp/watchdog/server/tests/WatchdogPerfServiceTest.cpp b/cpp/watchdog/server/tests/WatchdogPerfServiceTest.cpp
index 2bc9803..864d6d1 100644
--- a/cpp/watchdog/server/tests/WatchdogPerfServiceTest.cpp
+++ b/cpp/watchdog/server/tests/WatchdogPerfServiceTest.cpp
@@ -17,13 +17,10 @@
 #include "LooperStub.h"
 #include "MockDataProcessor.h"
 #include "MockProcDiskStats.h"
-#include "MockProcPidStat.h"
 #include "MockProcStat.h"
-#include "MockUidIoStats.h"
-#include "ProcPidDir.h"
-#include "ProcPidStat.h"
+#include "MockUidStatsCollector.h"
 #include "ProcStat.h"
-#include "UidIoStats.h"
+#include "UidStatsCollector.h"
 #include "WatchdogPerfService.h"
 
 #include <WatchdogProperties.sysprop.h>
@@ -42,12 +39,10 @@
 using ::android::sp;
 using ::android::String16;
 using ::android::wp;
-using ::android::automotive::watchdog::internal::PowerCycle;
 using ::android::automotive::watchdog::testing::LooperStub;
 using ::android::base::Error;
 using ::android::base::Result;
 using ::testing::_;
-using ::testing::DefaultValue;
 using ::testing::InSequence;
 using ::testing::Mock;
 using ::testing::NiceMock;
@@ -71,19 +66,17 @@
 
     void injectFakes() {
         looperStub = sp<LooperStub>::make();
-        mockUidIoStats = sp<NiceMock<MockUidIoStats>>::make();
+        mockUidStatsCollector = sp<MockUidStatsCollector>::make();
         mockProcDiskStats = sp<NiceMock<MockProcDiskStats>>::make();
         mockProcStat = sp<NiceMock<MockProcStat>>::make();
-        mockProcPidStat = sp<NiceMock<MockProcPidStat>>::make();
         mockDataProcessor = sp<StrictMock<MockDataProcessor>>::make();
 
         {
             Mutex::Autolock lock(service->mMutex);
             service->mHandlerLooper = looperStub;
-            service->mUidIoStats = mockUidIoStats;
+            service->mUidStatsCollector = mockUidStatsCollector;
             service->mProcDiskStats = mockProcDiskStats;
             service->mProcStat = mockProcStat;
-            service->mProcPidStat = mockProcPidStat;
         }
         EXPECT_CALL(*mockDataProcessor, init()).Times(1);
         ASSERT_RESULT_OK(service->registerDataProcessor(mockDataProcessor));
@@ -114,19 +107,17 @@
     }
 
     void verifyAndClearExpectations() {
-        Mock::VerifyAndClearExpectations(mockUidIoStats.get());
+        Mock::VerifyAndClearExpectations(mockUidStatsCollector.get());
         Mock::VerifyAndClearExpectations(mockProcStat.get());
-        Mock::VerifyAndClearExpectations(mockProcPidStat.get());
         Mock::VerifyAndClearExpectations(mockDataProcessor.get());
     }
 
     sp<WatchdogPerfService> service;
     // Below fields are populated only on injectFakes.
     sp<LooperStub> looperStub;
-    sp<MockUidIoStats> mockUidIoStats;
+    sp<MockUidStatsCollector> mockUidStatsCollector;
     sp<MockProcDiskStats> mockProcDiskStats;
     sp<MockProcStat> mockProcStat;
-    sp<MockProcPidStat> mockProcPidStat;
     sp<MockDataProcessor> mockDataProcessor;
 };
 
@@ -139,13 +130,13 @@
 
     ASSERT_RESULT_OK(servicePeer->start());
 
-    EXPECT_CALL(*servicePeer->mockUidIoStats, collect()).Times(2);
+    EXPECT_CALL(*servicePeer->mockUidStatsCollector, collect()).Times(2);
     EXPECT_CALL(*servicePeer->mockProcStat, collect()).Times(2);
-    EXPECT_CALL(*servicePeer->mockProcPidStat, collect()).Times(2);
     EXPECT_CALL(*servicePeer->mockDataProcessor,
-                onBoottimeCollection(_, wp<UidIoStats>(servicePeer->mockUidIoStats),
-                                     wp<ProcStat>(servicePeer->mockProcStat),
-                                     wp<ProcPidStat>(servicePeer->mockProcPidStat)))
+                onBoottimeCollection(_,
+                                     wp<UidStatsCollectorInterface>(
+                                             servicePeer->mockUidStatsCollector),
+                                     wp<ProcStat>(servicePeer->mockProcStat)))
             .Times(2);
 
     // Make sure the collection event changes from EventType::INIT to
@@ -206,17 +197,15 @@
 
     ASSERT_RESULT_OK(servicePeer.start());
 
-    wp<UidIoStats> uidIoStats(servicePeer.mockUidIoStats);
+    wp<UidStatsCollectorInterface> uidStatsCollector(servicePeer.mockUidStatsCollector);
     wp<IProcDiskStatsInterface> procDiskStats(servicePeer.mockProcDiskStats);
     wp<ProcStat> procStat(servicePeer.mockProcStat);
-    wp<ProcPidStat> procPidStat(servicePeer.mockProcPidStat);
 
     // #1 Boot-time collection
-    EXPECT_CALL(*servicePeer.mockUidIoStats, collect()).Times(1);
+    EXPECT_CALL(*servicePeer.mockUidStatsCollector, collect()).Times(1);
     EXPECT_CALL(*servicePeer.mockProcStat, collect()).Times(1);
-    EXPECT_CALL(*servicePeer.mockProcPidStat, collect()).Times(1);
     EXPECT_CALL(*servicePeer.mockDataProcessor,
-                onBoottimeCollection(_, uidIoStats, procStat, procPidStat))
+                onBoottimeCollection(_, uidStatsCollector, procStat))
             .Times(1);
 
     ASSERT_RESULT_OK(servicePeer.looperStub->pollCache());
@@ -228,11 +217,10 @@
     servicePeer.verifyAndClearExpectations();
 
     // #2 Boot-time collection
-    EXPECT_CALL(*servicePeer.mockUidIoStats, collect()).Times(1);
+    EXPECT_CALL(*servicePeer.mockUidStatsCollector, collect()).Times(1);
     EXPECT_CALL(*servicePeer.mockProcStat, collect()).Times(1);
-    EXPECT_CALL(*servicePeer.mockProcPidStat, collect()).Times(1);
     EXPECT_CALL(*servicePeer.mockDataProcessor,
-                onBoottimeCollection(_, uidIoStats, procStat, procPidStat))
+                onBoottimeCollection(_, uidStatsCollector, procStat))
             .Times(1);
 
     ASSERT_RESULT_OK(servicePeer.looperStub->pollCache());
@@ -245,11 +233,10 @@
     servicePeer.verifyAndClearExpectations();
 
     // #3 Last boot-time collection
-    EXPECT_CALL(*servicePeer.mockUidIoStats, collect()).Times(1);
+    EXPECT_CALL(*servicePeer.mockUidStatsCollector, collect()).Times(1);
     EXPECT_CALL(*servicePeer.mockProcStat, collect()).Times(1);
-    EXPECT_CALL(*servicePeer.mockProcPidStat, collect()).Times(1);
     EXPECT_CALL(*servicePeer.mockDataProcessor,
-                onBoottimeCollection(_, uidIoStats, procStat, procPidStat))
+                onBoottimeCollection(_, uidStatsCollector, procStat))
             .Times(1);
 
     ASSERT_RESULT_OK(service->onBootFinished());
@@ -286,12 +273,10 @@
     servicePeer.verifyAndClearExpectations();
 
     // #6 Periodic collection
-    EXPECT_CALL(*servicePeer.mockUidIoStats, collect()).Times(1);
+    EXPECT_CALL(*servicePeer.mockUidStatsCollector, collect()).Times(1);
     EXPECT_CALL(*servicePeer.mockProcStat, collect()).Times(1);
-    EXPECT_CALL(*servicePeer.mockProcPidStat, collect()).Times(1);
     EXPECT_CALL(*servicePeer.mockDataProcessor,
-                onPeriodicCollection(_, SystemState::NORMAL_MODE, uidIoStats, procStat,
-                                     procPidStat))
+                onPeriodicCollection(_, SystemState::NORMAL_MODE, uidStatsCollector, procStat))
             .Times(1);
 
     ASSERT_RESULT_OK(servicePeer.looperStub->pollCache());
@@ -312,12 +297,10 @@
 
     ASSERT_RESULT_OK(service->onCustomCollection(-1, args));
 
-    EXPECT_CALL(*servicePeer.mockUidIoStats, collect()).Times(1);
+    EXPECT_CALL(*servicePeer.mockUidStatsCollector, collect()).Times(1);
     EXPECT_CALL(*servicePeer.mockProcStat, collect()).Times(1);
-    EXPECT_CALL(*servicePeer.mockProcPidStat, collect()).Times(1);
     EXPECT_CALL(*servicePeer.mockDataProcessor,
-                onCustomCollection(_, SystemState::NORMAL_MODE, _, uidIoStats, procStat,
-                                   procPidStat))
+                onCustomCollection(_, SystemState::NORMAL_MODE, _, uidStatsCollector, procStat))
             .Times(1);
 
     ASSERT_RESULT_OK(servicePeer.looperStub->pollCache());
@@ -329,12 +312,10 @@
     servicePeer.verifyAndClearExpectations();
 
     // #8 Custom collection
-    EXPECT_CALL(*servicePeer.mockUidIoStats, collect()).Times(1);
+    EXPECT_CALL(*servicePeer.mockUidStatsCollector, collect()).Times(1);
     EXPECT_CALL(*servicePeer.mockProcStat, collect()).Times(1);
-    EXPECT_CALL(*servicePeer.mockProcPidStat, collect()).Times(1);
     EXPECT_CALL(*servicePeer.mockDataProcessor,
-                onCustomCollection(_, SystemState::NORMAL_MODE, _, uidIoStats, procStat,
-                                   procPidStat))
+                onCustomCollection(_, SystemState::NORMAL_MODE, _, uidStatsCollector, procStat))
             .Times(1);
 
     ASSERT_RESULT_OK(servicePeer.looperStub->pollCache());
@@ -362,12 +343,10 @@
             << "Invalid collection event";
 
     // #10 Switch to periodic collection
-    EXPECT_CALL(*servicePeer.mockUidIoStats, collect()).Times(1);
+    EXPECT_CALL(*servicePeer.mockUidStatsCollector, collect()).Times(1);
     EXPECT_CALL(*servicePeer.mockProcStat, collect()).Times(1);
-    EXPECT_CALL(*servicePeer.mockProcPidStat, collect()).Times(1);
     EXPECT_CALL(*servicePeer.mockDataProcessor,
-                onPeriodicCollection(_, SystemState::NORMAL_MODE, uidIoStats, procStat,
-                                     procPidStat))
+                onPeriodicCollection(_, SystemState::NORMAL_MODE, uidStatsCollector, procStat))
             .Times(1);
 
     ASSERT_RESULT_OK(servicePeer.looperStub->pollCache());
@@ -398,9 +377,8 @@
 
     ASSERT_RESULT_OK(servicePeer.start());
 
-    ON_CALL(*servicePeer.mockUidIoStats, enabled()).WillByDefault(Return(false));
+    ON_CALL(*servicePeer.mockUidStatsCollector, enabled()).WillByDefault(Return(false));
     ON_CALL(*servicePeer.mockProcStat, enabled()).WillByDefault(Return(false));
-    ON_CALL(*servicePeer.mockProcPidStat, enabled()).WillByDefault(Return(false));
 
     // Collection should terminate and call data processor's terminate method on error.
     EXPECT_CALL(*servicePeer.mockDataProcessor, terminate()).Times(1);
@@ -422,7 +400,7 @@
 
     // Inject data collector error.
     Result<void> errorRes = Error() << "Failed to collect data";
-    EXPECT_CALL(*servicePeer.mockUidIoStats, collect()).WillOnce(Return(errorRes));
+    EXPECT_CALL(*servicePeer.mockUidStatsCollector, collect()).WillOnce(Return(errorRes));
 
     // Collection should terminate and call data processor's terminate method on error.
     EXPECT_CALL(*servicePeer.mockDataProcessor, terminate()).Times(1);
@@ -447,9 +425,10 @@
     // Inject data processor error.
     Result<void> errorRes = Error() << "Failed to process data";
     EXPECT_CALL(*servicePeer.mockDataProcessor,
-                onBoottimeCollection(_, wp<UidIoStats>(servicePeer.mockUidIoStats),
-                                     wp<ProcStat>(servicePeer.mockProcStat),
-                                     wp<ProcPidStat>(servicePeer.mockProcPidStat)))
+                onBoottimeCollection(_,
+                                     wp<UidStatsCollectorInterface>(
+                                             servicePeer.mockUidStatsCollector),
+                                     wp<ProcStat>(servicePeer.mockProcStat)))
             .WillOnce(Return(errorRes));
 
     // Collection should terminate and call data processor's terminate method on error.
@@ -484,16 +463,15 @@
     int maxIterations = static_cast<int>(kTestCustomCollectionDuration.count() /
                                          kTestCustomCollectionInterval.count());
     for (int i = 0; i <= maxIterations; ++i) {
-        EXPECT_CALL(*servicePeer.mockUidIoStats, collect()).Times(1);
+        EXPECT_CALL(*servicePeer.mockUidStatsCollector, collect()).Times(1);
         EXPECT_CALL(*servicePeer.mockProcStat, collect()).Times(1);
-        EXPECT_CALL(*servicePeer.mockProcPidStat, collect()).Times(1);
         EXPECT_CALL(*servicePeer.mockDataProcessor,
                     onCustomCollection(_, SystemState::NORMAL_MODE,
                                        UnorderedElementsAreArray(
                                                {"android.car.cts", "system_server"}),
-                                       wp<UidIoStats>(servicePeer.mockUidIoStats),
-                                       wp<ProcStat>(servicePeer.mockProcStat),
-                                       wp<ProcPidStat>(servicePeer.mockProcPidStat)))
+                                       wp<UidStatsCollectorInterface>(
+                                               servicePeer.mockUidStatsCollector),
+                                       wp<ProcStat>(servicePeer.mockProcStat)))
                 .Times(1);
 
         ASSERT_RESULT_OK(servicePeer.looperStub->pollCache());
@@ -529,10 +507,9 @@
 
     ASSERT_NO_FATAL_FAILURE(startPeriodicCollection(&servicePeer));
 
-    wp<UidIoStats> uidIoStats(servicePeer.mockUidIoStats);
+    wp<UidStatsCollectorInterface> uidStatsCollector(servicePeer.mockUidStatsCollector);
     wp<IProcDiskStatsInterface> procDiskStats(servicePeer.mockProcDiskStats);
     wp<ProcStat> procStat(servicePeer.mockProcStat);
-    wp<ProcPidStat> procPidStat(servicePeer.mockProcPidStat);
 
     // Periodic monitor issuing an alert to start new collection.
     EXPECT_CALL(*servicePeer.mockProcDiskStats, collect()).Times(1);
@@ -549,12 +526,10 @@
             << " seconds interval";
     servicePeer.verifyAndClearExpectations();
 
-    EXPECT_CALL(*servicePeer.mockUidIoStats, collect()).Times(1);
+    EXPECT_CALL(*servicePeer.mockUidStatsCollector, collect()).Times(1);
     EXPECT_CALL(*servicePeer.mockProcStat, collect()).Times(1);
-    EXPECT_CALL(*servicePeer.mockProcPidStat, collect()).Times(1);
     EXPECT_CALL(*servicePeer.mockDataProcessor,
-                onPeriodicCollection(_, SystemState::NORMAL_MODE, uidIoStats, procStat,
-                                     procPidStat))
+                onPeriodicCollection(_, SystemState::NORMAL_MODE, uidStatsCollector, procStat))
             .Times(1);
 
     ASSERT_RESULT_OK(servicePeer.looperStub->pollCache());
@@ -575,7 +550,7 @@
     ASSERT_NO_FATAL_FAILURE(skipPeriodicMonitorEvents(&servicePeer));
 
     EXPECT_CALL(*servicePeer.mockDataProcessor,
-                onPeriodicCollection(_, SystemState::NORMAL_MODE, _, _, _))
+                onPeriodicCollection(_, SystemState::NORMAL_MODE, _, _))
             .Times(1);
 
     ASSERT_RESULT_OK(servicePeer.looperStub->pollCache());
@@ -587,7 +562,7 @@
     service->setSystemState(SystemState::GARAGE_MODE);
 
     EXPECT_CALL(*servicePeer.mockDataProcessor,
-                onPeriodicCollection(_, SystemState::GARAGE_MODE, _, _, _))
+                onPeriodicCollection(_, SystemState::GARAGE_MODE, _, _))
             .Times(1);
 
     ASSERT_RESULT_OK(servicePeer.looperStub->pollCache());
@@ -599,7 +574,7 @@
     service->setSystemState(SystemState::NORMAL_MODE);
 
     EXPECT_CALL(*servicePeer.mockDataProcessor,
-                onPeriodicCollection(_, SystemState::NORMAL_MODE, _, _, _))
+                onPeriodicCollection(_, SystemState::NORMAL_MODE, _, _))
             .Times(1);
 
     ASSERT_RESULT_OK(servicePeer.looperStub->pollCache());
diff --git a/cpp/watchdog/server/tests/WatchdogServiceHelperTest.cpp b/cpp/watchdog/server/tests/WatchdogServiceHelperTest.cpp
index 99b65df..de05300 100644
--- a/cpp/watchdog/server/tests/WatchdogServiceHelperTest.cpp
+++ b/cpp/watchdog/server/tests/WatchdogServiceHelperTest.cpp
@@ -16,6 +16,7 @@
 
 #include "MockCarWatchdogServiceForSystem.h"
 #include "MockWatchdogProcessService.h"
+#include "PackageInfoTestUtils.h"
 #include "WatchdogServiceHelper.h"
 
 #include <binder/IBinder.h>
@@ -69,22 +70,6 @@
 
 }  // namespace internal
 
-namespace {
-
-PackageInfo constructPackageInfo(const char* packageName, int32_t uid, UidType uidType,
-                                 ComponentType componentType,
-                                 ApplicationCategoryType appCategoryType) {
-    PackageInfo packageInfo;
-    packageInfo.packageIdentifier.name = packageName;
-    packageInfo.packageIdentifier.uid = uid;
-    packageInfo.uidType = uidType;
-    packageInfo.componentType = componentType;
-    packageInfo.appCategoryType = appCategoryType;
-    return packageInfo;
-}
-
-}  // namespace
-
 class WatchdogServiceHelperTest : public ::testing::Test {
 protected:
     virtual void SetUp() {
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml b/packages/CarDeveloperOptions/res/values/strings.xml
similarity index 79%
rename from car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
rename to packages/CarDeveloperOptions/res/values/strings.xml
index 4fab05c..5d24671 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
+++ b/packages/CarDeveloperOptions/res/values/strings.xml
@@ -15,7 +15,6 @@
   ~ limitations under the License.
   -->
 <resources>
-    <string name="config_rearViewCameraActivity" translatable="false">
-        com.google.android.car.evs/com.google.android.car.evs.CarEvsCameraPreviewActivity
-    </string>
-</resources>
+    <string name="car_pref_category_title">Car</string>
+    <string name="car_ui_plugin_enabled_pref_title">Enable Car UI library plugin</string>
+</resources>
\ No newline at end of file
diff --git a/packages/CarDeveloperOptions/src/com/android/car/developeroptions/CarDevelopmentCarUiLibController.java b/packages/CarDeveloperOptions/src/com/android/car/developeroptions/CarDevelopmentCarUiLibController.java
new file mode 100644
index 0000000..619c565
--- /dev/null
+++ b/packages/CarDeveloperOptions/src/com/android/car/developeroptions/CarDevelopmentCarUiLibController.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.developeroptions;
+
+import android.content.Context;
+import android.os.SystemProperties;
+
+import androidx.preference.Preference;
+import androidx.preference.SwitchPreference;
+
+/**
+ * Preference controller for Car UI library plugin enablement.
+ */
+public class CarDevelopmentCarUiLibController extends CarDevelopmentPreferenceController {
+    private static final String CAR_UI_PLUGIN_ENABLED_KEY = "car_ui_plugin_enabled";
+    static final String CAR_UI_PLUGIN_ENABLED_PROPERTY =
+            "persist.sys.automotive.car.ui.plugin.enabled";
+
+
+    public CarDevelopmentCarUiLibController(Context context) {
+        super(context);
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return CAR_UI_PLUGIN_ENABLED_KEY;
+    }
+
+    @Override
+    public String getPreferenceTitle() {
+        return mContext.getString(R.string.car_ui_plugin_enabled_pref_title);
+    }
+
+    @Override
+    String getPreferenceSummary() {
+        return null;
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        SystemProperties.set(CAR_UI_PLUGIN_ENABLED_PROPERTY, String.valueOf(newValue));
+        return true;
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        final boolean pluginEnabled = SystemProperties.getBoolean(
+                CAR_UI_PLUGIN_ENABLED_PROPERTY, false /* default */);
+        ((SwitchPreference) mPreference).setChecked(pluginEnabled);
+    }
+}
diff --git a/packages/CarDeveloperOptions/src/com/android/car/developeroptions/CarDevelopmentPreferenceController.java b/packages/CarDeveloperOptions/src/com/android/car/developeroptions/CarDevelopmentPreferenceController.java
new file mode 100644
index 0000000..68f6528
--- /dev/null
+++ b/packages/CarDeveloperOptions/src/com/android/car/developeroptions/CarDevelopmentPreferenceController.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.developeroptions;
+
+import android.annotation.Nullable;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settingslib.development.DeveloperOptionsPreferenceController;
+
+/**
+ * Parent class for Car feature preferences.*
+ */
+public abstract class CarDevelopmentPreferenceController extends
+        DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener,
+        PreferenceControllerMixin {
+
+    abstract String getPreferenceTitle();
+    @Nullable
+    abstract String getPreferenceSummary();
+
+    CarDevelopmentPreferenceController(Context context) {
+        super(context);
+    }
+}
diff --git a/packages/CarDeveloperOptions/src/com/android/car/developeroptions/CarDevelopmentSettingsDashboardFragment.java b/packages/CarDeveloperOptions/src/com/android/car/developeroptions/CarDevelopmentSettingsDashboardFragment.java
index 72e591f..d74b8ea 100644
--- a/packages/CarDeveloperOptions/src/com/android/car/developeroptions/CarDevelopmentSettingsDashboardFragment.java
+++ b/packages/CarDeveloperOptions/src/com/android/car/developeroptions/CarDevelopmentSettingsDashboardFragment.java
@@ -24,6 +24,11 @@
 import android.content.pm.PackageManager;
 import android.os.Bundle;
 
+import androidx.annotation.XmlRes;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.SwitchPreference;
+
 import com.android.car.ui.toolbar.MenuItem;
 import com.android.car.ui.toolbar.Toolbar;
 import com.android.car.ui.toolbar.ToolbarController;
@@ -40,27 +45,68 @@
  * {@link PREFERENCES_TO_REMOVE} constant.
  */
 public class CarDevelopmentSettingsDashboardFragment extends DevelopmentSettingsDashboardFragment {
+    static final String PREF_KEY_CAR_CATEGORY = "car_development_category";
+    static final String PREF_KEY_DEBUG_MISC_CATEGORY = "debug_misc_category";
 
-    private ToolbarController mToolbar;
+    private final List<CarDevelopmentPreferenceController> mCarFeatureControllers =
+            new ArrayList<>();
 
     @Override
     public void onActivityCreated(Bundle icicle) {
         super.onActivityCreated(icicle);
-        mToolbar = getToolbar();
-        if (mToolbar != null) {
+        ToolbarController toolbar = getToolbar();
+        if (toolbar != null) {
             List<MenuItem> items = getToolbarMenuItems();
-            mToolbar.setTitle(getPreferenceScreen().getTitle());
-            mToolbar.setMenuItems(items);
-            mToolbar.setNavButtonMode(Toolbar.NavButtonMode.BACK);
-            mToolbar.setState(Toolbar.State.SUBPAGE);
+            toolbar.setTitle(getPreferenceScreen().getTitle());
+            toolbar.setMenuItems(items);
+            toolbar.setNavButtonMode(Toolbar.NavButtonMode.BACK);
+            toolbar.setState(Toolbar.State.SUBPAGE);
         }
     }
 
     @Override
+    public void addPreferencesFromResource(@XmlRes int preferencesResId) {
+        super.addPreferencesFromResource(preferencesResId);
+
+        int miscPrefCategory = getPreferenceScreen().findPreference(
+                PREF_KEY_DEBUG_MISC_CATEGORY).getOrder();
+        PreferenceCategory carCategory = new PreferenceCategory(getContext());
+        carCategory.setOrder(miscPrefCategory + 1);
+        carCategory.setKey(PREF_KEY_CAR_CATEGORY);
+        carCategory.setTitle(getContext().getString(R.string.car_pref_category_title));
+        getPreferenceScreen().addPreference(carCategory);
+
+        for (CarDevelopmentPreferenceController controller : mCarFeatureControllers) {
+            addCarPreference(controller);
+        }
+    }
+
+    void addCarPreference(CarDevelopmentPreferenceController controller) {
+        final Preference dynamicPreference;
+        if (controller instanceof CarDevelopmentCarUiLibController) {
+            dynamicPreference = new SwitchPreference(getContext());
+        } else {
+            throw new UnsupportedOperationException(
+                    "Unexpected controller type " + controller.getClass().getSimpleName());
+        }
+
+        dynamicPreference.setKey(controller.getPreferenceKey());
+        dynamicPreference.setTitle(controller.getPreferenceTitle());
+        final String summary = controller.getPreferenceSummary();
+        if (summary != null) {
+            dynamicPreference.setSummary(summary);
+        }
+
+        ((PreferenceCategory) getPreferenceScreen().findPreference(PREF_KEY_CAR_CATEGORY))
+                .addPreference(dynamicPreference);
+    }
+
+    @Override
     protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
         List<AbstractPreferenceController> controllers = super.createPreferenceControllers(context);
         removeControllers(controllers);
         addHiddenControllers(context, controllers);
+        addCarControllers(context, controllers);
         return controllers;
     }
 
@@ -102,6 +148,12 @@
         }
     }
 
+    private void addCarControllers(Context context,
+            List<AbstractPreferenceController> controllers) {
+        mCarFeatureControllers.add(new CarDevelopmentCarUiLibController(context));
+        controllers.addAll(mCarFeatureControllers);
+    }
+
     private boolean isDeveloperOptionsModuleEnabled() {
         PackageManager pm = getContext().getPackageManager();
         ComponentName component = getActivity().getComponentName();
diff --git a/packages/ScriptExecutor/Android.bp b/packages/ScriptExecutor/Android.bp
index af0d132..033728b 100644
--- a/packages/ScriptExecutor/Android.bp
+++ b/packages/ScriptExecutor/Android.bp
@@ -80,6 +80,7 @@
 
     srcs: [
         ":iscriptexecutor_aidl",
+        ":iscriptexecutorconstants_aidl",
         "src/**/*.java"
     ],
 
@@ -98,8 +99,8 @@
     aidl: {
         include_dirs: [
             // TODO(b/198195711): Remove once we compile against SDK.
-	    "frameworks/native/aidl/binder", // For PersistableBundle.aidl
-	],
+            "frameworks/native/aidl/binder", // For PersistableBundle.aidl
+        ],
     },
 
     jni_libs: [
@@ -119,8 +120,8 @@
     aidl: {
         include_dirs: [
             // TODO(b/198195711): Remove once we compile against SDK.
-	    "frameworks/native/aidl/binder", // For PersistableBundle.aidl
-	],
+            "frameworks/native/aidl/binder", // For PersistableBundle.aidl
+        ],
     },
 }
 
diff --git a/packages/ScriptExecutor/src/BundleWrapper.cpp b/packages/ScriptExecutor/src/BundleWrapper.cpp
index d447d34..8957833 100644
--- a/packages/ScriptExecutor/src/BundleWrapper.cpp
+++ b/packages/ScriptExecutor/src/BundleWrapper.cpp
@@ -49,10 +49,11 @@
                             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::putLong(const char* key, int64_t value) {
+    jmethodID putLongMethod =
+            mJNIEnv->GetMethodID(mBundleClass, "putLong", "(Ljava/lang/String;J)V");
+    mJNIEnv->CallVoidMethod(mBundle, putLongMethod, mJNIEnv->NewStringUTF(key),
+                            static_cast<jlong>(value));
 }
 
 void BundleWrapper::putDouble(const char* key, double value) {
@@ -65,10 +66,36 @@
 void BundleWrapper::putString(const char* key, const char* value) {
     jmethodID putStringMethod = mJNIEnv->GetMethodID(mBundleClass, "putString",
                                                      "(Ljava/lang/String;Ljava/lang/String;)V");
+    // TODO(b/201008922): Handle a case when NewStringUTF returns nullptr (fails
+    // to create a string).
     mJNIEnv->CallVoidMethod(mBundle, putStringMethod, mJNIEnv->NewStringUTF(key),
                             mJNIEnv->NewStringUTF(value));
 }
 
+void BundleWrapper::putLongArray(const char* key, const std::vector<int64_t>& value) {
+    jmethodID putLongArrayMethod =
+            mJNIEnv->GetMethodID(mBundleClass, "putLongArray", "(Ljava/lang/String;[J)V");
+
+    jlongArray array = mJNIEnv->NewLongArray(value.size());
+    mJNIEnv->SetLongArrayRegion(array, 0, value.size(), &value[0]);
+    mJNIEnv->CallVoidMethod(mBundle, putLongArrayMethod, mJNIEnv->NewStringUTF(key), array);
+}
+
+void BundleWrapper::putStringArray(const char* key, const std::vector<std::string>& value) {
+    jmethodID putStringArrayMethod =
+            mJNIEnv->GetMethodID(mBundleClass, "putStringArray",
+                                 "(Ljava/lang/String;[Ljava/lang/String;)V");
+
+    jobjectArray array =
+            mJNIEnv->NewObjectArray(value.size(), mJNIEnv->FindClass("java/lang/String"), nullptr);
+    // TODO(b/201008922): Handle a case when NewStringUTF returns nullptr (fails
+    // to create a string).
+    for (int i = 0; i < value.size(); i++) {
+        mJNIEnv->SetObjectArrayElement(array, i, mJNIEnv->NewStringUTF(value[i].c_str()));
+    }
+    mJNIEnv->CallVoidMethod(mBundle, putStringArrayMethod, mJNIEnv->NewStringUTF(key), array);
+}
+
 jobject BundleWrapper::getBundle() {
     return mBundle;
 }
diff --git a/packages/ScriptExecutor/src/BundleWrapper.h b/packages/ScriptExecutor/src/BundleWrapper.h
index 4a0f9bb..7303bd0 100644
--- a/packages/ScriptExecutor/src/BundleWrapper.h
+++ b/packages/ScriptExecutor/src/BundleWrapper.h
@@ -19,6 +19,9 @@
 
 #include "jni.h"
 
+#include <string>
+#include <vector>
+
 namespace com {
 namespace android {
 namespace car {
@@ -37,9 +40,11 @@
     // 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 putLong(const char* key, int64_t value);
     void putDouble(const char* key, double value);
     void putString(const char* key, const char* value);
+    void putLongArray(const char* key, const std::vector<int64_t>& value);
+    void putStringArray(const char* key, const std::vector<std::string>& value);
 
     jobject getBundle();
 
diff --git a/packages/ScriptExecutor/src/JniUtils.cpp b/packages/ScriptExecutor/src/JniUtils.cpp
index e943129..3152722 100644
--- a/packages/ScriptExecutor/src/JniUtils.cpp
+++ b/packages/ScriptExecutor/src/JniUtils.cpp
@@ -44,8 +44,12 @@
 
     jclass booleanClass = env->FindClass("java/lang/Boolean");
     jclass integerClass = env->FindClass("java/lang/Integer");
+    jclass longClass = env->FindClass("java/lang/Long");
     jclass numberClass = env->FindClass("java/lang/Number");
     jclass stringClass = env->FindClass("java/lang/String");
+    jclass intArrayClass = env->FindClass("[I");
+    jclass longArrayClass = env->FindClass("[J");
+    jclass stringArrayClass = env->FindClass("[Ljava/lang/String;");
     // TODO(b/188816922): Handle more types such as float and integer arrays,
     // and perhaps nested Bundles.
 
@@ -67,6 +71,10 @@
         } 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, longClass)) {
+            jmethodID longMethod = env->GetMethodID(longClass, "longValue", "()J");
+            lua_pushinteger(luaEngine->getLuaState(), env->CallLongMethod(value, longMethod));
+
         } 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.
@@ -74,9 +82,66 @@
             /* 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);
+            // Produces a string in Modified UTF-8 encoding. Any null character
+            // inside the original string is converted into two-byte encoding.
+            // This way we can directly use the output of GetStringUTFChars in C API that
+            // expects a null-terminated string.
+            const char* rawStringValue =
+                    env->GetStringUTFChars(static_cast<jstring>(value), nullptr);
             lua_pushstring(luaEngine->getLuaState(), rawStringValue);
-            env->ReleaseStringUTFChars((jstring)value, rawStringValue);
+            env->ReleaseStringUTFChars(static_cast<jstring>(value), rawStringValue);
+        } else if (env->IsInstanceOf(value, intArrayClass)) {
+            jintArray intArray = static_cast<jintArray>(value);
+            const auto kLength = env->GetArrayLength(intArray);
+            // Arrays are represented as a table of sequential elements in Lua.
+            // We are creating a nested table to represent this array. We specify number of elements
+            // in the Java array to preallocate memory accordingly.
+            lua_createtable(luaEngine->getLuaState(), kLength, 0);
+            jint* rawIntArray = env->GetIntArrayElements(intArray, nullptr);
+            // Fills in the table at stack idx -2 with key value pairs, where key is a
+            // Lua index and value is an integer from the byte array at that index
+            for (int i = 0; i < kLength; i++) {
+                // Stack at index -1 is rawIntArray[i] after this push.
+                lua_pushinteger(luaEngine->getLuaState(), rawIntArray[i]);
+                lua_rawseti(luaEngine->getLuaState(), /* idx= */ -2,
+                            i + 1);  // lua index starts from 1
+            }
+            // JNI_ABORT is used because we do not need to copy back elements.
+            env->ReleaseIntArrayElements(intArray, rawIntArray, JNI_ABORT);
+        } else if (env->IsInstanceOf(value, longArrayClass)) {
+            jlongArray longArray = static_cast<jlongArray>(value);
+            const auto kLength = env->GetArrayLength(longArray);
+            // Arrays are represented as a table of sequential elements in Lua.
+            // We are creating a nested table to represent this array. We specify number of elements
+            // in the Java array to preallocate memory accordingly.
+            lua_createtable(luaEngine->getLuaState(), kLength, 0);
+            jlong* rawLongArray = env->GetLongArrayElements(longArray, nullptr);
+            // Fills in the table at stack idx -2 with key value pairs, where key is a
+            // Lua index and value is an integer from the byte array at that index
+            for (int i = 0; i < kLength; i++) {
+                lua_pushinteger(luaEngine->getLuaState(), rawLongArray[i]);
+                lua_rawseti(luaEngine->getLuaState(), /* idx= */ -2,
+                            i + 1);  // lua index starts from 1
+            }
+            // JNI_ABORT is used because we do not need to copy back elements.
+            env->ReleaseLongArrayElements(longArray, rawLongArray, JNI_ABORT);
+        } else if (env->IsInstanceOf(value, stringArrayClass)) {
+            jobjectArray stringArray = static_cast<jobjectArray>(value);
+            const auto kLength = env->GetArrayLength(stringArray);
+            // Arrays are represented as a table of sequential elements in Lua.
+            // We are creating a nested table to represent this array. We specify number of elements
+            // in the Java array to preallocate memory accordingly.
+            lua_createtable(luaEngine->getLuaState(), kLength, 0);
+            // Fills in the table at stack idx -2 with key value pairs, where key is a Lua index and
+            // value is an string value extracted from the object array at that index
+            for (int i = 0; i < kLength; i++) {
+                jstring element = static_cast<jstring>(env->GetObjectArrayElement(stringArray, i));
+                const char* rawStringValue = env->GetStringUTFChars(element, nullptr);
+                lua_pushstring(luaEngine->getLuaState(), rawStringValue);
+                env->ReleaseStringUTFChars(element, rawStringValue);
+                // lua index starts from 1
+                lua_rawseti(luaEngine->getLuaState(), /* idx= */ -2, i + 1);
+            }
         } else {
             // Other types are not implemented yet, skipping.
             continue;
diff --git a/packages/ScriptExecutor/src/LuaEngine.cpp b/packages/ScriptExecutor/src/LuaEngine.cpp
index 39aa38e..023333d 100644
--- a/packages/ScriptExecutor/src/LuaEngine.cpp
+++ b/packages/ScriptExecutor/src/LuaEngine.cpp
@@ -21,7 +21,10 @@
 #include <android-base/logging.h>
 #include <com/android/car/telemetry/scriptexecutorinterface/IScriptExecutorConstants.h>
 
+#include <sstream>
+#include <string>
 #include <utility>
+#include <vector>
 
 extern "C" {
 #include "lauxlib.h"
@@ -42,6 +45,141 @@
     ZERO_RETURNED_RESULTS = 0,
 };
 
+// TODO(199415783): Revisit the topic of limits to potentially move it to standalone file.
+constexpr int MAX_ARRAY_SIZE = 1000;
+
+// 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.
+//
+// Returns false if the conversion encountered unrecoverable error.
+// Otherwise, returns true for success.
+// TODO(b/200849134): Refactor this function.
+bool convertLuaTableToBundle(lua_State* lua, BundleWrapper* bundleWrapper,
+                             ScriptExecutorListener* listener) {
+    // 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.
+        // TODO(199531928): Consider putting limits on key sizes as well.
+        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->putLong(key, static_cast<int64_t>(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)) {
+            // TODO(199415783): We need to have a limit on how long these strings could be.
+            bundleWrapper->putString(key, lua_tostring(lua, /* index = */ -1));
+        } else if (lua_istable(lua, /* index =*/-1)) {
+            // Lua uses tables to represent an array.
+
+            // TODO(199438375): Document to users that we expect tables to be either only indexed or
+            // keyed but not both. If the table contains consecutively indexed values starting from
+            // 1, we will treat it as an array. lua_rawlen call returns the size of the indexed
+            // part. We copy this part into an array, but any keyed values in this table are
+            // ignored. There is a test that documents this current behavior. If a user wants a
+            // nested table to be represented by a PersistableBundle object, they must make sure
+            // that the nested table does not contain indexed data, including no key=1.
+            const auto kTableLength = lua_rawlen(lua, -1);
+            if (kTableLength > MAX_ARRAY_SIZE) {
+                std::ostringstream out;
+                out << "Returned table " << key << " exceeds maximum allowed size of "
+                    << MAX_ARRAY_SIZE
+                    << " elements. This key-value cannot be unpacked successfully. This error "
+                       "is unrecoverable.";
+                listener->onError(IScriptExecutorConstants::ERROR_TYPE_LUA_SCRIPT_ERROR,
+                                  out.str().c_str(), "");
+                return false;
+            }
+            if (kTableLength <= 0) {
+                std::ostringstream out;
+                out << "A value with key=" << key
+                    << " appears to be a nested table that does not represent an array of data. "
+                       "Such nested tables are not supported yet. This script error is "
+                       "unrecoverable.";
+                listener->onError(IScriptExecutorConstants::ERROR_TYPE_LUA_SCRIPT_ERROR,
+                                  out.str().c_str(), "");
+                return false;
+            }
+
+            std::vector<int64_t> longArray;
+            std::vector<std::string> stringArray;
+            int originalLuaType = LUA_TNIL;
+            for (int i = 0; i < kTableLength; i++) {
+                lua_rawgeti(lua, -1, i + 1);
+                // Lua allows arrays to have values of varying type. We need to force all Lua
+                // arrays to stick to single type within the same array. We use the first value
+                // in the array to determine the type of all values in the array that follow
+                // after. If the second, third, etc element of the array does not match the type
+                // of the first element we stop the extraction and return an error via a
+                // callback.
+                if (i == 0) {
+                    originalLuaType = lua_type(lua, /* index = */ -1);
+                }
+                int currentType = lua_type(lua, /* index= */ -1);
+                if (currentType != originalLuaType) {
+                    std::ostringstream out;
+                    out << "Returned Lua arrays must have elements of the same type. Returned "
+                           "table with key="
+                        << key << " has the first element of type=" << originalLuaType
+                        << ", but the element at index=" << i + 1 << " has type=" << currentType
+                        << ". Integer type codes are defined in lua.h file. This error is "
+                           "unrecoverable.";
+                    listener->onError(IScriptExecutorConstants::ERROR_TYPE_LUA_SCRIPT_ERROR,
+                                      out.str().c_str(), "");
+                    lua_pop(lua, 1);
+                    return false;
+                }
+                switch (currentType) {
+                    case LUA_TNUMBER:
+                        if (!lua_isinteger(lua, /* index = */ -1)) {
+                            LOG(WARNING) << "Floating array types are not supported yet. Skipping.";
+                        } else {
+                            longArray.push_back(lua_tointeger(lua, /* index = */ -1));
+                        }
+                        break;
+                    case LUA_TSTRING:
+                        // TODO(b/200833728): Investigate optimizations to minimize string
+                        // copying. For example, populate JNI object array one element at a
+                        // time, as we go.
+                        stringArray.push_back(lua_tostring(lua, /* index = */ -1));
+                        break;
+                    default:
+                        LOG(WARNING) << "Lua array with elements of type=" << currentType
+                                     << " are not supported. Skipping.";
+                }
+                lua_pop(lua, 1);
+            }
+            switch (originalLuaType) {
+                case LUA_TNUMBER:
+                    bundleWrapper->putLongArray(key, longArray);
+                    break;
+                case LUA_TSTRING:
+                    bundleWrapper->putStringArray(key, stringArray);
+                    break;
+            }
+        } else {
+            // not supported yet...
+            // TODO(199439259): Instead of logging here, log and send to user instead, and continue
+            // unpacking the rest of the table.
+            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.
+    }
+    return true;
+}
+
 }  // namespace
 
 ScriptExecutorListener* LuaEngine::sListener = nullptr;
@@ -87,6 +225,7 @@
 
     // 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;
 }
@@ -109,52 +248,48 @@
     // 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);
+    return lua_pcall(mLuaState, /* nargs= */ 2, /* 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)) {
-        // TODO(b/193565932): Return programming error through binder callback interface.
-        LOG(ERROR) << "Only a single input argument, a Lua table object, expected here";
+        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());
-    // 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 for the popped key. 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 1 element from the stack.
-        lua_pop(lua, 1);
-        // The key is at index -1, the table is at index -2 now.
+    if (convertLuaTableToBundle(lua, &bundleWrapper, sListener)) {
+        // Forward the populated Bundle object to Java callback.
+        sListener->onSuccess(bundleWrapper.getBundle());
     }
 
-    // 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());
+    if (convertLuaTableToBundle(lua, &bundleWrapper, sListener)) {
+        // 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;
diff --git a/packages/ScriptExecutor/src/LuaEngine.h b/packages/ScriptExecutor/src/LuaEngine.h
index 20c7ae5..0774108 100644
--- a/packages/ScriptExecutor/src/LuaEngine.h
+++ b/packages/ScriptExecutor/src/LuaEngine.h
@@ -63,13 +63,25 @@
     // 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 Android Bundle and forwarded to
+    // 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.
diff --git a/packages/ScriptExecutor/src/ScriptExecutorJni.cpp b/packages/ScriptExecutor/src/ScriptExecutorJni.cpp
index 6cb0670..a5add58 100644
--- a/packages/ScriptExecutor/src/ScriptExecutorJni.cpp
+++ b/packages/ScriptExecutor/src/ScriptExecutorJni.cpp
@@ -108,13 +108,8 @@
         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 publishedData, convert to Lua table and push it to Lua stack.
+    pushBundleToLuaTable(env, engine, publishedData);
 
     // Unpack bundle in savedState, convert to Lua table and push it to Lua
     // stack.
diff --git a/packages/ScriptExecutor/src/ScriptExecutorListener.cpp b/packages/ScriptExecutor/src/ScriptExecutorListener.cpp
index 8815887..739c71d 100644
--- a/packages/ScriptExecutor/src/ScriptExecutorListener.cpp
+++ b/packages/ScriptExecutor/src/ScriptExecutorListener.cpp
@@ -37,23 +37,23 @@
 
 void ScriptExecutorListener::onSuccess(jobject bundle) {
     JNIEnv* env = getCurrentJNIEnv();
-    if (mScriptExecutorListener == nullptr) {
-        env->FatalError(
-                "mScriptExecutorListener must point to a valid listener object, not nullptr.");
-    }
     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();
-    if (mScriptExecutorListener == nullptr) {
-        env->FatalError(
-                "mScriptExecutorListener must point to a valid listener object, not nullptr.");
-    }
     jclass listenerClass = env->GetObjectClass(mScriptExecutorListener);
     jmethodID onErrorMethod =
             env->GetMethodID(listenerClass, "onError", "(ILjava/lang/String;Ljava/lang/String;)V");
diff --git a/packages/ScriptExecutor/src/ScriptExecutorListener.h b/packages/ScriptExecutor/src/ScriptExecutorListener.h
index f58ba0d..392bc77 100644
--- a/packages/ScriptExecutor/src/ScriptExecutorListener.h
+++ b/packages/ScriptExecutor/src/ScriptExecutorListener.h
@@ -33,7 +33,7 @@
 
     virtual ~ScriptExecutorListener();
 
-    void onScriptFinished() {}
+    void onScriptFinished(jobject bundle);
 
     void onSuccess(jobject bundle);
 
diff --git a/packages/ScriptExecutor/src/com/android/car/scriptexecutor/ScriptExecutor.java b/packages/ScriptExecutor/src/com/android/car/scriptexecutor/ScriptExecutor.java
index bd364bc..06e318b 100644
--- a/packages/ScriptExecutor/src/com/android/car/scriptexecutor/ScriptExecutor.java
+++ b/packages/ScriptExecutor/src/com/android/car/scriptexecutor/ScriptExecutor.java
@@ -21,11 +21,19 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
 
 import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutor;
+import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutorConstants;
 import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutorListener;
 
+import java.io.IOException;
+import java.io.InputStream;
+
 /**
  * Executes Lua code in an isolated process with provided source code
  * and input arguments.
@@ -52,6 +60,34 @@
                     nativeInvokeScript(mLuaEnginePtr, scriptBody, functionName, publishedData,
                             savedState, listener));
         }
+
+        @Override
+        public void invokeScriptForLargeInput(String scriptBody, String functionName,
+                ParcelFileDescriptor publishedDataFileDescriptor, PersistableBundle savedState,
+                IScriptExecutorListener listener) {
+            mNativeHandler.post(() -> {
+                PersistableBundle publishedData;
+                try (InputStream input = new ParcelFileDescriptor.AutoCloseInputStream(
+                        publishedDataFileDescriptor)) {
+                    publishedData = PersistableBundle.readFromStream(input);
+                } catch (IOException e) {
+                    try {
+                        listener.onError(IScriptExecutorConstants.ERROR_TYPE_SCRIPT_EXECUTOR_ERROR,
+                                e.getMessage(), "");
+                    } catch (RemoteException remoteException) {
+                        if (Log.isLoggable(TAG, Log.ERROR)) {
+                            // At least log "message" here, in case it was never sent back via
+                            // the callback.
+                            Slog.e(TAG, "failed while calling listener with exception ", e);
+                        }
+                    }
+                    return;
+                }
+
+                nativeInvokeScript(mLuaEnginePtr, scriptBody, functionName, publishedData,
+                        savedState, listener);
+            });
+        }
     }
 
     private IScriptExecutorImpl mScriptExecutorBinder;
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
index a1a5979..3292009 100644
--- a/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/JniUtilsTest.java
+++ b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/JniUtilsTest.java
@@ -19,7 +19,6 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.os.PersistableBundle;
-import android.util.Log;
 
 import org.junit.After;
 import org.junit.Before;
@@ -36,11 +35,15 @@
     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 String INT_ARRAY_KEY = "int_array_key";
+    private static final String LONG_ARRAY_KEY = "long_array_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";
+    private static final int[] INT_ARRAY_VALUE = new int[]{1, 2, 3};
+    private static final long[] LONG_ARRAY_VALUE = new long[]{1, 2, 3, 4};
 
     // Pointer to Lua Engine instantiated in native space.
     private long mLuaEnginePtr = 0;
@@ -77,13 +80,19 @@
      * 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);
 
+    private native boolean nativeHasIntArrayValue(long luaEnginePtr, String key, int[] value);
+
+    private native boolean nativeHasLongArrayValue(long luaEnginePtr, String key, long[] 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.
@@ -109,7 +118,6 @@
         assertThat(nativeHasStringValue(mLuaEnginePtr, STRING_KEY, STRING_VALUE)).isTrue();
     }
 
-
     @Test
     public void pushBundleToLuaTable_wrongKey() {
         PersistableBundle bundle = new PersistableBundle();
@@ -122,4 +130,22 @@
         // Check contents of Lua table.
         assertThat(nativeHasBooleanValue(mLuaEnginePtr, "wrong key", BOOLEAN_VALUE)).isFalse();
     }
+
+    @Test
+    public void pushBundleToLuaTable_arrays() {
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putIntArray(INT_ARRAY_KEY, INT_ARRAY_VALUE);
+        bundle.putLongArray(LONG_ARRAY_KEY, LONG_ARRAY_VALUE);
+
+        // Invokes the corresponding helper method to convert the bundle
+        // to Lua table on Lua stack.
+        nativePushBundleToLuaTableCaller(mLuaEnginePtr, bundle);
+
+        // Check contents of Lua table.
+        // Java int and long arrays both end up being arrays of Lua's Integer type,
+        // which is interpreted as a 8-byte int type.
+        assertThat(nativeHasIntArrayValue(mLuaEnginePtr, INT_ARRAY_KEY, INT_ARRAY_VALUE)).isTrue();
+        assertThat(
+                nativeHasLongArrayValue(mLuaEnginePtr, LONG_ARRAY_KEY, LONG_ARRAY_VALUE)).isTrue();
+    }
 }
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
index eae1377..efc34d3 100644
--- a/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/JniUtilsTestHelper.cpp
+++ b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/JniUtilsTestHelper.cpp
@@ -27,6 +27,44 @@
 namespace scriptexecutor {
 namespace {
 
+template <typename T>
+bool hasIntegerArray(JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, T rawInputArray,
+                     const int arrayLength) {
+    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_istable(luaState, -1)) {
+        result = false;
+    } else {
+        // First, compare the input and Lua array sizes.
+        const auto kActualLength = lua_rawlen(luaState, -1);
+        if (arrayLength != kActualLength) {
+            // No need to compare further if number of elements in the two arrays are not equal.
+            result = false;
+        } else {
+            // Do element by element comparison.
+            bool is_equal = true;
+            for (int i = 0; i < arrayLength; ++i) {
+                lua_rawgeti(luaState, -1, i + 1);
+                if (!lua_isinteger(luaState, /* index = */ -1) ||
+                    (lua_tointeger(luaState, /* index = */ -1) != rawInputArray[i])) {
+                    is_equal = false;
+                }
+                lua_pop(luaState, 1);
+                if (!is_equal) break;
+            }
+            result = is_equal;
+        }
+    }
+    lua_pop(luaState, 1);
+    return result;
+}
+
 extern "C" {
 
 #include "lua.h"
@@ -130,6 +168,24 @@
     return result;
 }
 
+JNIEXPORT bool JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeHasIntArrayValue(
+        JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jintArray value) {
+    jint* rawInputArray = env->GetIntArrayElements(value, nullptr);
+    const auto kInputLength = env->GetArrayLength(value);
+    bool result = hasIntegerArray(env, object, luaEnginePtr, key, rawInputArray, kInputLength);
+    env->ReleaseIntArrayElements(value, rawInputArray, JNI_ABORT);
+    return result;
+}
+
+JNIEXPORT bool JNICALL Java_com_android_car_scriptexecutor_JniUtilsTest_nativeHasLongArrayValue(
+        JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jlongArray value) {
+    jlong* rawInputArray = env->GetLongArrayElements(value, nullptr);
+    const auto kInputLength = env->GetArrayLength(value);
+    bool result = hasIntegerArray(env, object, luaEnginePtr, key, rawInputArray, kInputLength);
+    env->ReleaseLongArrayElements(value, rawInputArray, JNI_ABORT);
+    return result;
+}
+
 }  //  extern "C"
 
 }  // namespace
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
index 144e7b9..52b5cc7 100644
--- a/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/ScriptExecutorTest.java
+++ b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/ScriptExecutorTest.java
@@ -17,6 +17,7 @@
 package com.android.car.scriptexecutor;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.fail;
 
@@ -25,6 +26,7 @@
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -40,6 +42,7 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.io.OutputStream;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -53,20 +56,22 @@
 
     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 mSuccessLatch = new CountDownLatch(1);
-        public final CountDownLatch mErrorLatch = new CountDownLatch(1);
+        public final CountDownLatch mResponseLatch = new CountDownLatch(1);
 
         @Override
-        public void onScriptFinished(byte[] result) {
+        public void onScriptFinished(PersistableBundle result) {
+            mFinalResult = result;
+            mResponseLatch.countDown();
         }
 
         @Override
         public void onSuccess(PersistableBundle stateToPersist) {
             mSavedBundle = stateToPersist;
-            mSuccessLatch.countDown();
+            mResponseLatch.countDown();
         }
 
         @Override
@@ -74,30 +79,20 @@
             mErrorType = errorType;
             mMessage = message;
             mStackTrace = stackTrace;
-            mErrorLatch.countDown();
+            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 mPublishedData = new PersistableBundle();
     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_SUCCESS_TIMEOUT_SEC = 10;
-    private static final int SCRIPT_ERROR_TIMEOUT_SEC = 10;
+    private static final int SCRIPT_PROCESSING_TIMEOUT_SEC = 10;
 
 
     private final ServiceConnection mScriptExecutorConnection =
@@ -114,16 +109,16 @@
                 }
             };
 
-    // Helper method to invoke the script and wait for it to complete and return the result.
-    private void runScriptAndWaitForResult(String script, String function,
-            PersistableBundle previousState)
+    // Helper method to invoke the script and wait for it to complete and return a response.
+    private void runScriptAndWaitForResponse(String script, String function,
+            PersistableBundle publishedData, PersistableBundle previousState)
             throws RemoteException {
-        mScriptExecutor.invokeScript(script, function, mPublishedData, previousState,
+        mScriptExecutor.invokeScript(script, function, publishedData, previousState,
                 mFakeScriptExecutorListener);
         try {
-            if (!mFakeScriptExecutorListener.mSuccessLatch.await(SCRIPT_SUCCESS_TIMEOUT_SEC,
+            if (!mFakeScriptExecutorListener.mResponseLatch.await(SCRIPT_PROCESSING_TIMEOUT_SEC,
                     TimeUnit.SECONDS)) {
-                fail("Failed to get on_success called by the script on time");
+                fail("Failed to get the callback method called by the script on time");
             }
         } catch (InterruptedException e) {
             e.printStackTrace();
@@ -132,17 +127,8 @@
     }
 
     private void runScriptAndWaitForError(String script, String function) throws RemoteException {
-        mScriptExecutor.invokeScript(script, function, mPublishedData, new PersistableBundle(),
-                mFakeScriptExecutorListener);
-        try {
-            if (!mFakeScriptExecutorListener.mErrorLatch.await(SCRIPT_ERROR_TIMEOUT_SEC,
-                    TimeUnit.SECONDS)) {
-                fail("Failed to get on_error called by the script on time");
-            }
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-            fail(e.getMessage());
-        }
+        runScriptAndWaitForResponse(script, function, new PersistableBundle(),
+                new PersistableBundle());
     }
 
     @Before
@@ -158,86 +144,72 @@
     }
 
     @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"
+                "function hello(data, state)\n"
                         + "    result = {hello=\"world\"}\n"
                         + "    on_success(result)\n"
                         + "end\n";
 
 
-        runScriptAndWaitForResult(returnResultScript, "hello", mSavedState);
+        runScriptAndWaitForResponse(returnResultScript, "hello", mPublishedData, 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");
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.getString("hello")).isEqualTo(
+                "world");
     }
 
     @Test
-    public void invokeScript_allSupportedTypes() throws RemoteException {
+    public void invokeScript_allSupportedPrimitiveTypes() throws RemoteException {
         String script =
-                "function knows(state)\n"
+                "function knows(data, state)\n"
                         + "    result = {string=\"hello\", boolean=true, integer=1, number=1.1}\n"
                         + "    on_success(result)\n"
                         + "end\n";
 
 
-        runScriptAndWaitForResult(script, "knows", mSavedState);
+        runScriptAndWaitForResponse(script, "knows", mPublishedData, 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.getString("string")).isEqualTo(
+                "hello");
         assertThat(mFakeScriptExecutorListener.mSavedBundle.getBoolean("boolean")).isEqualTo(true);
-        assertThat(mFakeScriptExecutorListener.mSavedBundle.getInt("integer")).isEqualTo(1);
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.getLong("integer")).isEqualTo(1);
         assertThat(mFakeScriptExecutorListener.mSavedBundle.getDouble("number")).isEqualTo(1.1);
     }
 
     @Test
     public void invokeScript_skipsUnsupportedTypes() throws RemoteException {
         String script =
-                "function nested(state)\n"
+                "function nested(data, 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";
 
 
-        runScriptAndWaitForResult(script, "nested", mSavedState);
+        runScriptAndWaitForResponse(script, "nested", mPublishedData, 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();
+        // Verify that expected error is received.
+        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
+                IScriptExecutorConstants.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mFakeScriptExecutorListener.mMessage).contains(
+                "nested tables are not supported");
     }
 
     @Test
     public void invokeScript_emptyBundle() throws RemoteException {
         String script =
-                "function empty(state)\n"
+                "function empty(data, state)\n"
                         + "    result = {}\n"
                         + "    on_success(result)\n"
                         + "end\n";
 
 
-        runScriptAndWaitForResult(script, "empty", mSavedState);
+        runScriptAndWaitForResponse(script, "empty", mPublishedData, mSavedState);
 
         // If a script returns empty table as the result, we get an empty bundle.
         assertThat(mFakeScriptExecutorListener.mSavedBundle).isNotNull();
@@ -248,9 +220,8 @@
     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"
+                "function update(data, state)\n"
                         + "    result = {y = state.x+1}\n"
                         + "    on_success(result)\n"
                         + "end\n";
@@ -258,21 +229,20 @@
         previousState.putInt("x", 1);
 
 
-        runScriptAndWaitForResult(script, "update", previousState);
+        runScriptAndWaitForResponse(script, "update", mPublishedData, 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);
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.getLong("y")).isEqualTo(2);
     }
 
     @Test
-    public void invokeScript_allSupportedTypesWorkRoundTripWithKeyNamesPreserved()
+    public void invokeScript_allSupportedPrimitiveTypesWorkRoundTripWithKeyNamesPreserved()
             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.
+        // Here we verify that all supported primitive types in supplied previous state Bundle
+        // are interpreted by the script as expected.
         String script =
-                "function update_all(state)\n"
+                "function update_all(data, state)\n"
                         + "    result = {}\n"
                         + "    result.integer = state.integer + 1\n"
                         + "    result.number = state.number + 0.1\n"
@@ -287,11 +257,11 @@
         previousState.putString("string", "ABRA");
 
 
-        runScriptAndWaitForResult(script, "update_all", previousState);
+        runScriptAndWaitForResponse(script, "update_all", mPublishedData, 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.getLong("integer")).isEqualTo(2);
         assertThat(mFakeScriptExecutorListener.mSavedBundle.getDouble("number")).isEqualTo(0.2);
         assertThat(mFakeScriptExecutorListener.mSavedBundle.getBoolean("boolean")).isEqualTo(true);
         assertThat(mFakeScriptExecutorListener.mSavedBundle.getString("string")).isEqualTo(
@@ -299,6 +269,198 @@
     }
 
     @Test
+    public void invokeScript_allSupportedArrayTypesWorkRoundTripWithKeyNamesPreserved()
+            throws RemoteException {
+        // Here we verify that all supported array types in supplied previous state Bundle are
+        // interpreted by the script as expected.
+        String script =
+                "function arrays(data, state)\n"
+                        + "    result = {}\n"
+                        + "    result.int_array = state.int_array\n"
+                        + "    result.long_array = state.long_array\n"
+                        + "    result.string_array = state.string_array\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+        PersistableBundle previousState = new PersistableBundle();
+        int[] int_array = new int[]{1, 2};
+        long[] int_array_in_long = new long[]{1, 2};
+        long[] long_array = new long[]{1, 2, 3};
+        String[] string_array = new String[]{"one", "two", "three"};
+        previousState.putIntArray("int_array", int_array);
+        previousState.putLongArray("long_array", long_array);
+        previousState.putStringArray("string_array", string_array);
+
+
+        runScriptAndWaitForResponse(script, "arrays", mPublishedData, previousState);
+
+        // Verify that keys are preserved but the values are modified as expected.
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(3);
+        // Lua has only one lua_Integer. Here Java long is used to represent it when data is
+        // transferred from Lua to CarTelemetryService.
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.getLongArray("int_array")).isEqualTo(
+                int_array_in_long);
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.getLongArray("long_array")).isEqualTo(
+                long_array);
+        assertThat(
+                mFakeScriptExecutorListener.mSavedBundle.getStringArray("string_array")).isEqualTo(
+                string_array);
+    }
+
+    @Test
+    public void invokeScript_modifiesArray()
+            throws RemoteException {
+        // Verify that an array modified by a script is properly sent back by the callback.
+        String script =
+                "function modify_array(data, state)\n"
+                        + "    result = {}\n"
+                        + "    result.long_array = state.long_array\n"
+                        + "    result.long_array[2] = 100\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+        PersistableBundle previousState = new PersistableBundle();
+        long[] long_array = new long[]{1, 2, 3};
+        previousState.putLongArray("long_array", long_array);
+        long[] expected_array = new long[]{1, 100, 3};
+
+
+        runScriptAndWaitForResponse(script, "modify_array", mPublishedData, previousState);
+
+        // Verify that keys are preserved but the values are modified as expected.
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(1);
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.getLongArray("long_array")).isEqualTo(
+                expected_array);
+    }
+
+    @Test
+    public void invokeScript_processesStringArray()
+            throws RemoteException {
+        // Verify that an array modified by a script is properly sent back by the callback.
+        String script =
+                "function process_string_array(data, state)\n"
+                        + "    result = {}\n"
+                        + "    result.answer = state.string_array[1] .. state.string_array[2]\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+        PersistableBundle previousState = new PersistableBundle();
+        String[] string_array = new String[]{"Hello ", "world!"};
+        previousState.putStringArray("string_array", string_array);
+
+        runScriptAndWaitForResponse(script, "process_string_array", mPublishedData, previousState);
+
+        // Verify that keys are preserved but the values are modified as expected.
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(1);
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.getString("answer")).isEqualTo(
+                "Hello world!");
+    }
+
+    @Test
+    public void invokeScript_arraysWithLengthAboveLimitCauseError()
+            throws RemoteException {
+        // Verifies that arrays pushed by Lua that have their size over the limit cause error.
+        String script =
+                "function size_limit(data, state)\n"
+                        + "    result = {}\n"
+                        + "    result.huge_array = {}\n"
+                        + "    for i=1, 10000 do\n"
+                        + "        result.huge_array[i]=i\n"
+                        + "    end\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+        runScriptAndWaitForResponse(script, "size_limit", mPublishedData, mSavedState);
+
+        // Verify that expected error is received.
+        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
+                IScriptExecutorConstants.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
+                "Returned table huge_array exceeds maximum allowed size of 1000 "
+                        + "elements. This key-value cannot be unpacked successfully. This error "
+                        + "is unrecoverable.");
+    }
+
+    @Test
+    public void invokeScript_arrayContainingVaryingTypesCausesError()
+            throws RemoteException {
+        // Verifies that values in returned array must be the same integer type.
+        // For example string values in a Lua array are not allowed.
+        String script =
+                "function table_with_numbers_and_strings(data, state)\n"
+                        + "    result = {}\n"
+                        + "    result.mixed_array = state.long_array\n"
+                        + "    result.mixed_array[2] = 'a'\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+        PersistableBundle previousState = new PersistableBundle();
+        long[] long_array = new long[]{1, 2, 3};
+        previousState.putLongArray("long_array", long_array);
+
+        runScriptAndWaitForResponse(script, "table_with_numbers_and_strings", mPublishedData,
+                previousState);
+
+        // Verify that expected error is received.
+        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
+                IScriptExecutorConstants.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mFakeScriptExecutorListener.mMessage).contains(
+                "Returned Lua arrays must have elements of the same type.");
+    }
+
+    @Test
+    public void invokeScript_InTablesWithBothKeysAndIndicesCopiesOnlyIndexedData()
+            throws RemoteException {
+        // Documents the current behavior that copies only indexed values in a Lua table that
+        // contains both keyed and indexed data.
+        String script =
+                "function keys_and_indices(data, state)\n"
+                        + "    result = {}\n"
+                        + "    result.mixed_array = state.long_array\n"
+                        + "    result.mixed_array['a'] = 130\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+        PersistableBundle previousState = new PersistableBundle();
+        long[] long_array = new long[]{1, 2, 3};
+        previousState.putLongArray("long_array", long_array);
+
+        runScriptAndWaitForResponse(script, "keys_and_indices", mPublishedData, previousState);
+
+        // Verify that keys are preserved but the values are modified as expected.
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(1);
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.getLongArray("mixed_array")).isEqualTo(
+                long_array);
+    }
+
+    @Test
+    public void invokeScript_noLuaBufferOverflowForLargeInputArrays() throws RemoteException {
+        // Tests that arrays with length that exceed internal Lua buffer size of 20 elements
+        // do not cause buffer overflow and are handled properly.
+        String script =
+                "function large_input_array(data, state)\n"
+                        + "    sum = 0\n"
+                        + "    for _, val in ipairs(state.long_array) do\n"
+                        + "        sum = sum + val\n"
+                        + "    end\n"
+                        + "    result = {total = sum}\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+        PersistableBundle previousState = new PersistableBundle();
+        int n = 10000;
+        long[] longArray = new long[n];
+        for (int i = 0; i < n; i++) {
+            longArray[i] = i;
+        }
+        previousState.putLongArray("long_array", longArray);
+        long expected_sum =
+                (longArray[0] + longArray[n - 1]) * n / 2; // sum of an arithmetic sequence.
+
+        runScriptAndWaitForResponse(script, "large_input_array", mPublishedData, previousState);
+
+        // Verify that keys are preserved but the values are modified as expected.
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(1);
+        assertThat(mFakeScriptExecutorListener.mSavedBundle.getLong("total")).isEqualTo(
+                expected_sum);
+    }
+
+    @Test
     public void invokeScript_scriptCallsOnError() throws RemoteException {
         String script =
                 "function calls_on_error()\n"
@@ -350,5 +512,261 @@
         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(data, 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", mPublishedData,
+                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.getLong("data")).isEqualTo(2);
+    }
+
+    @Test
+    public void invokeScript_allPrimitiveSupportedTypesForReturningFinalResult()
+            throws RemoteException {
+        // Here we verify that all supported primitive types are present in the returned final
+        // result bundle are present.
+        String script =
+                "function finalize_all(data, 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", mPublishedData, previousState);
+
+        // Verify that keys are preserved but the values are modified as expected.
+        assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(4);
+        assertThat(mFakeScriptExecutorListener.mFinalResult.getLong("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(data, state)\n"
+                        + "    result = {}\n"
+                        + "    on_script_finished(result)\n"
+                        + "end\n";
+
+
+        runScriptAndWaitForResponse(script, "empty_final_result", mPublishedData, 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(data, 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",
+                mPublishedData, 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(data, state)\n"
+                        + "    result = {}\n"
+                        + "    extra = 1\n"
+                        + "    on_success(result, extra)\n"
+                        + "end\n";
+
+
+        runScriptAndWaitForResponse(script, "wrong_number_of_outputs_in_on_success",
+                mPublishedData, 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(data, state)\n"
+                        + "    result = 1\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+
+        runScriptAndWaitForResponse(script, "wrong_type_in_on_success",
+                mPublishedData, 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(data, state)\n"
+                        + "    result = 1\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+
+        runScriptAndWaitForResponse(script, "wrong_type_in_on_script_finished",
+                mPublishedData, 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");
+    }
+
+    @Test
+    public void invokeScriptLargeInput_largePublishedData() throws Exception {
+        // Verifies that large input does not overwhelm Binder's buffer because pipes are used
+        // instead.
+        String script =
+                "function large_published_data(data, state)\n"
+                        + "    sum = 0\n"
+                        + "    for _, val in ipairs(data.array) do\n"
+                        + "        sum = sum + val\n"
+                        + "    end\n"
+                        + "    result = {total = sum}\n"
+                        + "    on_script_finished(result)\n"
+                        + "end\n";
+
+        ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
+        ParcelFileDescriptor writeFd = fds[1];
+        ParcelFileDescriptor readFd = fds[0];
+
+        PersistableBundle bundle = new PersistableBundle();
+        int n = 1 << 20; // 1024 * 1024 values, roughly 1 Million.
+        long[] array8Mb = new long[n];
+        for (int i = 0; i < n; i++) {
+            array8Mb[i] = i;
+        }
+        bundle.putLongArray("array", array8Mb);
+        long expectedSum =
+                (array8Mb[0] + array8Mb[n - 1]) * n / 2; // sum of an arithmetic sequence.
+
+        mScriptExecutor.invokeScriptForLargeInput(script, "large_published_data", readFd,
+                mSavedState,
+                mFakeScriptExecutorListener);
+
+        readFd.close();
+        try (OutputStream outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(writeFd)) {
+            bundle.writeToStream(outputStream);
+        }
+
+        boolean gotResponse = mFakeScriptExecutorListener.mResponseLatch.await(
+                SCRIPT_PROCESSING_TIMEOUT_SEC,
+                TimeUnit.SECONDS);
+
+        assertWithMessage("Failed to get the callback method called by the script on time")
+                .that(gotResponse).isTrue();
+        assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(1);
+        assertThat(mFakeScriptExecutorListener.mFinalResult.getLong("total"))
+                .isEqualTo(expectedSum);
+    }
+
+    @Test
+    public void invokeScript_bothPublishedDataAndPreviousStateAreProvided() throws RemoteException {
+        // Verifies that both published data and previous state PersistableBundles
+        // are piped into script.
+        String script =
+                "function data_and_state(data, state)\n"
+                        + "    result = {answer = data.a .. data.b .. state.c .. state.d}\n"
+                        + "    on_script_finished(result)\n"
+                        + "end\n";
+
+        PersistableBundle publishedData = new PersistableBundle();
+        publishedData.putString("a", "A");
+        publishedData.putString("b", "B");
+
+        PersistableBundle previousState = new PersistableBundle();
+        previousState.putString("c", "C");
+        previousState.putString("d", "D");
+
+        runScriptAndWaitForResponse(script, "data_and_state", publishedData, previousState);
+
+        // If a script returns empty table as the final result, we get an empty bundle.
+        assertThat(mFakeScriptExecutorListener.mFinalResult).isNotNull();
+        assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(1);
+        assertThat(mFakeScriptExecutorListener.mFinalResult.getString("answer")).isEqualTo(
+                "ABCD");
+    }
+
+    @Test
+    public void invokeScript_outputIntAndLongAreTreatedAsLong() throws RemoteException {
+        // Verifies that we treat output both integer and long as long integer type although we
+        // distinguish between int and long in the script input.
+        String script =
+                "function int_and_long_are_output_long(data, state)\n"
+                        + "    result = {int = data.int, long = state.long}\n"
+                        + "    on_script_finished(result)\n"
+                        + "end\n";
+
+        PersistableBundle publishedData = new PersistableBundle();
+        publishedData.putInt("int", 100);
+
+        PersistableBundle previousState = new PersistableBundle();
+        previousState.putLong("long", 200);
+
+        runScriptAndWaitForResponse(script, "int_and_long_are_output_long",
+                publishedData, previousState);
+
+        // If a script returns empty table as the final result, we get an empty bundle.
+        assertThat(mFakeScriptExecutorListener.mFinalResult).isNotNull();
+        assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(2);
+        // getInt should always return "empty" value (zero) because all integer types are treated
+        // as Java long.
+        assertThat(mFakeScriptExecutorListener.mFinalResult.getInt("int")).isEqualTo(0);
+        assertThat(mFakeScriptExecutorListener.mFinalResult.getInt("long")).isEqualTo(0);
+        // Instead all expected integer values are successfully retrieved using getLong method
+        // from the output bundle.
+        assertThat(mFakeScriptExecutorListener.mFinalResult.getLong("int")).isEqualTo(100);
+        assertThat(mFakeScriptExecutorListener.mFinalResult.getLong("long")).isEqualTo(200);
+    }
 }
 
diff --git a/service/jni/evs/StreamHandler.cpp b/service/jni/evs/StreamHandler.cpp
index 6f64160..826ff02 100644
--- a/service/jni/evs/StreamHandler.cpp
+++ b/service/jni/evs/StreamHandler.cpp
@@ -161,9 +161,17 @@
         auto it = mReceivedBuffers.begin();
         while (it != mReceivedBuffers.end()) {
             if (it->bufferId == buffer.bufferId) {
+                // We intentionally do not update the iterator to detect a
+                // request to return an unknown buffer.
                 mReceivedBuffers.erase(it);
                 break;
             }
+            ++it;
+        }
+
+        if (it == mReceivedBuffers.end()) {
+            LOG(DEBUG) << "Ignores a request to return unknown buffer";
+            return;
         }
     }
 
diff --git a/service/res/values-af/strings.xml b/service/res/values-af/strings.xml
index c301554..a0632c5 100644
--- a/service/res/values-af/strings.xml
+++ b/service/res/values-af/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Stel later terug"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Inligtingvermaakstelsel stel terug as kar begin."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Die motor moet geparkeer wees om terug te stel."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-am/strings.xml b/service/res/values-am/strings.xml
index 75710a5..321c2c8 100644
--- a/service/res/values-am/strings.xml
+++ b/service/res/values-am/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"በኋላ ዳግም አስጀምር"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"መኪናው በሚቀጥለው ጊዜ ሲጀምር የኢንፎቴይንመንት ሥርዓቱ ዳግም ይጀምራል።"</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"ዳግም ማስጀመርን ለመጀመር መኪናው መቆም አለበት።"</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-ar/strings.xml b/service/res/values-ar/strings.xml
index f1a6290..b4adb69 100644
--- a/service/res/values-ar/strings.xml
+++ b/service/res/values-ar/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"إعادة الضبط لاحقًا"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"سيُعاد ضبط \"نظام الترفيه والمعلومات\" عند تشغيل السيارة لاحقًا."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"يجب أن تكون السيارة متوقفة لبدء إعادة الضبط."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-as/strings.xml b/service/res/values-as/strings.xml
index 566c908..4d9c517 100644
--- a/service/res/values-as/strings.xml
+++ b/service/res/values-as/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"পাছত ৰিছেট কৰক"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"ইনফ’টেইনমেণ্ট ছিষ্টেমটোৱে পৰৱৰ্তী সময়ত গাড়ীখন ষ্টাৰ্ট হ\'লে ৰিছেট কৰিব।"</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"ৰিছেট কৰাটো আৰম্ভ কৰিবলৈ গাড়ীখন ৰখাই থ\'বই লাগিব।"</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-az/strings.xml b/service/res/values-az/strings.xml
index 462f07b..499a22a 100644
--- a/service/res/values-az/strings.xml
+++ b/service/res/values-az/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Sonra sıfırlayın"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Məlumat-əyləncə sistemi avtomobil növbəti dəfə işə düşəndə sıfırlanacaq."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Sıfırlamanı başlatmaq üçün avtomobil park edilməlidir."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-b+sr+Latn/strings.xml b/service/res/values-b+sr+Latn/strings.xml
index d6879f4..0b8fa0e 100644
--- a/service/res/values-b+sr+Latn/strings.xml
+++ b/service/res/values-b+sr+Latn/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Resetuj kasnije"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Sis. za info-zab. se reset. kada opet upal. kola."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Morate da parkirate kola za početak resetovanja."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-be/strings.xml b/service/res/values-be/strings.xml
index 3782c86..3c47c8b 100644
--- a/service/res/values-be/strings.xml
+++ b/service/res/values-be/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Скінуць пазней"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Скід адбудзецца пры наступным запуску аўтамабіля."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Каб пачаць скід налад, прыпаркуйце аўтамабіль."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-bg/strings.xml b/service/res/values-bg/strings.xml
index 5242ae5..a9ab535 100644
--- a/service/res/values-bg/strings.xml
+++ b/service/res/values-bg/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Нулиране по-късно"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Основното у-во ще се нулира при следващото стартиране на колата."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Колата трябва да е паркирана, за да старт. нулирането."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-bn/strings.xml b/service/res/values-bn/strings.xml
index 3e30d75..eff0449 100644
--- a/service/res/values-bn/strings.xml
+++ b/service/res/values-bn/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"পরে রিসেট করুন"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"ইনফোটেইনমেন্ট সিস্টেম পরের বার গাড়ি চালু হলে রিসেট হবে।"</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"রিসেট শুরু করতে গাড়িকে পার্কিং অবস্থায় থাকতে হবে।"</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-bs/strings.xml b/service/res/values-bs/strings.xml
index a4f2241..edac948 100644
--- a/service/res/values-bs/strings.xml
+++ b/service/res/values-bs/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Vrati na zad. kasnije"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Info-zab. sist. će se vratiti na zadano kad opet pokrenete auto."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Da počnete vraćanje na zadano, parkirajte automobil."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-ca/strings.xml b/service/res/values-ca/strings.xml
index b0c6377..818b091 100644
--- a/service/res/values-ca/strings.xml
+++ b/service/res/values-ca/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Restableix més tard"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"El sist. d\'infoentreteniment es restablirà en engegar el cotxe."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"El cotxe ha d\'estar aparcat per al restabliment."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-cs/strings.xml b/service/res/values-cs/strings.xml
index b0a4aad..bc13a7f 100644
--- a/service/res/values-cs/strings.xml
+++ b/service/res/values-cs/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Resetovat později"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Systém se resetuje při příštím nastartování auta."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Před zahájením resetu musí být auto zaparkované."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-da/strings.xml b/service/res/values-da/strings.xml
index cac56bd..5379600 100644
--- a/service/res/values-da/strings.xml
+++ b/service/res/values-da/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Nulstil senere"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Infotainmentsystemet nulstilles, næste gang bilen starter."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Nulstillingen kan kun startes, når bilen er parkeret."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-de/strings.xml b/service/res/values-de/strings.xml
index 156265d..18c646b 100644
--- a/service/res/values-de/strings.xml
+++ b/service/res/values-de/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Später zurücksetzen"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Das Infotainmentsystem wird beim nächsten Start zurückgesetzt."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Zum Zurücksetzen muss das Auto stehen."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-el/strings.xml b/service/res/values-el/strings.xml
index 5023f46..f37ef3f 100644
--- a/service/res/values-el/strings.xml
+++ b/service/res/values-el/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Επαναφορά αργότερα"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Επαναφ. συστήμ. ενημέρ. και ψυχαγ. στην επόμενη εκκίνηση αυτοκ."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Σταθμεύστε το αυτοκίνητο για έναρξη επαναφοράς."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-en-rAU/strings.xml b/service/res/values-en-rAU/strings.xml
index 8542b74..3965f97 100644
--- a/service/res/values-en-rAU/strings.xml
+++ b/service/res/values-en-rAU/strings.xml
@@ -182,4 +182,12 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Reset later"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"The infotainment system will reset the next time the car starts."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"The car must be parked to start the reset."</string>
+    <string name="resource_overuse_notification_title" msgid="3385149030747234969">"<xliff:g id="ID_1">^1</xliff:g> is affecting your system performance"</string>
+    <string name="resource_overuse_notification_text_disable_app" msgid="4538000369374274293">"Disable app to improve system performance. You can enable the app once again in Settings."</string>
+    <string name="resource_overuse_notification_text_prioritize_app" msgid="4782324719261106243">"Prioritise app to keep using app."</string>
+    <string name="resource_overuse_notification_text_uninstall_app" msgid="531108846448668467">"Uninstall app to improve system performance."</string>
+    <string name="resource_overuse_notification_button_disable_app" msgid="5511548570206345274">"Disable app"</string>
+    <string name="resource_overuse_notification_button_prioritize_app" msgid="5327141954014335559">"Prioritise app"</string>
+    <string name="resource_overuse_notification_button_uninstall_app" msgid="7327141273608850448">"Uninstall app"</string>
+    <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"<xliff:g id="ID_1">^1</xliff:g> has been disabled. You can enable it again in Settings."</string>
 </resources>
diff --git a/service/res/values-en-rCA/strings.xml b/service/res/values-en-rCA/strings.xml
index 8542b74..3965f97 100644
--- a/service/res/values-en-rCA/strings.xml
+++ b/service/res/values-en-rCA/strings.xml
@@ -182,4 +182,12 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Reset later"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"The infotainment system will reset the next time the car starts."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"The car must be parked to start the reset."</string>
+    <string name="resource_overuse_notification_title" msgid="3385149030747234969">"<xliff:g id="ID_1">^1</xliff:g> is affecting your system performance"</string>
+    <string name="resource_overuse_notification_text_disable_app" msgid="4538000369374274293">"Disable app to improve system performance. You can enable the app once again in Settings."</string>
+    <string name="resource_overuse_notification_text_prioritize_app" msgid="4782324719261106243">"Prioritise app to keep using app."</string>
+    <string name="resource_overuse_notification_text_uninstall_app" msgid="531108846448668467">"Uninstall app to improve system performance."</string>
+    <string name="resource_overuse_notification_button_disable_app" msgid="5511548570206345274">"Disable app"</string>
+    <string name="resource_overuse_notification_button_prioritize_app" msgid="5327141954014335559">"Prioritise app"</string>
+    <string name="resource_overuse_notification_button_uninstall_app" msgid="7327141273608850448">"Uninstall app"</string>
+    <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"<xliff:g id="ID_1">^1</xliff:g> has been disabled. You can enable it again in Settings."</string>
 </resources>
diff --git a/service/res/values-en-rGB/strings.xml b/service/res/values-en-rGB/strings.xml
index 8542b74..3965f97 100644
--- a/service/res/values-en-rGB/strings.xml
+++ b/service/res/values-en-rGB/strings.xml
@@ -182,4 +182,12 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Reset later"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"The infotainment system will reset the next time the car starts."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"The car must be parked to start the reset."</string>
+    <string name="resource_overuse_notification_title" msgid="3385149030747234969">"<xliff:g id="ID_1">^1</xliff:g> is affecting your system performance"</string>
+    <string name="resource_overuse_notification_text_disable_app" msgid="4538000369374274293">"Disable app to improve system performance. You can enable the app once again in Settings."</string>
+    <string name="resource_overuse_notification_text_prioritize_app" msgid="4782324719261106243">"Prioritise app to keep using app."</string>
+    <string name="resource_overuse_notification_text_uninstall_app" msgid="531108846448668467">"Uninstall app to improve system performance."</string>
+    <string name="resource_overuse_notification_button_disable_app" msgid="5511548570206345274">"Disable app"</string>
+    <string name="resource_overuse_notification_button_prioritize_app" msgid="5327141954014335559">"Prioritise app"</string>
+    <string name="resource_overuse_notification_button_uninstall_app" msgid="7327141273608850448">"Uninstall app"</string>
+    <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"<xliff:g id="ID_1">^1</xliff:g> has been disabled. You can enable it again in Settings."</string>
 </resources>
diff --git a/service/res/values-en-rIN/strings.xml b/service/res/values-en-rIN/strings.xml
index 8542b74..3965f97 100644
--- a/service/res/values-en-rIN/strings.xml
+++ b/service/res/values-en-rIN/strings.xml
@@ -182,4 +182,12 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Reset later"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"The infotainment system will reset the next time the car starts."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"The car must be parked to start the reset."</string>
+    <string name="resource_overuse_notification_title" msgid="3385149030747234969">"<xliff:g id="ID_1">^1</xliff:g> is affecting your system performance"</string>
+    <string name="resource_overuse_notification_text_disable_app" msgid="4538000369374274293">"Disable app to improve system performance. You can enable the app once again in Settings."</string>
+    <string name="resource_overuse_notification_text_prioritize_app" msgid="4782324719261106243">"Prioritise app to keep using app."</string>
+    <string name="resource_overuse_notification_text_uninstall_app" msgid="531108846448668467">"Uninstall app to improve system performance."</string>
+    <string name="resource_overuse_notification_button_disable_app" msgid="5511548570206345274">"Disable app"</string>
+    <string name="resource_overuse_notification_button_prioritize_app" msgid="5327141954014335559">"Prioritise app"</string>
+    <string name="resource_overuse_notification_button_uninstall_app" msgid="7327141273608850448">"Uninstall app"</string>
+    <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"<xliff:g id="ID_1">^1</xliff:g> has been disabled. You can enable it again in Settings."</string>
 </resources>
diff --git a/service/res/values-en-rXC/strings.xml b/service/res/values-en-rXC/strings.xml
index b84bee0..8804ae0 100644
--- a/service/res/values-en-rXC/strings.xml
+++ b/service/res/values-en-rXC/strings.xml
@@ -182,4 +182,12 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‏‎‏‎‏‎‏‎‎‎‎‎‎‏‏‎‏‏‎‎‏‏‏‏‏‎‎‏‏‏‏‏‏‎‎‎‏‏‏‎‎‏‏‏‏‏‎‏‎‎‎‎‎‏‏‎Reset Later‎‏‎‎‏‎"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‎‏‎‎‏‏‎‏‎‎‏‏‏‎‎‎‎‎‏‎‎‏‏‎‏‎‎‏‏‏‎‏‏‎‏‏‎‏‏‏‏‏‎‏‎‏‏‎‎‏‎‎‎‎‎The infotainment system will reset the next time the car starts.‎‏‎‎‏‎"</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‎‎‏‏‎‏‎‏‏‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‏‏‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‏‎‎‏‎The car must be parked to start the reset.‎‏‎‎‏‎"</string>
+    <string name="resource_overuse_notification_title" msgid="3385149030747234969">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‏‏‏‏‎‏‎‎‏‏‏‎‏‏‏‎‎‎‏‏‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‎‎‏‎‎‏‏‎‎‎‏‎‏‎‎‏‏‎‎‏‎‎‏‎‎‏‏‎<xliff:g id="ID_1">^1</xliff:g>‎‏‎‎‏‏‏‎ is affecting your system performance‎‏‎‎‏‎"</string>
+    <string name="resource_overuse_notification_text_disable_app" msgid="4538000369374274293">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‏‏‎‏‎‎‎‏‏‎‏‏‏‎‏‎‎‏‎‏‎‏‏‏‎‎‏‏‏‎‎‏‎‎‏‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‎‏‎Disable app to improve system performance. You can enable the app once again in Settings.‎‏‎‎‏‎"</string>
+    <string name="resource_overuse_notification_text_prioritize_app" msgid="4782324719261106243">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‏‎‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‎‎‏‎‎‎‎‎‏‏‎‎‎‎‎‎‎‏‎‎‎‎‏‏‎Prioritize app to keep using app.‎‏‎‎‏‎"</string>
+    <string name="resource_overuse_notification_text_uninstall_app" msgid="531108846448668467">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‏‎‏‏‏‎‎‎‎‎‏‎‏‎‏‏‏‎‏‎‎‏‎‎‎‏‏‎‏‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‏‏‎‎‏‏‎Uninstall app to improve system performance.‎‏‎‎‏‎"</string>
+    <string name="resource_overuse_notification_button_disable_app" msgid="5511548570206345274">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎‏‎‎‎‎‏‏‏‎‎‏‎‎‏‎‎‎‎‏‎‏‏‏‎‏‏‎‎‎‎‏‏‎‎‎‎‎‏‏‏‎‏‎‎Disable app‎‏‎‎‏‎"</string>
+    <string name="resource_overuse_notification_button_prioritize_app" msgid="5327141954014335559">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‏‎‏‏‎‏‏‏‎‎‏‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‏‎‏‏‎‏‎‎‏‎‎‏‎‎‎‏‏‏‎Prioritize app‎‏‎‎‏‎"</string>
+    <string name="resource_overuse_notification_button_uninstall_app" msgid="7327141273608850448">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‏‏‏‏‎‎‏‏‏‏‎‎‎‎‏‎‏‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‏‏‏‏‎‎‎‎‏‏‎‎‎‎‎‏‎‎‎‎‎Uninstall app‎‏‎‎‏‎"</string>
+    <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‎‎‏‎‏‏‎‎‎‎‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‏‎‎‏‏‎‎‎‎‏‎‏‏‎‎‏‎‏‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="ID_1">^1</xliff:g>‎‏‎‎‏‏‏‎ has been disabled. You can enable it again in Settings.‎‏‎‎‏‎"</string>
 </resources>
diff --git a/service/res/values-es-rUS/strings.xml b/service/res/values-es-rUS/strings.xml
index 61192df..8a75bcd 100644
--- a/service/res/values-es-rUS/strings.xml
+++ b/service/res/values-es-rUS/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Restablecer luego"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Se restablecerá el sistema cuando arranques el auto."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Estaciona el vehículo para el restablecimiento."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-es/strings.xml b/service/res/values-es/strings.xml
index 12ee40f..77e1aea 100644
--- a/service/res/values-es/strings.xml
+++ b/service/res/values-es/strings.xml
@@ -182,4 +182,12 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Restablecer más tarde"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"El sistema se restablecerá cuando arranques el coche."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"El coche debe estar aparcado para restablecerlo."</string>
+    <string name="resource_overuse_notification_title" msgid="3385149030747234969">"<xliff:g id="ID_1">^1</xliff:g> está afectando al rendimiento de tu sistema"</string>
+    <string name="resource_overuse_notification_text_disable_app" msgid="4538000369374274293">"Inhabilita la aplicación para mejorar el rendimiento del sistema. Puedes habilitarla de nuevo en Ajustes."</string>
+    <string name="resource_overuse_notification_text_prioritize_app" msgid="4782324719261106243">"Prioriza la aplicación para seguir usándola."</string>
+    <string name="resource_overuse_notification_text_uninstall_app" msgid="531108846448668467">"Desinstala la aplicación para mejorar el rendimiento del sistema."</string>
+    <string name="resource_overuse_notification_button_disable_app" msgid="5511548570206345274">"Inhabilitar aplicación"</string>
+    <string name="resource_overuse_notification_button_prioritize_app" msgid="5327141954014335559">"Priorizar aplicación"</string>
+    <string name="resource_overuse_notification_button_uninstall_app" msgid="7327141273608850448">"Desinstalar aplicación"</string>
+    <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"<xliff:g id="ID_1">^1</xliff:g> se ha inhabilitado. Puedes habilitarla de nuevo en Ajustes."</string>
 </resources>
diff --git a/service/res/values-et/strings.xml b/service/res/values-et/strings.xml
index 328f8ad..c02f324 100644
--- a/service/res/values-et/strings.xml
+++ b/service/res/values-et/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Lähtesta hiljem"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Teabe ja meelelahutuse süsteem lähtestatakse järgmisel korral, kui auto käivitatakse."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Lähtestamise alustamiseks peab auto olema pargitud."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-eu/strings.xml b/service/res/values-eu/strings.xml
index 8a7737d..496444b 100644
--- a/service/res/values-eu/strings.xml
+++ b/service/res/values-eu/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Berrezarri geroago"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Informazio- eta aisia-sistema autoa abiarazten den hurrengo aldian berrezarriko da."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Autoak aparkatuta egon behar du berrezartzea hasteko."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-fa/strings.xml b/service/res/values-fa/strings.xml
index b457177..ab34a7a 100644
--- a/service/res/values-fa/strings.xml
+++ b/service/res/values-fa/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"بعداً بازنشانی شود"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"دفعه بعدی که خودرو راه‌اندازی شد، سیستم اطلاعات-سرگرمی بازنشانی می‌شود."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"برای شروع بازنشانی، خودرو باید پارک شده باشد."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-fi/strings.xml b/service/res/values-fi/strings.xml
index 48cb46a..6bce0d6 100644
--- a/service/res/values-fi/strings.xml
+++ b/service/res/values-fi/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Palauta myöhemmin"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Järjestelmä palautetaan, kun auto taas käynnistyy."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Auto on pysäköitävä palautusta varten."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-fr-rCA/strings.xml b/service/res/values-fr-rCA/strings.xml
index 41cc1ba..c2fcf76 100644
--- a/service/res/values-fr-rCA/strings.xml
+++ b/service/res/values-fr-rCA/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Réinit. plus tard"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Le système d\'infodivertissement se réinitialisera au prochain démarrage du véhicule."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Garez-vous pour lancer la réinitialisation."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-fr/strings.xml b/service/res/values-fr/strings.xml
index 2e876bf..7fc1770 100644
--- a/service/res/values-fr/strings.xml
+++ b/service/res/values-fr/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Réinitialiser plus tard"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Le système d\'infoloisirs se réinitialisera au prochain démarrage de la voiture."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Garez-vous pour lancer la réinitialisation."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-gl/strings.xml b/service/res/values-gl/strings.xml
index fec88b2..7c66b7d 100644
--- a/service/res/values-gl/strings.xml
+++ b/service/res/values-gl/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Restablecer despois"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"O sistema restablecerase cando se volva arrincar o coche."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"O coche debe estar aparcado para restablecelo."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-gu/strings.xml b/service/res/values-gu/strings.xml
index 8146cc2..3cd0a0d 100644
--- a/service/res/values-gu/strings.xml
+++ b/service/res/values-gu/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"પછીથી રીસેટ કરો"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"આગલી વખતે જ્યારે કારને ચાલુ કરવામાં આવે ત્યારે ઇન્ફોટેનમેન્ટ સિસ્ટમ રીસેટ થશે."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"રીસેટ શરૂ કરવા માટે કાર પાર્ક કરેલી હોવી જરૂરી છે."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-hi/strings.xml b/service/res/values-hi/strings.xml
index fe135a0..e53cc67 100644
--- a/service/res/values-hi/strings.xml
+++ b/service/res/values-hi/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"बाद में रीसेट करें"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"अगली बार कार के स्टार्ट होने पर, सूचना और मनोरंजन की सुविधा देने वाला डिवाइस रीसेट होगा."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"रीसेट शुरू करने के लिए ज़रूरी है कि कार पार्क हो."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-hr/strings.xml b/service/res/values-hr/strings.xml
index b2d20a9..4e133e7 100644
--- a/service/res/values-hr/strings.xml
+++ b/service/res/values-hr/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Resetiraj kasnije"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Sustav za informiranje i zabavu resetirat će se kad pokrenete auto."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Da biste resetirali, automobil mora biti parkiran."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-hu/strings.xml b/service/res/values-hu/strings.xml
index e64c727..5cd154d 100644
--- a/service/res/values-hu/strings.xml
+++ b/service/res/values-hu/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Visszaállítás később"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"A rendszer a következő indításkor áll vissza."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"A visszaállításhoz le kell parkolni az autót."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-hy/strings.xml b/service/res/values-hy/strings.xml
index fdacf52..bebdb38 100644
--- a/service/res/values-hy/strings.xml
+++ b/service/res/values-hy/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Վերակայել ավելի ուշ"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Համակարգը կվերակայվի, երբ մեքենան հաջորդ անգամ միացնեք։"</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Վերակայումը սկսելու համար մեքենան կայանեք։"</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-in/strings.xml b/service/res/values-in/strings.xml
index befdb9f..ea35c07 100644
--- a/service/res/values-in/strings.xml
+++ b/service/res/values-in/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Reset Nanti"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Sistem infotainmen akan direset nanti."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Mobil harus diparkir untuk memulai reset."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-is/strings.xml b/service/res/values-is/strings.xml
index 6c6cb91..a7c8c2d 100644
--- a/service/res/values-is/strings.xml
+++ b/service/res/values-is/strings.xml
@@ -182,4 +182,12 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Endurstilla síðar"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Upplýsinga- og afþreyingarkerfið endurstillist næst þegar bíllinn er ræstur."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Bíllinn verður að vera kyrr til að endurstilla."</string>
+    <string name="resource_overuse_notification_title" msgid="3385149030747234969">"<xliff:g id="ID_1">^1</xliff:g> hefur áhrif á afköst kerfisins"</string>
+    <string name="resource_overuse_notification_text_disable_app" msgid="4538000369374274293">"Slökktu á forriti til að bæta afköst kerfisins. Þú getur kveikt aftur á forritinu í stillingunum."</string>
+    <string name="resource_overuse_notification_text_prioritize_app" msgid="4782324719261106243">"Forgangsraðaðu forriti til að nota forrit áfram."</string>
+    <string name="resource_overuse_notification_text_uninstall_app" msgid="531108846448668467">"Fjarlægðu forrit til að bæta afköst kerfisins."</string>
+    <string name="resource_overuse_notification_button_disable_app" msgid="5511548570206345274">"Slökkva á forriti"</string>
+    <string name="resource_overuse_notification_button_prioritize_app" msgid="5327141954014335559">"Forgangsraða forriti"</string>
+    <string name="resource_overuse_notification_button_uninstall_app" msgid="7327141273608850448">"Fjarlægja forrit"</string>
+    <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"Slökkt var á <xliff:g id="ID_1">^1</xliff:g>. Þú getur kveikt á því aftur í stillingunum."</string>
 </resources>
diff --git a/service/res/values-it/strings.xml b/service/res/values-it/strings.xml
index 566a98a..483b140 100644
--- a/service/res/values-it/strings.xml
+++ b/service/res/values-it/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Ripristina più tardi"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Il sistema di infotainment verrà ripristinato alla successiva accensione del motore."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Parcheggia per avviare il ripristino."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-iw/strings.xml b/service/res/values-iw/strings.xml
index c591716..a2d4a98 100644
--- a/service/res/values-iw/strings.xml
+++ b/service/res/values-iw/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"לאיפוס מאוחר יותר"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"מערכת המידע והבידור תתאפס בהתנעה הבאה של המכונית."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"אפשר להתחיל באיפוס רק כשהמכונית חונה."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-ja/strings.xml b/service/res/values-ja/strings.xml
index 1e8d7df..ca74e68 100644
--- a/service/res/values-ja/strings.xml
+++ b/service/res/values-ja/strings.xml
@@ -182,4 +182,12 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"後でリセット"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"インフォテインメント システムは、車の次回始動時にリセットされます。"</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"リセットを開始するには、車を駐車してください。"</string>
+    <string name="resource_overuse_notification_title" msgid="3385149030747234969">"<xliff:g id="ID_1">^1</xliff:g> がシステムのパフォーマンスに影響しています"</string>
+    <string name="resource_overuse_notification_text_disable_app" msgid="4538000369374274293">"システムのパフォーマンスを改善するには、アプリを無効にしてください。[設定] でアプリを有効に戻すことができます。"</string>
+    <string name="resource_overuse_notification_text_prioritize_app" msgid="4782324719261106243">"アプリを引き続き使用するには、優先アプリとして指定してください。"</string>
+    <string name="resource_overuse_notification_text_uninstall_app" msgid="531108846448668467">"システムのパフォーマンスを改善するには、アプリをアンインストールしてください。"</string>
+    <string name="resource_overuse_notification_button_disable_app" msgid="5511548570206345274">"アプリを無効にする"</string>
+    <string name="resource_overuse_notification_button_prioritize_app" msgid="5327141954014335559">"優先アプリとして指定する"</string>
+    <string name="resource_overuse_notification_button_uninstall_app" msgid="7327141273608850448">"アプリをアンインストールする"</string>
+    <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"<xliff:g id="ID_1">^1</xliff:g> を無効にしました。[設定] で有効に戻すことができます。"</string>
 </resources>
diff --git a/service/res/values-ka/strings.xml b/service/res/values-ka/strings.xml
index 38f615d..b2fcc4f 100644
--- a/service/res/values-ka/strings.xml
+++ b/service/res/values-ka/strings.xml
@@ -182,4 +182,12 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"მოგვიან. გადაყენება"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"გართობის/საინფორმაციო სისტემა გადაყენდება მანქანის შემდეგ დაქოქვაზე."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"გადაყენებისთვის მანქანა პარკირებული უნდა იყოს."</string>
+    <string name="resource_overuse_notification_title" msgid="3385149030747234969">"<xliff:g id="ID_1">^1</xliff:g> აფერხებს თქვენი სისტემის ეფექტურობას"</string>
+    <string name="resource_overuse_notification_text_disable_app" msgid="4538000369374274293">"სისტემის ეფექტურობის გასაუმჯობესებლად გათიშეთ აპი. აპის ხელახლა ჩართვა პარამეტრებიდან შეგიძლიათ."</string>
+    <string name="resource_overuse_notification_text_prioritize_app" msgid="4782324719261106243">"აპის გამოყენების გასაგრძელებლად საჭიროა მისი პრიორიტეტიზაცია."</string>
+    <string name="resource_overuse_notification_text_uninstall_app" msgid="531108846448668467">"სისტემის ეფექტურობის გასაუმჯობესებლად საჭიროა აპის დეინსტალაცია."</string>
+    <string name="resource_overuse_notification_button_disable_app" msgid="5511548570206345274">"აპის გათიშვა"</string>
+    <string name="resource_overuse_notification_button_prioritize_app" msgid="5327141954014335559">"აპის პრიორიტეტიზაცია"</string>
+    <string name="resource_overuse_notification_button_uninstall_app" msgid="7327141273608850448">"აპის დეინსტალაცია"</string>
+    <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"<xliff:g id="ID_1">^1</xliff:g> გაითიშა. შეგიძლიათ, ხელახლა ჩართოთ პარამეტრებში."</string>
 </resources>
diff --git a/service/res/values-kk/strings.xml b/service/res/values-kk/strings.xml
index b7c0075..32c9228 100644
--- a/service/res/values-kk/strings.xml
+++ b/service/res/values-kk/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Кейінірек қайтару"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Ақпараттық ойын-сауық жүйесі келесіде көлік оталған кезде бастапқы күйге қайтарылады."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Бастапқы күйге қайтару үшін көлікті тұраққа қою керек."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-km/strings.xml b/service/res/values-km/strings.xml
index 17187af..c9701c0 100644
--- a/service/res/values-km/strings.xml
+++ b/service/res/values-km/strings.xml
@@ -182,4 +182,12 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"ចាប់ផ្ដើមឡើងវិញពេលក្រោយ"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"ប្រព័ន្ធ​ព័ត៌មាន​ និងកម្សាន្តនឹងកំណត់ឡើងវិញ នៅពេលរថយន្តចាប់ផ្ដើមលើកក្រោយ។"</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"ត្រូវតែចតរថយន្ត ដើម្បីចាប់ផ្ដើមការកំណត់ឡើងវិញ។"</string>
+    <string name="resource_overuse_notification_title" msgid="3385149030747234969">"<xliff:g id="ID_1">^1</xliff:g> កំពុងធ្វើឱ្យ​ប៉ះពាល់ដល់​ប្រតិបត្តិការប្រព័ន្ធរបស់អ្នក"</string>
+    <string name="resource_overuse_notification_text_disable_app" msgid="4538000369374274293">"បិទកម្មវិធី ដើម្បីកែលម្អ​ប្រតិបត្តិការប្រព័ន្ធ។ អ្នកអាចបើក​កម្មវិធីម្ដងទៀត​នៅក្នុងការកំណត់។"</string>
+    <string name="resource_overuse_notification_text_prioritize_app" msgid="4782324719261106243">"កំណត់អាទិភាព​កម្មវិធី ដើម្បីបន្តប្រើ​កម្មវិធី។"</string>
+    <string name="resource_overuse_notification_text_uninstall_app" msgid="531108846448668467">"លុបកម្មវិធី ដើម្បីកែលម្អ​ប្រតិបត្តិការប្រព័ន្ធ។"</string>
+    <string name="resource_overuse_notification_button_disable_app" msgid="5511548570206345274">"បិទកម្មវិធី"</string>
+    <string name="resource_overuse_notification_button_prioritize_app" msgid="5327141954014335559">"កំណត់អាទិភាព​កម្មវិធី"</string>
+    <string name="resource_overuse_notification_button_uninstall_app" msgid="7327141273608850448">"លុប​កម្មវិធី"</string>
+    <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"<xliff:g id="ID_1">^1</xliff:g> ត្រូវបានបិទ។ អ្នកអាចបើកវា​ម្ដងទៀតនៅ​ក្នុងការកំណត់។"</string>
 </resources>
diff --git a/service/res/values-kn/strings.xml b/service/res/values-kn/strings.xml
index 2feb952..f44622e 100644
--- a/service/res/values-kn/strings.xml
+++ b/service/res/values-kn/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"ನಂತರ ರೀಸೆಟ್ ಮಾಡಿ"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"ಮುಂದಿನ ಬಾರಿ ಕಾರ್ ಆರಂಭವಾದಾಗ ಇನ್‌ಫೋಟೈನ್‌ಮೆಂಟ್ ಸಿಸ್ಟಂ ಅನ್ನು ರೀಸೆಟ್ ಮಾಡಲಾಗುತ್ತದೆ."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"ರೀಸೆಟ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಲು ಕಾರ್ ಅನ್ನು ನಿಲ್ಲಿಸಿರಬೇಕು."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-ko/strings.xml b/service/res/values-ko/strings.xml
index 2abaa2c..15cd347 100644
--- a/service/res/values-ko/strings.xml
+++ b/service/res/values-ko/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"나중에 초기화"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"다음에 차 시동이 걸리면 인포테인먼트 시스템이 초기화됩니다."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"초기화하려면 차량이 주차된 상태여야 합니다."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-ky/strings.xml b/service/res/values-ky/strings.xml
index 04afe22..67c064d 100644
--- a/service/res/values-ky/strings.xml
+++ b/service/res/values-ky/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Кийинчерээк баштапкы абалга келтирүү"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Инфозоок тутуму унаа кайра от алганда баштапкы абалга келтирилет."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Баштапкы абалга келтирүү үчүн унааны токтотуңуз."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-lo/strings.xml b/service/res/values-lo/strings.xml
index 1b60468..c146aea 100644
--- a/service/res/values-lo/strings.xml
+++ b/service/res/values-lo/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"ຣີເຊັດໃນພາຍຫຼັງ"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"ລະບົບສາລະບັນເທີງຈະຣີເຊັດໃນຄັ້ງຕໍ່ໄປທີ່ລົດຕິດຈັກ."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"ຕ້ອງຈອດລົດເພື່ອເລີ່ມຕົ້ນການຣີເຊັດ."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-lt/strings.xml b/service/res/values-lt/strings.xml
index 51ed42c..ca2051d 100644
--- a/service/res/values-lt/strings.xml
+++ b/service/res/values-lt/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Vėliau nustatyti iš naujo"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Informacinė pramoginė sistema iš naujo bus nustatyta kitą kartą užvedus automobilį."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Norint nustatyti iš naujo, automobilis turi stovėti."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-lv/strings.xml b/service/res/values-lv/strings.xml
index d1990e2..79b9f92 100644
--- a/service/res/values-lv/strings.xml
+++ b/service/res/values-lv/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Atiestatīt vēlāk"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Informatīvi izklaidējošā sistēma tiks atiestatīta, nākamreiz palaižot automašīnu."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Automašīnai jābūt apturētai, lai sāktu atiestatīšanu."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-mk/strings.xml b/service/res/values-mk/strings.xml
index 8c33039..e5b61ca 100644
--- a/service/res/values-mk/strings.xml
+++ b/service/res/values-mk/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Ресетирај подоцна"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Системот ќе се ресетира при следното вклучување."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Автомобилот мора да биде паркиран за ресетирањето."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-ml/strings.xml b/service/res/values-ml/strings.xml
index 3b44cc7..859bda9 100644
--- a/service/res/values-ml/strings.xml
+++ b/service/res/values-ml/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"പിന്നീട് റീസെറ്റ് ചെയ്യൂ"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"അടുത്ത തവണ കാർ സ്റ്റാർട്ട് ചെയ്യുമ്പോൾ ഈ ഇൻഫോറ്റേയിൻമെന്റ് സിസ്റ്റം റീസെറ്റ് ചെയ്യും."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"റീസെറ്റ് ആരംഭിക്കാൻ കാർ പാർക്ക് ചെയ്‌തിരിക്കണം."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-mn/strings.xml b/service/res/values-mn/strings.xml
index 9463cf8..26c5a62 100644
--- a/service/res/values-mn/strings.xml
+++ b/service/res/values-mn/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Дараа шинэчлэх"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Машин дараа асахад инфотэйнмент системийг шинэчилнэ"</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Шинэчилж эхлэхэд машиныг зогсоосон байх ёстой."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-mr/strings.xml b/service/res/values-mr/strings.xml
index 7a189d5..7173d57 100644
--- a/service/res/values-mr/strings.xml
+++ b/service/res/values-mr/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"नंतर रीसेट करा"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"पुढील वेळी कार सुरू झाल्यावर इंफोटेनमेंट सिस्टम रीसेट होईल."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"रीसेट सुरू करण्यासाठी कार पार्क केलेली असावी."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-ms/strings.xml b/service/res/values-ms/strings.xml
index 805a1c4..1b94ec1 100644
--- a/service/res/values-ms/strings.xml
+++ b/service/res/values-ms/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Tetapkan Semula Kemudian"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Sistem maklumat hibur akan ditetapkan semula selepas kereta dihidupkan nanti."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Kereta mesti diparkir untuk memulakan penetapan semula."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-my/strings.xml b/service/res/values-my/strings.xml
index db73b9d..6da64ef 100644
--- a/service/res/values-my/strings.xml
+++ b/service/res/values-my/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"နောက်မှ ပြင်ဆင်သတ်မှတ်ရန်"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"နောက်တစ်ကြိမ် ကားစက်နှိုးသောအခါ သတင်းနှင့်ဖျော်ဖြေရေး စနစ်ကို ပြင်ဆင်သတ်မှတ်ပါမည်။"</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"ပြင်ဆင်သတ်မှတ်မှု စတင်ရန် ကားကို ရပ်နားထားရမည်။"</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-nb/strings.xml b/service/res/values-nb/strings.xml
index 86fe916..92e6b33 100644
--- a/service/res/values-nb/strings.xml
+++ b/service/res/values-nb/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Tilbakestill senere"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Infotainment-systemet tilbakestilles neste gang bilen starter."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Bilen må være parkert før tilbakestillingen kan starte."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-ne/strings.xml b/service/res/values-ne/strings.xml
index 7697da8..6cfda97 100644
--- a/service/res/values-ne/strings.xml
+++ b/service/res/values-ne/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"पछि रिसेट गर्नुहोस्"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"अर्को पटक कार स्टार्ट हुँदा इन्फोटेनमेन्ट प्रणाली रिसेट हुने छ।"</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"रिसेट गर्न कार पार्क गरिएको अवस्थामा हुनु पर्छ।"</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-nl/strings.xml b/service/res/values-nl/strings.xml
index 7cff70e..1b8ae40 100644
--- a/service/res/values-nl/strings.xml
+++ b/service/res/values-nl/strings.xml
@@ -182,4 +182,12 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Later resetten"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Het infotainmentsysteem wordt gereset als de auto weer wordt gestart."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Parkeer de auto om de reset te starten."</string>
+    <string name="resource_overuse_notification_title" msgid="3385149030747234969">"<xliff:g id="ID_1">^1</xliff:g> is van invloed op je systeemprestaties"</string>
+    <string name="resource_overuse_notification_text_disable_app" msgid="4538000369374274293">"Zet de app uit om de systeemprestaties te verbeteren. Je kunt de app weer aanzetten via Instellingen."</string>
+    <string name="resource_overuse_notification_text_prioritize_app" msgid="4782324719261106243">"Prioriteer de app als je deze wilt blijven gebruiken."</string>
+    <string name="resource_overuse_notification_text_uninstall_app" msgid="531108846448668467">"Verwijder de app om de systeemprestaties te verbeteren."</string>
+    <string name="resource_overuse_notification_button_disable_app" msgid="5511548570206345274">"App uitzetten"</string>
+    <string name="resource_overuse_notification_button_prioritize_app" msgid="5327141954014335559">"App prioriteren"</string>
+    <string name="resource_overuse_notification_button_uninstall_app" msgid="7327141273608850448">"App verwijderen"</string>
+    <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"<xliff:g id="ID_1">^1</xliff:g> is uitgezet. Je kunt de app weer aanzetten via Instellingen."</string>
 </resources>
diff --git a/service/res/values-or/strings.xml b/service/res/values-or/strings.xml
index 0aad28e..c9794a4 100644
--- a/service/res/values-or/strings.xml
+++ b/service/res/values-or/strings.xml
@@ -182,4 +182,12 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"ପରେ ରିସେଟ୍ କରନ୍ତୁ"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"କାର୍ ଷ୍ଟାର୍ଟ ହେଲେ ଇନଫୋଟେନମେଣ୍ଟ ସିଷ୍ଟମ ରିସେଟ୍ ହେବ।"</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"ରିସେଟ୍ ଆରମ୍ଭ କରିବାକୁ କାର୍ ପାର୍କ କରାଯିବା ଆବଶ୍ୟକ।"</string>
+    <string name="resource_overuse_notification_title" msgid="3385149030747234969">"<xliff:g id="ID_1">^1</xliff:g> ଆପଣଙ୍କ ସିଷ୍ଟମର ପରଫରମାନ୍ସକୁ ପ୍ରଭାବିତ କରୁଛି"</string>
+    <string name="resource_overuse_notification_text_disable_app" msgid="4538000369374274293">"ସିଷ୍ଟମର ପରଫରମାନ୍ସକୁ ଉନ୍ନତ କରିବା ପାଇଁ ଆପକୁ ଅକ୍ଷମ କରନ୍ତୁ। ଆପଣ ସେଟିଂସରେ ପୁଣି ଥରେ ଆପକୁ ସକ୍ଷମ କରିପାରିବେ।"</string>
+    <string name="resource_overuse_notification_text_prioritize_app" msgid="4782324719261106243">"ଆପ ବ୍ୟବହାର କରିବା ଜାରି ରଖିବା ପାଇଁ ଆପକୁ ପ୍ରାଥମିକତା ଦିଅନ୍ତୁ।"</string>
+    <string name="resource_overuse_notification_text_uninstall_app" msgid="531108846448668467">"ସିଷ୍ଟମର ପରଫରମାନ୍ସକୁ ଉନ୍ନତ କରିବା ପାଇଁ ଆପକୁ ଅନଇନଷ୍ଟଲ କରନ୍ତୁ।"</string>
+    <string name="resource_overuse_notification_button_disable_app" msgid="5511548570206345274">"ଆପକୁ ଅକ୍ଷମ କରନ୍ତୁ"</string>
+    <string name="resource_overuse_notification_button_prioritize_app" msgid="5327141954014335559">"ଆପକୁ ପ୍ରାଥମିକତା ଦିଅନ୍ତୁ"</string>
+    <string name="resource_overuse_notification_button_uninstall_app" msgid="7327141273608850448">"ଆପକୁ ଅନଇନଷ୍ଟଲ କରନ୍ତୁ"</string>
+    <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"<xliff:g id="ID_1">^1</xliff:g>କୁ ଅକ୍ଷମ କରିଦିଆଯାଇଛି। ଆପଣ ସେଟିଂସରେ ପୁଣି ଏହାକୁ ସକ୍ଷମ କରିପାରିବେ।"</string>
 </resources>
diff --git a/service/res/values-pa/strings.xml b/service/res/values-pa/strings.xml
index 42a8a86..de85871 100644
--- a/service/res/values-pa/strings.xml
+++ b/service/res/values-pa/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"ਬਾਅਦ ਵਿੱਚ ਰੀਸੈੱਟ ਕਰੋ"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"ਅਗਲੀ ਵਾਰ ਕਾਰ ਦੇ ਸ਼ੁਰੂ ਹੋਣ \'ਤੇ ਵਾਹਨ ਆਡੀਓ ਸਿਸਟਮ ਰੀਸੈੱਟ ਹੋਵੇਗਾ।"</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"ਰੀਸੈੱਟ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਕਾਰ ਪਾਰਕ ਕੀਤੀ ਜਾਣੀ ਲਾਜ਼ਮੀ ਹੈ।"</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-pl/strings.xml b/service/res/values-pl/strings.xml
index 41af14a..14cabdb 100644
--- a/service/res/values-pl/strings.xml
+++ b/service/res/values-pl/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Zresetuj później"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"System zresetuje się, gdy samochód się uruchomi."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Aby rozpocząć resetowanie, zaparkuj samochód."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-pt-rPT/strings.xml b/service/res/values-pt-rPT/strings.xml
index a2714a9..f13f6c4 100644
--- a/service/res/values-pt-rPT/strings.xml
+++ b/service/res/values-pt-rPT/strings.xml
@@ -182,4 +182,12 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Repor mais tarde"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"O sist. infoentretenim. é reposto quando ligar o carro de novo."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"O carro deve estar estacionado para a reposição."</string>
+    <string name="resource_overuse_notification_title" msgid="3385149030747234969">"A app <xliff:g id="ID_1">^1</xliff:g> está a afetar o desempenho do sistema."</string>
+    <string name="resource_overuse_notification_text_disable_app" msgid="4538000369374274293">"Desative a app para melhorar o desempenho do sistema. Pode ativar a app novamente nas Definições."</string>
+    <string name="resource_overuse_notification_text_prioritize_app" msgid="4782324719261106243">"Dê prioridade à app para continuar a utilizá-la."</string>
+    <string name="resource_overuse_notification_text_uninstall_app" msgid="531108846448668467">"Desinstale a app para melhorar o desempenho do sistema."</string>
+    <string name="resource_overuse_notification_button_disable_app" msgid="5511548570206345274">"Desativar app"</string>
+    <string name="resource_overuse_notification_button_prioritize_app" msgid="5327141954014335559">"Dar prioridade à app"</string>
+    <string name="resource_overuse_notification_button_uninstall_app" msgid="7327141273608850448">"Desinstalar app"</string>
+    <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"A app <xliff:g id="ID_1">^1</xliff:g> foi desativada. Pode ativá-la novamente nas Definições."</string>
 </resources>
diff --git a/service/res/values-pt/strings.xml b/service/res/values-pt/strings.xml
index 5d86714..440bc41 100644
--- a/service/res/values-pt/strings.xml
+++ b/service/res/values-pt/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Redefinir mais tarde"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"O sistema de infoentretenimento se redefinirá ao ligar o carro."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"É preciso estacionar para iniciar a redefinição."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-ro/strings.xml b/service/res/values-ro/strings.xml
index 4be284a..dd4a523 100644
--- a/service/res/values-ro/strings.xml
+++ b/service/res/values-ro/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Resetați mai târziu"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Sistemul de infotainment se va reseta când pornește mașina."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Mașina trebuie să fie parcată pentru a reseta."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-ru/strings.xml b/service/res/values-ru/strings.xml
index 45fd8ee..230ecc4 100644
--- a/service/res/values-ru/strings.xml
+++ b/service/res/values-ru/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Сбросить позже"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Сброс произойдет при следующем запуске автомобиля."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Чтобы сбросить настройки, припаркуйте автомобиль."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-si/strings.xml b/service/res/values-si/strings.xml
index 2c4c87f..f0d4789 100644
--- a/service/res/values-si/strings.xml
+++ b/service/res/values-si/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"පසුව යළි සකසන්න"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"ඊළඟට මෝටර් රථය අරඹන විට තොරතුරු විනෝදාස්වාද පද්ධතිය යළි සැකසේ."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"යළි සැකසීම ඇරඹීමට මෝටර් රථය නවතා තැබිය යුතුය."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-sk/strings.xml b/service/res/values-sk/strings.xml
index 4483020..17cafc5 100644
--- a/service/res/values-sk/strings.xml
+++ b/service/res/values-sk/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Resetovať neskôr"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Palubný systém sa resetuje pri naštartovaní auta."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Ak chcete resetovať, auto musí byť zaparkované."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-sl/strings.xml b/service/res/values-sl/strings.xml
index 476cb85..d4bf51c 100644
--- a/service/res/values-sl/strings.xml
+++ b/service/res/values-sl/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Ponastavi pozneje"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Informativno-razvedrilni sistem se bo ponastavil ob naslednjem zagonu avta."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Za začetek ponastavitve mora biti avto parkiran."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-sq/strings.xml b/service/res/values-sq/strings.xml
index 01bc06e..bcf0d63 100644
--- a/service/res/values-sq/strings.xml
+++ b/service/res/values-sq/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Rivendose më vonë"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Sistemi info-argëtues do të rivendoset herën tjetër që do të ndizet makina."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Makina duhet të jetë e parkuar për të nisur rivendosjen."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-sr/strings.xml b/service/res/values-sr/strings.xml
index a50b736..d315923 100644
--- a/service/res/values-sr/strings.xml
+++ b/service/res/values-sr/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Ресетуј касније"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Сис. за инфо-заб. се ресет. када опет упал. кола."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Морате да паркирате кола за почетак ресетовања."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-sv/strings.xml b/service/res/values-sv/strings.xml
index d33c17f..dd65246 100644
--- a/service/res/values-sv/strings.xml
+++ b/service/res/values-sv/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Återställ senare"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Infotainmentsystemet återställs när bilen startas."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Bilen måste vara parkerad när du återställer."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-sw/strings.xml b/service/res/values-sw/strings.xml
index f577aaf..f64453c 100644
--- a/service/res/values-sw/strings.xml
+++ b/service/res/values-sw/strings.xml
@@ -182,4 +182,12 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Weka Upya Baadaye"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Mfumo wa habari na burudani utawekwa upya gari litakapowashwa."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Ni lazima uegeshe gari ili uanze kuweka upya."</string>
+    <string name="resource_overuse_notification_title" msgid="3385149030747234969">"<xliff:g id="ID_1">^1</xliff:g> inaathiri utendaji wa mfumo wako"</string>
+    <string name="resource_overuse_notification_text_disable_app" msgid="4538000369374274293">"Zima programu ili uboreshe utendaji wa mfumo. Unaweza kuwasha programu tena kwenye Mipangilio."</string>
+    <string name="resource_overuse_notification_text_prioritize_app" msgid="4782324719261106243">"Ipe programu kipaumbele ili uendelee kuitumia."</string>
+    <string name="resource_overuse_notification_text_uninstall_app" msgid="531108846448668467">"Ondoa programu ili uboreshe utendaji wa mfumo."</string>
+    <string name="resource_overuse_notification_button_disable_app" msgid="5511548570206345274">"Zima programu"</string>
+    <string name="resource_overuse_notification_button_prioritize_app" msgid="5327141954014335559">"Ipe programu kipaumbele"</string>
+    <string name="resource_overuse_notification_button_uninstall_app" msgid="7327141273608850448">"Ondoa programu"</string>
+    <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"<xliff:g id="ID_1">^1</xliff:g> imezimwa. Unaweza kuiwasha tena katika Mipangilio."</string>
 </resources>
diff --git a/service/res/values-ta/strings.xml b/service/res/values-ta/strings.xml
index c0c5981..dbb9377 100644
--- a/service/res/values-ta/strings.xml
+++ b/service/res/values-ta/strings.xml
@@ -182,4 +182,12 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"பிறகு மீட்டமை"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"அடுத்தமுறை காரை இயக்கத் தொடங்கும்போது இன்ஃபோடெயின்மென்ட் சிஸ்டம் மீட்டமைக்கப்படும்."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"மீட்டமைத்தலைத் தொடங்க காரை நிறுத்த வேண்டும்."</string>
+    <string name="resource_overuse_notification_title" msgid="3385149030747234969">"உங்கள் சிஸ்டத்தின் செயல்திறனை <xliff:g id="ID_1">^1</xliff:g> பாதிக்கிறது"</string>
+    <string name="resource_overuse_notification_text_disable_app" msgid="4538000369374274293">"சிஸ்டத்தின் செயல்திறனை மேம்படுத்த ஆப்ஸை முடக்கவும். அமைப்புகளுக்குச் சென்று மீண்டும் ஆப்ஸை இயக்கலாம்."</string>
+    <string name="resource_overuse_notification_text_prioritize_app" msgid="4782324719261106243">"ஆப்ஸைத் தொடர்ந்து பயன்படுத்த அதை முன்னுரிமைப்படுத்தவும்."</string>
+    <string name="resource_overuse_notification_text_uninstall_app" msgid="531108846448668467">"சிஸ்டத்தின் செயல்திறனை மேம்படுத்த ஆப்ஸை நிறுவல் நீக்கவும்."</string>
+    <string name="resource_overuse_notification_button_disable_app" msgid="5511548570206345274">"ஆப்ஸை முடக்கு"</string>
+    <string name="resource_overuse_notification_button_prioritize_app" msgid="5327141954014335559">"ஆப்ஸை முன்னுரிமைப்படுத்துங்கள்"</string>
+    <string name="resource_overuse_notification_button_uninstall_app" msgid="7327141273608850448">"ஆப்ஸை நிறுவல் நீக்குங்கள்"</string>
+    <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"<xliff:g id="ID_1">^1</xliff:g> முடக்கப்பட்டது. அமைப்புகளுக்குச் சென்று மீண்டும் அதை இயக்கலாம்."</string>
 </resources>
diff --git a/service/res/values-te/strings.xml b/service/res/values-te/strings.xml
index 87e6dc8..d893e87 100644
--- a/service/res/values-te/strings.xml
+++ b/service/res/values-te/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"తర్వాత రీసెట్ చేయి"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"ఇన్ఫోటైన్‌మెంట్ సిస్టం కారు స్టార్టయ్యాక రీసెట్ అవుతుంది."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"రీసెట్ ప్రారంభించడానికి మీ కారును పార్క్ చేయండి."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-th/strings.xml b/service/res/values-th/strings.xml
index 3b2ebe5..2e734d6 100644
--- a/service/res/values-th/strings.xml
+++ b/service/res/values-th/strings.xml
@@ -182,4 +182,12 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"รีเซ็ตภายหลัง"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"ระบบสาระบันเทิงจะรีเซ็ตเมื่อสตาร์ทรถในครั้งถัดไป"</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"ต้องจอดรถเพื่อเริ่มการรีเซ็ต"</string>
+    <string name="resource_overuse_notification_title" msgid="3385149030747234969">"<xliff:g id="ID_1">^1</xliff:g> กำลังส่งผลต่อประสิทธิภาพการทำงานของระบบ"</string>
+    <string name="resource_overuse_notification_text_disable_app" msgid="4538000369374274293">"ปิดใช้แอปเพื่อเพิ่มประสิทธิภาพระบบ คุณเปิดใช้แอปได้อีกครั้งในการตั้งค่า"</string>
+    <string name="resource_overuse_notification_text_prioritize_app" msgid="4782324719261106243">"ตั้งเป็นแอปสำคัญเพื่อใช้แอปต่อไป"</string>
+    <string name="resource_overuse_notification_text_uninstall_app" msgid="531108846448668467">"ถอนการติดตั้งแอปเพื่อเพิ่มประสิทธิภาพระบบ"</string>
+    <string name="resource_overuse_notification_button_disable_app" msgid="5511548570206345274">"ปิดใช้แอป"</string>
+    <string name="resource_overuse_notification_button_prioritize_app" msgid="5327141954014335559">"ตั้งเป็นแอปสำคัญ"</string>
+    <string name="resource_overuse_notification_button_uninstall_app" msgid="7327141273608850448">"ถอนการติดตั้งแอป"</string>
+    <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"ปิดใช้ <xliff:g id="ID_1">^1</xliff:g> แล้ว คุณจะเปิดใช้ได้อีกครั้งในการตั้งค่า"</string>
 </resources>
diff --git a/service/res/values-tl/strings.xml b/service/res/values-tl/strings.xml
index 2862c72..d4aa4ac 100644
--- a/service/res/values-tl/strings.xml
+++ b/service/res/values-tl/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"I-reset Mamaya"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Magre-reset ang infotainment system sa susunod na mag-start ang sasakyan."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Dapat nakaparada ang sasakyan para makapag-reset."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-tr/strings.xml b/service/res/values-tr/strings.xml
index 0a15817..79e613f 100644
--- a/service/res/values-tr/strings.xml
+++ b/service/res/values-tr/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Daha Sonra Sıfırla"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Bilgi-eğlence sistemi, araç tekrar çalıştırıldığında sıfırlanacaktır."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Sıfırlamayı başlatmak için araç park edilmelidir."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-uk/strings.xml b/service/res/values-uk/strings.xml
index 6f3cf6f..da99aec 100644
--- a/service/res/values-uk/strings.xml
+++ b/service/res/values-uk/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Скинути пізніше"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Систему буде скинуто, коли ви наступного разу заведете авто."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Налаштування не можна скинути, доки ви за кермом."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-ur/strings.xml b/service/res/values-ur/strings.xml
index c29597b..3572ef1 100644
--- a/service/res/values-ur/strings.xml
+++ b/service/res/values-ur/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"بعد میں ری سیٹ کریں"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"اگلی بار کار کے شروع ہونے پر معلوماتی انٹرٹینمنٹ سسٹم ری سیٹ ہو جائے گا۔"</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"ری سیٹ شروع کرنے کیلئے کار کا پارک ہونا ضروری ہے۔"</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-uz/strings.xml b/service/res/values-uz/strings.xml
index c4a0941..5654872 100644
--- a/service/res/values-uz/strings.xml
+++ b/service/res/values-uz/strings.xml
@@ -182,4 +182,12 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Keyinroq bajarish"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Axborot-hordiq tizimi avtomobil yonganda tiklanadi"</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Asliga qaytarish uchun avtomobilni toʻxtating."</string>
+    <string name="resource_overuse_notification_title" msgid="3385149030747234969">"<xliff:g id="ID_1">^1</xliff:g> ilovasi tizim unumdorligiga taʼsir qilmoqda."</string>
+    <string name="resource_overuse_notification_text_disable_app" msgid="4538000369374274293">"Tizim unumdorligini oshirish uchun ilovani faolsizlantiring. Ilovani Sozlamalar oqali qayta yoqishingiz mumkin."</string>
+    <string name="resource_overuse_notification_text_prioritize_app" msgid="4782324719261106243">"Foydalanishda davom etish uchun ilovaga ustunlik bering."</string>
+    <string name="resource_overuse_notification_text_uninstall_app" msgid="531108846448668467">"Tizim unumdorligini yaxshilash uchun ilovani oʻchirib tashlang."</string>
+    <string name="resource_overuse_notification_button_disable_app" msgid="5511548570206345274">"Ilovani faolsizlantirish"</string>
+    <string name="resource_overuse_notification_button_prioritize_app" msgid="5327141954014335559">"Ilovaga ustunlik berish"</string>
+    <string name="resource_overuse_notification_button_uninstall_app" msgid="7327141273608850448">"Ilovani oʻchirib tashlash"</string>
+    <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"<xliff:g id="ID_1">^1</xliff:g> faolsizlantirildi. Uni sozlamalar orqali qayta yoqishingiz mumkin."</string>
 </resources>
diff --git a/service/res/values-vi/strings.xml b/service/res/values-vi/strings.xml
index 8ace1ce..312bdb5 100644
--- a/service/res/values-vi/strings.xml
+++ b/service/res/values-vi/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Đặt lại sau"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Hệ thống thông tin giải trí sẽ được đặt lại vào lần khởi động xe tiếp theo."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Bạn phải đỗ xe để bắt đầu quá trình đặt lại."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-zh-rCN/strings.xml b/service/res/values-zh-rCN/strings.xml
index 0497b25..b1006c1 100644
--- a/service/res/values-zh-rCN/strings.xml
+++ b/service/res/values-zh-rCN/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"以后再重置"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"信息娱乐系统将在下次汽车发动时重置。"</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"必须停车才能启动重置流程。"</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-zh-rHK/strings.xml b/service/res/values-zh-rHK/strings.xml
index d54e578..9e21222 100644
--- a/service/res/values-zh-rHK/strings.xml
+++ b/service/res/values-zh-rHK/strings.xml
@@ -182,4 +182,12 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"稍後重設"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"資訊娛樂系統將在汽車下次啟動時重設。"</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"必須泊車才能開始重設。"</string>
+    <string name="resource_overuse_notification_title" msgid="3385149030747234969">"<xliff:g id="ID_1">^1</xliff:g> 正在影響系統效能"</string>
+    <string name="resource_overuse_notification_text_disable_app" msgid="4538000369374274293">"停用應用程式即可改善系統效能。您可以在「設定」中再次啟用應用程式。"</string>
+    <string name="resource_overuse_notification_text_prioritize_app" msgid="4782324719261106243">"優先處理應用程式即可繼續使用應用程式。"</string>
+    <string name="resource_overuse_notification_text_uninstall_app" msgid="531108846448668467">"解除安裝應用程式即可改善系統效能。"</string>
+    <string name="resource_overuse_notification_button_disable_app" msgid="5511548570206345274">"停用應用程式"</string>
+    <string name="resource_overuse_notification_button_prioritize_app" msgid="5327141954014335559">"優先處理應用程式"</string>
+    <string name="resource_overuse_notification_button_uninstall_app" msgid="7327141273608850448">"解除安裝應用程式"</string>
+    <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"「<xliff:g id="ID_1">^1</xliff:g>」已停用。您可以在「設定」中再次啟用。"</string>
 </resources>
diff --git a/service/res/values-zh-rTW/strings.xml b/service/res/values-zh-rTW/strings.xml
index b305673..64d9ee7 100644
--- a/service/res/values-zh-rTW/strings.xml
+++ b/service/res/values-zh-rTW/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"稍後重設"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"資訊娛樂系統將在下次車輛發動時重設。"</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"必須停車才能啟動重設程序。"</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values-zu/strings.xml b/service/res/values-zu/strings.xml
index bf5e9d46..110c878 100644
--- a/service/res/values-zu/strings.xml
+++ b/service/res/values-zu/strings.xml
@@ -182,4 +182,20 @@
     <string name="factory_reset_later_button" msgid="2401829720674483843">"Setha Kabusha Kamuva"</string>
     <string name="factory_reset_later_text" msgid="5896142140528784784">"Isistimu ye-infotainment izosethwa kabusha ngesikhathi esizayo lapho imoto iqala."</string>
     <string name="factory_reset_driving_text" msgid="6702298505761254553">"Imoto kufanele ipakwe ukuze uqale ukusetha kabusha."</string>
+    <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
+    <skip />
+    <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
+    <skip />
+    <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
+    <skip />
 </resources>
diff --git a/service/res/values/strings.xml b/service/res/values/strings.xml
index 0709784..416981b 100644
--- a/service/res/values/strings.xml
+++ b/service/res/values/strings.xml
@@ -584,4 +584,20 @@
     <string name="new_user_managed_device_acceptance" translatable="false">Accept and continue</string>
     <string name="new_user_managed_notification_title" translatable="false">Managed device</string>
 
+    <!-- Title of notification shown when an app overuses system resources [CHAR LIMIT=100] -->
+    <string name="resource_overuse_notification_title"><xliff:g name="app_name" example="Maps">^1</xliff:g> is affecting your system performance</string>
+    <!-- Message of notification shown when an app overuses system resources [CHAR LIMIT=NONE] -->
+    <string name="resource_overuse_notification_text_disable_app">Disable app to improve system performance. You can enable the app once again in Settings.</string>
+    <!-- Message of notification shown when an app overuses system resources [CHAR LIMIT=NONE] -->
+    <string name="resource_overuse_notification_text_prioritize_app">Prioritize app to keep using app.</string>
+    <!-- Message of notification shown when an app overuses system resources [CHAR LIMIT=NONE] -->
+    <string name="resource_overuse_notification_text_uninstall_app">Uninstall app to improve system performance.</string>
+    <!-- Label for button that will disable the app now [CHAR LIMIT=30] -->
+    <string name="resource_overuse_notification_button_disable_app">Disable app</string>
+    <!-- Label for button that will redirect user to prioritize app setting [CHAR LIMIT=30] -->
+    <string name="resource_overuse_notification_button_prioritize_app">Prioritize app</string>
+    <!-- Label for button that will redirect user to uninstall app setting [CHAR LIMIT=30] -->
+    <string name="resource_overuse_notification_button_uninstall_app">Uninstall app</string>
+    <!-- Text of the toast shown when the app is disabled [CHAR_LIMIT=100]-->
+    <string name="resource_overuse_toast_disable_app_now"><xliff:g name="app_name" example="Maps">^1</xliff:g> has been disabled. You can enable it again in Settings.</string>
 </resources>
diff --git a/service/src/com/android/car/CarShellCommand.java b/service/src/com/android/car/CarShellCommand.java
index 2e16d10..f1fec38 100644
--- a/service/src/com/android/car/CarShellCommand.java
+++ b/service/src/com/android/car/CarShellCommand.java
@@ -41,6 +41,7 @@
 import android.car.Car;
 import android.car.CarOccupantZoneManager;
 import android.car.VehiclePropertyIds;
+import android.car.content.pm.CarPackageManager;
 import android.car.input.CarInputManager;
 import android.car.input.CustomInputEvent;
 import android.car.input.RotaryEvent;
@@ -205,6 +206,9 @@
     private static final String COMMAND_WATCHDOG_CONTROL_PROCESS_HEALTH_CHECK =
             "watchdog-control-health-check";
 
+    private static final String COMMAND_DRIVING_SAFETY_SET_REGION =
+            "set-drivingsafety-region";
+
     private static final String[] CREATE_OR_MANAGE_USERS_PERMISSIONS = new String[] {
             android.Manifest.permission.CREATE_USERS,
             android.Manifest.permission.MANAGE_USERS
@@ -637,6 +641,10 @@
         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.");
+
+        pw.printf("\t%s [REGION_STRING]", COMMAND_DRIVING_SAFETY_SET_REGION);
+        pw.println("\t  Set driving safety region.");
+        pw.println("\t  Skipping REGION_STRING leads into resetting to all regions");
     }
 
     private static int showInvalidArguments(IndentingPrintWriter pw) {
@@ -972,6 +980,9 @@
             case COMMAND_WATCHDOG_CONTROL_PROCESS_HEALTH_CHECK:
                 controlWatchdogProcessHealthCheck(args, writer);
                 break;
+            case COMMAND_DRIVING_SAFETY_SET_REGION:
+                setDrivingSafetyRegion(args, writer);
+                break;
             default:
                 writer.println("Unknown command: \"" + cmd + "\"");
                 showHelp(writer);
@@ -2155,6 +2166,17 @@
         }
     }
 
+    private void setDrivingSafetyRegion(String[] args, IndentingPrintWriter writer) {
+        if (args.length != 1 && args.length != 2) {
+            showInvalidArguments(writer);
+            return;
+        }
+        String region = args.length == 2 ? args[1] : CarPackageManager.DRIVING_SAFETY_REGION_ALL;
+        writer.println("Set driving safety region to:" + region);
+        CarLocalServices.getService(CarPackageManagerService.class).resetDrivingSafetyRegion(
+                region);
+    }
+
     private void getRearviewCameraId(IndentingPrintWriter writer) {
         writer.printf("CarEvsService is using %s for the rearview.\n",
                 mCarEvsService.getRearviewCameraIdFromCommand());
diff --git a/service/src/com/android/car/FastPairGattServer.java b/service/src/com/android/car/FastPairGattServer.java
index 6cbaae0..6fc537d 100644
--- a/service/src/com/android/car/FastPairGattServer.java
+++ b/service/src/com/android/car/FastPairGattServer.java
@@ -86,6 +86,7 @@
     private static final boolean DBG = FastPairUtils.DBG;
     private static final int MAX_KEY_COUNT = 10;
     private static final int KEY_LIFESPAN = 10_000;
+    private static final int INVALID = -1;
 
     private final boolean mAutomaticPasskeyConfirmation;
     private final byte[] mModelId;
@@ -95,7 +96,7 @@
     private ArrayList<AccountKey> mKeys = new ArrayList<>();
     private BluetoothGattServer mBluetoothGattServer;
     private BluetoothManager mBluetoothManager;
-    private int mPairingPasskey = -1;
+    private int mPairingPasskey = INVALID;
     private int mFailureCount = 0;
     private int mSuccessCount = 0;
     private BluetoothGattService mFastPairService = new BluetoothGattService(
@@ -149,7 +150,9 @@
         @Override
         public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
             super.onConnectionStateChange(device, status, newState);
-            Log.d(TAG, "onConnectionStateChange " + newState + "Device: " + device.toString());
+            if (DBG) {
+                Log.d(TAG, "onConnectionStateChange " + newState + "Device: " + device.toString());
+            }
             if (newState == 0) {
                 mPairingPasskey = -1;
                 mSharedSecretKey = null;
@@ -249,12 +252,13 @@
                 Log.d(TAG, intent.getAction());
             }
             if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(intent.getAction())) {
-                if (DBG) {
-                    Log.d(TAG, "PairingCode " + intent
-                                    .getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, -1));
-                }
                 mRemotePairingDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-                mPairingPasskey = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, -1);
+                mPairingPasskey = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, INVALID);
+                if (DBG) {
+                    Log.d(TAG, "DeviceAddress: " + mRemotePairingDevice
+                            + " PairingCode: " + mPairingPasskey);
+                }
+                sendPairingResponse(mPairingPasskey);
             }
         }
     };
@@ -309,6 +313,9 @@
         } catch (Exception e) {
             Log.e(TAG, e.toString());
         }
+        if (DBG) {
+            Log.w(TAG, "Encryption Failed, clear key");
+        }
         mHandler.removeCallbacks(mClearSharedSecretKey);
         mSharedSecretKey = null;
         return null;
@@ -439,7 +446,7 @@
 
         byte[] encryptedRequest = Arrays.copyOfRange(pairingRequest, 0, 16);
         if (DBG) {
-            Log.d(TAG, "Checking " + possibleKeys.size() + "Keys");
+            Log.d(TAG, "Checking " + possibleKeys.size() + " Keys");
         }
         // check all the keys for a valid pairing request
         for (SecretKeySpec key : possibleKeys) {
@@ -486,7 +493,7 @@
                     .getRemoteDevice(remoteAddressBytes);
             if (DBG) {
                 Log.d(TAG, "Local RPA = " + mLocalRpaDevice);
-                Log.d(TAG, "Decrypted, LocalMacAddress" + localAddress + "remoteAddress"
+                Log.d(TAG, "Decrypted, LocalMacAddress: " + localAddress + " remoteAddress: "
                         + reportedDevice.toString());
             }
             // Test that the received device address matches this devices address
@@ -494,7 +501,6 @@
                 if (DBG) {
                     Log.d(TAG, "SecretKey Validated");
                 }
-
                 // encrypt and respond to the seeker with the local public address
                 byte[] rawResponse = new byte[16];
                 new Random().nextBytes(rawResponse);
@@ -536,16 +542,23 @@
                 }
                 mRemotePairingDevice.setPairingConfirmation(true);
             }
-        } else {
+        } else if (mPairingPasskey != INVALID) {
             Log.w(TAG, "Passkeys don't match, rejecting");
             mRemotePairingDevice.setPairingConfirmation(false);
         }
+        return true;
+    }
 
+    void sendPairingResponse(int passkey) {
+        if (!isConnected()) return;
+        if (DBG) {
+            Log.d(TAG, "sendPairingResponse + " + passkey);
+        }
         // Send an encrypted response to the seeker with the Bluetooth passkey as required
         byte[] decryptedResponse = new byte[16];
         new Random().nextBytes(decryptedResponse);
         ByteBuffer pairingPasskeyBytes = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(
-                mPairingPasskey);
+                passkey);
         decryptedResponse[0] = 0x3;
         decryptedResponse[1] = pairingPasskeyBytes.get(1);
         decryptedResponse[2] = pairingPasskeyBytes.get(2);
@@ -553,11 +566,9 @@
 
         mEncryptedResponse = encrypt(decryptedResponse);
         if (mEncryptedResponse == null) {
-            return false;
+            return;
         }
         mPasskeyCharacteristic.setValue(mEncryptedResponse);
-        return true;
-
     }
 
     /**
diff --git a/service/src/com/android/car/FastPairProvider.java b/service/src/com/android/car/FastPairProvider.java
index dc63058..90529d4 100644
--- a/service/src/com/android/car/FastPairProvider.java
+++ b/service/src/com/android/car/FastPairProvider.java
@@ -18,6 +18,7 @@
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -48,6 +49,7 @@
     private final Context mContext;
     private boolean mStarted;
     private int mScanMode;
+    private BluetoothAdapter mBluetoothAdapter;
     private FastPairAdvertiser mFastPairModelAdvertiser;
     private FastPairAdvertiser mFastPairAccountAdvertiser;
     private FastPairGattServer mFastPairGattServer;
@@ -93,7 +95,11 @@
                         Slog.d(TAG, "NewScanMode = " + mScanMode);
                     }
                     if (mScanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
-                        advertiseModelId();
+                        if (mBluetoothAdapter.isDiscovering()) {
+                            advertiseModelId();
+                        } else {
+                            stopAdvertising();
+                        }
                     } else if (mScanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE
                             && mFastPairGattServer != null
                             && !mFastPairGattServer.isConnected()) {
@@ -130,6 +136,7 @@
         mModelId = res.getInteger(R.integer.fastPairModelId);
         mAntiSpoofKey = res.getString(R.string.fastPairAntiSpoofKey);
         mAutomaticAcceptance = res.getBoolean(R.bool.fastPairAutomaticAcceptance);
+        mBluetoothAdapter = mContext.getSystemService(BluetoothManager.class).getAdapter();
     }
 
     /**
@@ -161,6 +168,20 @@
         }
     }
 
+    void stopAdvertising() {
+        mFastPairAdvertiserHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (mFastPairAccountAdvertiser != null) {
+                    mFastPairAccountAdvertiser.stopAdvertising();
+                }
+                if (mFastPairModelAdvertiser != null) {
+                    mFastPairModelAdvertiser.stopAdvertising();
+                }
+            }
+        });
+    }
+
     void advertiseModelId() {
         mFastPairAdvertiserHandler.post(new Runnable() {
             @Override
@@ -195,7 +216,6 @@
                 mFastPairAccountAdvertiser.advertiseAccountKeys();
             }
         });
-
     }
 
     void startGatt() {
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/am/FixedActivityService.java b/service/src/com/android/car/am/FixedActivityService.java
index bbc76ff..c969fc1 100644
--- a/service/src/com/android/car/am/FixedActivityService.java
+++ b/service/src/com/android/car/am/FixedActivityService.java
@@ -61,6 +61,7 @@
 import com.android.car.R;
 import com.android.car.user.CarUserService;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.List;
 
@@ -417,12 +418,20 @@
                 return false;
             }
             for (int i = mRunningActivities.size() - 1; i >= 0; i--) {
+                int displayIdForActivity = mRunningActivities.keyAt(i);
+                Display display = mDm.getDisplay(displayIdForActivity);
+                if (display == null) {
+                    Slog.e(TAG_AM, "Stop fixed activity for non-available display"
+                            + displayIdForActivity);
+                    mRunningActivities.removeAt(i);
+                    continue;
+                }
+
                 RunningActivityInfo activityInfo = mRunningActivities.valueAt(i);
                 activityInfo.isVisible = false;
                 if (isUserAllowedToLaunchActivity(activityInfo.userId)) {
                     continue;
                 }
-                final int displayIdForActivity = mRunningActivities.keyAt(i);
                 if (activityInfo.taskId != INVALID_TASK_ID) {
                     Slog.i(TAG_AM, "Finishing fixed activity on user switching:"
                             + activityInfo);
@@ -432,12 +441,6 @@
                         Slog.e(TAG_AM, "remote exception from AM", e);
                     }
                     CarServiceUtils.runOnMain(() -> {
-                        Display display = mDm.getDisplay(displayIdForActivity);
-                        if (display == null) {
-                            Slog.e(TAG_AM, "Display not available, cannot launnch window:"
-                                    + displayIdForActivity);
-                            return;
-                        }
                         Presentation p = new Presentation(mContext, display,
                                 android.R.style.Theme_Black_NoTitleBar_Fullscreen,
                                 // TYPE_PRESENTATION can't be used in the internal display.
@@ -546,7 +549,8 @@
         }
     }
 
-    private void launchIfNecessary() {
+    @VisibleForTesting
+    void launchIfNecessary() {
         launchIfNecessary(Display.INVALID_DISPLAY);
     }
 
@@ -609,6 +613,13 @@
         return true;
     }
 
+    @VisibleForTesting
+    RunningActivityInfo getRunningFixedActivity(int displayId) {
+        synchronized (mLock) {
+            return mRunningActivities.get(displayId);
+        }
+    }
+
     /**
      * Checks {@link InstrumentClusterRenderingService#startFixedActivityModeForDisplayAndUser(
      * Intent, ActivityOptions, int)}
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index f6a98d6..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) {
diff --git a/service/src/com/android/car/cluster/ClusterHomeService.java b/service/src/com/android/car/cluster/ClusterHomeService.java
index b032d93..3af771a 100644
--- a/service/src/com/android/car/cluster/ClusterHomeService.java
+++ b/service/src/com/android/car/cluster/ClusterHomeService.java
@@ -84,6 +84,7 @@
     private Insets mInsets = Insets.NONE;
     private int mUiType = ClusterHomeManager.UI_TYPE_CLUSTER_HOME;
     private Intent mLastIntent;
+    private int mLastIntentUserId = UserHandle.USER_SYSTEM;
 
     private final RemoteCallbackList<IClusterHomeCallback> mClientCallbacks =
             new RemoteCallbackList<>();
@@ -126,6 +127,7 @@
     private void initClusterDisplay() {
         int clusterDisplayId = mOccupantZoneService.getDisplayIdForDriver(
                 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER);
+        Slogf.d(TAG, "initClusterDisplay: displayId=%d", clusterDisplayId);
         if (clusterDisplayId == Display.INVALID_DISPLAY) {
             Slogf.i(TAG, "No cluster display is defined");
         }
@@ -152,7 +154,7 @@
         ActivityOptions activityOptions = ActivityOptions.makeBasic()
                 .setLaunchDisplayId(clusterDisplayId);
         mFixedActivityService.startFixedActivityModeForDisplayAndUser(
-                mLastIntent, activityOptions, clusterDisplayId, UserHandle.USER_SYSTEM);
+                mLastIntent, activityOptions, clusterDisplayId, mLastIntentUserId);
     }
 
     private final ICarOccupantZoneCallback mOccupantZoneCallback =
@@ -292,6 +294,7 @@
     @Override
     public boolean startFixedActivityModeAsUser(Intent intent,
             Bundle activityOptionsBundle, int userId) {
+        Slogf.d(TAG, "startFixedActivityModeAsUser: intent=%s, userId=%d", intent, userId);
         enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL);
         if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled");
         if (mClusterDisplayId == Display.INVALID_DISPLAY) {
@@ -302,6 +305,7 @@
         ActivityOptions activityOptions = ActivityOptions.fromBundle(activityOptionsBundle);
         activityOptions.setLaunchDisplayId(mClusterDisplayId);
         mLastIntent = intent;
+        mLastIntentUserId = userId;
         return mFixedActivityService.startFixedActivityModeForDisplayAndUser(
                 intent, activityOptions, mClusterDisplayId, userId);
     }
diff --git a/service/src/com/android/car/evs/CarEvsService.java b/service/src/com/android/car/evs/CarEvsService.java
index 1b19249..5c1a8ab 100644
--- a/service/src/com/android/car/evs/CarEvsService.java
+++ b/service/src/com/android/car/evs/CarEvsService.java
@@ -200,6 +200,9 @@
             synchronized (mLock) {
                 if (requestActivityIfNecessaryLocked()) {
                     Slog.i(TAG_EVS, "Requested to launch the activity.");
+                } else {
+                    // Ensure we stops streaming
+                    mStateEngine.execute(REQUEST_PRIORITY_HIGH, SERVICE_STATE_INACTIVE);
                 }
             }
         }
@@ -591,6 +594,9 @@
                 return;
             }
 
+            // Notify the client that the stream has ended.
+            notifyStreamStopped(callback);
+
             unlinkToDeathStreamCallbackLocked();
             mStreamCallback = null;
             Slog.i(TAG_EVS, "Last stream client has been disconnected.");
@@ -625,6 +631,8 @@
             return false;
         }
 
+        // Request to launch an activity again after cleaning up
+        mStateEngine.execute(REQUEST_PRIORITY_HIGH, SERVICE_STATE_INACTIVE);
         mStateEngine.execute(REQUEST_PRIORITY_HIGH, SERVICE_STATE_REQUESTED,
                 mLastEvsHalEvent.getServiceType());
         return true;
@@ -708,8 +716,8 @@
 
         synchronized (mLock) {
             int targetState = on ? SERVICE_STATE_REQUESTED : SERVICE_STATE_INACTIVE;
-            if (mStateEngine.execute(REQUEST_PRIORITY_HIGH, targetState, type) !=
-                    ERROR_NONE) {
+            if (mStateEngine.execute(REQUEST_PRIORITY_HIGH, targetState, type, /* token = */ null,
+                    mStreamCallback) != ERROR_NONE) {
                 Slog.e(TAG_EVS, "Failed to execute a service request.");
             }
 
@@ -1234,23 +1242,30 @@
         }
     }
 
-    /** Processes a streaming event and propagates it to registered clients */
-    private void processNewFrame(int id, @NonNull HardwareBuffer buffer) {
+    /**
+     * Processes a streaming event and propagates it to registered clients.
+     *
+     * @return True if this buffer is hold and used by the client, false otherwise.
+     */
+    private boolean processNewFrame(int id, @NonNull HardwareBuffer buffer) {
         Objects.requireNonNull(buffer);
 
         synchronized (mLock) {
-            mBufferRecords.add(id);
             if (mStreamCallback == null) {
-                return;
+                return false;
             }
 
             try {
                 mStreamCallback.onNewFrame(new CarEvsBufferDescriptor(id, buffer));
+                mBufferRecords.add(id);
             } catch (RemoteException e) {
                 // Likely the binder death incident
                 Slog.e(TAG_EVS, Log.getStackTraceString(e));
+                return false;
             }
         }
+
+        return true;
     }
 
     /** EVS stream event handler called after a native handler */
@@ -1261,7 +1276,11 @@
 
     /** EVS frame handler called after a native handler */
     private void postNativeFrameHandler(int id, HardwareBuffer buffer) {
-        processNewFrame(id, buffer);
+        if (!processNewFrame(id, buffer)) {
+            // No client uses this buffer.
+            Slog.d(TAG_EVS, "Returns buffer " + id + " because no client uses it.");
+            nativeDoneWithFrame(mNativeEvsServiceObj, id);
+        }
     }
 
     /** EVS service death handler called after a native handler */
diff --git a/service/src/com/android/car/hal/CarPropertyUtils.java b/service/src/com/android/car/hal/CarPropertyUtils.java
index 9ac2745..146a4fb 100644
--- a/service/src/com/android/car/hal/CarPropertyUtils.java
+++ b/service/src/com/android/car/hal/CarPropertyUtils.java
@@ -260,6 +260,12 @@
         int areaType = getVehicleAreaType(p.prop & VehicleArea.MASK);
 
         Class<?> clazz = getJavaClass(p.prop & VehiclePropertyType.MASK);
+        float maxSampleRate = 0f;
+        float minSampleRate = 0f;
+        if (p.changeMode != CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC) {
+            maxSampleRate = p.maxSampleRate;
+            minSampleRate = p.minSampleRate;
+        }
         if (p.areaConfigs.isEmpty()) {
             return CarPropertyConfig
                     .newBuilder(clazz, propertyId, areaType, /* capacity */ 1)
@@ -268,8 +274,8 @@
                     .setChangeMode(p.changeMode)
                     .setConfigArray(p.configArray)
                     .setConfigString(p.configString)
-                    .setMaxSampleRate(p.maxSampleRate)
-                    .setMinSampleRate(p.minSampleRate)
+                    .setMaxSampleRate(maxSampleRate)
+                    .setMinSampleRate(minSampleRate)
                     .build();
         } else {
             CarPropertyConfig.Builder builder = CarPropertyConfig
@@ -278,8 +284,8 @@
                     .setChangeMode(p.changeMode)
                     .setConfigArray(p.configArray)
                     .setConfigString(p.configString)
-                    .setMaxSampleRate(p.maxSampleRate)
-                    .setMinSampleRate(p.minSampleRate);
+                    .setMaxSampleRate(maxSampleRate)
+                    .setMinSampleRate(minSampleRate);
 
             for (VehicleAreaConfig area : p.areaConfigs) {
                 if (classMatched(Integer.class, clazz)) {
diff --git a/service/src/com/android/car/pm/CarAppMetadataReader.java b/service/src/com/android/car/pm/CarAppMetadataReader.java
index 2b67f25..0e7c136 100644
--- a/service/src/com/android/car/pm/CarAppMetadataReader.java
+++ b/service/src/com/android/car/pm/CarAppMetadataReader.java
@@ -15,19 +15,25 @@
  */
 package com.android.car.pm;
 
+import static android.car.content.pm.CarPackageManager.DRIVING_SAFETY_ACTIVITY_METADATA_REGIONS;
+
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.car.content.pm.CarPackageManager;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.Bundle;
-import android.util.Log;
 import android.util.Slog;
 
 import com.android.car.CarLog;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -49,19 +55,13 @@
     /** Name of the meta-data attribute of the Activity that denotes distraction optimized */
     private static final String DO_METADATA_ATTRIBUTE = "distractionOptimized";
 
-    /**
-     * Parses the given package and returns Distraction Optimized information, if present.
-     *
-     * @return Array of DO activity names in the given package
-     */
-    @Nullable
-    public static String[] findDistractionOptimizedActivitiesAsUser(Context context,
-            String packageName, int userId) throws NameNotFoundException {
-        final PackageManager pm = context.getPackageManager();
+    private static final List<String> ALL_REGION_ONLY = Collections.singletonList(
+            CarPackageManager.DRIVING_SAFETY_REGION_ALL);
 
-        // Check if any of the activities in the package are DO by checking all the
-        // <activity> elements. MATCH_DISABLED_COMPONENTS is included so that we are immediately
-        // prepared to respond to any components that toggle from disabled to enabled.
+    @Nullable
+    private static ActivityInfo[] getAllActivitiesForPackageAsUser(Context context,
+            String packageName, @UserIdInt int userId)  throws NameNotFoundException {
+        final PackageManager pm = context.getPackageManager();
         PackageInfo pkgInfo =
                 pm.getPackageInfoAsUser(
                         packageName, PackageManager.GET_ACTIVITIES
@@ -74,18 +74,43 @@
             return null;
         }
 
-        ActivityInfo[] activities = pkgInfo.activities;
+        return pkgInfo.activities;
+    }
+
+    /**
+     * Parses the given package and returns Distraction Optimized information, if present.
+     *
+     * @return Array of DO activity names in the given package
+     */
+    @Nullable
+    public static String[] findDistractionOptimizedActivitiesAsUser(Context context,
+            String packageName, @UserIdInt int userId, @NonNull String drivingSafetyRegion)
+            throws NameNotFoundException {
+
+
+        // Check if any of the activities in the package are DO by checking all the
+        // <activity> elements. MATCH_DISABLED_COMPONENTS is included so that we are immediately
+        // prepared to respond to any components that toggle from disabled to enabled.
+        ActivityInfo[] activities = getAllActivitiesForPackageAsUser(context, packageName, userId);
         if (activities == null) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
+            if (CarPackageManagerService.DBG) {
                 Slog.d(TAG, "Null Activities for " + packageName);
             }
             return null;
         }
         List<String> optimizedActivityList = new ArrayList();
         for (ActivityInfo activity : activities) {
-            Bundle mData = activity.metaData;
-            if (mData != null && mData.getBoolean(DO_METADATA_ATTRIBUTE, false)) {
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Bundle metaData = activity.metaData;
+            if (metaData == null) {
+                continue;
+            }
+            String regionString = metaData.getString(DRIVING_SAFETY_ACTIVITY_METADATA_REGIONS,
+                    CarPackageManager.DRIVING_SAFETY_REGION_ALL);
+            if (!isRegionSupported(regionString, drivingSafetyRegion)) {
+                continue;
+            }
+            if (metaData.getBoolean(DO_METADATA_ATTRIBUTE, false)) {
+                if (CarPackageManagerService.DBG) {
                     Slog.d(TAG,
                             "DO Activity:" + activity.packageName + "/" + activity.name);
                 }
@@ -97,4 +122,66 @@
         }
         return optimizedActivityList.toArray(new String[optimizedActivityList.size()]);
     }
+
+    /**
+     * Check {@link CarPackageManager#getSupportedDrivingSafetyRegionsForActivityAsUser(
+     * String, String, int)}.
+     */
+    public static List<String> getSupportedDrivingSafetyRegionsForActivityAsUser(Context context,
+            String packageName, String activityClassName, @UserIdInt int userId)
+            throws NameNotFoundException {
+        ActivityInfo[] activities = getAllActivitiesForPackageAsUser(context, packageName, userId);
+        if (activities == null) {
+            throw new NameNotFoundException();
+        }
+        for (ActivityInfo info: activities) {
+            if (!info.name.equals(activityClassName)) {
+                continue;
+            }
+            // Found activity
+            Bundle metaData = info.metaData;
+            if (metaData == null) {
+                return Collections.EMPTY_LIST;
+            }
+            if (!metaData.getBoolean(DO_METADATA_ATTRIBUTE, false)) {
+                // no distractionOptimized, so region metadata does not matter.
+                return Collections.EMPTY_LIST;
+            }
+            String regionString = metaData.getString(DRIVING_SAFETY_ACTIVITY_METADATA_REGIONS,
+                    CarPackageManager.DRIVING_SAFETY_REGION_ALL);
+            String[] regions = regionString.split(",");
+            for (String region: regions) {
+                if (CarPackageManager.DRIVING_SAFETY_REGION_ALL.equals(region)) {
+                    return ALL_REGION_ONLY;
+                }
+            }
+            return Arrays.asList(regions);
+        }
+        throw new NameNotFoundException();
+    }
+
+    private static boolean isRegionSupported(String regionString, String currentRegion) {
+        if (regionString.isEmpty()) { // not specified means all regions.
+            return true;
+        }
+        if (currentRegion.equals(CarPackageManager.DRIVING_SAFETY_REGION_ALL)) {
+            return true;
+        }
+        String[] regions = regionString.split(",");
+        for (String region: regions) {
+            if (CarPackageManager.DRIVING_SAFETY_REGION_ALL.equals(region)) {
+                return true;
+            }
+            if (currentRegion.equals(region)) {
+                return true;
+            }
+        }
+        // valid regions but does not match currentRegion.
+        if (CarPackageManagerService.DBG) {
+            Slog.d(TAG,
+                    "isRegionSupported not supported, regionString:" + regionString
+                            + " region:" + currentRegion);
+        }
+        return false;
+    }
 }
diff --git a/service/src/com/android/car/pm/CarPackageManagerService.java b/service/src/com/android/car/pm/CarPackageManagerService.java
index 10288b1..e8e4315 100644
--- a/service/src/com/android/car/pm/CarPackageManagerService.java
+++ b/service/src/com/android/car/pm/CarPackageManagerService.java
@@ -32,6 +32,11 @@
 import android.car.content.pm.ICarPackageManager;
 import android.car.drivingstate.CarUxRestrictions;
 import android.car.drivingstate.ICarUxRestrictionsChangeListener;
+import android.car.hardware.power.CarPowerPolicy;
+import android.car.hardware.power.CarPowerPolicyFilter;
+import android.car.hardware.power.ICarPowerPolicyListener;
+import android.car.hardware.power.PowerComponent;
+import android.car.user.CarUserManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -57,6 +62,8 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.ArraySet;
@@ -69,6 +76,7 @@
 import android.view.Display;
 import android.view.DisplayAddress;
 
+import com.android.car.CarLocalServices;
 import com.android.car.CarLog;
 import com.android.car.CarServiceBase;
 import com.android.car.CarServiceUtils;
@@ -76,6 +84,8 @@
 import com.android.car.R;
 import com.android.car.SystemActivityMonitoringService;
 import com.android.car.SystemActivityMonitoringService.TopTaskInfoContainer;
+import com.android.car.power.CarPowerManagementService;
+import com.android.car.user.CarUserService;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.utils.Slogf;
@@ -99,6 +109,8 @@
 
 public class CarPackageManagerService extends ICarPackageManager.Stub implements CarServiceBase {
 
+    static final boolean DBG = false;
+
     private static final String TAG = CarLog.tagFor(CarPackageManagerService.class);
 
     // Delimiters to parse packages and activities in the configuration XML resource.
@@ -107,6 +119,9 @@
     private static final int LOG_SIZE = 20;
     private static final String[] WINDOW_DUMP_ARGUMENTS = new String[]{"windows"};
 
+    private static final String PROPERTY_RO_DRIVING_SAFETY_REGION =
+            "ro.android.car.drivingsafetyregion";
+
     private final Context mContext;
     private final SystemActivityMonitoringService mSystemActivityMonitoringService;
     private final PackageManager mPackageManager;
@@ -153,8 +168,15 @@
     @GuardedBy("mLock")
     private final LinkedList<CarAppBlockingPolicy> mWaitingPolicies = new LinkedList<>();
 
+    @GuardedBy("mLock")
+    private String mCurrentDrivingSafetyRegion = CarPackageManager.DRIVING_SAFETY_REGION_ALL;
+    // Package name + '/' + className format
+    @GuardedBy("mLock")
+    private final ArraySet<String> mTempAllowedActivities = new ArraySet<>();
+
     private final CarUxRestrictionsManagerService mCarUxRestrictionsService;
-    private boolean mEnableActivityBlocking;
+    private final boolean mEnableActivityBlocking;
+
     private final ComponentName mActivityBlockingActivity;
     private final boolean mPreventTemplatedAppsFromShowingDialog;
     private final String mTemplateActivityClassName;
@@ -211,6 +233,32 @@
      */
     public static final String BLOCKING_INTENT_EXTRA_DISPLAY_ID = "display_id";
 
+    private final CarUserManager.UserLifecycleListener mUserLifecycleListener =
+            new CarUserManager.UserLifecycleListener() {
+                @Override
+                public void onEvent(CarUserManager.UserLifecycleEvent event) {
+                    if (event.getEventType()
+                            == CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING) {
+                        synchronized (mLock) {
+                            resetTempAllowedActivitiesLocked();
+                        }
+                    }
+                }
+            };
+
+    private final ICarPowerPolicyListener mDisplayPowerPolicyListener =
+            new ICarPowerPolicyListener.Stub() {
+                @Override
+                public void onPolicyChanged(CarPowerPolicy policy,
+                        CarPowerPolicy accumulatedPolicy) {
+                    if (!policy.isComponentEnabled(PowerComponent.DISPLAY)) {
+                        synchronized (mLock) {
+                            resetTempAllowedActivitiesLocked();
+                        }
+                    }
+                }
+            };
+
     public CarPackageManagerService(Context context,
             CarUxRestrictionsManagerService uxRestrictionsService,
             SystemActivityMonitoringService systemActivityMonitoringService) {
@@ -256,13 +304,92 @@
         mSystemActivityMonitoringService.restartTask(taskId);
     }
 
-    private void doSetAppBlockingPolicy(String packageName, CarAppBlockingPolicy policy,
-            int flags) {
+    @Override
+    public String getCurrentDrivingSafetyRegion() {
+        assertAppBlockingOrDrivingStatePermission();
+        synchronized (mLock) {
+            return mCurrentDrivingSafetyRegion;
+        }
+    }
+
+    private String getComponentNameString(String packageName, String className) {
+        return packageName + '/' + className;
+    }
+
+    @Override
+    public void controlOneTimeActivityBlockingBypassingAsUser(String packageName,
+            String activityClassName, boolean bypass, @UserIdInt int userId) {
+        assertAppBlockingPermission();
+        if (!callerCanQueryPackage(packageName)) {
+            throw new SecurityException("cannot query other package");
+        }
+        try {
+            // Read this to check the validity of pkg / activity name. Not checking this can allow
+            // bad apps to be allowed later.
+            CarAppMetadataReader.getSupportedDrivingSafetyRegionsForActivityAsUser(mContext,
+                    packageName, activityClassName, userId);
+        } catch (NameNotFoundException e) {
+            throw new ServiceSpecificException(CarPackageManager.ERROR_CODE_NO_PACKAGE,
+                    e.getMessage());
+        }
+        String componentName = getComponentNameString(packageName, activityClassName);
+        synchronized (mLock) {
+            if (bypass) {
+                mTempAllowedActivities.add(componentName);
+            } else {
+                mTempAllowedActivities.remove(componentName);
+            }
+        }
+        if (!bypass) { // block top activities if bypassing is disabled.
+            blockTopActivitiesIfNecessary();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void resetTempAllowedActivitiesLocked() {
+        mTempAllowedActivities.clear();
+    }
+
+    @Override
+    public List<String> getSupportedDrivingSafetyRegionsForActivityAsUser(String packageName,
+            String activityClassName, @UserIdInt int userId) {
+        assertAppBlockingOrDrivingStatePermission();
+        if (!callerCanQueryPackage(packageName)) {
+            throw new SecurityException("cannot query other package");
+        }
+        long token = Binder.clearCallingIdentity();
+        try {
+            return CarAppMetadataReader.getSupportedDrivingSafetyRegionsForActivityAsUser(mContext,
+                    packageName, activityClassName, userId);
+        } catch (NameNotFoundException e) {
+            throw new ServiceSpecificException(CarPackageManager.ERROR_CODE_NO_PACKAGE,
+                    e.getMessage());
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private void assertAppBlockingOrDrivingStatePermission() {
+        if (mContext.checkCallingOrSelfPermission(Car.PERMISSION_CONTROL_APP_BLOCKING)
+                != PackageManager.PERMISSION_GRANTED && mContext.checkCallingOrSelfPermission(
+                Car.PERMISSION_CAR_DRIVING_STATE) != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException(
+                    "requires permission " + Car.PERMISSION_CONTROL_APP_BLOCKING + " or "
+                            + Car.PERMISSION_CAR_DRIVING_STATE);
+        }
+    }
+
+    private void assertAppBlockingPermission() {
         if (mContext.checkCallingOrSelfPermission(Car.PERMISSION_CONTROL_APP_BLOCKING)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException(
                     "requires permission " + Car.PERMISSION_CONTROL_APP_BLOCKING);
         }
+    }
+
+    private void doSetAppBlockingPolicy(String packageName, CarAppBlockingPolicy policy,
+            int flags) {
+        assertAppBlockingPermission();
         CarServiceUtils.assertPackageName(mContext, packageName);
         if (policy == null) {
             throw new IllegalArgumentException("policy cannot be null");
@@ -302,6 +429,11 @@
                 Slog.d(TAG, "isActivityDistractionOptimized" + dumpPoliciesLocked(false));
             }
 
+            if (mTempAllowedActivities.contains(getComponentNameString(packageName,
+                    className))) {
+                return true;
+            }
+
             for (int i = mTopActivityWithDialogPerDisplay.size() - 1; i >= 0; i--) {
                 ComponentName activityWithDialog = mTopActivityWithDialogPerDisplay.get(
                         mTopActivityWithDialogPerDisplay.keyAt(i));
@@ -493,13 +625,24 @@
 
     @Override
     public void init() {
+        String safetyRegion = SystemProperties.get(PROPERTY_RO_DRIVING_SAFETY_REGION, "");
         synchronized (mLock) {
+            setDrivingSafetyRegionWithCheckLocked(safetyRegion);
             mHandler.requestInit();
         }
+        CarLocalServices.getService(CarUserService.class).addUserLifecycleListener(
+                mUserLifecycleListener);
+        CarLocalServices.getService(CarPowerManagementService.class).addPowerPolicyListener(
+                new CarPowerPolicyFilter.Builder().setComponents(PowerComponent.DISPLAY).build(),
+                mDisplayPowerPolicyListener);
     }
 
     @Override
     public void release() {
+        CarLocalServices.getService(CarPowerManagementService.class).removePowerPolicyListener(
+                mDisplayPowerPolicyListener);
+        CarLocalServices.getService(CarUserService.class).removeUserLifecycleListener(
+                mUserLifecycleListener);
         synchronized (mLock) {
             mHandler.requestRelease();
             // wait for release do be done. This guarantees that init is done.
@@ -519,6 +662,7 @@
                 mProxies.clear();
             }
             mWaitingPolicies.clear();
+            resetTempAllowedActivitiesLocked();
             mLock.notifyAll();
         }
         mContext.unregisterReceiver(mPackageParsingEventReceiver);
@@ -529,6 +673,28 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private void setDrivingSafetyRegionWithCheckLocked(String region) {
+        if (region.isEmpty()) {
+            mCurrentDrivingSafetyRegion = CarPackageManager.DRIVING_SAFETY_REGION_ALL;
+        } else {
+            mCurrentDrivingSafetyRegion = region;
+        }
+    }
+
+    /**
+     * Reset driving stat and all dynamically added allow list so that region information for
+     * all packages are reset. This also resets one time allow list.
+     */
+    public void resetDrivingSafetyRegion(@NonNull String region) {
+        synchronized (mLock) {
+            setDrivingSafetyRegionWithCheckLocked(region);
+            resetTempAllowedActivitiesLocked();
+            mActivityAllowlistMap.clear();
+            mActivityDenylistPackages.clear();
+        }
+    }
+
     // run from HandlerThread
     private void doHandleInit() {
         startAppBlockingPolicies();
@@ -771,9 +937,8 @@
         }
 
         try {
-            String[] doActivities =
-                    CarAppMetadataReader.findDistractionOptimizedActivitiesAsUser(
-                            mContext, info.packageName, userId);
+            String[] doActivities = findDistractionOptimizedActivitiesAsUser(info.packageName,
+                    userId);
             if (doActivities != null) {
                 // Some of the activities in this app are Distraction Optimized.
                 if (Log.isLoggable(TAG, Log.DEBUG)) {
@@ -842,10 +1007,14 @@
 
         synchronized (mLock) {
             if (wrapper != null) {
-                Slog.d(TAG, "Package: " + packageName + " added in allowlist.");
+                if (DBG) {
+                    Slog.d(TAG, "Package: " + packageName + " added in allowlist.");
+                }
                 mActivityAllowlistMap.put(packageName, wrapper);
             } else {
-                Slog.d(TAG, "Package: " + packageName + " added in denylist.");
+                if (DBG) {
+                    Slog.d(TAG, "Package: " + packageName + " added in denylist.");
+                }
                 mActivityDenylistPackages.add(packageName);
             }
         }
@@ -1026,9 +1195,9 @@
         synchronized (mLock) {
             writer.println("*CarPackageManagerService*");
             writer.println("mEnableActivityBlocking:" + mEnableActivityBlocking);
-            writer.println("mPreventTemplatedAppsFromShowingDialog"
+            writer.println("mPreventTemplatedAppsFromShowingDialog:"
                     + mPreventTemplatedAppsFromShowingDialog);
-            writer.println("mTemplateActivityClassName" + mTemplateActivityClassName);
+            writer.println("mTemplateActivityClassName:" + mTemplateActivityClassName);
             List<String> restrictions = new ArrayList<>(mUxRestrictionsListeners.size());
             for (int i = 0; i < mUxRestrictionsListeners.size(); i++) {
                 int displayId = mUxRestrictionsListeners.keyAt(i);
@@ -1040,6 +1209,10 @@
             writer.println(" Blocked activity log:");
             mBlockedActivityLogs.dump(writer);
             writer.print(dumpPoliciesLocked(true));
+            writer.print("mCurrentDrivingSafetyRegion:");
+            writer.println(mCurrentDrivingSafetyRegion);
+            writer.print("mTempAllowedActivities:");
+            writer.println(mTempAllowedActivities);
         }
     }
 
@@ -1158,13 +1331,11 @@
         if (allowed) {
             return;
         }
-        synchronized (mLock) {
-            if (!mEnableActivityBlocking) {
-                Slog.d(TAG, "Current activity " + topTask.topActivity
-                        + " not allowed, blocking disabled. Number of tasks in stack:"
-                        + topTask.taskInfo.childTaskIds.length);
-                return;
-            }
+        if (!mEnableActivityBlocking) {
+            Slog.d(TAG, "Current activity " + topTask.topActivity
+                    + " not allowed, blocking disabled. Number of tasks in stack:"
+                    + topTask.taskInfo.childTaskIds.length);
+            return;
         }
         if (Log.isLoggable(TAG, Log.DEBUG)) {
             Slog.d(TAG, "Current activity " + topTask.topActivity
@@ -1343,13 +1514,23 @@
     @Nullable
     public String[] getDistractionOptimizedActivities(String pkgName) {
         try {
-            return CarAppMetadataReader.findDistractionOptimizedActivitiesAsUser(mContext, pkgName,
+            return findDistractionOptimizedActivitiesAsUser(pkgName,
                     mActivityManager.getCurrentUser());
         } catch (NameNotFoundException e) {
             return null;
         }
     }
 
+    private String[] findDistractionOptimizedActivitiesAsUser(String pkgName, int userId)
+            throws NameNotFoundException {
+        String regionString;
+        synchronized (mLock) {
+            regionString = mCurrentDrivingSafetyRegion;
+        }
+        return CarAppMetadataReader.findDistractionOptimizedActivitiesAsUser(mContext, pkgName,
+                userId, regionString);
+    }
+
     /**
      * Reading policy and setting policy can take time. Run it in a separate handler thread.
      */
diff --git a/service/src/com/android/car/power/SilentModeHandler.java b/service/src/com/android/car/power/SilentModeHandler.java
index 2631c01..bb78810 100644
--- a/service/src/com/android/car/power/SilentModeHandler.java
+++ b/service/src/com/android/car/power/SilentModeHandler.java
@@ -39,9 +39,9 @@
  * Class to handle Silent Mode and Non-Silent Mode.
  *
  * <p>This monitors {@code /sys/power/pm_silentmode_hw_state} to figure out when to switch to Silent
- * Mode and updates {@code /sys/power/pm_silentmode_kernel} to tell early-init services about Silent
- * Mode change. Also, it handles forced Silent Mode for testing purpose, which is given through
- * reboot reason.
+ * Mode and updates {@code /sys/power/pm_silentmode_kernel_state} to tell early-init services about
+ * Silent Mode change. Also, it handles forced Silent Mode for testing purpose, which is given
+ * through reboot reason.
  */
 final class SilentModeHandler {
     static final String SILENT_MODE_FORCED_SILENT = "forced-silent";
@@ -53,7 +53,7 @@
     private static final String SYSFS_FILENAME_HW_STATE_MONITORING =
             "/sys/power/pm_silentmode_hw_state";
     private static final String SYSFS_FILENAME_KERNEL_SILENTMODE =
-            "/sys/power/pm_silentmode_kernel";
+            "/sys/power/pm_silentmode_kernel_state";
     private static final String VALUE_SILENT_MODE = "1";
     private static final String VALUE_NON_SILENT_MODE = "0";
     private static final String SYSTEM_BOOT_REASON = "sys.boot.reason";
diff --git a/service/src/com/android/car/telemetry/CarTelemetryService.java b/service/src/com/android/car/telemetry/CarTelemetryService.java
index 07ae879..f48d8d8 100644
--- a/service/src/com/android/car/telemetry/CarTelemetryService.java
+++ b/service/src/com/android/car/telemetry/CarTelemetryService.java
@@ -15,34 +15,44 @@
  */
 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_NONE;
+import static android.car.telemetry.CarTelemetryManager.ERROR_METRICS_CONFIG_PARSE_FAILED;
+import static android.car.telemetry.CarTelemetryManager.ERROR_METRICS_CONFIG_UNKNOWN;
 
 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.os.Handler;
 import android.os.HandlerThread;
+import android.os.PersistableBundle;
+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.car.CarServiceUtils;
 import com.android.car.systeminterface.SystemInterface;
-import com.android.internal.annotations.GuardedBy;
+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.io.ByteArrayOutputStream;
 import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
+import java.io.IOException;
 
 /**
  * CarTelemetryService manages OEM telemetry collection, processing and communication
@@ -50,45 +60,55 @@
  */
 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;
-    private final File mRootDirectory;
+    private final CarPropertyService mCarPropertyService;
     private final HandlerThread mTelemetryThread = CarServiceUtils.getHandlerThread(
             CarTelemetryService.class.getSimpleName());
     private final Handler mTelemetryHandler = new Handler(mTelemetryThread.getLooper());
-    @GuardedBy("mLock")
-    private final Map<String, Integer> mNameVersionMap = new HashMap<>();
-    private final Object mLock = new Object();
 
-    @GuardedBy("mLock")
     private ICarTelemetryServiceListener mListener;
+    private DataBroker mDataBroker;
+    private DataBrokerController mDataBrokerController;
     private MetricsConfigStore mMetricsConfigStore;
+    private PublisherFactory mPublisherFactory;
+    private ResultStore mResultStore;
+    private StatsManagerProxy mStatsManagerProxy;
+    private SystemMonitor mSystemMonitor;
 
-    public CarTelemetryService(Context context) {
+    public CarTelemetryService(Context context, CarPropertyService carPropertyService) {
         mContext = context;
-        SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class);
-        // full root directory path is /data/system/car/telemetry
-        mRootDirectory = new File(systemInterface.getSystemCarDir(), TELEMETRY_DIR);
+        mCarPropertyService = carPropertyService;
     }
 
     @Override
     public void init() {
         mTelemetryHandler.post(() -> {
-            mMetricsConfigStore = new MetricsConfigStore(mRootDirectory);
-            mMetricsConfigStore.getActiveMetricsConfigs();
-            // TODO(b/197343030): mDataBroker.addMetricsConfig to start metrics collection
+            SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class);
+            // full root directory path is /data/system/car/telemetry
+            File rootDirectory = new File(systemInterface.getSystemCarDir(), TELEMETRY_DIR);
+            // initialize all necessary components
+            mMetricsConfigStore = new MetricsConfigStore(rootDirectory);
+            mResultStore = new ResultStore(rootDirectory);
+            mStatsManagerProxy = new StatsManagerImpl(
+                    mContext.getSystemService(StatsManager.class));
+            mPublisherFactory = new PublisherFactory(mCarPropertyService, mTelemetryHandler,
+                    mStatsManagerProxy, rootDirectory);
+            mDataBroker = new DataBrokerImpl(mContext, mPublisherFactory, mResultStore);
+            mSystemMonitor = SystemMonitor.create(mContext, mTelemetryHandler);
+            // controller starts metrics collection after boot complete
+            mDataBrokerController = new DataBrokerController(mDataBroker, mTelemetryHandler,
+                    mMetricsConfigStore, mSystemMonitor,
+                    systemInterface.getSystemStateInterface());
         });
     }
 
     @Override
     public void release() {
-        // nothing to do
+        // TODO(b/197969149): prevent threading issue, block main thread
+        mTelemetryHandler.post(() -> mResultStore.flushToDisk());
     }
 
     @Override
@@ -104,9 +124,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;
+        });
     }
 
     /**
@@ -115,150 +138,181 @@
     @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. This method assumes
+     * {@link #setListener(ICarTelemetryServiceListener)} is called. Otherwise it does nothing.
      *
-     * @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");
+        mTelemetryHandler.post(() -> {
+            if (mListener == null) {
+                Slog.w(CarLog.TAG_TELEMETRY, "ICarTelemetryServiceListener is not set");
+                return;
+            }
+            Slog.d(CarLog.TAG_TELEMETRY, "Adding metrics config " + key.getName()
+                    + " to car telemetry service");
+            TelemetryProto.MetricsConfig metricsConfig = null;
+            int status = ERROR_METRICS_CONFIG_UNKNOWN;
+            try {
+                metricsConfig = TelemetryProto.MetricsConfig.parseFrom(config);
+            } catch (InvalidProtocolBufferException e) {
+                Slog.e(CarLog.TAG_TELEMETRY, "Failed to parse MetricsConfig.", e);
+                status = ERROR_METRICS_CONFIG_PARSE_FAILED;
+            }
+            // if config can be parsed, add it to persistent storage
+            if (metricsConfig != null) {
+                status = mMetricsConfigStore.addMetricsConfig(metricsConfig);
+                // TODO(b/199410900): update logic once metrics configs have expiration dates
+                mDataBroker.addMetricsConfiguration(metricsConfig);
+            }
+            // If no error (a config is successfully added), script results from an older version
+            // should be deleted
+            if (status == ERROR_METRICS_CONFIG_NONE) {
+                mResultStore.deleteResult(key.getName());
+            }
+            try {
+                mListener.onAddMetricsConfigStatus(key, status);
+            } catch (RemoteException e) {
+                Slog.w(CarLog.TAG_TELEMETRY, "error with ICarTelemetryServiceListener", e);
+            }
+        });
     }
 
     /**
-     * Removes a manifest based on the key.
+     * Removes a metrics config based on the key. This will also remove outputs produced by the
+     * MetricsConfig. This method assumes {@link #setListener(ICarTelemetryServiceListener)} is
+     * called. Otherwise it does nothing.
+     *
+     * @param key the unique identifier of a MetricsConfig.
      */
     @Override
-    public boolean removeManifest(@NonNull ManifestKey key) {
-        mContext.enforceCallingOrSelfPermission(
-                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
-        synchronized (mLock) {
-            return removeManifestLocked(key);
-        }
+    public void removeMetricsConfig(@NonNull MetricsConfigKey key) {
+        mTelemetryHandler.post(() -> {
+            if (mListener == null) {
+                Slog.w(CarLog.TAG_TELEMETRY, "ICarTelemetryServiceListener is not set");
+                return;
+            }
+            Slog.d(CarLog.TAG_TELEMETRY, "Removing metrics config " + key.getName()
+                    + " from car telemetry service");
+            // TODO(b/198792767): Check both config name and config version for deletion
+            // TODO(b/199540952): Stop and remove config from data broker
+            mResultStore.deleteResult(key.getName()); // delete the config's script results
+            boolean success = mMetricsConfigStore.deleteMetricsConfig(key.getName());
+            try {
+                mListener.onRemoveMetricsConfigStatus(key, success);
+            } catch (RemoteException e) {
+                Slog.w(CarLog.TAG_TELEMETRY, "error with ICarTelemetryServiceListener", e);
+            }
+        });
     }
 
     /**
-     * Removes all manifests.
+     * Removes all MetricsConfigs. This will also remove all MetricsConfig outputs.
      */
     @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(() -> {
+            // TODO(b/199540952): Stop and remove all configs from DataBroker
+            Slog.d(CarLog.TAG_TELEMETRY,
+                    "Removing all metrics config from car telemetry service");
+            mMetricsConfigStore.deleteAllMetricsConfigs();
+            mResultStore.deleteAllResults();
+        });
     }
 
     /**
      * Sends script results associated with the given key using the
-     * {@link ICarTelemetryServiceListener}.
+     * {@link ICarTelemetryServiceListener}. This method assumes listener is set. Otherwise it
+     * does nothing.
+     *
+     * @param key the unique identifier of a MetricsConfig.
      */
     @Override
-    public void sendFinishedReports(@NonNull ManifestKey key) {
-        // TODO(b/184087869): Implement
+    public void sendFinishedReports(@NonNull MetricsConfigKey key) {
         mContext.enforceCallingOrSelfPermission(
-                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
-        if (DEBUG) {
-            Slog.d(TAG, "Flushing reports for a manifest");
-        }
+                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "sendFinishedReports");
+        mTelemetryHandler.post(() -> {
+            if (mListener == null) {
+                Slog.w(CarLog.TAG_TELEMETRY, "ICarTelemetryServiceListener is not set");
+                return;
+            }
+            if (DEBUG) {
+                Slog.d(CarLog.TAG_TELEMETRY,
+                        "Flushing reports for metrics config " + key.getName());
+            }
+            PersistableBundle result = mResultStore.getFinalResult(key.getName(), true);
+            TelemetryProto.TelemetryError error = mResultStore.getError(key.getName(), true);
+            if (result != null) {
+                sendFinalResult(key, result);
+            } else if (error != null) {
+                sendError(key, error);
+            } else {
+                Slog.w(CarLog.TAG_TELEMETRY, "config " + key.getName()
+                        + " did not produce any results");
+            }
+        });
     }
 
     /**
-     * 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");
+    private void sendFinalResult(MetricsConfigKey key, PersistableBundle result) {
+        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+            result.writeToStream(bos);
+            mListener.onResult(key, bos.toByteArray());
+        } catch (RemoteException e) {
+            Slog.w(CarLog.TAG_TELEMETRY, "error with ICarTelemetryServiceListener", e);
+        } catch (IOException e) {
+            Slog.w(CarLog.TAG_TELEMETRY, "failed to write bundle to output stream", e);
         }
     }
 
-    @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;
+    private void sendError(MetricsConfigKey key, TelemetryProto.TelemetryError error) {
         try {
-            metricsConfig = TelemetryProto.MetricsConfig.parseFrom(configProto);
-        } catch (InvalidProtocolBufferException e) {
-            Slog.e(TAG, "Failed to parse MetricsConfig.", e);
-            return ERROR_PARSE_MANIFEST_FAILED;
+            mListener.onError(key, error.toByteArray());
+        } catch (RemoteException e) {
+            Slog.w(CarLog.TAG_TELEMETRY, "error with ICarTelemetryServiceListener", e);
         }
-        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;
+    @VisibleForTesting
+    Handler getTelemetryHandler() {
+        return mTelemetryHandler;
     }
 
-    @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
+    ResultStore getResultStore() {
+        return mResultStore;
+    }
+
+    @VisibleForTesting
+    MetricsConfigStore getMetricsConfigStore() {
+        return mMetricsConfigStore;
     }
 }
diff --git a/service/src/com/android/car/telemetry/MetricsConfigStore.java b/service/src/com/android/car/telemetry/MetricsConfigStore.java
index 10125d7..ff17d0e 100644
--- a/service/src/com/android/car/telemetry/MetricsConfigStore.java
+++ b/service/src/com/android/car/telemetry/MetricsConfigStore.java
@@ -16,6 +16,12 @@
 
 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;
@@ -26,39 +32,46 @@
 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 worker thread.
+ * accessed on the telemetry thread.
  */
-class MetricsConfigStore {
+public 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) {
+    public MetricsConfigStore(File rootDirectory) {
         mConfigDirectory = new File(rootDirectory, METRICS_CONFIG_DIR);
         mConfigDirectory.mkdirs();
-    }
-
-    /**
-     * Returns all active {@link TelemetryProto.MetricsConfig} from disk.
-     */
-    List<TelemetryProto.MetricsConfig> getActiveMetricsConfigs() {
+        mActiveConfigs = new ArrayMap<>();
+        mNameVersionMap = new ArrayMap<>();
         // TODO(b/197336485): Add expiration date check for MetricsConfig
-        List<TelemetryProto.MetricsConfig> activeConfigs = new ArrayList<>();
         for (File file : mConfigDirectory.listFiles()) {
             try {
                 byte[] serializedConfig = Files.readAllBytes(file.toPath());
-                activeConfigs.add(TelemetryProto.MetricsConfig.parseFrom(serializedConfig));
+                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();
             }
         }
-        return activeConfigs;
+    }
+
+    /**
+     * Returns all active {@link TelemetryProto.MetricsConfig} from disk.
+     */
+    public List<TelemetryProto.MetricsConfig> getActiveMetricsConfigs() {
+        return new ArrayList<>(mActiveConfigs.values());
     }
 
     /**
@@ -67,8 +80,17 @@
      * @param metricsConfig the config to be persisted to disk.
      * @return true if the MetricsConfig should start receiving data, false otherwise.
      */
-    boolean addMetricsConfig(TelemetryProto.MetricsConfig metricsConfig) {
-        // TODO(b/197336485): Check version and expiration date for MetricsConfig
+    public 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;
+        }
+        mActiveConfigs.put(metricsConfig.getName(), metricsConfig);
+        mNameVersionMap.put(metricsConfig.getName(), metricsConfig.getVersion());
         try {
             Files.write(
                     new File(mConfigDirectory, metricsConfig.getName()).toPath(),
@@ -76,12 +98,23 @@
         } 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 true;
+        return ERROR_METRICS_CONFIG_NONE;
     }
 
     /** Deletes the MetricsConfig from disk. Returns the success status. */
-    boolean deleteMetricsConfig(String metricsConfigName) {
+    public boolean deleteMetricsConfig(String metricsConfigName) {
+        mActiveConfigs.remove(metricsConfigName);
+        mNameVersionMap.remove(metricsConfigName);
         return new File(mConfigDirectory, metricsConfigName).delete();
     }
+
+    /** Deletes all MetricsConfigs from disk. */
+    public void deleteAllMetricsConfigs() {
+        mActiveConfigs.clear();
+        for (File file : mConfigDirectory.listFiles()) {
+            file.delete();
+        }
+    }
 }
diff --git a/service/src/com/android/car/telemetry/ResultStore.java b/service/src/com/android/car/telemetry/ResultStore.java
index 27cb50a..9c8b2fe 100644
--- a/service/src/com/android/car/telemetry/ResultStore.java
+++ b/service/src/com/android/car/telemetry/ResultStore.java
@@ -16,18 +16,16 @@
 
 package com.android.car.telemetry;
 
-import android.os.Handler;
 import android.os.PersistableBundle;
 import android.util.ArrayMap;
 import android.util.Slog;
 
 import com.android.car.CarLog;
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.util.Map;
@@ -35,6 +33,7 @@
 
 /**
  * Disk storage for interim and final metrics statistics.
+ * All methods in this class should be invoked from the telemetry thread.
  */
 public class ResultStore {
 
@@ -43,34 +42,30 @@
     @VisibleForTesting
     static final String INTERIM_RESULT_DIR = "interim";
     @VisibleForTesting
+    static final String ERROR_RESULT_DIR = "error";
+    @VisibleForTesting
     static final String FINAL_RESULT_DIR = "final";
 
-    private final Object mLock = new Object();
     /** Map keys are MetricsConfig names, which are also the file names in disk. */
-    @GuardedBy("mLock")
     private final Map<String, InterimResult> mInterimResultCache = new ArrayMap<>();
 
     private final File mInterimResultDirectory;
+    private final File mErrorResultDirectory;
     private final File mFinalResultDirectory;
-    private final Handler mWorkerHandler; // for all non I/O operations
-    private final Handler mIoHandler; // for all I/O operations
 
-    ResultStore(Handler handler, Handler ioHandler, File rootDirectory) {
-        mWorkerHandler = handler;
-        mIoHandler = ioHandler;
+    ResultStore(File rootDirectory) {
         mInterimResultDirectory = new File(rootDirectory, INTERIM_RESULT_DIR);
+        mErrorResultDirectory = new File(rootDirectory, ERROR_RESULT_DIR);
         mFinalResultDirectory = new File(rootDirectory, FINAL_RESULT_DIR);
         mInterimResultDirectory.mkdirs();
+        mErrorResultDirectory.mkdirs();
         mFinalResultDirectory.mkdirs();
         // load results into memory to reduce the frequency of disk access
-        synchronized (mLock) {
-            loadInterimResultsIntoMemoryLocked();
-        }
+        loadInterimResultsIntoMemory();
     }
 
     /** Reads interim results into memory for faster access. */
-    @GuardedBy("mLock")
-    private void loadInterimResultsIntoMemoryLocked() {
+    private void loadInterimResultsIntoMemory() {
         for (File file : mInterimResultDirectory.listFiles()) {
             try (FileInputStream fis = new FileInputStream(file)) {
                 mInterimResultCache.put(
@@ -78,7 +73,7 @@
                         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
+                // TODO(b/197153560): record failure
             }
         }
     }
@@ -88,12 +83,10 @@
      * {@link com.android.car.telemetry.TelemetryProto.MetricsConfig}.
      */
     public PersistableBundle getInterimResult(String metricsConfigName) {
-        synchronized (mLock) {
-            if (!mInterimResultCache.containsKey(metricsConfigName)) {
-                return null;
-            }
-            return mInterimResultCache.get(metricsConfigName).getBundle();
+        if (!mInterimResultCache.containsKey(metricsConfigName)) {
+            return null;
         }
+        return mInterimResultCache.get(metricsConfigName).getBundle();
     }
 
     /**
@@ -101,11 +94,7 @@
      * {@link com.android.car.telemetry.TelemetryProto.MetricsConfig}.
      */
     public void putInterimResult(String metricsConfigName, PersistableBundle result) {
-        synchronized (mLock) {
-            mInterimResultCache.put(
-                    metricsConfigName,
-                    new InterimResult(result, /* dirty = */ true));
-        }
+        mInterimResultCache.put(metricsConfigName, new InterimResult(result, /* dirty = */ true));
     }
 
     /**
@@ -114,39 +103,24 @@
      *
      * @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.
+     * @return the final result as PersistableBundle if exists, null otherwise
      */
-    public void getFinalResult(
-            String metricsConfigName, boolean deleteResult, FinalResultCallback callback) {
-        // I/O operations should happen on I/O thread
-        mIoHandler.post(() -> {
-            synchronized (mLock) {
-                loadFinalResultLockedOnIoThread(metricsConfigName, deleteResult, callback);
-            }
-        });
-    }
-
-    @GuardedBy("mLock")
-    private void loadFinalResultLockedOnIoThread(
-            String metricsConfigName, boolean deleteResult, FinalResultCallback callback) {
+    public PersistableBundle getFinalResult(String metricsConfigName, boolean deleteResult) {
         File file = new File(mFinalResultDirectory, metricsConfigName);
         // if no final result exists for this metrics config, return immediately
         if (!file.exists()) {
-            mWorkerHandler.post(() -> callback.onFinalResult(metricsConfigName, null));
-            return;
+            return null;
         }
+        PersistableBundle result = null;
         try (FileInputStream fis = new FileInputStream(file)) {
-            PersistableBundle bundle = PersistableBundle.readFromStream(fis);
-            // invoke callback on worker thread
-            mWorkerHandler.post(() -> callback.onFinalResult(metricsConfigName, bundle));
+            result = PersistableBundle.readFromStream(fis);
         } catch (IOException e) {
             Slog.w(CarLog.TAG_TELEMETRY, "Failed to get final result from disk.", e);
-            mWorkerHandler.post(() -> callback.onFinalResult(metricsConfigName, null));
         }
         if (deleteResult) {
             file.delete();
         }
+        return result;
     }
 
     /**
@@ -154,40 +128,88 @@
      * {@link com.android.car.telemetry.TelemetryProto.MetricsConfig}.
      */
     public void putFinalResult(String metricsConfigName, PersistableBundle result) {
-        synchronized (mLock) {
-            mIoHandler.post(() -> {
-                writeSingleResultToFileOnIoThread(mFinalResultDirectory, metricsConfigName, result);
-                deleteSingleFileOnIoThread(mInterimResultDirectory, metricsConfigName);
-            });
+        writePersistableBundleToFile(mFinalResultDirectory, metricsConfigName, result);
+        deleteFileInDirectory(mInterimResultDirectory, metricsConfigName);
+        mInterimResultCache.remove(metricsConfigName);
+    }
+
+    /** Returns the error result produced by the metrics config if exists, null otherwise. */
+    public TelemetryProto.TelemetryError getError(
+            String metricsConfigName, boolean deleteResult) {
+        File file = new File(mErrorResultDirectory, metricsConfigName);
+        // if no error exists for this metrics config, return immediately
+        if (!file.exists()) {
+            return null;
+        }
+        TelemetryProto.TelemetryError result = null;
+        try {
+            byte[] serializedBytes = Files.readAllBytes(file.toPath());
+            result = TelemetryProto.TelemetryError.parseFrom(serializedBytes);
+        } catch (IOException e) {
+            Slog.w(CarLog.TAG_TELEMETRY, "Failed to get error result from disk.", e);
+        }
+        if (deleteResult) {
+            file.delete();
+        }
+        return result;
+    }
+
+    /** Stores the error object produced by the script. */
+    public void putError(String metricsConfigName, TelemetryProto.TelemetryError error) {
+        mInterimResultCache.remove(metricsConfigName);
+        try {
+            Files.write(
+                    new File(mErrorResultDirectory, metricsConfigName).toPath(),
+                    error.toByteArray());
+            deleteFileInDirectory(mInterimResultDirectory, metricsConfigName);
             mInterimResultCache.remove(metricsConfigName);
+        } catch (IOException e) {
+            Slog.w(CarLog.TAG_TELEMETRY, "Failed to write data to file", e);
+            // TODO(b/197153560): record failure
         }
     }
 
     /** Persists data to disk. */
     public void flushToDisk() {
-        mIoHandler.post(() -> {
-            synchronized (mLock) {
-                writeInterimResultsToFileLockedOnIoThread();
-                deleteStaleDataOnIoThread(mInterimResultDirectory, mFinalResultDirectory);
-            }
-        });
+        writeInterimResultsToFile();
+        deleteAllStaleData(mInterimResultDirectory, mFinalResultDirectory);
+    }
+
+    /**
+     * Deletes script result associated with the given config name. If result does not exist, this
+     * method does not do anything.
+     */
+    public void deleteResult(String metricsConfigName) {
+        mInterimResultCache.remove(metricsConfigName);
+        deleteFileInDirectory(mInterimResultDirectory, metricsConfigName);
+        deleteFileInDirectory(mFinalResultDirectory, metricsConfigName);
+    }
+
+    /** Deletes all interim and final results stored in disk. */
+    public void deleteAllResults() {
+        mInterimResultCache.clear();
+        for (File interimResult : mInterimResultDirectory.listFiles()) {
+            interimResult.delete();
+        }
+        for (File finalResult : mFinalResultDirectory.listFiles()) {
+            finalResult.delete();
+        }
     }
 
     /** Writes dirty interim results to disk. */
-    @GuardedBy("mLock")
-    private void writeInterimResultsToFileLockedOnIoThread() {
+    private void writeInterimResultsToFile() {
         mInterimResultCache.forEach((metricsConfigName, interimResult) -> {
             // only write dirty data
             if (!interimResult.isDirty()) {
                 return;
             }
-            writeSingleResultToFileOnIoThread(
+            writePersistableBundleToFile(
                     mInterimResultDirectory, metricsConfigName, interimResult.getBundle());
         });
     }
 
     /** Deletes data that are older than some threshold in the given directories. */
-    private void deleteStaleDataOnIoThread(File... dirs) {
+    private void deleteAllStaleData(File... dirs) {
         long currTimeMs = System.currentTimeMillis();
         for (File dir : dirs) {
             for (File file : dir.listFiles()) {
@@ -202,30 +224,22 @@
     /**
      * Converts a {@link PersistableBundle} into byte array and saves the results to a file.
      */
-    private void writeSingleResultToFileOnIoThread(
+    private void writePersistableBundleToFile(
             File dir, String metricsConfigName, PersistableBundle result) {
-        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
-            result.writeToStream(outputStream);
-            Files.write(
-                    new File(dir, metricsConfigName).toPath(),
-                    outputStream.toByteArray());
+        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
+            // TODO(b/197153560): record failure
         }
     }
 
     /** Deletes a the given file in the given directory if it exists. */
-    private void deleteSingleFileOnIoThread(File interimResultDirectory, String metricsConfigName) {
+    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;
@@ -249,7 +263,4 @@
             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/databroker/DataBroker.java b/service/src/com/android/car/telemetry/databroker/DataBroker.java
index f22a8ca..d7cc2e6 100644
--- a/service/src/com/android/car/telemetry/databroker/DataBroker.java
+++ b/service/src/com/android/car/telemetry/databroker/DataBroker.java
@@ -55,13 +55,11 @@
     /**
      * Adds a {@link ScriptExecutionTask} to the priority queue. This method will schedule the
      * next task if a task is not currently running.
-     * This method can be called from any thread, and it is thread-safe.
      */
     void addTaskToQueue(ScriptExecutionTask task);
 
     /**
-     * Sends a message to the handler to poll and execute a task.
-     * This method is thread-safe.
+     * Checks system health state and executes a task if condition allows.
      */
     void scheduleNextTask();
 
diff --git a/service/src/com/android/car/telemetry/databroker/DataBrokerController.java b/service/src/com/android/car/telemetry/databroker/DataBrokerController.java
index a5fa1cb..8df74be 100644
--- a/service/src/com/android/car/telemetry/databroker/DataBrokerController.java
+++ b/service/src/com/android/car/telemetry/databroker/DataBrokerController.java
@@ -16,10 +16,17 @@
 
 package com.android.car.telemetry.databroker;
 
+import android.os.Handler;
+
+import com.android.car.systeminterface.SystemStateInterface;
+import com.android.car.telemetry.MetricsConfigStore;
+import com.android.car.telemetry.TelemetryProto;
 import com.android.car.telemetry.TelemetryProto.MetricsConfig;
 import com.android.car.telemetry.systemmonitor.SystemMonitor;
 import com.android.car.telemetry.systemmonitor.SystemMonitorEvent;
 
+import java.time.Duration;
+
 /**
  * DataBrokerController instantiates the DataBroker and manages what Publishers
  * it can read from based current system states and policies.
@@ -30,15 +37,42 @@
     public static final int TASK_PRIORITY_MED = 50;
     public static final int TASK_PRIORITY_LOW = 100;
 
-    private MetricsConfig mMetricsConfig;
     private final DataBroker mDataBroker;
+    private final Handler mTelemetryHandler;
+    private final MetricsConfigStore mMetricsConfigStore;
     private final SystemMonitor mSystemMonitor;
+    private final SystemStateInterface mSystemStateInterface;
 
-    public DataBrokerController(DataBroker dataBroker, SystemMonitor systemMonitor) {
+    public DataBrokerController(
+            DataBroker dataBroker,
+            Handler telemetryHandler,
+            MetricsConfigStore metricsConfigStore,
+            SystemMonitor systemMonitor,
+            SystemStateInterface systemStateInterface) {
         mDataBroker = dataBroker;
-        mDataBroker.setOnScriptFinishedCallback(this::onScriptFinished);
+        mTelemetryHandler = telemetryHandler;
+        mMetricsConfigStore = metricsConfigStore;
         mSystemMonitor = systemMonitor;
+        mSystemStateInterface = systemStateInterface;
+
+        mDataBroker.setOnScriptFinishedCallback(this::onScriptFinished);
         mSystemMonitor.setSystemMonitorCallback(this::onSystemMonitorEvent);
+        mSystemStateInterface.scheduleActionForBootCompleted(
+                this::startMetricsCollection, Duration.ZERO);
+    }
+
+    /**
+     * Starts collecting data. Once data is sent by publishers, DataBroker will arrange scripts to
+     * run. This method is called by some thread on executor service, therefore the work needs to
+     * be posted on the telemetry thread.
+     */
+    private void startMetricsCollection() {
+        mTelemetryHandler.post(() -> {
+            for (TelemetryProto.MetricsConfig config :
+                    mMetricsConfigStore.getActiveMetricsConfigs()) {
+                mDataBroker.addMetricsConfiguration(config);
+            }
+        });
     }
 
     /**
@@ -47,8 +81,7 @@
      * @param metricsConfig the metrics config.
      */
     public void onNewMetricsConfig(MetricsConfig metricsConfig) {
-        mMetricsConfig = metricsConfig;
-        mDataBroker.addMetricsConfiguration(mMetricsConfig);
+        mDataBroker.addMetricsConfiguration(metricsConfig);
     }
 
     /**
@@ -74,7 +107,7 @@
                 || event.getMemoryUsageLevel() == SystemMonitorEvent.USAGE_LEVEL_HI) {
             mDataBroker.setTaskExecutionPriority(TASK_PRIORITY_HI);
         } else if (event.getCpuUsageLevel() == SystemMonitorEvent.USAGE_LEVEL_MED
-                    || event.getMemoryUsageLevel() == SystemMonitorEvent.USAGE_LEVEL_MED) {
+                || event.getMemoryUsageLevel() == SystemMonitorEvent.USAGE_LEVEL_MED) {
             mDataBroker.setTaskExecutionPriority(TASK_PRIORITY_MED);
         } else {
             mDataBroker.setTaskExecutionPriority(TASK_PRIORITY_LOW);
diff --git a/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java b/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java
index 94df370..695254c 100644
--- a/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java
+++ b/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java
@@ -25,6 +25,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -41,17 +42,16 @@
 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.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.OutputStream;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.PriorityBlockingQueue;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Implementation of the data path component of CarTelemetryService. Forwards the published data
@@ -61,8 +61,16 @@
 public class DataBrokerImpl implements DataBroker {
 
     private static final int MSG_HANDLE_TASK = 1;
-    @VisibleForTesting
-    static final int MSG_BIND_TO_SCRIPT_EXECUTOR = 2;
+    private static final int MSG_BIND_TO_SCRIPT_EXECUTOR = 2;
+
+    /** Bind to script executor 5 times before entering disabled state. */
+    private static final int MAX_BIND_SCRIPT_EXECUTOR_ATTEMPTS = 5;
+
+    /**
+     * If script input exceeds this size, it will be piped to script executor instead of directly
+     * passed via binder call.
+     */
+    private static final int LARGE_SCRIPT_INPUT_SIZE_BYTES = 20 * 1024; // 20 kb
 
     private static final String SCRIPT_EXECUTOR_PACKAGE = "com.android.car.scriptexecutor";
     private static final String SCRIPT_EXECUTOR_CLASS =
@@ -72,26 +80,9 @@
     private final PublisherFactory mPublisherFactory;
     private final ResultStore mResultStore;
     private final ScriptExecutorListener mScriptExecutorListener;
-    private final Object mLock = new Object();
-    private final HandlerThread mWorkerThread = CarServiceUtils.getHandlerThread(
+    private final HandlerThread mTelemetryThread = CarServiceUtils.getHandlerThread(
             CarTelemetryService.class.getSimpleName());
-    private final Handler mWorkerHandler = new TaskHandler(mWorkerThread.getLooper());
-
-    /** Thread-safe int to determine which data can be processed. */
-    private final AtomicInteger mPriority = new AtomicInteger(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 final AtomicReference<String> mCurrentScriptName = new AtomicReference<>(null);
-
-    /**
-     * If something irrecoverable happened, DataBroker should enter into a disabled state to prevent
-     * doing futile work.
-     */
-    private final AtomicBoolean mDisabled = new AtomicBoolean(false);
+    private final Handler mTelemetryHandler = new TaskHandler(mTelemetryThread.getLooper());
 
     /** Thread-safe priority queue for scheduling tasks. */
     private final PriorityBlockingQueue<ScriptExecutionTask> mTaskQueue =
@@ -101,38 +92,68 @@
      * Maps MetricsConfig's name to its subscriptions. This map is useful when removing a
      * MetricsConfig.
      */
-    @GuardedBy("mLock")
     private final Map<String, List<DataSubscriber>> mSubscriptionMap = new ArrayMap<>();
-    private final AtomicReference<IScriptExecutor> mScriptExecutor = new AtomicReference<>();
+
+    /**
+     * If something irrecoverable happened, DataBroker should enter into a disabled state to prevent
+     * doing futile work.
+     */
+    private boolean mDisabled = false;
+
+    /** Current number of attempts to bind to ScriptExecutor. */
+    private int mBindScriptExecutorAttempts = 0;
+
+    /** Priority of current system to determine if a {@link ScriptExecutionTask} can run. */
+    private int mPriority = 1;
+
+    /** Waiting period between attempts to bind script executor. Can be shortened for tests. */
+    @VisibleForTesting long mBindScriptExecutorDelayMillis = 3_000L;
+
+    /**
+     * 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) {
-            mScriptExecutor.set(IScriptExecutor.Stub.asInterface(service));
-            scheduleNextTask();
+            mTelemetryHandler.post(() -> {
+                mScriptExecutor = IScriptExecutor.Stub.asInterface(service);
+                scheduleNextTask();
+            });
         }
 
         @Override
         public void onServiceDisconnected(ComponentName name) {
-            mScriptExecutor.set(null);
-            cleanupBoundService();
+            // TODO(b/198684473): clean up the state after script executor disconnects
+            mTelemetryHandler.post(() -> {
+                mScriptExecutor = null;
+                unbindScriptExecutor();
+            });
         }
     };
 
-    private ScriptFinishedCallback mScriptFinishedCallback;
-
     public DataBrokerImpl(
             Context context, PublisherFactory publisherFactory, ResultStore resultStore) {
         mContext = context;
         mPublisherFactory = publisherFactory;
         mResultStore = resultStore;
         mScriptExecutorListener = new ScriptExecutorListener(this);
-        bindScriptExecutor();
+        mPublisherFactory.setFailureConsumer(this::onPublisherFailure);
     }
 
-    /** Binds to ScriptExecutor. */
+    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 script executor is nonnull
-        if (mDisabled.get() || mScriptExecutor.get() != null) {
+        // 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();
@@ -142,15 +163,32 @@
                 mServiceConnection,
                 Context.BIND_AUTO_CREATE,
                 UserHandle.SYSTEM);
-        if (!success) {
-            Slog.w(CarLog.TAG_TELEMETRY, "failed to get valid connection to ScriptExecutor");
-            unbindScriptExecutor();
+        if (success) {
+            mBindScriptExecutorAttempts = 0; // reset
+            return;
+        }
+        unbindScriptExecutor();
+        mBindScriptExecutorAttempts++;
+        if (mBindScriptExecutorAttempts < MAX_BIND_SCRIPT_EXECUTOR_ATTEMPTS) {
+            Slog.w(CarLog.TAG_TELEMETRY,
+                    "failed to get valid connection to ScriptExecutor, retrying in "
+                            + mBindScriptExecutorDelayMillis + "ms.");
+            mTelemetryHandler.sendEmptyMessageDelayed(MSG_BIND_TO_SCRIPT_EXECUTOR,
+                    mBindScriptExecutorDelayMillis);
+        } else {
+            Slog.w(CarLog.TAG_TELEMETRY, "failed to get valid connection to ScriptExecutor, "
+                    + "disabling DataBroker");
             disableBroker();
         }
     }
 
-    /** Unbinds {@link ScriptExecutor} to release the connection. */
+    /**
+     * 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) {
@@ -160,52 +198,28 @@
     }
 
     /**
-     * Cleans up the state after ScriptExecutor is killed by the system & service disconnects.
+     * Enters into a disabled state because something irrecoverable happened.
+     * TODO(b/200841260): expose the state to the caller.
      */
-    private void cleanupBoundService() {
-        // TODO(b/187743369): clean up the state after script executor disconnects
-        unbindScriptExecutor();
-        mCurrentScriptName.set(null);
-    }
-
-    /** Enters into a disabled state because something irrecoverable happened. */
     private void disableBroker() {
-        mDisabled.set(true);
+        mDisabled = true;
         // remove all MetricConfigs, disable all publishers, stop receiving data
-        synchronized (mLock) {
-            for (String metricsConfigName : mSubscriptionMap.keySet()) {
-                // if no subscriber, remove key from map
-                if (mSubscriptionMap.get(metricsConfigName).size() == 0) {
-                    mSubscriptionMap.remove(metricsConfigName);
-                } else {
-                    // otherwise get the metrics config from the DataSubscriber and remove it
-                    removeMetricsConfiguration(mSubscriptionMap.get(metricsConfigName).get(0)
-                            .getMetricsConfig());
-                }
+        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 void addMetricsConfiguration(MetricsConfig metricsConfig) {
-        if (mDisabled.get()) {
-            return;
-        }
         // TODO(b/187743369): pass status back to caller
-        mWorkerHandler.post(() -> addMetricsConfigurationOnHandlerThread(metricsConfig));
-    }
-
-    private void addMetricsConfigurationOnHandlerThread(MetricsConfig metricsConfig) {
-        // this method can only be called from the thread that the handler is running at
-        if (Looper.myLooper() != mWorkerHandler.getLooper()) {
-            throw new RuntimeException(
-                    "addMetricsConfigurationOnHandlerThread is not called from handler thread");
-        }
-        synchronized (mLock) {
-            // if metricsConfig already exists, it should not be added again
-            if (mSubscriptionMap.containsKey(metricsConfig.getName())) {
-                return;
-            }
+        // 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<>(
@@ -230,33 +244,17 @@
                 return;
             }
         }
-        synchronized (mLock) {
-            mSubscriptionMap.put(metricsConfig.getName(), dataSubscribers);
-        }
+        mSubscriptionMap.put(metricsConfig.getName(), dataSubscribers);
     }
 
     @Override
     public void removeMetricsConfiguration(MetricsConfig metricsConfig) {
         // TODO(b/187743369): pass status back to caller
-        mWorkerHandler.post(() -> removeMetricsConfigurationOnHandlerThread(metricsConfig));
-    }
-
-    private void removeMetricsConfigurationOnHandlerThread(MetricsConfig metricsConfig) {
-        // this method can only be called from the thread that the handler is running at
-        if (Looper.myLooper() != mWorkerHandler.getLooper()) {
-            throw new RuntimeException(
-                    "removeMetricsConfigurationOnHandlerThread is not called from handler thread");
-        }
-        synchronized (mLock) {
-            if (!mSubscriptionMap.containsKey(metricsConfig.getName())) {
-                return;
-            }
+        if (!mSubscriptionMap.containsKey(metricsConfig.getName())) {
+            return;
         }
         // get the subscriptions associated with this MetricsConfig, remove it from the map
-        List<DataSubscriber> dataSubscribers;
-        synchronized (mLock) {
-            dataSubscribers = mSubscriptionMap.remove(metricsConfig.getName());
-        }
+        List<DataSubscriber> dataSubscribers = mSubscriptionMap.remove(metricsConfig.getName());
         // for each subscriber, remove it from publishers
         for (DataSubscriber subscriber : dataSubscribers) {
             AbstractPublisher publisher = mPublisherFactory.getPublisher(
@@ -278,7 +276,7 @@
 
     @Override
     public void addTaskToQueue(ScriptExecutionTask task) {
-        if (mDisabled.get()) {
+        if (mDisabled) {
             return;
         }
         mTaskQueue.add(task);
@@ -292,20 +290,19 @@
      * 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.
+     *  will also have different threading behavior. Update javadoc when the behavior is decided.
      */
     @Override
     public void scheduleNextTask() {
-        if (mDisabled.get() || mCurrentScriptName.get() != null || mTaskQueue.peek() == null) {
+        if (mDisabled || mTelemetryHandler.hasMessages(MSG_HANDLE_TASK)) {
             return;
         }
-        mWorkerHandler.sendMessage(mWorkerHandler.obtainMessage(MSG_HANDLE_TASK));
+        mTelemetryHandler.sendEmptyMessage(MSG_HANDLE_TASK);
     }
 
     @Override
     public void setOnScriptFinishedCallback(ScriptFinishedCallback callback) {
-        if (mDisabled.get()) {
+        if (mDisabled) {
             return;
         }
         mScriptFinishedCallback = callback;
@@ -313,23 +310,21 @@
 
     @Override
     public void setTaskExecutionPriority(int priority) {
-        if (mDisabled.get()) {
+        if (mDisabled) {
             return;
         }
-        mPriority.set(priority);
+        mPriority = priority;
         scheduleNextTask(); // when priority updates, schedule a task which checks task queue
     }
 
     @VisibleForTesting
     Map<String, List<DataSubscriber>> getSubscriptionMap() {
-        synchronized (mLock) {
-            return new ArrayMap<>((ArrayMap<String, List<DataSubscriber>>) mSubscriptionMap);
-        }
+        return new ArrayMap<>((ArrayMap<String, List<DataSubscriber>>) mSubscriptionMap);
     }
 
     @VisibleForTesting
-    Handler getWorkerHandler() {
-        return mWorkerHandler;
+    Handler getTelemetryHandler() {
+        return mTelemetryHandler;
     }
 
     @VisibleForTesting
@@ -344,77 +339,124 @@
      * lower priority number to be polled.
      */
     private void pollAndExecuteTask() {
-        // this method can only be called from the thread that the handler is running at
-        if (Looper.myLooper() != mWorkerHandler.getLooper()) {
-            throw new RuntimeException("pollAndExecuteTask is not called from handler thread");
-        }
-        // all checks are thread-safe
-        if (mDisabled.get()
-                || mCurrentScriptName.get() != null
-                || mTaskQueue.peek() == null
-                || mTaskQueue.peek().getPriority() > mPriority.get()) {
+        // check databroker state is ready to run script
+        if (mDisabled || mCurrentScriptName != null) {
             return;
         }
-        IScriptExecutor scriptExecutor = mScriptExecutor.get();
-        if (scriptExecutor == null) {
-            Slog.w(CarLog.TAG_TELEMETRY, "script executor is null, cannot execute task");
-            bindScriptExecutor();
+        // check task is valid and ready to be run
+        ScriptExecutionTask task = mTaskQueue.peek();
+        if (task == null || task.getPriority() > mPriority) {
             return;
         }
-        ScriptExecutionTask task = mTaskQueue.poll();
-        if (task == null) {
+        // if script executor is null, bind service
+        if (mScriptExecutor == null) {
+            Slog.w(CarLog.TAG_TELEMETRY,
+                    "script executor is null, cannot execute task");
+            // upon successful binding, a task will be scheduled to run if there are any
+            mTelemetryHandler.sendEmptyMessage(MSG_BIND_TO_SCRIPT_EXECUTOR);
             return;
         }
-        MetricsConfig metricsConfig = task.getMetricsConfig();
-        mCurrentScriptName.set(metricsConfig.getName()); // signal the start of script execution
+        mTaskQueue.poll(); // remove task from queue
+        // update current name because a script is currently running
+        mCurrentScriptName = task.getMetricsConfig().getName();
         try {
-            scriptExecutor.invokeScript(
-                    metricsConfig.getScript(),
+            if (task.getDataSizeBytes() >= LARGE_SCRIPT_INPUT_SIZE_BYTES) {
+                Slog.d(CarLog.TAG_TELEMETRY, "invoking script executor for large input");
+                invokeScriptForLargeInput(task);
+            } else {
+                Slog.d(CarLog.TAG_TELEMETRY, "invoking script executor");
+                mScriptExecutor.invokeScript(
+                        task.getMetricsConfig().getScript(),
+                        task.getHandlerName(),
+                        task.getData(),
+                        mResultStore.getInterimResult(mCurrentScriptName),
+                        mScriptExecutorListener);
+            }
+        } catch (RemoteException e) {
+            Slog.w(CarLog.TAG_TELEMETRY, "remote exception occurred invoking script", e);
+            unbindScriptExecutor();
+            addTaskToQueue(task); // will trigger scheduleNextTask() and re-binding scriptexecutor
+        } catch (IOException e) {
+            Slog.w(CarLog.TAG_TELEMETRY, "Either unable to create pipe or failed to pipe data"
+                    + " to ScriptExecutor. Skipping the published data", e);
+            mCurrentScriptName = null;
+            scheduleNextTask(); // drop this task and schedule the next one
+        }
+    }
+
+    /**
+     * Sets up pipes, invokes ScriptExecutor#invokeScriptForLargeInput() API, and writes the
+     * script input to the pipe.
+     *
+     * @param task containing all the necessary parameters for ScriptExecutor API.
+     * @throws IOException if cannot create pipe or cannot write the bundle to pipe.
+     * @throws RemoteException if ScriptExecutor failed.
+     */
+    private void invokeScriptForLargeInput(ScriptExecutionTask task)
+            throws IOException, RemoteException {
+        ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
+        ParcelFileDescriptor readFd = fds[0];
+        ParcelFileDescriptor writeFd = fds[1];
+        try {
+            mScriptExecutor.invokeScriptForLargeInput(
+                    task.getMetricsConfig().getScript(),
                     task.getHandlerName(),
-                    task.getData(),
-                    null, // TODO(b/197027637): PersistableBundle cannot be converted into Bundle
+                    readFd,
+                    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.set(null);
+            closeQuietly(readFd);
+            closeQuietly(writeFd);
+            throw e;
+        }
+        closeQuietly(readFd);
+
+        Slog.d(CarLog.TAG_TELEMETRY, "writing large script data to pipe");
+        try (OutputStream outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(writeFd)) {
+            task.getData().writeToStream(outputStream);
+        }
+    }
+
+    /** Quietly closes Java Closeables, ignoring IOException. */
+    private void closeQuietly(Closeable closeable) {
+        try {
+            closeable.close();
+        } catch (IOException e) {
+            // Ignore
         }
     }
 
     /** Stores final metrics and schedules the next task. */
-    private void onScriptFinished(byte[] result) {
-        // TODO(b/197027637): update API to use PersistableBundle
-        //                    mResultStore.putFinalResult(mCurrentScriptName.get(), result);
-        mCurrentScriptName.set(null);
-        scheduleNextTask();
+    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) {
-        // TODO(b/197027637): update API to use PersistableBundle
-        PersistableBundle persistableBundle = new PersistableBundle();
-        for (String key : stateToPersist.keySet()) {
-            Object value = stateToPersist.get(key);
-            if (value instanceof Integer) {
-                persistableBundle.putInt(key, (int) value);
-            } else if (value instanceof Double) {
-                persistableBundle.putDouble(key, (double) value);
-            } else if (value instanceof Boolean) {
-                persistableBundle.putBoolean(key, (boolean) value);
-            } else if (value instanceof String) {
-                persistableBundle.putString(key, (String) value);
-            }
-        }
-        mResultStore.putInterimResult(mCurrentScriptName.get(), persistableBundle);
-        mCurrentScriptName.set(null);
-        scheduleNextTask();
+        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) {
-        // TODO(b/197027637): create error object
-        mCurrentScriptName.set(null);
-        scheduleNextTask();
+        mTelemetryHandler.post(() -> {
+            TelemetryProto.TelemetryError.Builder error = TelemetryProto.TelemetryError.newBuilder()
+                    .setErrorType(TelemetryProto.TelemetryError.ErrorType.forNumber(errorType))
+                    .setMessage(message);
+            if (stackTrace != null) {
+                error.setStackTrace(stackTrace);
+            }
+            mResultStore.putError(mCurrentScriptName, error.build());
+            mCurrentScriptName = null;
+            scheduleNextTask();
+        });
     }
 
     /** Listens for script execution status. Methods are called on the binder thread. */
@@ -426,7 +468,7 @@
         }
 
         @Override
-        public void onScriptFinished(byte[] result) {
+        public void onScriptFinished(PersistableBundle result) {
             DataBrokerImpl dataBroker = mWeakDataBroker.get();
             if (dataBroker == null) {
                 return;
@@ -472,6 +514,9 @@
                 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 337b03c..9ab7604 100644
--- a/service/src/com/android/car/telemetry/databroker/DataSubscriber.java
+++ b/service/src/com/android/car/telemetry/databroker/DataSubscriber.java
@@ -21,17 +21,11 @@
 
 import com.android.car.telemetry.TelemetryProto;
 
+import java.util.Objects;
+
 /**
  * Subscriber class that receives published data and schedules tasks for execution.
- * The class is thread-safe as long as
- * {@link TelemetryProto.MetricsConfig} does not change during runtime.
- *
- * <p>TODO(b/187743369): thread-safety can change if priority can be updated in runtime. Update
- *                    javadoc once priority is concretely defined.
- *                    Must be thread-safe, as #push() method may be called by multiple threads.
- *
- * <p>TODO(b/187743369): implement equals() and hash() functions, as they are used in publishers
- *                       to check equality of subscribers.
+ * All methods of this class must be accessed on telemetry thread.
  */
 public class DataSubscriber {
 
@@ -64,13 +58,11 @@
     /**
      * Creates a {@link ScriptExecutionTask} and pushes it to the priority queue where the task
      * will be pending execution.
-     *
-     * <p>This method is thread-safe and doesn't block.
      */
     public void push(PersistableBundle data) {
         ScriptExecutionTask task = new ScriptExecutionTask(
                 this, data, SystemClock.elapsedRealtime());
-        mDataBroker.addTaskToQueue(task); // thread-safe
+        mDataBroker.addTaskToQueue(task);
     }
 
     /** Returns the {@link TelemetryProto.MetricsConfig}. */
@@ -87,4 +79,21 @@
     public int getPriority() {
         return mSubscriber.getPriority();
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof DataSubscriber)) {
+            return false;
+        }
+        DataSubscriber other = (DataSubscriber) o;
+        return mMetricsConfig.getName().equals(other.getMetricsConfig().getName())
+                && mMetricsConfig.getVersion() == other.getMetricsConfig().getVersion()
+                && mSubscriber.getHandler().equals(other.getSubscriber().getHandler());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mMetricsConfig.getName(), mMetricsConfig.getVersion(),
+                mSubscriber.getHandler());
+    }
 }
diff --git a/service/src/com/android/car/telemetry/databroker/ScriptExecutionTask.java b/service/src/com/android/car/telemetry/databroker/ScriptExecutionTask.java
index a1fb522..648fc79 100644
--- a/service/src/com/android/car/telemetry/databroker/ScriptExecutionTask.java
+++ b/service/src/com/android/car/telemetry/databroker/ScriptExecutionTask.java
@@ -16,6 +16,7 @@
 
 package com.android.car.telemetry.databroker;
 
+import android.os.Parcel;
 import android.os.PersistableBundle;
 
 import com.android.car.telemetry.TelemetryProto;
@@ -69,6 +70,18 @@
         return mSubscriber.getMetricsConfig().equals(metricsConfig);
     }
 
+    /**
+     * Returns the script input data size in bytes.
+     * TODO(b/201545154): Investigate how to get bundle size without making a full copy.
+     */
+    public int getDataSizeBytes() {
+        Parcel parcel = Parcel.obtain();
+        parcel.writePersistableBundle(mData);
+        int size = parcel.dataSize();
+        parcel.recycle();
+        return size;
+    }
+
     @Override
     public int compareTo(ScriptExecutionTask other) {
         if (getPriority() < other.getPriority()) {
diff --git a/service/src/com/android/car/telemetry/proto/atoms.proto b/service/src/com/android/car/telemetry/proto/atoms.proto
index 225785e..8f14b38 100644
--- a/service/src/com/android/car/telemetry/proto/atoms.proto
+++ b/service/src/com/android/car/telemetry/proto/atoms.proto
@@ -27,6 +27,11 @@
   oneof pushed {
     AppStartMemoryStateCaptured app_start_memory_state_captured = 55;
   }
+
+  // Pulled events will start at field 10000.
+  oneof pulled {
+    ProcessMemoryState process_memory_state = 10013;
+  }
 }
 
 message AppStartMemoryStateCaptured {
@@ -40,3 +45,14 @@
   optional int64 cache_in_bytes = 7;
   optional int64 swap_in_bytes = 8;
 }
+
+message ProcessMemoryState {
+  optional int32 uid = 1;
+  optional string process_name = 2;
+  optional int32 oom_adj_score = 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
index a3ec1ef..37d74f9 100644
--- a/service/src/com/android/car/telemetry/proto/stats_log.proto
+++ b/service/src/com/android/car/telemetry/proto/stats_log.proto
@@ -26,16 +26,30 @@
 
 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 DimensionsValue {
+  oneof value {
+    int32 value_int = 3;
+    uint64 value_str_hash = 8;
+  }
+  reserved 1, 2, 4, 5, 6, 7;
 }
 
 message EventMetricData {
   optional int64 elapsed_timestamp_nanos = 1;
   optional Atom atom = 2;
-  optional AggregatedAtomInfo aggregated_atom_info = 4;
-  reserved 3;
+  reserved 3, 4;
+}
+
+message GaugeBucketInfo {
+  repeated Atom atom = 3;
+  repeated int64 elapsed_timestamp_nanos = 4;
+  reserved 1, 2, 5, 6, 7, 8, 9;
+}
+
+message GaugeMetricData {
+  repeated GaugeBucketInfo bucket_info = 3;
+  repeated DimensionsValue dimension_leaf_values_in_what = 4;
+  reserved 1, 2, 5, 6;
 }
 
 message StatsLogReport {
@@ -45,13 +59,19 @@
     repeated EventMetricData data = 1;
   }
 
+  message GaugeMetricDataWrapper {
+    repeated GaugeMetricData data = 1;
+    reserved 2;
+  }
+
   oneof data {
     EventMetricDataWrapper event_metrics = 4;
+    GaugeMetricDataWrapper gauge_metrics = 8;
   }
 
   optional bool is_active = 14;
 
-  reserved 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16;
+  reserved 2, 3, 5, 6, 7, 9, 10, 11, 12, 13, 15, 16;
 }
 
 message ConfigMetricsReport {
@@ -69,7 +89,9 @@
   }
   optional DumpReportReason dump_report_reason = 8;
 
-  reserved 2, 3, 4, 5, 6, 7, 9;
+  repeated string strings = 9;
+
+  reserved 2, 3, 4, 5, 6, 7;
 }
 
 message ConfigMetricsReportList {
diff --git a/service/src/com/android/car/telemetry/proto/statsd_config.proto b/service/src/com/android/car/telemetry/proto/statsd_config.proto
index fe92f28..15ea9d5 100644
--- a/service/src/com/android/car/telemetry/proto/statsd_config.proto
+++ b/service/src/com/android/car/telemetry/proto/statsd_config.proto
@@ -33,6 +33,14 @@
   reserved 1, 6, 7, 8, 9, 10, 1000;
 }
 
+message FieldMatcher {
+  optional int32 field = 1;
+
+  repeated FieldMatcher child = 3;
+
+  reserved 2;
+}
+
 message SimpleAtomMatcher {
   optional int32 atom_id = 1;
 
@@ -49,6 +57,11 @@
   reserved 3;
 }
 
+message FieldFilter {
+  optional bool include_all = 1 [default = false];
+  optional FieldMatcher fields = 2;
+}
+
 message EventMetric {
   optional int64 id = 1;
   optional int64 what = 2;
@@ -59,6 +72,34 @@
   reserved 101;
 }
 
+message GaugeMetric {
+  optional int64 id = 1;
+
+  optional int64 what = 2;
+
+  optional FieldFilter gauge_fields_filter = 3;
+
+  optional FieldMatcher dimensions_in_what = 5;
+
+  optional TimeUnit bucket = 6;
+
+  enum SamplingType {
+    RANDOM_ONE_SAMPLE = 1;
+    CONDITION_CHANGE_TO_TRUE = 3;
+    FIRST_N_SAMPLES = 4;
+    reserved 2;
+  }
+  optional SamplingType sampling_type = 9 [default = RANDOM_ONE_SAMPLE];
+
+  optional int64 max_num_gauge_atoms_per_bucket = 11 [default = 10];
+
+  optional int32 max_pull_delay_sec = 13 [default = 30];
+
+  reserved 4, 7, 8, 10, 12, 14;
+  reserved 100;
+  reserved 101;
+}
+
 message PullAtomPackages {
   optional int32 atom_id = 1;
 
@@ -70,6 +111,8 @@
 
   repeated EventMetric event_metric = 2;
 
+  repeated GaugeMetric gauge_metric = 5;
+
   repeated AtomMatcher atom_matcher = 7;
 
   repeated string allowed_log_source = 12;
@@ -84,7 +127,7 @@
 
   repeated int32 whitelisted_atom_ids = 24;
 
-  reserved 3, 4, 5, 6, 8, 9, 10, 11, 13, 14, 17, 18, 19, 21, 22, 25;
+  reserved 3, 4, 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 768b31b..b06f519 100644
--- a/service/src/com/android/car/telemetry/proto/telemetry.proto
+++ b/service/src/com/android/car/telemetry/proto/telemetry.proto
@@ -83,6 +83,8 @@
     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;
+    // Collects memory state of processes in 5-minute buckets (1 memory measurement per bucket).
+    PROCESS_MEMORY_STATE = 2;
   }
 
   // Required.
@@ -116,3 +118,37 @@
   // Ranges from 0 to 100. 0 being highest priority and 100 being lowest.
   optional int32 priority = 3;
 }
+
+// A message that encapsulates an error that's produced when collecting metrics.
+// Any changes here should also be reflected on
+// p/s/C/service/src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutorConstants.aidl
+message TelemetryError {
+  enum ErrorType {
+    // Not used.
+    UNSPECIFIED = 0;
+
+    // Used when an error occurs in the ScriptExecutor code.
+    SCRIPT_EXECUTOR_ERROR = 1;
+
+    // Used when an error occurs while executing the Lua script (such as errors returned by
+    // lua_pcall)
+    LUA_RUNTIME_ERROR = 2;
+
+    // Used to log errors by a script itself, for instance, when a script received inputs outside
+    // of expected range.
+    LUA_SCRIPT_ERROR = 3;
+  }
+
+  // Required.
+  // A type that indicates the category of the error.
+  optional ErrorType error_type = 1;
+
+  // Required.
+  // Human readable message explaining the error or how to fix it.
+  optional string message = 2;
+
+  // Optional.
+  // If there is an exception, there will be stack trace. However this information is not always
+  // available.
+  optional string stack_trace = 3;
+}
diff --git a/service/src/com/android/car/telemetry/publisher/AbstractPublisher.java b/service/src/com/android/car/telemetry/publisher/AbstractPublisher.java
index 2c761e1..e91831e 100644
--- a/service/src/com/android/car/telemetry/publisher/AbstractPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/AbstractPublisher.java
@@ -18,6 +18,8 @@
 
 import com.android.car.telemetry.databroker.DataSubscriber;
 
+import java.util.function.BiConsumer;
+
 /**
  * Abstract class for publishers. It is 1-1 with data source and manages sending data to
  * subscribers. Publisher stops itself when there are no subscribers.
@@ -26,9 +28,17 @@
  * configuration. Single publisher instance can send data as several
  * {@link com.android.car.telemetry.TelemetryProto.Publisher} to subscribers.
  *
- * <p>Child classes must be thread-safe.
+ * <p>Child classes must be called from the telemetry thread.
  */
 public abstract class AbstractPublisher {
+    // 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.
      *
@@ -57,4 +67,12 @@
 
     /** Returns true if the publisher already has this data subscriber. */
     public abstract boolean hasDataSubscriber(DataSubscriber subscriber);
+
+    /**
+     * Notifies the failure consumer that this publisher cannot recover from the hard failure.
+     * For example, it cannot connect to the underlying service.
+     */
+    protected void notifyFailureConsumer(Throwable error) {
+        mFailureConsumer.accept(this, error);
+    }
 }
diff --git a/service/src/com/android/car/telemetry/publisher/AtomDataConverter.java b/service/src/com/android/car/telemetry/publisher/AtomDataConverter.java
new file mode 100644
index 0000000..b87218e
--- /dev/null
+++ b/service/src/com/android/car/telemetry/publisher/AtomDataConverter.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.os.PersistableBundle;
+
+import com.android.car.telemetry.AtomsProto;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class for converting atom data to {@link PersistableBundle} compatible format.
+ */
+public class AtomDataConverter {
+    static final String UID = "uid";
+    static final String PROCESS_NAME = "process_name";
+    static final String ACTIVITY_NAME = "activity_name";
+    static final String PAGE_FAULT = "page_fault";
+    static final String PAGE_MAJOR_FAULT = "page_major_fault";
+    static final String RSS_IN_BYTES = "rss_in_bytes";
+    static final String CACHE_IN_BYTES = "cache_in_bytes";
+    static final String SWAP_IN_BYTES = "swap_in_bytes";
+    static final String STATE = "state";
+    static final String OOM_ADJ_SCORE = "oom_adj_score";
+
+    /**
+     * Converts a list of atoms to separate the atoms fields values into arrays to be put into the
+     * {@link PersistableBundle}.
+     * The list of atoms must contain atoms of same type.
+     * Only fields with types allowed in {@link PersistableBundle} are added to the bundle.
+     *
+     * @param atoms list of {@link AtomsProto.Atom} of the same type.
+     * @param bundle the {@link PersistableBundle} to hold the converted atom fields.
+     */
+    static void convertAtomsList(List<AtomsProto.Atom> atoms, PersistableBundle bundle) {
+        // The atoms are either pushed or pulled type atoms.
+        switch (atoms.get(0).getPushedCase()) {
+            case APP_START_MEMORY_STATE_CAPTURED:
+                convertAppStartMemoryStateCapturedAtoms(atoms, bundle);
+                break;
+            default:
+                break;
+        }
+        switch (atoms.get(0).getPulledCase()) {
+            case PROCESS_MEMORY_STATE:
+                convertProcessMemoryStateAtoms(atoms, bundle);
+                break;
+            default:
+                break;
+        }
+    }
+
+    /**
+     * Converts {@link AtomsProto.AppStartMemoryStateCaptured} atoms.
+     *
+     * @param atoms the list of {@link AtomsProto.AppStartMemoryStateCaptured} atoms.
+     * @param bundle the {@link PersistableBundle} to hold the converted atom fields.
+     */
+    private static void convertAppStartMemoryStateCapturedAtoms(
+                List<AtomsProto.Atom> atoms, PersistableBundle bundle) {
+        List<Integer> uid = null;
+        List<String> processName = null;
+        List<String> activityName = null;
+        List<Long> pageFault = null;
+        List<Long> pageMajorFault = null;
+        List<Long> rssInBytes = null;
+        List<Long> cacheInBytes = null;
+        List<Long> swapInBytes = null;
+        for (AtomsProto.Atom atom : atoms) {
+            AtomsProto.AppStartMemoryStateCaptured atomData = atom.getAppStartMemoryStateCaptured();
+            // Atom fields may be filtered thus not collected, need to check availability.
+            if (atomData.hasUid()) {
+                if (uid == null) {
+                    uid = new ArrayList();
+                }
+                uid.add(atomData.getUid());
+            }
+            if (atomData.hasProcessName()) {
+                if (processName == null) {
+                    processName = new ArrayList<>();
+                }
+                processName.add(atomData.getProcessName());
+            }
+            if (atomData.hasActivityName()) {
+                if (activityName == null) {
+                    activityName = new ArrayList<>();
+                }
+                activityName.add(atomData.getActivityName());
+            }
+            if (atomData.hasPageFault()) {
+                if (pageFault == null) {
+                    pageFault = new ArrayList<>();
+                }
+                pageFault.add(atomData.getPageFault());
+            }
+            if (atomData.hasPageMajorFault()) {
+                if (pageMajorFault == null) {
+                    pageMajorFault = new ArrayList<>();
+                }
+                pageMajorFault.add(atomData.getPageMajorFault());
+            }
+            if (atomData.hasRssInBytes()) {
+                if (rssInBytes == null) {
+                    rssInBytes = new ArrayList<>();
+                }
+                rssInBytes.add(atomData.getRssInBytes());
+            }
+            if (atomData.hasCacheInBytes()) {
+                if (cacheInBytes == null) {
+                    cacheInBytes = new ArrayList<>();
+                }
+                cacheInBytes.add(atomData.getCacheInBytes());
+            }
+            if (atomData.hasSwapInBytes()) {
+                if (swapInBytes == null) {
+                    swapInBytes = new ArrayList<>();
+                }
+                swapInBytes.add(atomData.getSwapInBytes());
+            }
+        }
+        if (uid != null) {
+            bundle.putIntArray(UID, uid.stream().mapToInt(i -> i).toArray());
+        }
+        if (processName != null) {
+            bundle.putStringArray(
+                    PROCESS_NAME, processName.toArray(new String[0]));
+        }
+        if (activityName != null) {
+            bundle.putStringArray(
+                    ACTIVITY_NAME, activityName.toArray(new String[0]));
+        }
+        if (pageFault != null) {
+            bundle.putLongArray(PAGE_FAULT, pageFault.stream().mapToLong(i -> i).toArray());
+        }
+        if (pageMajorFault != null) {
+            bundle.putLongArray(
+                    PAGE_MAJOR_FAULT, pageMajorFault.stream().mapToLong(i -> i).toArray());
+        }
+        if (rssInBytes != null) {
+            bundle.putLongArray(RSS_IN_BYTES, rssInBytes.stream().mapToLong(i -> i).toArray());
+        }
+        if (cacheInBytes != null) {
+            bundle.putLongArray(
+                    CACHE_IN_BYTES, cacheInBytes.stream().mapToLong(i -> i).toArray());
+        }
+        if (swapInBytes != null) {
+            bundle.putLongArray(
+                    SWAP_IN_BYTES, swapInBytes.stream().mapToLong(i -> i).toArray());
+        }
+    }
+
+    /**
+     * Converts {@link AtomsProto.ProcessMemoryState} atoms.
+     *
+     * @param atoms the list of {@link AtomsProto.ProcessMemoryState} atoms.
+     * @param bundle the {@link PersistableBundle} to hold the converted atom fields.
+     */
+    private static void convertProcessMemoryStateAtoms(
+                List<AtomsProto.Atom> atoms, PersistableBundle bundle) {
+        List<Integer> uid = null;
+        List<String> processName = null;
+        List<Integer> oomAdjScore = null;
+        List<Long> pageFault = null;
+        List<Long> pageMajorFault = null;
+        List<Long> rssInBytes = null;
+        List<Long> cacheInBytes = null;
+        List<Long> swapInBytes = null;
+        for (AtomsProto.Atom atom : atoms) {
+            AtomsProto.ProcessMemoryState atomData = atom.getProcessMemoryState();
+            // Atom fields may be filtered thus not collected, need to check availability.
+            if (atomData.hasUid()) {
+                if (uid == null) {
+                    uid = new ArrayList();
+                }
+                uid.add(atomData.getUid());
+            }
+            if (atomData.hasProcessName()) {
+                if (processName == null) {
+                    processName = new ArrayList<>();
+                }
+                processName.add(atomData.getProcessName());
+            }
+            if (atomData.hasOomAdjScore()) {
+                if (oomAdjScore == null) {
+                    oomAdjScore = new ArrayList<>();
+                }
+                oomAdjScore.add(atomData.getOomAdjScore());
+            }
+            if (atomData.hasPageFault()) {
+                if (pageFault == null) {
+                    pageFault = new ArrayList<>();
+                }
+                pageFault.add(atomData.getPageFault());
+            }
+            if (atomData.hasPageMajorFault()) {
+                if (pageMajorFault == null) {
+                    pageMajorFault = new ArrayList<>();
+                }
+                pageMajorFault.add(atomData.getPageMajorFault());
+            }
+            if (atomData.hasRssInBytes()) {
+                if (rssInBytes == null) {
+                    rssInBytes = new ArrayList<>();
+                }
+                rssInBytes.add(atomData.getRssInBytes());
+            }
+            if (atomData.hasCacheInBytes()) {
+                if (cacheInBytes == null) {
+                    cacheInBytes = new ArrayList<>();
+                }
+                cacheInBytes.add(atomData.getCacheInBytes());
+            }
+            if (atomData.hasSwapInBytes()) {
+                if (swapInBytes == null) {
+                    swapInBytes = new ArrayList<>();
+                }
+                swapInBytes.add(atomData.getSwapInBytes());
+            }
+        }
+        if (uid != null) {
+            bundle.putIntArray(UID, uid.stream().mapToInt(i -> i).toArray());
+        }
+        if (processName != null) {
+            bundle.putStringArray(
+                    PROCESS_NAME, processName.toArray(new String[0]));
+        }
+        if (oomAdjScore != null) {
+            bundle.putIntArray(
+                    OOM_ADJ_SCORE, oomAdjScore.stream().mapToInt(i -> i).toArray());
+        }
+        if (pageFault != null) {
+            bundle.putLongArray(PAGE_FAULT, pageFault.stream().mapToLong(i -> i).toArray());
+        }
+        if (pageMajorFault != null) {
+            bundle.putLongArray(
+                    PAGE_MAJOR_FAULT, pageMajorFault.stream().mapToLong(i -> i).toArray());
+        }
+        if (rssInBytes != null) {
+            bundle.putLongArray(RSS_IN_BYTES, rssInBytes.stream().mapToLong(i -> i).toArray());
+        }
+        if (cacheInBytes != null) {
+            bundle.putLongArray(
+                    CACHE_IN_BYTES, cacheInBytes.stream().mapToLong(i -> i).toArray());
+        }
+        if (swapInBytes != null) {
+            bundle.putLongArray(
+                    SWAP_IN_BYTES, swapInBytes.stream().mapToLong(i -> i).toArray());
+        }
+    }
+}
diff --git a/service/src/com/android/car/telemetry/publisher/CarTelemetrydPublisher.java b/service/src/com/android/car/telemetry/publisher/CarTelemetrydPublisher.java
index feeeada..490ab83 100644
--- a/service/src/com/android/car/telemetry/publisher/CarTelemetrydPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/CarTelemetrydPublisher.java
@@ -16,10 +16,10 @@
 
 package com.android.car.telemetry.publisher;
 
-import android.annotation.Nullable;
 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;
@@ -29,12 +29,12 @@
 import com.android.car.CarLog;
 import com.android.car.telemetry.TelemetryProto;
 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.android.server.utils.Slogf;
 
 import java.util.ArrayList;
+import java.util.function.BiConsumer;
 
 /**
  * Publisher for cartelemtryd service (aka ICarTelemetry).
@@ -50,57 +50,60 @@
     private static final String SERVICE_NAME = ICarTelemetryInternal.DESCRIPTOR + "/default";
     private static final int BINDER_FLAGS = 0;
 
-    private final Object mLock = new Object();
-
-    @Nullable
-    @GuardedBy("mLock")
     private ICarTelemetryInternal mCarTelemetryInternal;
 
-    @GuardedBy("mLock")
     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(CarDataInternal[] dataList) throws RemoteException {
+        public void onCarDataReceived(final CarDataInternal[] dataList) throws RemoteException {
             if (DEBUG) {
                 Slog.d(CarLog.TAG_TELEMETRY,
                         "Received " + dataList.length + " CarData from cartelemetryd");
             }
-            onCarDataListReceived(dataList);
+            // TODO(b/189142577): Create custom Handler and post message to improve performance
+            mTelemetryHandler.post(() -> onCarDataListReceived(dataList));
         }
     };
 
-    /** Called when binder for ICarTelemetry service is died. */
-    void onBinderDied() {
-        synchronized (mLock) {
-            if (mCarTelemetryInternal != null) {
-                mCarTelemetryInternal.asBinder().unlinkToDeath(this::onBinderDied, BINDER_FLAGS);
-            }
-            // TODO(b/193680465): try reconnecting again
-            mCarTelemetryInternal = null;
-        }
+    CarTelemetrydPublisher(BiConsumer<AbstractPublisher, Throwable> failureConsumer,
+            Handler telemetryHandler) {
+        super(failureConsumer);
+        this.mTelemetryHandler = telemetryHandler;
     }
 
-    /**
-     * Connects to ICarTelemetryInternal service and starts listening for CarData.
-     *
-     * @throws IllegalStateException if it cannot connect to ICarTelemetryInternal service.
-     */
-    @GuardedBy("mLock")
-    private void connectToCarTelemetrydLocked() {
+    /** 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;  // already connected
+            return;
         }
         IBinder binder = ServiceManager.checkService(SERVICE_NAME);
         if (binder == null) {
-            throw new IllegalStateException(
-                    "Failed to connect to ICarTelemetryInternal: service is not ready");
+            notifyFailureConsumer(new IllegalStateException(
+                    "Failed to connect to the ICarTelemetryInternal: service is not ready"));
+            return;
         }
         try {
             binder.linkToDeath(this::onBinderDied, BINDER_FLAGS);
         } catch (RemoteException e) {
-            throw new IllegalStateException(
-                    "Failed to connect to ICarTelemetryInternal: linkToDeath failed", e);
+            notifyFailureConsumer(new IllegalStateException(
+                    "Failed to connect to the ICarTelemetryInternal: linkToDeath failed", e));
+            return;
         }
         mCarTelemetryInternal = ICarTelemetryInternal.Stub.asInterface(binder);
         try {
@@ -108,9 +111,9 @@
         } catch (RemoteException e) {
             binder.unlinkToDeath(this::onBinderDied, BINDER_FLAGS);
             mCarTelemetryInternal = null;
-            throw new IllegalStateException(
-                    "Failed to connect to ICarTelemetryInternal: Cannot set CarData listener",
-                    e);
+            notifyFailureConsumer(new IllegalStateException(
+                    "Failed to connect to the ICarTelemetryInternal: Cannot set CarData listener",
+                    e));
         }
     }
 
@@ -119,8 +122,7 @@
      *
      * @throws IllegalStateException if fails to clear the listener.
      */
-    @GuardedBy("mLock")
-    private void disconnectFromCarTelemetrydLocked() {
+    private void disconnectFromCarTelemetryd() {
         if (mCarTelemetryInternal == null) {
             return;  // already disconnected
         }
@@ -135,9 +137,7 @@
 
     @VisibleForTesting
     boolean isConnectedToCarTelemetryd() {
-        synchronized (mLock) {
-            return mCarTelemetryInternal != null;
-        }
+        return mCarTelemetryInternal != null;
     }
 
     @Override
@@ -156,46 +156,34 @@
                 "Invalid CarData ID " + carDataId
                         + ". Please see CarData.proto for the list of available IDs.");
 
-        synchronized (mLock) {
-            try {
-                connectToCarTelemetrydLocked();
-            } catch (IllegalStateException e) {
-                Slog.e(CarLog.TAG_TELEMETRY, "Failed to connect to ICarTelemetry", e);
-                // TODO(b/193680465): add retry reconnecting
-            }
-            mSubscribers.add(subscriber);
-        }
+        mSubscribers.add(subscriber);
+
+        connectToCarTelemetryd();
 
         Slogf.d(CarLog.TAG_TELEMETRY, "Subscribing to CarDat.id=%d", carDataId);
     }
 
     @Override
     public void removeDataSubscriber(DataSubscriber subscriber) {
-        synchronized (mLock) {
-            mSubscribers.remove(subscriber);
-            if (mSubscribers.isEmpty()) {
-                disconnectFromCarTelemetrydLocked();
-            }
+        mSubscribers.remove(subscriber);
+        if (mSubscribers.isEmpty()) {
+            disconnectFromCarTelemetryd();
         }
     }
 
     @Override
     public void removeAllDataSubscribers() {
-        synchronized (mLock) {
-            mSubscribers.clear();
-            disconnectFromCarTelemetrydLocked();
-        }
+        mSubscribers.clear();
+        disconnectFromCarTelemetryd();
     }
 
     @Override
     public boolean hasDataSubscriber(DataSubscriber subscriber) {
-        synchronized (mLock) {
-            return mSubscribers.contains(subscriber);
-        }
+        return mSubscribers.contains(subscriber);
     }
 
     /**
-     * Called when publisher receives new car data list. It's executed on a Binder thread.
+     * 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/ConfigMetricsReportListConverter.java b/service/src/com/android/car/telemetry/publisher/ConfigMetricsReportListConverter.java
new file mode 100644
index 0000000..b3f2b39
--- /dev/null
+++ b/service/src/com/android/car/telemetry/publisher/ConfigMetricsReportListConverter.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.telemetry.publisher;
+
+import android.os.PersistableBundle;
+
+import com.android.car.telemetry.StatsLogProto;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class for converting metrics report list data to {@link PersistableBundle} compatible format.
+ */
+public class ConfigMetricsReportListConverter {
+    /**
+     * Converts metrics report list to map of metric_id to {@link PersistableBundle} format where
+     * each PersistableBundle containing arrays of metric fields data.
+     *
+     * Example:
+     * Given a ConfigMetricsReportList like this:
+     * {
+     *   reports: {
+     *     metrics: {
+     *       metric_id: 1234
+     *       event_metrics: {
+     *         data: {...}
+     *       }
+     *       metric_id: 2345
+     *       event_metrics: {
+     *         data: {...}
+     *       }
+     *       metric_id: 3456
+     *       gauge_metrics: {
+     *         data: {...}
+     *       }
+     *     }
+     *     metrics: {
+     *       metric_id: 3456
+     *       gauge_metrics: {
+     *         data: {...}
+     *       }
+     *     }
+     *   }
+     * }
+     * Will result in a map of this form:
+     * {
+     *   "1234" : {...}  // PersistableBundle containing metric 1234's data
+     *   "2345" : {...}
+     *   "3456" : {...}
+     * }
+     *
+     * @param reportList the {@link StatsLogProto.ConfigMetricsReportList} to be converted.
+     * @return a {@link PersistableBundle} containing mapping of metric id to metric data.
+     */
+    static Map<Long, PersistableBundle> convert(StatsLogProto.ConfigMetricsReportList reportList) {
+        // Map metric id to StatsLogReport list so that separate reports can be combined.
+        Map<Long, List<StatsLogProto.StatsLogReport>> metricsStatsReportMap = new HashMap<>();
+        // ConfigMetricsReportList is for one config. Normally only 1 report exists unless
+        // the previous report did not upload after shutdown, then at most 2 reports can exist.
+        for (StatsLogProto.ConfigMetricsReport report : reportList.getReportsList()) {
+            // Each statsReport is for a different metric in the report.
+            for (StatsLogProto.StatsLogReport statsReport : report.getMetricsList()) {
+                Long metricId = statsReport.getMetricId();
+                if (!metricsStatsReportMap.containsKey(metricId)) {
+                    metricsStatsReportMap.put(
+                            metricId, new ArrayList<StatsLogProto.StatsLogReport>());
+                }
+                metricsStatsReportMap.get(metricId).add(statsReport);
+            }
+        }
+        Map<Long, PersistableBundle> metricIdBundleMap = new HashMap<>();
+        // For each metric extract the metric data list from the combined stats reports,
+        // convert to bundle data.
+        for (Map.Entry<Long, List<StatsLogProto.StatsLogReport>>
+                    entry : metricsStatsReportMap.entrySet()) {
+            PersistableBundle statsReportBundle = new PersistableBundle();
+            Long metricId = entry.getKey();
+            List<StatsLogProto.StatsLogReport> statsReportList = entry.getValue();
+            switch (statsReportList.get(0).getDataCase()) {
+                case EVENT_METRICS:
+                    List<StatsLogProto.EventMetricData> eventDataList = new ArrayList<>();
+                    for (StatsLogProto.StatsLogReport statsReport : statsReportList) {
+                        eventDataList.addAll(statsReport.getEventMetrics().getDataList());
+                    }
+                    EventMetricDataConverter.convertEventDataList(
+                            eventDataList, statsReportBundle);
+                    break;
+                case GAUGE_METRICS:
+                    List<StatsLogProto.GaugeMetricData> gaugeDataList = new ArrayList<>();
+                    for (StatsLogProto.StatsLogReport statsReport : statsReportList) {
+                        gaugeDataList.addAll(statsReport.getGaugeMetrics().getDataList());
+                    }
+                    GaugeMetricDataConverter.convertGaugeDataList(
+                            gaugeDataList, statsReportBundle);
+                    break;
+                default:
+                    break;
+            }
+            metricIdBundleMap.put(metricId, statsReportBundle);
+        }
+        return metricIdBundleMap;
+    }
+}
diff --git a/service/src/com/android/car/telemetry/publisher/EventMetricDataConverter.java b/service/src/com/android/car/telemetry/publisher/EventMetricDataConverter.java
new file mode 100644
index 0000000..a1faac3
--- /dev/null
+++ b/service/src/com/android/car/telemetry/publisher/EventMetricDataConverter.java
@@ -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.
+ */
+
+package com.android.car.telemetry.publisher;
+
+import android.os.PersistableBundle;
+
+import com.android.car.telemetry.AtomsProto;
+import com.android.car.telemetry.StatsLogProto;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class for converting event metric data to {@link PersistableBundle} compatible format.
+ */
+public class EventMetricDataConverter {
+    static final String ELAPSED_TIME_NANOS = "elapsed_timestamp_nanos";
+
+    /**
+     * Converts a list of {@link StatsLogProto.EventMetricData} to {@link PersistableBundle}
+     * format such that along with the elapsed time array each field of the atom has an associated
+     * array containing the field's data in order received, matching the elapsed time array order.
+     *
+     * Example:
+     * {
+     *   elapsed_timestamp_nanos: [32948395739, 45623453646, ...]
+     *   uid: [1000, 1100, ...]
+     *   ...
+     * }
+     * @param eventDataList the list of {@link StatsLogProto.EventMetricData} to be converted.
+     * @param bundle the {@link PersistableBundle} to hold the converted values.
+     */
+    static void convertEventDataList(
+                List<StatsLogProto.EventMetricData> eventDataList, PersistableBundle bundle) {
+        List<Long> elapsedTimes = new ArrayList<>();
+        List<AtomsProto.Atom> atoms = new ArrayList<>();
+        for (StatsLogProto.EventMetricData eventData : eventDataList) {
+            elapsedTimes.add(eventData.getElapsedTimestampNanos());
+            atoms.add(eventData.getAtom());
+        }
+        AtomDataConverter.convertAtomsList(atoms, bundle);
+        bundle.putLongArray(
+                ELAPSED_TIME_NANOS, elapsedTimes.stream().mapToLong(i -> i).toArray());
+    }
+}
diff --git a/service/src/com/android/car/telemetry/publisher/GaugeMetricDataConverter.java b/service/src/com/android/car/telemetry/publisher/GaugeMetricDataConverter.java
new file mode 100644
index 0000000..66c9b53
--- /dev/null
+++ b/service/src/com/android/car/telemetry/publisher/GaugeMetricDataConverter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.os.PersistableBundle;
+
+import com.android.car.telemetry.AtomsProto;
+import com.android.car.telemetry.StatsLogProto;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class for converting gauge metric data to {@link PersistableBundle} compatible format.
+ */
+public class GaugeMetricDataConverter {
+    static final String DIMENSION_DELIMITOR = "-";
+    static final String ELAPSED_TIME = "elapsed_timestamp_nanos";
+
+    /**
+     * Converts a list of {@link StatsLogProto.GaugeMetricData} to {@link PersistableBundle}
+     * format such that along with the elapsed time array each field of the atom has an associated
+     * array containing the field's data in order received, matching the elapsed time array order.
+     * The atoms are extracted out of the each bucket while preserving the order they had in the
+     * bucket.
+     *
+     * Example:
+     * {
+     *   elapsed_timestamp_nanos: [32948395739, 45623453646, ...]
+     *   uid: [1000, 1100, ...]
+     *   ...
+     * }
+     *
+     * @param gaugeDataList the list of {@link StatsLogProto.GaugeMetricData} to be converted.
+     * @param bundle the {@link PersistableBundle} to hold the converted values.
+     */
+    static void convertGaugeDataList(
+            List<StatsLogProto.GaugeMetricData> gaugeDataList, PersistableBundle bundle) {
+        // TODO(b/200064146): translate the dimension strings to get uid and package_name.
+        List<Long> elapsedTimes = new ArrayList<>();
+        List<AtomsProto.Atom> atoms = new ArrayList<>();
+        for (StatsLogProto.GaugeMetricData gaugeData : gaugeDataList) {
+            for (StatsLogProto.GaugeBucketInfo bi : gaugeData.getBucketInfoList()) {
+                elapsedTimes.addAll(bi.getElapsedTimestampNanosList());
+                atoms.addAll(bi.getAtomList());
+            }
+        }
+        AtomDataConverter.convertAtomsList(atoms, bundle);
+        bundle.putLongArray(
+                ELAPSED_TIME, elapsedTimes.stream().mapToLong(i -> i).toArray());
+    }
+}
diff --git a/service/src/com/android/car/telemetry/publisher/PublisherFactory.java b/service/src/com/android/car/telemetry/publisher/PublisherFactory.java
index fb32742..2e08191 100644
--- a/service/src/com/android/car/telemetry/publisher/PublisherFactory.java
+++ b/service/src/com/android/car/telemetry/publisher/PublisherFactory.java
@@ -16,49 +16,67 @@
 
 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.io.File;
+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 File mRootDirectory;
+    private final Handler mTelemetryHandler;
     private final StatsManagerProxy mStatsManager;
-    private final SharedPreferences mSharedPreferences;
     private VehiclePropertyPublisher mVehiclePropertyPublisher;
+    private CarTelemetrydPublisher mCarTelemetrydPublisher;
     private StatsPublisher mStatsPublisher;
 
+    private BiConsumer<AbstractPublisher, Throwable> mFailureConsumer;
+
     public PublisherFactory(
             CarPropertyService carPropertyService,
+            Handler handler,
             StatsManagerProxy statsManager,
-            SharedPreferences sharedPreferences) {
+            File rootDirectory) {
         mCarPropertyService = carPropertyService;
+        mTelemetryHandler = handler;
         mStatsManager = statsManager;
-        mSharedPreferences = sharedPreferences;
+        mRootDirectory = rootDirectory;
     }
 
-    /** 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;
-                // TODO(b/189142577): add cartelemetry publisher here
+                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(mStatsManager, mSharedPreferences);
+                        mStatsPublisher = new StatsPublisher(
+                                mFailureConsumer, mStatsManager, mRootDirectory);
                     }
                     return mStatsPublisher;
                 default:
@@ -67,4 +85,13 @@
             }
         }
     }
+
+    /**
+     * 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/StatsPublisher.java b/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
index bf342d9..68fb9bc 100644
--- a/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
@@ -16,8 +16,9 @@
 
 package com.android.car.telemetry.publisher;
 
+import static com.android.car.telemetry.AtomsProto.Atom.APP_START_MEMORY_STATE_CAPTURED_FIELD_NUMBER;
+
 import android.app.StatsManager.StatsUnavailableException;
-import android.content.SharedPreferences;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.PersistableBundle;
@@ -25,6 +26,7 @@
 import android.util.Slog;
 
 import com.android.car.CarLog;
+import com.android.car.telemetry.AtomsProto;
 import com.android.car.telemetry.StatsLogProto;
 import com.android.car.telemetry.StatsdConfigProto;
 import com.android.car.telemetry.StatsdConfigProto.StatsdConfig;
@@ -37,19 +39,22 @@
 
 import com.google.protobuf.InvalidProtocolBufferException;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
 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} or
- * {@link #removeDataSubscriber} to clean-up these configs from StatsD store.
- *
- * <p>Thread-safe.
+ * 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.
@@ -57,23 +62,51 @@
     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;
+    static final int PROCESS_MEMORY_STATE_MATCHER_ID = 3;
+    @VisibleForTesting
+    static final int PROCESS_MEMORY_STATE_GAUGE_METRIC_ID = 4;
+
+    // The file that contains stats config key and stats config version
+    @VisibleForTesting
+    static final String SAVED_STATS_CONFIGS_FILE = "stats_config_keys_versions";
 
     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-";
+    private static final String BUNDLE_CONFIG_KEY_PREFIX = "statsd-publisher-config-id-";
+    private static final String BUNDLE_CONFIG_VERSION_PREFIX = "statsd-publisher-config-version-";
+
+    @VisibleForTesting
+    static final StatsdConfigProto.FieldMatcher PROCESS_MEMORY_STATE_FIELDS_MATCHER =
+            StatsdConfigProto.FieldMatcher.newBuilder()
+                    .setField(
+                            AtomsProto.Atom.PROCESS_MEMORY_STATE_FIELD_NUMBER)
+                    .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
+                            .setField(
+                                    AtomsProto.ProcessMemoryState.OOM_ADJ_SCORE_FIELD_NUMBER))
+                    .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
+                            .setField(
+                                    AtomsProto.ProcessMemoryState.PAGE_FAULT_FIELD_NUMBER))
+                    .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
+                            .setField(
+                                    AtomsProto.ProcessMemoryState.PAGE_MAJOR_FAULT_FIELD_NUMBER))
+                    .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
+                            .setField(
+                                    AtomsProto.ProcessMemoryState.RSS_IN_BYTES_FIELD_NUMBER))
+                    .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
+                            .setField(
+                                    AtomsProto.ProcessMemoryState.CACHE_IN_BYTES_FIELD_NUMBER))
+                    .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
+                            .setField(
+                                    AtomsProto.ProcessMemoryState.SWAP_IN_BYTES_FIELD_NUMBER))
+            .build();
 
     // TODO(b/197766340): remove unnecessary lock
     private final Object mLock = new Object();
 
     private final StatsManagerProxy mStatsManager;
-    private final SharedPreferences mSharedPreferences;
-    private final Handler mHandler;
+    private final File mSavedStatsConfigsFile;
+    private final Handler mTelemetryHandler;
 
     // True if the publisher is periodically pulling reports from StatsD.
     private final AtomicBoolean mIsPullingReports = new AtomicBoolean(false);
@@ -88,16 +121,51 @@
     @GuardedBy("mLock")
     private final LongSparseArray<DataSubscriber> mConfigKeyToSubscribers = new LongSparseArray<>();
 
-    StatsPublisher(StatsManagerProxy statsManager, SharedPreferences sharedPreferences) {
-        this(statsManager, sharedPreferences, new Handler(Looper.myLooper()));
+    private final PersistableBundle mSavedStatsConfigs;
+
+    // TODO(b/198331078): Use telemetry thread
+    StatsPublisher(
+            BiConsumer<AbstractPublisher, Throwable> failureConsumer,
+            StatsManagerProxy statsManager,
+            File rootDirectory) {
+        this(failureConsumer, statsManager, rootDirectory, new Handler(Looper.myLooper()));
     }
 
     @VisibleForTesting
     StatsPublisher(
-            StatsManagerProxy statsManager, SharedPreferences sharedPreferences, Handler handler) {
+            BiConsumer<AbstractPublisher, Throwable> failureConsumer,
+            StatsManagerProxy statsManager,
+            File rootDirectory,
+            Handler handler) {
+        super(failureConsumer);
         mStatsManager = statsManager;
-        mSharedPreferences = sharedPreferences;
-        mHandler = handler;
+        mTelemetryHandler = handler;
+        mSavedStatsConfigsFile = new File(rootDirectory, SAVED_STATS_CONFIGS_FILE);
+        mSavedStatsConfigs = loadBundle();
+    }
+
+    /** Loads the PersistableBundle containing stats config keys and versions from disk. */
+    private PersistableBundle loadBundle() {
+        try (FileInputStream fileInputStream = new FileInputStream(mSavedStatsConfigsFile)) {
+            return PersistableBundle.readFromStream(fileInputStream);
+        } catch (IOException e) {
+            // TODO(b/199947533): handle failure
+            Slog.e(CarLog.TAG_TELEMETRY,
+                    "Failed to read file " + mSavedStatsConfigsFile.getAbsolutePath(), e);
+            return new PersistableBundle();
+        }
+    }
+
+    /** Writes the PersistableBundle containing stats config keys and versions to disk. */
+    private void saveBundle() {
+        try (FileOutputStream fileOutputStream = new FileOutputStream(mSavedStatsConfigsFile)) {
+            mSavedStatsConfigs.writeToStream(fileOutputStream);
+        } catch (IOException e) {
+            // TODO(b/199947533): handle failure
+            Slog.e(CarLog.TAG_TELEMETRY,
+                    "Cannot write to " + mSavedStatsConfigsFile.getAbsolutePath()
+                            + ". Added stats config info is lost.", e);
+        }
     }
 
     @Override
@@ -113,26 +181,32 @@
         }
 
         if (!mIsPullingReports.getAndSet(true)) {
-            mHandler.postDelayed(mPullReportsPeriodically, PULL_REPORTS_PERIOD.toMillis());
+            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 {
-                StatsLogProto.ConfigMetricsReportList report =
-                        StatsLogProto.ConfigMetricsReportList.parseFrom(
-                                mStatsManager.getReports(configKey));
-                // 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());
-                    mConfigKeyToSubscribers.get(configKey).push(data);
-                }
+                processReport(configKey, StatsLogProto.ConfigMetricsReportList.parseFrom(
+                        mStatsManager.getReports(configKey)));
             } catch (StatsUnavailableException e) {
-                // TODO(b/189143813): retry if stats is not available
+                // 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,
@@ -141,19 +215,21 @@
         }
 
         if (mIsPullingReports.get()) {
-            mHandler.postDelayed(mPullReportsPeriodically, PULL_REPORTS_PERIOD.toMillis());
+            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;
+            for (String key : mSavedStatsConfigs.keySet()) {
+                // filter out all the config versions
+                if (!key.startsWith(BUNDLE_CONFIG_KEY_PREFIX)) {
+                    continue;
                 }
-                result.add((long) value);
-            });
+                // the remaining values are config keys
+                result.add(mSavedStatsConfigs.getLong(key));
+            }
         }
         return result;
     }
@@ -180,7 +256,7 @@
 
         if (mConfigKeyToSubscribers.size() == 0) {
             mIsPullingReports.set(false);
-            mHandler.removeCallbacks(mPullReportsPeriodically);
+            mTelemetryHandler.removeCallbacks(mPullReportsPeriodically);
         }
     }
 
@@ -188,27 +264,32 @@
     @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;
+            for (String key : mSavedStatsConfigs.keySet()) {
+                // filter out all the config versions
+                if (!key.startsWith(BUNDLE_CONFIG_KEY_PREFIX)) {
+                    continue;
                 }
-                long configKey = (long) value;
+                // the remaining values are config keys
+                long configKey = mSavedStatsConfigs.getLong(key);
                 try {
                     mStatsManager.removeConfig(configKey);
-                    String sharedPrefVersion = buildSharedPrefConfigVersionKey(configKey);
-                    editor.remove(key).remove(sharedPrefVersion);
+                    String bundleVersion = buildBundleConfigVersionKey(configKey);
+                    mSavedStatsConfigs.remove(key);
+                    mSavedStatsConfigs.remove(bundleVersion);
                 } catch (StatsUnavailableException e) {
-                    Slog.w(CarLog.TAG_TELEMETRY, "Failed to remove config " + configKey, e);
-                    // TODO(b/189143813): if StatsManager is not ready, retry N times and hard fail
-                    //                    after by notifying DataBroker.
+                    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();
+            }
+            saveBundle();
+            mSavedStatsConfigs.clear();
         }
         mIsPullingReports.set(false);
-        mHandler.removeCallbacks(mPullReportsPeriodically);
+        mTelemetryHandler.removeCallbacks(mPullReportsPeriodically);
     }
 
     @Override
@@ -223,57 +304,63 @@
         }
     }
 
+    /** 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
+     * Returns the key for PersistableBundle to store/retrieve configKey associated with the
      * subscriber.
      */
-    private static String buildSharedPrefConfigKey(DataSubscriber subscriber) {
-        return SHARED_PREF_CONFIG_KEY_PREFIX + subscriber.getMetricsConfig().getName() + "-"
+    private static String buildBundleConfigKey(DataSubscriber subscriber) {
+        return BUNDLE_CONFIG_KEY_PREFIX + subscriber.getMetricsConfig().getName() + "-"
                 + subscriber.getSubscriber().getHandler();
     }
 
     /**
-     * Returns the key for SharedPreferences to store/retrieve {@link TelemetryProto.MetricsConfig}
+     * Returns the key for PersistableBundle 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;
+    private static String buildBundleConfigVersionKey(long configKey) {
+        return BUNDLE_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
+     * previously added config_keys in the persistable bundle and only updates StatsD when
      * the MetricsConfig (of CarTelemetryService) has a new version.
      */
     @GuardedBy("mLock")
     private long addStatsConfigLocked(DataSubscriber subscriber) {
-        String sharedPrefConfigKey = buildSharedPrefConfigKey(subscriber);
         long configKey = buildConfigKey(subscriber);
         // Store MetricsConfig (of CarTelemetryService) version per handler_function.
-        String sharedPrefVersion = buildSharedPrefConfigVersionKey(configKey);
-        StatsdConfig config = buildStatsdConfig(subscriber, configKey);
-        if (mSharedPreferences.contains(sharedPrefVersion)) {
-            int currentVersion = mSharedPreferences.getInt(sharedPrefVersion, 0);
-            if (currentVersion < subscriber.getMetricsConfig().getVersion()) {
-                // TODO(b/189143813): remove old version from StatsD
-                Slog.d(CarLog.TAG_TELEMETRY, "Removing old config from StatsD");
-            } else {
-                // Ignore if the MetricsConfig version is current or older.
+        String bundleVersion = buildBundleConfigVersionKey(configKey);
+        if (mSavedStatsConfigs.getInt(bundleVersion) != 0) {
+            int currentVersion = mSavedStatsConfigs.getInt(bundleVersion);
+            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 bundleConfigKey = buildBundleConfigKey(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();
+            mSavedStatsConfigs.putInt(bundleVersion, subscriber.getMetricsConfig().getVersion());
+            mSavedStatsConfigs.putLong(bundleConfigKey, configKey);
+            saveBundle();
         } 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;
     }
@@ -281,17 +368,22 @@
     /** Removes StatsdConfig and returns configKey. */
     @GuardedBy("mLock")
     private long removeStatsConfigLocked(DataSubscriber subscriber) {
-        String sharedPrefConfigKey = buildSharedPrefConfigKey(subscriber);
+        String bundleConfigKey = buildBundleConfigKey(subscriber);
         long configKey = buildConfigKey(subscriber);
         // Store MetricsConfig (of CarTelemetryService) version per handler_function.
-        String sharedPrefVersion = buildSharedPrefConfigVersionKey(configKey);
+        String bundleVersion = buildBundleConfigVersionKey(configKey);
         try {
             mStatsManager.removeConfig(configKey);
-            mSharedPreferences.edit().remove(sharedPrefVersion).remove(sharedPrefConfigKey).apply();
+            mSavedStatsConfigs.remove(bundleVersion);
+            mSavedStatsConfigs.remove(bundleConfigKey);
+            saveBundle();
         } catch (StatsUnavailableException e) {
-            Slog.w(CarLog.TAG_TELEMETRY, "Failed to remove config " + configKey, e);
-            // TODO(b/189143813): if StatsManager is not ready, retry N times and hard fail after
-            //                    by notifying DataBroker.
+            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;
     }
@@ -316,24 +408,63 @@
     static StatsdConfig buildStatsdConfig(DataSubscriber subscriber, long configId) {
         TelemetryProto.StatsPublisher.SystemMetric metric =
                 subscriber.getPublisherParam().getStats().getSystemMetric();
+        StatsdConfig.Builder builder = StatsdConfig.newBuilder()
+                // This id is not used in StatsD, but let's make it the same as config_key
+                // just in case.
+                .setId(configId)
+                .addAllowedLogSource("AID_SYSTEM");
+
         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();
+            return buildAppStartMemoryStateStatsdConfig(builder);
+        } else if (metric == TelemetryProto.StatsPublisher.SystemMetric.PROCESS_MEMORY_STATE) {
+            return buildProcessMemoryStateStatsdConfig(builder);
         } else {
             throw new IllegalArgumentException("Unsupported metric " + metric.name());
         }
     }
+
+    private static StatsdConfig buildAppStartMemoryStateStatsdConfig(StatsdConfig.Builder builder) {
+        return builder
+                .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(APP_START_MEMORY_STATE_CAPTURED_FIELD_NUMBER)))
+                .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))
+                .build();
+    }
+
+    private static StatsdConfig buildProcessMemoryStateStatsdConfig(StatsdConfig.Builder builder) {
+        return builder
+                .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
+                        // The id must be unique within StatsdConfig/matchers
+                        .setId(PROCESS_MEMORY_STATE_MATCHER_ID)
+                        .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
+                                .setAtomId(AtomsProto.Atom.PROCESS_MEMORY_STATE_FIELD_NUMBER)))
+                .addGaugeMetric(StatsdConfigProto.GaugeMetric.newBuilder()
+                        // The id must be unique within StatsdConfig/metrics
+                        .setId(PROCESS_MEMORY_STATE_GAUGE_METRIC_ID)
+                        .setWhat(PROCESS_MEMORY_STATE_MATCHER_ID)
+                        .setDimensionsInWhat(StatsdConfigProto.FieldMatcher.newBuilder()
+                                .setField(AtomsProto.Atom.PROCESS_MEMORY_STATE_FIELD_NUMBER)
+                                .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
+                                        .setField(1))  // ProcessMemoryState.uid
+                                .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
+                                        .setField(2))  // ProcessMemoryState.process_name
+                        )
+                        .setGaugeFieldsFilter(StatsdConfigProto.FieldFilter.newBuilder()
+                                .setFields(PROCESS_MEMORY_STATE_FIELDS_MATCHER)
+                        )  // setGaugeFieldsFilter
+                        .setSamplingType(
+                                StatsdConfigProto.GaugeMetric.SamplingType.RANDOM_ONE_SAMPLE)
+                        .setBucket(StatsdConfigProto.TimeUnit.FIVE_MINUTES)
+                )
+                .addPullAtomPackages(StatsdConfigProto.PullAtomPackages.newBuilder()
+                        .setAtomId(AtomsProto.Atom.PROCESS_MEMORY_STATE_FIELD_NUMBER)
+                        .addPackages("AID_SYSTEM"))
+                .build();
+    }
 }
diff --git a/service/src/com/android/car/telemetry/publisher/VehiclePropertyPublisher.java b/service/src/com/android/car/telemetry/publisher/VehiclePropertyPublisher.java
index ce692ad..dbfc002 100644
--- a/service/src/com/android/car/telemetry/publisher/VehiclePropertyPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/VehiclePropertyPublisher.java
@@ -20,6 +20,7 @@
 import android.car.hardware.CarPropertyConfig;
 import android.car.hardware.property.CarPropertyEvent;
 import android.car.hardware.property.ICarPropertyEventListener;
+import android.os.Handler;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.util.ArraySet;
@@ -31,18 +32,16 @@
 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.util.Preconditions;
 
 import java.util.List;
+import java.util.function.BiConsumer;
 
 /**
  * Publisher for Vehicle Property changes, aka {@code CarPropertyService}.
  *
  * <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.
- *
- * <p>Thread-safe.
  */
 public class VehiclePropertyPublisher extends AbstractPublisher {
     private static final boolean DEBUG = false;  // STOPSHIP if true
@@ -50,10 +49,8 @@
     /** Bundle key for {@link CarPropertyEvent}. */
     public static final String CAR_PROPERTY_EVENT_KEY = "car_property_event";
 
-    // Used to synchronize add/remove DataSubscriber and CarPropertyEvent listener calls.
-    private final Object mLock = new Object();
-
     private final CarPropertyService mCarPropertyService;
+    private final Handler mTelemetryHandler;
 
     // The class only reads, no need to synchronize this object.
     // Maps property_id to CarPropertyConfig.
@@ -62,7 +59,6 @@
     // 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.
-    @GuardedBy("mLock")
     private final SparseArray<ArraySet<DataSubscriber>> mCarPropertyToSubscribers =
             new SparseArray<>();
 
@@ -80,8 +76,11 @@
                 }
             };
 
-    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.
         List<CarPropertyConfig> propertyList = mCarPropertyService.getPropertyList();
         mCarPropertyList = new SparseArray<>(propertyList.size());
@@ -108,19 +107,17 @@
                         == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
                 "No access. Cannot read " + VehiclePropertyIds.toString(propertyId) + ".");
 
-        synchronized (mLock) {
-            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);
+        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
@@ -134,32 +131,28 @@
         }
         int propertyId = publisherParam.getVehicleProperty().getVehiclePropertyId();
 
-        synchronized (mLock) {
-            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);
-            }
+        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() {
-        synchronized (mLock) {
-            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();
+        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
@@ -169,11 +162,8 @@
             return false;
         }
         int propertyId = publisherParam.getVehicleProperty().getVehiclePropertyId();
-
-        synchronized (mLock) {
-            ArraySet<DataSubscriber> subscribers = mCarPropertyToSubscribers.get(propertyId);
-            return subscribers != null && subscribers.contains(subscriber);
-        }
+        ArraySet<DataSubscriber> subscribers = mCarPropertyToSubscribers.get(propertyId);
+        return subscribers != null && subscribers.contains(subscriber);
     }
 
     /**
@@ -181,19 +171,15 @@
      * worker thread.
      */
     private void onVehicleEvent(CarPropertyEvent event) {
-        PersistableBundle bundle = new PersistableBundle();
-        // TODO(b/197269115): Properly populate PersistableBundle with car property data.
-        ArraySet<DataSubscriber> subscribersClone;
-
-        synchronized (mLock) {
-            subscribersClone = new ArraySet<>(
+        // 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()));
-        }
-
-        // Call external methods outside of mLock. If the external method invokes this class's
-        // methods again, it will cause a deadlock.
-        for (DataSubscriber subscriber : subscribersClone) {
-            subscriber.push(bundle);
-        }
+            for (DataSubscriber subscriber : subscribersClone) {
+                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
index c768161..3101634 100644
--- a/service/src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutor.aidl
+++ b/service/src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutor.aidl
@@ -16,6 +16,7 @@
 
 package com.android.car.telemetry.scriptexecutorinterface;
 
+import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import com.android.car.telemetry.scriptexecutorinterface.IScriptExecutorListener;
 
@@ -23,19 +24,39 @@
  * An internal API provided by isolated Script Executor process
  * for executing Lua scripts in a sandboxed environment
  */
-interface IScriptExecutor {
+oneway interface IScriptExecutor {
   /**
-   * Executes a specified function in provided Lua script with given input arguments.
+   * Executes a specified function in a 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
+   * @param listener callback for the sandboxed environment 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);
+
+  /**
+   * Executes a specified function in a provided Lua script with given input arguments.
+   * This is a specialized version of invokeScript API above for a case when publishedData input
+   * could be potentially large and overflow Binder's buffer.
+   *
+   * @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 publishedDataFileDescriptor file descriptor which is be used to open a pipe to read
+   * large amount of input data. The input data is then handled by the provided Lua function.
+   * @param savedState key-value pairs preserved from the previous invocation of the function
+   * @param listener callback for the sandboxed environment to report back script execution results,
+   * errors, and logs
+   */
+  void invokeScriptForLargeInput(String scriptBody,
+                    String functionName,
+                    in ParcelFileDescriptor publishedDataFileDescriptor,
+                    in @nullable PersistableBundle savedState,
+                    in IScriptExecutorListener listener);
 }
diff --git a/service/src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutorListener.aidl b/service/src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutorListener.aidl
index 6bb1b04..dc64732 100644
--- a/service/src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutorListener.aidl
+++ b/service/src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutorListener.aidl
@@ -33,7 +33,7 @@
    *
    * @param result final results of the script that will be uploaded.
    */
-  void onScriptFinished(in byte[] result);
+  void onScriptFinished(in PersistableBundle result);
 
   /**
    * Called by ScriptExecutor when a function completes successfully and also provides
diff --git a/service/src/com/android/car/telemetry/systemmonitor/SystemMonitor.java b/service/src/com/android/car/telemetry/systemmonitor/SystemMonitor.java
index fec3fef..0dc5563 100644
--- a/service/src/com/android/car/telemetry/systemmonitor/SystemMonitor.java
+++ b/service/src/com/android/car/telemetry/systemmonitor/SystemMonitor.java
@@ -24,7 +24,6 @@
 import android.util.Slog;
 
 import com.android.car.CarLog;
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.BufferedReader;
@@ -34,6 +33,7 @@
 /**
  * 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 {
 
@@ -46,21 +46,16 @@
 
     private static final int POLL_INTERVAL_MILLIS = 60000;
 
-    private final Handler mWorkerHandler;
-
-    private final Object mLock = new Object();
+    private final Handler mTelemetryHandler;
 
     private final Context mContext;
     private final ActivityManager mActivityManager;
     private final String mLoadavgPath;
     private final Runnable mSystemLoadRunnable = this::getSystemLoadRepeated;
 
-    @GuardedBy("mLock")
     @Nullable private SystemMonitorCallback mCallback;
-    @GuardedBy("mLock")
     private boolean mSystemMonitorRunning = false;
 
-
     /**
      * Interface for receiving notifications about system monitor changes.
      */
@@ -85,9 +80,9 @@
     }
 
     @VisibleForTesting
-    SystemMonitor(Context context, Handler workerHandler, String loadavgPath) {
+    SystemMonitor(Context context, Handler telemetryHandler, String loadavgPath) {
         mContext = context;
-        mWorkerHandler = workerHandler;
+        mTelemetryHandler = telemetryHandler;
         mActivityManager = (ActivityManager)
                 mContext.getSystemService(Context.ACTIVITY_SERVICE);
         mLoadavgPath = loadavgPath;
@@ -99,11 +94,9 @@
      * @param callback the callback to nofify state changes on.
      */
     public void setSystemMonitorCallback(SystemMonitorCallback callback) {
-        synchronized (mLock) {
-            mCallback = callback;
-            if (!mSystemMonitorRunning) {
-                startSystemLoadMonitoring();
-            }
+        mCallback = callback;
+        if (!mSystemMonitorRunning) {
+            startSystemLoadMonitoring();
         }
     }
 
@@ -111,10 +104,9 @@
      * Unsets the {@link SystemMonitorCallback}.
      */
     public void unsetSystemMonitorCallback() {
-        synchronized (mLock) {
-            stopSystemLoadMonitoringLocked();
-            mCallback = null;
-        }
+        mTelemetryHandler.removeCallbacks(mSystemLoadRunnable);
+        mSystemMonitorRunning = false;
+        mCallback = null;
     }
 
     /**
@@ -201,25 +193,23 @@
      * The Runnable to repeatedly getting system load data with some interval.
      */
     private void getSystemLoadRepeated() {
-        synchronized (mLock) {
-            try {
-                CpuLoadavg cpuLoadAvg = getCpuLoad();
-                if (cpuLoadAvg == null) {
-                    return;
-                }
-                int numProcessors = Runtime.getRuntime().availableProcessors();
+        try {
+            CpuLoadavg cpuLoadAvg = getCpuLoad();
+            if (cpuLoadAvg == null) {
+                return;
+            }
+            int numProcessors = Runtime.getRuntime().availableProcessors();
 
-                MemoryInfo memInfo = getMemoryLoad();
+            MemoryInfo memInfo = getMemoryLoad();
 
-                SystemMonitorEvent event = new SystemMonitorEvent();
-                setEventCpuUsageLevel(event, cpuLoadAvg.mOneMinuteVal / numProcessors);
-                setEventMemUsageLevel(event, 1 - memInfo.availMem / memInfo.totalMem);
+            SystemMonitorEvent event = new SystemMonitorEvent();
+            setEventCpuUsageLevel(event, cpuLoadAvg.mOneMinuteVal / numProcessors);
+            setEventMemUsageLevel(event, 1 - memInfo.availMem / memInfo.totalMem);
 
-                mCallback.onSystemMonitorEvent(event);
-            } finally {
-                if (mSystemMonitorRunning) {
-                    mWorkerHandler.postDelayed(mSystemLoadRunnable, POLL_INTERVAL_MILLIS);
-                }
+            mCallback.onSystemMonitorEvent(event);
+        } finally {
+            if (mSystemMonitorRunning) {
+                mTelemetryHandler.postDelayed(mSystemLoadRunnable, POLL_INTERVAL_MILLIS);
             }
         }
     }
@@ -228,21 +218,8 @@
      * Starts system load monitoring.
      */
     private void startSystemLoadMonitoring() {
-        synchronized (mLock) {
-            mWorkerHandler.post(mSystemLoadRunnable);
-            mSystemMonitorRunning = true;
-        }
-    }
-
-    /**
-     * Stops system load monitoring.
-     */
-    @GuardedBy("mLock")
-    private void stopSystemLoadMonitoringLocked() {
-        synchronized (mLock) {
-            mWorkerHandler.removeCallbacks(mSystemLoadRunnable);
-            mSystemMonitorRunning = false;
-        }
+        mTelemetryHandler.post(mSystemLoadRunnable);
+        mSystemMonitorRunning = true;
     }
 
     static final class CpuLoadavg {
diff --git a/service/src/com/android/car/watchdog/PackageInfoHandler.java b/service/src/com/android/car/watchdog/PackageInfoHandler.java
index 5ee811d..e3f4bb8 100644
--- a/service/src/com/android/car/watchdog/PackageInfoHandler.java
+++ b/service/src/com/android/car/watchdog/PackageInfoHandler.java
@@ -292,4 +292,10 @@
         }
         return ComponentType.THIRD_PARTY;
     }
+
+    void setVendorPackagePrefixes(List<String> vendorPackagePrefixes) {
+        synchronized (mLock) {
+            mVendorPackagePrefixes = vendorPackagePrefixes;
+        }
+    }
 }
diff --git a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
index fee73b9..416795e 100644
--- a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
+++ b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
@@ -89,7 +89,6 @@
 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;
@@ -169,10 +168,6 @@
 
     /** Initializes the handler. */
     public void init() {
-        /*
-         * TODO(b/183947162): Opt-in to receive package change broadcast and handle package enabled
-         *  state changes.
-         */
         /* First database read is expensive, so post it on a separate handler thread. */
         mHandlerThread.getThreadHandler().post(() -> {
             readFromDatabase();
@@ -708,13 +703,12 @@
                 for (int pkgIdx = 0; pkgIdx < packages.size(); pkgIdx++) {
                     String packageName = packages.get(pkgIdx);
                     try {
-                        int oldEnabledState = -1;
                         if (!hasRecurringOveruse) {
-                            oldEnabledState = packageManager.getApplicationEnabledSetting(
+                            int currentEnabledState = packageManager.getApplicationEnabledSetting(
                                     packageName, userId);
-                            if (oldEnabledState == COMPONENT_ENABLED_STATE_DISABLED
-                                    || oldEnabledState == COMPONENT_ENABLED_STATE_DISABLED_USER
-                                    || oldEnabledState
+                            if (currentEnabledState == COMPONENT_ENABLED_STATE_DISABLED
+                                    || currentEnabledState == COMPONENT_ENABLED_STATE_DISABLED_USER
+                                    || currentEnabledState
                                     == COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
                                 continue;
                             }
@@ -724,9 +718,6 @@
                                 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);
@@ -744,17 +735,15 @@
         }
     }
 
-    /** Resets the resource overuse stats for the given generic package names. */
+    /** Resets the resource overuse settings and 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/192294393): When the stats are persisted in local DB, reset the stats
-                     *  for this package from local DB.
-                     */
+                    usage.verifyAndSetKillableStateLocked(/* isKillable= */ true);
+                    mWatchdogStorage.deleteUserPackage(usage.userId, usage.genericPackageName);
                 }
             }
         }
@@ -797,6 +786,8 @@
                         break;
                     case ComponentType.VENDOR:
                         mSafeToKillVendorPackages.addAll(internalConfigs.get(i).safeToKillPackages);
+                        mPackageInfoHandler.setVendorPackagePrefixes(
+                                internalConfigs.get(i).vendorPackagePrefixes);
                         break;
                     default:
                         // All third-party apps are killable.
@@ -804,7 +795,7 @@
                 }
             }
             if (DEBUG) {
-                Slogf.d(TAG, "Fetched and synced safe to kill packages.");
+                Slogf.d(TAG, "Fetched and synced resource overuse configs.");
             }
         }
     }
@@ -958,26 +949,7 @@
             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(entry.getKey(),
-                            usage.userId)
-                            != COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
-                        continue;
-                    }
-                    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.genericPackageName, usage.userId);
-                }
-            }
-            usage.resetStatsLocked();
+            mUsageByUserPackage.valueAt(i).resetStatsLocked();
         }
         mLatestStatsReportDate = currentDate;
         if (DEBUG) {
@@ -1465,23 +1437,15 @@
 
     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);
         ArrayMap<String, PerStateBytes> packageSpecificThresholds =
                 toPerStateBytesMap(internalConfig.packageSpecificThresholds);
         ArrayMap<String, PerStateBytes> appCategorySpecificThresholds =
                 toPerStateBytesMap(internalConfig.categorySpecificThresholds);
-        replaceKey.accept(appCategorySpecificThresholds, INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS,
+        replaceKey(appCategorySpecificThresholds, INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS,
                 ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MAPS);
-        replaceKey.accept(appCategorySpecificThresholds, INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA,
+        replaceKey(appCategorySpecificThresholds, INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA,
                 ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MEDIA);
         List<IoOveruseAlertThreshold> systemWideThresholds =
                 toIoOveruseAlertThresholds(internalConfig.systemWideThresholds);
@@ -1610,8 +1574,6 @@
         @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} */
@@ -1699,18 +1661,19 @@
 
         @GuardedBy("mLock")
         public void resetStatsLocked() {
-            oldEnabledStateByPackage.clear();
             ioUsage.resetStats();
         }
     }
     /** Defines I/O usage fields for a package. */
     public static final class PackageIoUsage {
+        private static final android.automotive.watchdog.PerStateBytes DEFAULT_PER_STATE_BYTES =
+                new android.automotive.watchdog.PerStateBytes();
         private android.automotive.watchdog.IoOveruseStats mIoOveruseStats;
         private android.automotive.watchdog.PerStateBytes mForgivenWriteBytes;
         private int mTotalTimesKilled;
 
         private PackageIoUsage() {
-            mForgivenWriteBytes = new android.automotive.watchdog.PerStateBytes();
+            mForgivenWriteBytes = DEFAULT_PER_STATE_BYTES;
             mTotalTimesKilled = 0;
         }
 
@@ -1777,7 +1740,7 @@
 
         void resetStats() {
             mIoOveruseStats = null;
-            mForgivenWriteBytes = null;
+            mForgivenWriteBytes = DEFAULT_PER_STATE_BYTES;
             mTotalTimesKilled = 0;
         }
     }
diff --git a/service/src/com/android/car/watchdog/WatchdogStorage.java b/service/src/com/android/car/watchdog/WatchdogStorage.java
index 46445a5..78b6927 100644
--- a/service/src/com/android/car/watchdog/WatchdogStorage.java
+++ b/service/src/com/android/car/watchdog/WatchdogStorage.java
@@ -158,6 +158,21 @@
         return entries;
     }
 
+    /** Deletes user package settings and resource overuse stats. */
+    public void deleteUserPackage(@UserIdInt int userId, String packageName) {
+        UserPackage userPackage = mUserPackagesByKey.get(UserPackage.getKey(userId, packageName));
+        if (userPackage == null) {
+            Slogf.w(TAG, "Failed to find unique database id for user id '%d' and package '%s",
+                    userId, packageName);
+            return;
+        }
+        mUserPackagesByKey.remove(userPackage.getKey());
+        mUserPackagesById.remove(userPackage.getUniqueId());
+        try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) {
+            UserPackageSettingsTable.deleteUserPackage(db, userId, packageName);
+        }
+    }
+
     /**
      * Returns the aggregated historical I/O overuse stats for the given user package or
      * {@code null} when stats are not available.
@@ -347,6 +362,15 @@
                 return userPackages;
             }
         }
+
+        public static void deleteUserPackage(SQLiteDatabase db, @UserIdInt int userId,
+                String packageName) {
+            String whereClause = COLUMN_USER_ID + "= ? and " + COLUMN_PACKAGE_NAME + "= ?";
+            String[] whereArgs = new String[]{String.valueOf(userId), packageName};
+            int deletedRows = db.delete(TABLE_NAME, whereClause, whereArgs);
+            Slogf.i(TAG, "Deleted %d user package settings db rows for user %d and package %s",
+                    deletedRows, userId, packageName);
+        }
     }
 
     /** Defines the I/O usage entry stored in the IoUsageStatsTable. */
diff --git a/tests/CarEvsCameraPreviewApp/Android.bp b/tests/CarEvsCameraPreviewApp/Android.bp
index 99f0505..fea6aaf 100644
--- a/tests/CarEvsCameraPreviewApp/Android.bp
+++ b/tests/CarEvsCameraPreviewApp/Android.bp
@@ -26,8 +26,8 @@
 
     resource_dirs: ["res"],
 
-    // This app uses system APIs.
-    sdk_version: "system_current",
+    // registerReceiverForAllUsers() is a hidden api.
+    platform_apis: true,
 
     certificate: "platform",
 
diff --git a/tests/CarEvsCameraPreviewApp/AndroidManifest.xml b/tests/CarEvsCameraPreviewApp/AndroidManifest.xml
index 6e4c254..201c90b 100644
--- a/tests/CarEvsCameraPreviewApp/AndroidManifest.xml
+++ b/tests/CarEvsCameraPreviewApp/AndroidManifest.xml
@@ -24,6 +24,8 @@
     <uses-permission android:name="android.car.permission.MONITOR_CAR_EVS_STATUS" />
 
     <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" />
+    <!-- for registerReceiverForAllUsers() -->
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
 
     <application android:label="@string/app_name"
             android:icon="@drawable/rearview"
@@ -38,6 +40,8 @@
                 android:showForAllUsers="true"
                 android:theme="@style/Theme.Transparent"
                 android:turnScreenOn="true">
+            <meta-data android:name="distractionOptimized"
+                    android:value="true"/>
         </activity>
 
         <activity android:name=".CarEvsCameraActivity"
@@ -54,6 +58,8 @@
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
+            <meta-data android:name="distractionOptimized"
+                    android:value="true"/>
         </activity>
 
     </application>
diff --git a/tests/CarEvsCameraPreviewApp/OWNERS b/tests/CarEvsCameraPreviewApp/OWNERS
new file mode 100644
index 0000000..9b47ecd
--- /dev/null
+++ b/tests/CarEvsCameraPreviewApp/OWNERS
@@ -0,0 +1,4 @@
+# Project owners
+ankitarora@google.com
+changyeon@google.com
+ycheo@google.com
diff --git a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml b/tests/CarEvsCameraPreviewApp/res/values/config.xml
similarity index 64%
copy from car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
copy to tests/CarEvsCameraPreviewApp/res/values/config.xml
index 4fab05c..85eb0b1 100644
--- a/car_product/rro/CarSystemUIEvsRRO/res/values/config.xml
+++ b/tests/CarEvsCameraPreviewApp/res/values/config.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2021 The Android Open Source Project
   ~
@@ -15,7 +14,10 @@
   ~ limitations under the License.
   -->
 <resources>
-    <string name="config_rearViewCameraActivity" translatable="false">
-        com.google.android.car.evs/com.google.android.car.evs.CarEvsCameraPreviewActivity
-    </string>
+    <!-- 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>
+
+    <!-- In-plane rotation angle of the rearview camera device in degree -->
+    <integer name="config_evsRearviewCameraInPlaneRotationAngle">0</integer>
 </resources>
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..cd6c840 100644
--- a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraPreviewActivity.java
+++ b/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraPreviewActivity.java
@@ -24,7 +24,10 @@
 import android.car.CarNotConnectedException;
 import android.car.evs.CarEvsBufferDescriptor;
 import android.car.evs.CarEvsManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.graphics.PixelFormat;
 import android.hardware.display.DisplayManager;
 import android.os.Bundle;
@@ -34,8 +37,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 +58,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 */
@@ -145,11 +149,32 @@
         }
     };
 
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
+                finish();
+            } else {
+                Log.e(TAG, "Unexpected intent " + intent);
+            }
+        }
+    };
+
+    // To close the PreviewActiivty when Home button is clicked.
+    private void registerBroadcastReceiver() {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+        // Need to register the receiver for all users, because we want to receive the Intent after
+        // the user is changed.
+        registerReceiverForAllUsers(mBroadcastReceiver, filter, null, null);
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         Log.d(TAG, "onCreate");
         super.onCreate(savedInstanceState);
 
+        registerBroadcastReceiver();
         parseExtra(getIntent());
 
         setShowWhenLocked(true);
@@ -164,8 +189,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 +199,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,8 +278,10 @@
         mDisplayManager.unregisterDisplayListener(mDisplayListener);
         if (mUseSystemWindow) {
             WindowManager wm = getSystemService(WindowManager.class);
-            wm.removeView(mPreviewContainer);
+            wm.removeView(mRootView);
         }
+
+        unregisterReceiver(mBroadcastReceiver);
     }
 
     private void handleVideoStreamLocked() {
diff --git a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/GLES20CarEvsCameraPreviewRenderer.java b/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/GLES20CarEvsCameraPreviewRenderer.java
index 6d8b1f4..f04d15b 100644
--- a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/GLES20CarEvsCameraPreviewRenderer.java
+++ b/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/GLES20CarEvsCameraPreviewRenderer.java
@@ -54,10 +54,10 @@
              1.0f, -1.0f, 0.0f };
 
     private static final float[] sVertCarTexData = {
-            0.0f, 0.0f,
-            1.0f, 0.0f,
-            0.0f, 1.0f,
-            1.0f, 1.0f };
+           -0.5f, -0.5f,
+            0.5f, -0.5f,
+           -0.5f,  0.5f,
+            0.5f,  0.5f };
 
     private static final float[] sIdentityMatrix = {
             1.0f, 0.0f, 0.0f, 0.0f,
@@ -105,9 +105,27 @@
         mVertCarPos = ByteBuffer.allocateDirect(sVertCarPosData.length * FLOAT_SIZE_BYTES)
                 .order(ByteOrder.nativeOrder()).asFloatBuffer();
         mVertCarPos.put(sVertCarPosData).position(0);
+
+        // Rotates the matrix in counter-clockwise
+        int angleInDegree = mContext.getResources().getInteger(
+                R.integer.config_evsRearviewCameraInPlaneRotationAngle);
+        double angleInRadian = Math.toRadians(angleInDegree);
+        float[] rotated = {0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f};
+        float sin = (float)Math.sin(angleInRadian);
+        float cos = (float)Math.cos(angleInRadian);
+
+        rotated[0] += cos * sVertCarTexData[0] - sin * sVertCarTexData[1];
+        rotated[1] += sin * sVertCarTexData[0] + cos * sVertCarTexData[1];
+        rotated[2] += cos * sVertCarTexData[2] - sin * sVertCarTexData[3];
+        rotated[3] += sin * sVertCarTexData[2] + cos * sVertCarTexData[3];
+        rotated[4] += cos * sVertCarTexData[4] - sin * sVertCarTexData[5];
+        rotated[5] += sin * sVertCarTexData[4] + cos * sVertCarTexData[5];
+        rotated[6] += cos * sVertCarTexData[6] - sin * sVertCarTexData[7];
+        rotated[7] += sin * sVertCarTexData[6] + cos * sVertCarTexData[7];
+
         mVertCarTex = ByteBuffer.allocateDirect(sVertCarTexData.length * FLOAT_SIZE_BYTES)
                 .order(ByteOrder.nativeOrder()).asFloatBuffer();
-        mVertCarTex.put(sVertCarTexData).position(0);
+        mVertCarTex.put(rotated).position(0);
     }
 
     public void clearBuffer() {
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/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/Android.bp b/tests/EmbeddedKitchenSinkApp/Android.bp
index ef70cc9..ac942aa 100644
--- a/tests/EmbeddedKitchenSinkApp/Android.bp
+++ b/tests/EmbeddedKitchenSinkApp/Android.bp
@@ -46,12 +46,14 @@
         "androidx.appcompat_appcompat",
         "car-admin-ui-lib",
         "car-ui-lib",
+        "car-qc-lib",
         "android.hidl.base-V1.0-java",
         "android.hardware.automotive.vehicle-V2.0-java",
         "vehicle-hal-support-lib-for-test",
         "com.android.car.keventreader-client",
         "guava",
         "android.car.cluster.navigation",
+        "cartelemetry-protos",
         "car-experimental-api-static-lib",
     ],
 
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
index af573f7..5c5029b 100644
--- a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -50,6 +50,9 @@
     <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"/>
+    <uses-permission android:name="android.car.permission.CONTROL_APP_BLOCKING"/>
+    <!-- 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/car_telemetry_test.xml b/tests/EmbeddedKitchenSinkApp/res/layout/car_telemetry_test.xml
new file mode 100644
index 0000000..2576b7d
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/car_telemetry_test.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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="vertical">
+    <LinearLayout
+        android:id="@+id/on_gear_change_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <Button
+            android:id="@+id/send_on_gear_change_config"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/send_on_gear_change"/>
+        <Button
+            android:id="@+id/remove_on_gear_change_config"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/remove_on_gear_change"/>
+        <Button
+            android:id="@+id/get_on_gear_change_report"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/get_on_gear_change"/>
+    </LinearLayout>
+    <TextView
+        android:id="@+id/output_textview"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="20dp"/>
+</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/qc_viewer.xml b/tests/EmbeddedKitchenSinkApp/res/layout/qc_viewer.xml
new file mode 100644
index 0000000..d48284b
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/qc_viewer.xml
@@ -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.
+  -->
+ <LinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center_horizontal">
+
+     <EditText
+         android:id="@+id/qc_uri_input"
+         android:layout_width="match_parent"
+         android:layout_height="wrap_content"
+         android:inputType="text"
+         android:singleLine="true"/>
+
+     <Button
+         android:id="@+id/submit_uri_btn"
+         android:layout_width="wrap_content"
+         android:layout_height="wrap_content"
+         android:text="Update QCView"/>
+
+     <com.android.car.qc.view.QCView
+         android:id="@+id/qc_view"
+         android:layout_width="match_parent"
+         android:layout_height="wrap_content"
+         android:gravity="center"
+         android:focusable="false"/>
+
+ </LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
index 2fb2de5..d43e715 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
@@ -381,4 +381,9 @@
     <!-- Fullscreen Activity Test -->
     <string name="nav_to_full_screen" translatable="false">Navigate to Full Screen</string>
     <string name="cancel" translatable="false">Cancel</string>
+
+    <!-- CarTelemetryService Test -->
+    <string name="send_on_gear_change" translatable="false">Send MetricsConfig on_gear_change</string>
+    <string name="remove_on_gear_change" translatable="false">Remove MetricsConfig on_gear_change</string>
+    <string name="get_on_gear_change" translatable="false">Get on_gear_change Report</string>
 </resources>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
index beeac2a..fd8819a 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
@@ -23,6 +23,7 @@
 import android.car.hardware.hvac.CarHvacManager;
 import android.car.hardware.power.CarPowerManager;
 import android.car.hardware.property.CarPropertyManager;
+import android.car.telemetry.CarTelemetryManager;
 import android.car.watchdog.CarWatchdogManager;
 import android.content.Context;
 import android.content.Intent;
@@ -66,12 +67,14 @@
 import com.google.android.car.kitchensink.power.PowerTestFragment;
 import com.google.android.car.kitchensink.projection.ProjectionFragment;
 import com.google.android.car.kitchensink.property.PropertyTestFragment;
+import com.google.android.car.kitchensink.qc.QCViewerFragment;
 import com.google.android.car.kitchensink.rotary.RotaryFragment;
 import com.google.android.car.kitchensink.sensor.SensorsTestFragment;
 import com.google.android.car.kitchensink.storagelifetime.StorageLifetimeFragment;
 import com.google.android.car.kitchensink.storagevolumes.StorageVolumesFragment;
 import com.google.android.car.kitchensink.systembars.SystemBarsFragment;
 import com.google.android.car.kitchensink.systemfeatures.SystemFeaturesFragment;
+import com.google.android.car.kitchensink.telemetry.CarTelemetryTestFragment;
 import com.google.android.car.kitchensink.touch.TouchTestFragment;
 import com.google.android.car.kitchensink.users.ProfileUserFragment;
 import com.google.android.car.kitchensink.users.UserFragment;
@@ -199,12 +202,14 @@
             new FragmentMenuEntry("profile_user", ProfileUserFragment.class),
             new FragmentMenuEntry("projection", ProjectionFragment.class),
             new FragmentMenuEntry("property test", PropertyTestFragment.class),
+            new FragmentMenuEntry("qc viewer", QCViewerFragment.class),
             new FragmentMenuEntry("rotary", RotaryFragment.class),
             new FragmentMenuEntry("sensors", SensorsTestFragment.class),
             new FragmentMenuEntry("storage lifetime", StorageLifetimeFragment.class),
             new FragmentMenuEntry("storage volumes", StorageVolumesFragment.class),
             new FragmentMenuEntry("system bars", SystemBarsFragment.class),
             new FragmentMenuEntry("system features", SystemFeaturesFragment.class),
+            new FragmentMenuEntry("telemetry", CarTelemetryTestFragment.class),
             new FragmentMenuEntry("touch test", TouchTestFragment.class),
             new FragmentMenuEntry("users", UserFragment.class),
             new FragmentMenuEntry("user restrictions", UserRestrictionsFragment.class),
@@ -223,6 +228,7 @@
     private CarSensorManager mSensorManager;
     private CarAppFocusManager mCarAppFocusManager;
     private CarProjectionManager mCarProjectionManager;
+    private CarTelemetryManager mCarTelemetryManager;
     private CarWatchdogManager mCarWatchdogManager;
     private Object mPropertyManagerReady = new Object();
 
@@ -250,6 +256,10 @@
         return mCarProjectionManager;
     }
 
+    public CarTelemetryManager getCarTelemetryManager() {
+        return mCarTelemetryManager;
+    }
+
     public CarWatchdogManager getCarWatchdogManager() {
         return mCarWatchdogManager;
     }
@@ -411,6 +421,8 @@
                     (CarAppFocusManager) car.getCarManager(Car.APP_FOCUS_SERVICE);
             mCarProjectionManager =
                     (CarProjectionManager) car.getCarManager(Car.PROJECTION_SERVICE);
+            mCarTelemetryManager =
+                    (CarTelemetryManager) car.getCarManager(Car.CAR_TELEMETRY_SERVICE);
             mCarWatchdogManager =
                     (CarWatchdogManager) car.getCarManager(Car.CAR_WATCHDOG_SERVICE);
             mPropertyManagerReady.notifyAll();
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/qc/QCViewerFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/qc/QCViewerFragment.java
new file mode 100644
index 0000000..20e96aa
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/qc/QCViewerFragment.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.kitchensink.qc;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.android.car.qc.controller.RemoteQCController;
+import com.android.car.qc.view.QCView;
+
+import com.google.android.car.kitchensink.R;
+
+public final class QCViewerFragment extends Fragment {
+    private static final String KEY_CURRENT_URI_STRING = "CURRENT_URI_STRING";
+
+    private RemoteQCController mController;
+    private String mUriString;
+    private EditText mInput;
+    private Button mButton;
+    private QCView mQCView;
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (savedInstanceState != null) {
+            mUriString = savedInstanceState.getString(KEY_CURRENT_URI_STRING);
+        }
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        View v = inflater.inflate(R.layout.qc_viewer, container, false);
+        mInput = v.findViewById(R.id.qc_uri_input);
+        mButton = v.findViewById(R.id.submit_uri_btn);
+        mQCView = v.findViewById(R.id.qc_view);
+
+        mButton.setOnClickListener(v1 -> {
+            mUriString = mInput.getText().toString();
+            Uri uri = Uri.parse(mUriString);
+            updateQCView(uri);
+        });
+
+        if (mUriString != null) {
+            Uri uri = Uri.parse(mUriString);
+            updateQCView(uri);
+        }
+
+        return v;
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putString(KEY_CURRENT_URI_STRING, mUriString);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        if (mController != null) {
+            mController.listen(true);
+        }
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        if (mController != null) {
+            mController.listen(false);
+        }
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        mUriString = null;
+        if (mController != null) {
+            mController.destroy();
+            mController = null;
+        }
+    }
+
+    private void updateQCView(Uri uri) {
+        if (uri == null) {
+            return;
+        }
+        if (mController != null) {
+            // destroy old controller
+            mController.destroy();
+        }
+
+        mController = new RemoteQCController(getContext(), uri);
+        mController.addObserver(mQCView);
+        mController.listen(true);
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/telemetry/CarTelemetryTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/telemetry/CarTelemetryTestFragment.java
new file mode 100644
index 0000000..d2f011f
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/telemetry/CarTelemetryTestFragment.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.kitchensink.telemetry;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.car.telemetry.CarTelemetryManager;
+import android.car.telemetry.MetricsConfigKey;
+import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.car.telemetry.TelemetryProto;
+
+import com.google.android.car.kitchensink.KitchenSinkActivity;
+import com.google.android.car.kitchensink.R;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+public class CarTelemetryTestFragment extends Fragment {
+    private static final String LUA_SCRIPT_ON_GEAR_CHANGE =
+            "function onGearChange(published_data, state)\n"
+                    + "    result = {data = \"Hello World!\"}\n"
+                    + "    on_script_finished(result)\n"
+                    + "end\n";
+    private static final TelemetryProto.Publisher VEHICLE_PROPERTY_PUBLISHER =
+            TelemetryProto.Publisher.newBuilder()
+                    .setVehicleProperty(
+                            TelemetryProto.VehiclePropertyPublisher.newBuilder()
+                                    .setVehiclePropertyId(VehicleProperty.GEAR_SELECTION)
+                                    .setReadRate(0f)
+                                    .build()
+                    ).build();
+    private static final TelemetryProto.Subscriber VEHICLE_PROPERTY_SUBSCRIBER =
+            TelemetryProto.Subscriber.newBuilder()
+                    .setHandler("onGearChange")
+                    .setPublisher(VEHICLE_PROPERTY_PUBLISHER)
+                    .setPriority(0)
+                    .build();
+    private static final TelemetryProto.MetricsConfig METRICS_CONFIG_ON_GEAR_CHANGE_V1 =
+            TelemetryProto.MetricsConfig.newBuilder()
+                    .setName("my_metrics_config")
+                    .setVersion(1)
+                    .setScript(LUA_SCRIPT_ON_GEAR_CHANGE)
+                    .addSubscribers(VEHICLE_PROPERTY_SUBSCRIBER)
+                    .build();
+    private static final MetricsConfigKey KEY_V1 = new MetricsConfigKey(
+            METRICS_CONFIG_ON_GEAR_CHANGE_V1.getName(),
+            METRICS_CONFIG_ON_GEAR_CHANGE_V1.getVersion());
+
+    private final Executor mExecutor = Executors.newSingleThreadExecutor();
+
+    private CarTelemetryManager mCarTelemetryManager;
+    private CarTelemetryResultsListenerImpl mListener;
+    private KitchenSinkActivity mActivity;
+    private TextView mOutputTextView;
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        mActivity = (KitchenSinkActivity) getActivity();
+        mCarTelemetryManager = mActivity.getCarTelemetryManager();
+        mListener = new CarTelemetryResultsListenerImpl();
+        mCarTelemetryManager.setListener(mExecutor, mListener);
+        super.onCreate(savedInstanceState);
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(
+            @NonNull LayoutInflater inflater,
+            @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.car_telemetry_test, container, false);
+
+        mOutputTextView = view.findViewById(R.id.output_textview);
+        Button sendGearConfigBtn = view.findViewById(R.id.send_on_gear_change_config);
+        Button getGearReportBtn = view.findViewById(R.id.get_on_gear_change_report);
+        Button removeGearConfigBtn = view.findViewById(R.id.remove_on_gear_change_config);
+
+        sendGearConfigBtn.setOnClickListener(this::onSendGearChangeConfigBtnClick);
+        removeGearConfigBtn.setOnClickListener(this::onRemoveGearChangeConfigBtnClick);
+        getGearReportBtn.setOnClickListener(this::onGetGearChangeReportBtnClick);
+
+        return view;
+    }
+
+    private void showOutput(String s) {
+        mActivity.runOnUiThread(() -> mOutputTextView.setText(s));
+    }
+
+    private void onSendGearChangeConfigBtnClick(View view) {
+        showOutput("Sending MetricsConfig that listen for gear change...");
+        mCarTelemetryManager.addMetricsConfig(KEY_V1,
+                METRICS_CONFIG_ON_GEAR_CHANGE_V1.toByteArray());
+    }
+
+    private void onRemoveGearChangeConfigBtnClick(View view) {
+        showOutput("Removing MetricsConfig that listens for gear change...");
+        mCarTelemetryManager.removeMetricsConfig(KEY_V1);
+    }
+
+    private void onGetGearChangeReportBtnClick(View view) {
+        showOutput("Fetching report... If nothing shows up after a few seconds, "
+                + "then no result exists");
+        mCarTelemetryManager.sendFinishedReports(KEY_V1);
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+    }
+
+    /**
+     * Implementation of the {@link CarTelemetryManager.CarTelemetryResultsListener}. They update
+     * the view to show the outputs from the APIs of {@link CarTelemetryManager}.
+     * The callbacks are executed in {@link mExecutor}.
+     */
+    private final class CarTelemetryResultsListenerImpl
+            implements CarTelemetryManager.CarTelemetryResultsListener {
+
+        @Override
+        public void onResult(@NonNull MetricsConfigKey key, @NonNull byte[] result) {
+            PersistableBundle bundle;
+            try (ByteArrayInputStream bis = new ByteArrayInputStream(result)) {
+                bundle = PersistableBundle.readFromStream(bis);
+            } catch (IOException e) {
+                bundle = null;
+            }
+            showOutput("Result is " + bundle.toString());
+        }
+
+        @Override
+        public void onError(@NonNull MetricsConfigKey key, @NonNull byte[] error) {
+        }
+
+        @Override
+        public void onAddMetricsConfigStatus(@NonNull MetricsConfigKey key, int statusCode) {
+            showOutput("Add MetricsConfig status: " + statusCode);
+        }
+
+        @Override
+        public void onRemoveMetricsConfigStatus(@NonNull MetricsConfigKey key, boolean success) {
+            showOutput("Remove MetricsConfig status: " + success);
+        }
+    }
+}
diff --git a/tests/NetworkPreferenceApp/Android.bp b/tests/NetworkPreferenceApp/Android.bp
index 22c5f85..e378450 100644
--- a/tests/NetworkPreferenceApp/Android.bp
+++ b/tests/NetworkPreferenceApp/Android.bp
@@ -43,7 +43,6 @@
 
     static_libs: [
         "vehicle-hal-support-lib",
-        "car-experimental-api-static-lib",
         "androidx.legacy_legacy-support-v4",
     ],
 
diff --git a/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/components/CarDriverDistractionManagerAdapter.java b/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/components/CarDriverDistractionManagerAdapter.java
index eb4d527..b2a096f 100644
--- a/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/components/CarDriverDistractionManagerAdapter.java
+++ b/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/components/CarDriverDistractionManagerAdapter.java
@@ -16,9 +16,7 @@
 package com.google.android.car.networking.preferenceupdater.components;
 
 import android.car.Car;
-import android.car.experimental.CarDriverDistractionManager;
-import android.car.experimental.DriverDistractionChangeEvent;
-import android.car.experimental.ExperimentalCar;
+import android.car.drivingstate.CarUxRestrictionsManager;
 import android.content.Context;
 
 /**
@@ -26,21 +24,15 @@
  * information about about driving state to the caller.
  */
 public final class CarDriverDistractionManagerAdapter {
-    private final CarDriverDistractionManager mCarDriverDistractionManager;
+    private final CarUxRestrictionsManager mCarUxRestrictionsManager;
     private final Car mCar;
 
     public CarDriverDistractionManagerAdapter(Context ctx) {
         // Connect to car service
         mCar = Car.createCar(ctx);
-        if (mCar.isFeatureEnabled(
-                ExperimentalCar.DRIVER_DISTRACTION_EXPERIMENTAL_FEATURE_SERVICE)) {
-            mCarDriverDistractionManager = (CarDriverDistractionManager) mCar.getCarManager(
-                    ExperimentalCar.DRIVER_DISTRACTION_EXPERIMENTAL_FEATURE_SERVICE);
-        } else {
-            mCarDriverDistractionManager = null;
-        }
+        mCarUxRestrictionsManager = (CarUxRestrictionsManager) mCar.getCarManager(
+                Car.CAR_UX_RESTRICTION_SERVICE);
     }
-
     /** Method that has to be called during destroy. */
     public void destroy() {
         mCar.disconnect();
@@ -50,24 +42,8 @@
      * Returns true/false boolean based on the whether driver can be distracted right now or not
      */
     public boolean allowedToBeDistracted() {
-        if (mCarDriverDistractionManager == null) {
-            // This means we could not bind to CarDriverDistractionManager. Return true.
-            return true;
-        }
-        DriverDistractionChangeEvent event = mCarDriverDistractionManager.getLastDistractionEvent();
-        /**
-         * event.getAwarenessPercentage returns the current driver awareness value, a float number
-         * between 0.0 -> 1.0, which represents a percentage of the required awareness from driver.
-         *  - 0.0 indicates that the driver has no situational awareness of the surrounding
-         *    environment, which means driver is allawed to be distracted.
-         *  - 1.0 indicates that the driver has the target amount of situational awareness
-         *    necessary for the current driving environment, meaning driver can not be distracted at
-         *    this moment.
-         */
-        if (event.getAwarenessPercentage() > 0) {
-            // To simplify logic, we consider everything above 0% as dangerous and return false.
-            return false;
-        }
-        return true;
+        return mCarUxRestrictionsManager == null
+                || !mCarUxRestrictionsManager.getCurrentCarUxRestrictions()
+                        .isRequiresDistractionOptimization();
     }
 }
diff --git a/tests/android_car_api_test/AndroidManifest.xml b/tests/android_car_api_test/AndroidManifest.xml
index 7ae6fde..3e91a40 100644
--- a/tests/android_car_api_test/AndroidManifest.xml
+++ b/tests/android_car_api_test/AndroidManifest.xml
@@ -34,6 +34,44 @@
                 <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
+        <activity android:name=".TestDrivingSafetyAllRegionActivity"
+                  android:exported="true">
+            <meta-data android:name="distractionOptimized" android:value="true"/>
+        </activity>
+        <activity android:name=".TestDrivingSafetyExplicitAllRegionsActivity"
+                  android:exported="true">
+            <meta-data android:name="distractionOptimized" android:value="true"/>
+            <!-- not necessary as all region is the default state but this is still valid -->
+            <meta-data android:name="android.car.drivingsafetyregions"
+                       android:value="android.car.drivingsafetyregion.all"/>
+        </activity>
+        <activity android:name=".TestDrivingSafetyOneRegionActivity"
+                  android:exported="true">
+            <meta-data android:name="distractionOptimized" android:value="true"/>
+            <meta-data android:name="android.car.drivingsafetyregions"
+                       android:value="com.android.car.test.drivingsafetyregion.1"/>
+        </activity>
+        <activity android:name=".TestDrivingSafetyTwoRegionsActivity"
+                  android:exported="true">
+            <meta-data android:name="distractionOptimized" android:value="true"/>
+            <meta-data android:name="android.car.drivingsafetyregions"
+                       android:value="com.android.car.test.drivingsafetyregion.1,com.android.car.test.drivingsafetyregion.2"/>
+        </activity>
+        <activity android:name=".TestDrivingSafetyRegion1OnlyActivity"
+                  android:exported="true">
+            <!--No distractionOptimized, so this app will be unsafe. -->
+            <meta-data android:name="android.car.drivingsafetyregions"
+                       android:value="com.android.test.drivingsafetyregion.1"/>
+        </activity>
+        <activity android:name=".TestDrivingSafetyRegionAllOnlyActivity"
+                  android:exported="true">
+            <!--No distractionOptimized, so this app will be unsafe. -->
+            <meta-data android:name="android.car.drivingsafetyregions"
+                       android:value="android.car.drivingsafetyregion.all"/>
+        </activity>
+        <activity android:name=".TestDrivingSafetyRegionNoMetadataActivity"
+                  android:exported="true">
+        </activity>
         <service android:name=".CarProjectionManagerTest$TestService"
              android:exported="true"/>
     </application>
diff --git a/tests/android_car_api_test/src/android/car/apitest/DrivingSafetyRegionTest.java b/tests/android_car_api_test/src/android/car/apitest/DrivingSafetyRegionTest.java
new file mode 100644
index 0000000..473a8b1
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/DrivingSafetyRegionTest.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 android.car.apitest;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+import static org.testng.Assert.assertThrows;
+
+import android.app.ActivityManager;
+import android.car.Car;
+import android.car.content.pm.CarPackageManager;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+@SmallTest
+public class DrivingSafetyRegionTest extends CarApiTestBase {
+    private static final String REGION1 = "com.android.car.test.drivingsafetyregion.1";
+    private static final String REGION2 = "com.android.car.test.drivingsafetyregion.2";
+    private static final String REGION3 = "com.android.car.test.drivingsafetyregion.3";
+
+    private static final String TEST_PACKAGE_NAME = "android.car.apitest";
+
+    private CarPackageManager mCarPackageManager;
+    private String mOriginalDrivingSafetyRegion = null;
+
+    private final int mCurrentUser = ActivityManager.getCurrentUser();
+
+    @Before
+    public void setUp() {
+        mCarPackageManager = (CarPackageManager) getCar().getCarManager(Car.PACKAGE_SERVICE);
+
+        assertThat(mCarPackageManager).isNotNull();
+
+        mOriginalDrivingSafetyRegion = mCarPackageManager.getCurrentDrivingSafetyRegion();
+
+        assertThat(mOriginalDrivingSafetyRegion).isNotNull();
+
+        // cannot run this in user build as region change is not allowed in user build for shell.
+        assumeTrue(Build.IS_ENG || Build.IS_USERDEBUG);
+    }
+
+    @After
+    public void tearDown() {
+        if (mOriginalDrivingSafetyRegion != null) {
+            setDrivingSafetyRegion(mOriginalDrivingSafetyRegion);
+        }
+    }
+
+    @Test
+    public void testImplicitAllRegions() throws Exception {
+        doTestAllRegions(TestDrivingSafetyAllRegionActivity.class.getName());
+    }
+
+    @Test
+    public void testExplicitAllRegions() throws Exception {
+        doTestAllRegions(TestDrivingSafetyExplicitAllRegionsActivity.class.getName());
+    }
+
+    private void doTestAllRegions(String activityClassName) throws Exception {
+        assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+                activityClassName)).isTrue();
+
+        List<String> regions = mCarPackageManager.getSupportedDrivingSafetyRegionsForActivityAsUser(
+                TEST_PACKAGE_NAME, activityClassName, ActivityManager.getCurrentUser());
+
+        assertThat(regions).containsExactly(CarPackageManager.DRIVING_SAFETY_REGION_ALL);
+
+        // all region app should be safe always regardless of bypassing / region change
+        setDrivingSafetyRegion(REGION1);
+
+        assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+                activityClassName)).isTrue();
+
+        mCarPackageManager.controlTemporaryActivityBlockingBypassingAsUser(TEST_PACKAGE_NAME,
+                activityClassName, true, mCurrentUser);
+
+        assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+                activityClassName)).isTrue();
+
+        mCarPackageManager.controlTemporaryActivityBlockingBypassingAsUser(TEST_PACKAGE_NAME,
+                activityClassName, false, mCurrentUser);
+
+        assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+                activityClassName)).isTrue();
+    }
+
+    @Test
+    public void testOneRegionOnly() throws Exception {
+        String activityClassName = TestDrivingSafetyOneRegionActivity.class.getName();
+
+        List<String> regions = mCarPackageManager.getSupportedDrivingSafetyRegionsForActivityAsUser(
+                TEST_PACKAGE_NAME, activityClassName, ActivityManager.getCurrentUser());
+
+        assertThat(regions).containsExactly(REGION1);
+
+        setDrivingSafetyRegion(REGION1);
+
+        assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+                activityClassName)).isTrue();
+
+        setDrivingSafetyRegion(REGION2);
+
+        assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+                activityClassName)).isFalse();
+
+        mCarPackageManager.controlTemporaryActivityBlockingBypassingAsUser(TEST_PACKAGE_NAME,
+                activityClassName, true, mCurrentUser);
+
+        assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+                activityClassName)).isTrue();
+
+        mCarPackageManager.controlTemporaryActivityBlockingBypassingAsUser(TEST_PACKAGE_NAME,
+                activityClassName, false, mCurrentUser);
+
+        assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+                activityClassName)).isFalse();
+
+        setDrivingSafetyRegion(CarPackageManager.DRIVING_SAFETY_REGION_ALL);
+
+        assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+                activityClassName)).isTrue();
+    }
+
+    @Test
+    public void testTwoRegionsOnly() throws Exception {
+        String activityClassName = TestDrivingSafetyTwoRegionsActivity.class.getName();
+
+        List<String> regions = mCarPackageManager.getSupportedDrivingSafetyRegionsForActivityAsUser(
+                TEST_PACKAGE_NAME, activityClassName, ActivityManager.getCurrentUser());
+
+        assertThat(regions).containsExactly(REGION1, REGION2);
+
+        setDrivingSafetyRegion(REGION1);
+
+        assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+                activityClassName)).isTrue();
+
+        setDrivingSafetyRegion(REGION2);
+
+        assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+                activityClassName)).isTrue();
+
+        setDrivingSafetyRegion(REGION3);
+
+        assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+                activityClassName)).isFalse();
+
+        setDrivingSafetyRegion(CarPackageManager.DRIVING_SAFETY_REGION_ALL);
+
+        assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+                activityClassName)).isTrue();
+    }
+
+    @Test
+    public void testRegion1OnlyActivity() throws Exception {
+        doTestRegionOnlyOrNoRegionCase(TestDrivingSafetyRegion1OnlyActivity.class.getName());
+    }
+
+    @Test
+    public void testRegionAllOnlyActivity() throws Exception {
+        doTestRegionOnlyOrNoRegionCase(TestDrivingSafetyRegionAllOnlyActivity.class.getName());
+    }
+
+    @Test
+    public void testRegionNoMetadataActivity() throws Exception {
+        doTestRegionOnlyOrNoRegionCase(TestDrivingSafetyRegionNoMetadataActivity.class.getName());
+    }
+
+    private void doTestRegionOnlyOrNoRegionCase(String activityClassName) throws Exception {
+        List<String> regions = mCarPackageManager.getSupportedDrivingSafetyRegionsForActivityAsUser(
+                TEST_PACKAGE_NAME, activityClassName, ActivityManager.getCurrentUser());
+
+        // not distraction optimized, so list should be empty.
+        assertThat(regions).isEmpty();
+
+        assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+                activityClassName)).isFalse();
+
+        // should not be safe for any region.
+        setDrivingSafetyRegion(CarPackageManager.DRIVING_SAFETY_REGION_ALL);
+
+        assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+                activityClassName)).isFalse();
+
+        setDrivingSafetyRegion(REGION1);
+
+        assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+                activityClassName)).isFalse();
+
+        setDrivingSafetyRegion(REGION2);
+
+        assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+                activityClassName)).isFalse();
+
+        mCarPackageManager.controlTemporaryActivityBlockingBypassingAsUser(TEST_PACKAGE_NAME,
+                activityClassName, true, mCurrentUser);
+
+        assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+                activityClassName)).isTrue();
+
+        mCarPackageManager.controlTemporaryActivityBlockingBypassingAsUser(TEST_PACKAGE_NAME,
+                activityClassName, false, mCurrentUser);
+
+        assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+                activityClassName)).isFalse();
+    }
+
+    @Test
+    public void testNoPackage() {
+        String noPkg = "NoSuchPackage";
+
+        assertThrows(PackageManager.NameNotFoundException.class,
+                () -> mCarPackageManager.getSupportedDrivingSafetyRegionsForActivityAsUser(
+                        noPkg, "", mCurrentUser));
+
+        assertThrows(PackageManager.NameNotFoundException.class,
+                () -> mCarPackageManager.controlTemporaryActivityBlockingBypassingAsUser(
+                        noPkg, "", true, mCurrentUser));
+    }
+
+    @Test
+    public void testNoActivity() {
+        String noSuchActivity = "NoSuchActivity";
+
+        assertThrows(PackageManager.NameNotFoundException.class,
+                () -> mCarPackageManager.getSupportedDrivingSafetyRegionsForActivityAsUser(
+                        TEST_PACKAGE_NAME, noSuchActivity, mCurrentUser));
+
+        assertThrows(PackageManager.NameNotFoundException.class,
+                () -> mCarPackageManager.controlTemporaryActivityBlockingBypassingAsUser(
+                        TEST_PACKAGE_NAME, noSuchActivity, true, mCurrentUser));
+    }
+
+    @Test
+    public void testResetEmptyRegion() {
+        setDrivingSafetyRegion(REGION1);
+
+        assertThat(mCarPackageManager.getCurrentDrivingSafetyRegion()).isEqualTo(REGION1);
+
+        // no arg means all
+        runShellCommand("cmd car_service set-drivingsafety-region");
+
+        assertThat(mCarPackageManager.getCurrentDrivingSafetyRegion()).isEqualTo(
+                CarPackageManager.DRIVING_SAFETY_REGION_ALL);
+    }
+
+    private void setDrivingSafetyRegion(String region) {
+        runShellCommand("cmd car_service set-drivingsafety-region  " + region);
+    }
+}
diff --git a/car-lib/src/android/car/telemetry/ManifestKey.aidl b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyAllRegionActivity.java
similarity index 82%
copy from car-lib/src/android/car/telemetry/ManifestKey.aidl
copy to tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyAllRegionActivity.java
index 25097df..48af4dd 100644
--- a/car-lib/src/android/car/telemetry/ManifestKey.aidl
+++ b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyAllRegionActivity.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package android.car.telemetry;
+package android.car.apitest;
 
-/**
- * @hide
- */
-parcelable ManifestKey;
\ No newline at end of file
+import android.app.Activity;
+
+public class TestDrivingSafetyAllRegionActivity extends Activity {
+}
diff --git a/car-lib/src/android/car/telemetry/ManifestKey.aidl b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyExplicitAllRegionsActivity.java
similarity index 81%
copy from car-lib/src/android/car/telemetry/ManifestKey.aidl
copy to tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyExplicitAllRegionsActivity.java
index 25097df..f369acc 100644
--- a/car-lib/src/android/car/telemetry/ManifestKey.aidl
+++ b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyExplicitAllRegionsActivity.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package android.car.telemetry;
+package android.car.apitest;
 
-/**
- * @hide
- */
-parcelable ManifestKey;
\ No newline at end of file
+import android.app.Activity;
+
+public class TestDrivingSafetyExplicitAllRegionsActivity extends Activity {
+}
diff --git a/car-lib/src/android/car/telemetry/ManifestKey.aidl b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyOneRegionActivity.java
similarity index 82%
copy from car-lib/src/android/car/telemetry/ManifestKey.aidl
copy to tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyOneRegionActivity.java
index 25097df..623e5e0 100644
--- a/car-lib/src/android/car/telemetry/ManifestKey.aidl
+++ b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyOneRegionActivity.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package android.car.telemetry;
+package android.car.apitest;
 
-/**
- * @hide
- */
-parcelable ManifestKey;
\ No newline at end of file
+import android.app.Activity;
+
+public class TestDrivingSafetyOneRegionActivity extends Activity {
+}
diff --git a/car-lib/src/android/car/telemetry/ManifestKey.aidl b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyRegion1OnlyActivity.java
similarity index 81%
copy from car-lib/src/android/car/telemetry/ManifestKey.aidl
copy to tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyRegion1OnlyActivity.java
index 25097df..2256330 100644
--- a/car-lib/src/android/car/telemetry/ManifestKey.aidl
+++ b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyRegion1OnlyActivity.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package android.car.telemetry;
+package android.car.apitest;
 
-/**
- * @hide
- */
-parcelable ManifestKey;
\ No newline at end of file
+import android.app.Activity;
+
+public class TestDrivingSafetyRegion1OnlyActivity extends Activity {
+}
diff --git a/car-lib/src/android/car/telemetry/ManifestKey.aidl b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyRegionAllOnlyActivity.java
similarity index 81%
copy from car-lib/src/android/car/telemetry/ManifestKey.aidl
copy to tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyRegionAllOnlyActivity.java
index 25097df..863d308 100644
--- a/car-lib/src/android/car/telemetry/ManifestKey.aidl
+++ b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyRegionAllOnlyActivity.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package android.car.telemetry;
+package android.car.apitest;
 
-/**
- * @hide
- */
-parcelable ManifestKey;
\ No newline at end of file
+import android.app.Activity;
+
+public class TestDrivingSafetyRegionAllOnlyActivity extends Activity {
+}
diff --git a/car-lib/src/android/car/telemetry/ManifestKey.aidl b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyRegionNoMetadataActivity.java
similarity index 81%
copy from car-lib/src/android/car/telemetry/ManifestKey.aidl
copy to tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyRegionNoMetadataActivity.java
index 25097df..e633b6c 100644
--- a/car-lib/src/android/car/telemetry/ManifestKey.aidl
+++ b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyRegionNoMetadataActivity.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package android.car.telemetry;
+package android.car.apitest;
 
-/**
- * @hide
- */
-parcelable ManifestKey;
\ No newline at end of file
+import android.app.Activity;
+
+public class TestDrivingSafetyRegionNoMetadataActivity extends Activity {
+}
diff --git a/car-lib/src/android/car/telemetry/ManifestKey.aidl b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyTwoRegionsActivity.java
similarity index 81%
copy from car-lib/src/android/car/telemetry/ManifestKey.aidl
copy to tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyTwoRegionsActivity.java
index 25097df..3124b81 100644
--- a/car-lib/src/android/car/telemetry/ManifestKey.aidl
+++ b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyTwoRegionsActivity.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package android.car.telemetry;
+package android.car.apitest;
 
-/**
- * @hide
- */
-parcelable ManifestKey;
\ No newline at end of file
+import android.app.Activity;
+
+public class TestDrivingSafetyTwoRegionsActivity extends Activity {
+}
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/input/CarInputManagerTest.java b/tests/carservice_test/src/com/android/car/input/CarInputManagerTest.java
index 5cb55c3..af14bbf 100644
--- a/tests/carservice_test/src/com/android/car/input/CarInputManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/input/CarInputManagerTest.java
@@ -20,6 +20,7 @@
 import static android.hardware.automotive.vehicle.V2_0.CustomInputType.CUSTOM_EVENT_F1;
 
 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.Mockito.spy;
@@ -74,10 +75,16 @@
 
     private final class CaptureCallback implements CarInputManager.CarInputCaptureCallback {
 
-        private static final long EVENT_WAIT_TIME = 500;
+        private static final long EVENT_WAIT_TIME = 5_000;
 
         private final Object mLock = new Object();
 
+        private final String mName;
+
+        private CaptureCallback(String name) {
+            mName = name;
+        }
+
         // Stores passed events. Last one in front
         @GuardedBy("mLock")
         private final LinkedList<Pair<Integer, List<KeyEvent>>> mKeyEvents = new LinkedList<>();
@@ -149,19 +156,24 @@
         }
 
         private void waitForStateChange() throws Exception {
-            mStateChangeWait.tryAcquire(EVENT_WAIT_TIME, TimeUnit.MILLISECONDS);
+            assertWithMessage("Failed to acquire semaphore in %s ms", EVENT_WAIT_TIME).that(
+                    mStateChangeWait.tryAcquire(EVENT_WAIT_TIME, TimeUnit.MILLISECONDS)).isTrue();
         }
 
         private void waitForKeyEvent() throws Exception {
-            mKeyEventWait.tryAcquire(EVENT_WAIT_TIME, TimeUnit.MILLISECONDS);
+            assertWithMessage("Failed to acquire semaphore in %s ms", EVENT_WAIT_TIME).that(
+                    mKeyEventWait.tryAcquire(EVENT_WAIT_TIME, TimeUnit.MILLISECONDS)).isTrue();
         }
 
         private void waitForRotaryEvent() throws Exception {
-            mRotaryEventWait.tryAcquire(EVENT_WAIT_TIME, TimeUnit.MILLISECONDS);
+            assertWithMessage("Failed to acquire semaphore in %s ms", EVENT_WAIT_TIME).that(
+                    mRotaryEventWait.tryAcquire(EVENT_WAIT_TIME, TimeUnit.MILLISECONDS)).isTrue();
         }
 
         private void waitForCustomInputEvent() throws Exception {
-            mCustomInputEventWait.tryAcquire(EVENT_WAIT_TIME, TimeUnit.MILLISECONDS);
+            assertWithMessage("Failed to acquire semaphore in %s ms", EVENT_WAIT_TIME).that(
+                    mCustomInputEventWait.tryAcquire(
+                            EVENT_WAIT_TIME, TimeUnit.MILLISECONDS)).isTrue();
         }
 
         private LinkedList<Pair<Integer, List<KeyEvent>>> getkeyEvents() {
@@ -196,11 +208,16 @@
                 return r;
             }
         }
+
+        @Override
+        public String toString() {
+            return "CaptureCallback{mName='" + mName + "'}";
+        }
     }
 
-    private final CaptureCallback mCallback0 = new CaptureCallback();
-    private final CaptureCallback mCallback1 = new CaptureCallback();
-    private final CaptureCallback mCallback2 = new CaptureCallback();
+    private final CaptureCallback mCallback0 = new CaptureCallback("callback0");
+    private final CaptureCallback mCallback1 = new CaptureCallback("callback1");
+    private final CaptureCallback mCallback2 = new CaptureCallback("callback2");
 
     @Override
     protected synchronized void configureMockedHal() {
@@ -493,7 +510,7 @@
                         CarInputManager.INPUT_TYPE_NAVIGATE_KEYS}, 0, mCallback1);
         assertThat(r).isEqualTo(CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED);
 
-        mCallback1.waitForStateChange();
+        mCallback0.waitForStateChange();
         assertLastStateChange(CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
                 new int[]{CarInputManager.INPUT_TYPE_DPAD_KEYS},
                 mCallback0);
@@ -539,7 +556,6 @@
         CarInputManager carInputManager1 = createAnotherCarInputManager();
 
         Log.i(TAG, "requestInputEventCapture callback 0");
-
         int r = carInputManager0.requestInputEventCapture(
                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
                 new int[]{
@@ -675,7 +691,7 @@
         injectKeyEvent(true, KeyEvent.KEYCODE_NAVIGATE_NEXT);
 
         // Assert: ensure KeyEvent was delivered
-        mCallback1.waitForKeyEvent();
+        mCallback0.waitForKeyEvent();
         assertLastKeyEvent(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, true,
                 KeyEvent.KEYCODE_NAVIGATE_NEXT, mCallback0);
 
@@ -714,7 +730,7 @@
      * Events dispatched to main, so this should guarantee that all event dispatched are completed.
      */
     private void waitForDispatchToMain() {
-        // Needs to twice as it is dispatched to main inside car service once and it is
+        // Needs to be invoked twice as it is dispatched to main inside car service once and it is
         // dispatched to main inside CarInputManager once.
         CarServiceUtils.runOnMainSync(() -> {});
         CarServiceUtils.runOnMainSync(() -> {});
diff --git a/tests/carservice_unit_test/src/android/car/test/mocks/AndroidMockitoHelperTest.java b/tests/carservice_unit_test/src/android/car/test/mocks/AndroidMockitoHelperTest.java
index e1099da..b495220 100644
--- a/tests/carservice_unit_test/src/android/car/test/mocks/AndroidMockitoHelperTest.java
+++ b/tests/carservice_unit_test/src/android/car/test/mocks/AndroidMockitoHelperTest.java
@@ -24,6 +24,7 @@
 import static android.car.test.mocks.AndroidMockitoHelper.mockUmCreateUser;
 import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetAliveUsers;
 import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetSystemUser;
+import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetUserHandles;
 import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetUserInfo;
 import static android.car.test.mocks.AndroidMockitoHelper.mockUmIsHeadlessSystemUserMode;
 import static android.car.test.mocks.AndroidMockitoHelper.mockUmIsUserRunning;
@@ -160,6 +161,16 @@
     }
 
     @Test
+    public void testMockUmGetUserHandles() {
+        UserHandle user1 = UserHandle.of(100);
+        UserHandle user2 = UserHandle.of(200);
+
+        mockUmGetUserHandles(mMockedUserManager, true, 100, 200);
+
+        assertThat(mMockedUserManager.getUserHandles(true)).containsExactly(user1, user2).inOrder();
+    }
+
+    @Test
     public void testMockBinderGetCallingUserHandle() {
         mockBinderGetCallingUserHandle(TEST_USER_ID);
 
diff --git a/tests/carservice_unit_test/src/com/android/car/BluetoothFastPairTest.java b/tests/carservice_unit_test/src/com/android/car/BluetoothFastPairTest.java
index d7a8f3b..ebc9c2e 100644
--- a/tests/carservice_unit_test/src/com/android/car/BluetoothFastPairTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/BluetoothFastPairTest.java
@@ -24,7 +24,9 @@
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothAdapter;
@@ -120,6 +122,7 @@
     static final int TEST_PIN_NUMBER = 66051;
     static final int TEST_MODEL_ID = 4386;
     static final byte[] TEST_MODEL_ID_BYTES = {0x00, 0x11, 0x22};
+    static final int ASYNC_CALL_TIMEOUT_MILLIS = 200;
     byte[] mAdvertisementExpectedResults = new byte[]{0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x11, 0x00};
     @Mock
@@ -339,12 +342,27 @@
         assertThat(mTestGattServer.validatePairingRequest(encryptedRequest,
                 testKey.getKeySpec())).isTrue();
         //send Wrong Pairing Key
-        sendPairingKey(-1);
+        sendPairingKey(-2);
         mTestGattServer.processPairingKey(encryptedPairingKey);
         verify(mMockBluetoothDevice).setPairingConfirmation(false);
     }
 
     @Test
+    public void testNoPairingKey() {
+        FastPairUtils.AccountKey testKey = new FastPairUtils.AccountKey(TEST_SHARED_SECRET);
+        mTestGattServer.setSharedSecretKey(testKey.toBytes());
+        byte[] encryptedRequest = mTestGattServer.encrypt(TEST_PAIRING_REQUEST);
+
+        byte[] encryptedPairingKey = mTestGattServer.encrypt(TEST_PAIRING_KEY);
+
+        assertThat(mTestGattServer.validatePairingRequest(encryptedRequest,
+                testKey.getKeySpec())).isTrue();
+        mTestGattServer.processPairingKey(encryptedPairingKey);
+        verifyNoMoreInteractions(mMockBluetoothDevice);
+    }
+
+
+    @Test
     public void testValidPairingKeyAutoAccept() {
         FastPairUtils.AccountKey testKey = new FastPairUtils.AccountKey(TEST_SHARED_SECRET);
         mTestGattServer.setSharedSecretKey(testKey.toBytes());
@@ -440,10 +458,27 @@
                 .isEqualTo(TEST_MODEL_ID_BYTES);
     }
 
+    @Test
+    public void testStopAdvertisements() {
+        mTestFastPairProvider.start();
+        when(mMockBluetoothAdapter.isDiscovering()).thenReturn(true);
+        Intent scanMode = new Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
+        scanMode.putExtra(BluetoothAdapter.EXTRA_SCAN_MODE,
+                BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
+        mTestFastPairProvider.mDiscoveryModeChanged.onReceive(mMockContext, scanMode);
+
+        when(mMockBluetoothAdapter.isDiscovering()).thenReturn(false);
+        scanMode.putExtra(BluetoothAdapter.EXTRA_SCAN_MODE,
+                BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
+        mTestFastPairProvider.mDiscoveryModeChanged.onReceive(mMockContext, scanMode);
+        verify(mMockLeAdvertiser, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).stopAdvertisingSet(any());
+    }
+
     void sendPairingKey(int pairingKey) {
         Intent pairingRequest = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
         pairingRequest.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, pairingKey);
         pairingRequest.putExtra(BluetoothDevice.EXTRA_DEVICE, mMockBluetoothDevice);
         mTestGattServer.mPairingAttemptsReceiver.onReceive(mMockContext, pairingRequest);
+
     }
 }
diff --git a/tests/carservice_unit_test/src/com/android/car/am/FixedActivityServiceTest.java b/tests/carservice_unit_test/src/com/android/car/am/FixedActivityServiceTest.java
index b22831d..4a9f0c7 100644
--- a/tests/carservice_unit_test/src/com/android/car/am/FixedActivityServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/am/FixedActivityServiceTest.java
@@ -87,6 +87,8 @@
     private CarUserService mCarUserService;
     @Mock
     private CarPowerManager mCarPowerManager;
+    @Mock
+    private Display mValidDisplay;
 
     private FixedActivityService mFixedActivityService;
 
@@ -102,6 +104,7 @@
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         doReturn(mCarUserService).when(() -> CarLocalServices.getService(CarUserService.class));
         doReturn(mCarPowerManager).when(() -> CarLocalServices.createCarPowerManager(mContext));
+        when(mDisplayManager.getDisplay(mValidDisplayId)).thenReturn(mValidDisplay);
         mFixedActivityService = new FixedActivityService(mContext, mActivityManager, mUserManager,
                 mDisplayManager);
     }
@@ -252,6 +255,7 @@
         assertThat(ret).isTrue();
 
         int anotherValidDisplayId = mValidDisplayId + 1;
+        when(mDisplayManager.getDisplay(anotherValidDisplayId)).thenReturn(mValidDisplay);
         ret = mFixedActivityService.startFixedActivityModeForDisplayAndUser(anotherIntent,
                 options, anotherValidDisplayId, userId);
         verify(mContext).startActivityAsUser(eq(anotherIntent), any(Bundle.class),
@@ -272,6 +276,46 @@
     }
 
     @Test
+    public void testStartFixedActivityModeForDisplayAndUser_unavailableDisplay() {
+        int userId = 10;
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        ActivityOptions options = new ActivityOptions(new Bundle());
+        int unavailableDisplayId = mValidDisplayId + 1;
+
+        boolean started = mFixedActivityService.startFixedActivityModeForDisplayAndUser(
+                intent, options, unavailableDisplayId, userId);
+        assertThat(started).isFalse();
+    }
+
+    @Test
+    public void testStartFixedActivityModeForDisplayAndUser_displayRemoved()
+            throws Exception {
+        int displayToBeRemoved = mValidDisplayId + 1;
+        when(mDisplayManager.getDisplay(displayToBeRemoved)).thenReturn(
+                mValidDisplay, // for startFixedActivityModeForDisplayAndUser
+                mValidDisplay, // for launchIf
+                null);
+        int userId = 10;
+        ActivityOptions options = new ActivityOptions(new Bundle());
+        Intent intent = expectComponentAvailable("test_package", "com.test.dude", userId);
+        mockAmGetCurrentUser(userId);
+        expectNoActivityStack();
+
+        boolean started = mFixedActivityService.startFixedActivityModeForDisplayAndUser(
+                intent, options, displayToBeRemoved, userId);
+        assertThat(started).isTrue();
+        assertThat(mFixedActivityService.getRunningFixedActivity(displayToBeRemoved)).isNotNull();
+
+        // The display is still valid.
+        mFixedActivityService.launchIfNecessary();
+        assertThat(mFixedActivityService.getRunningFixedActivity(displayToBeRemoved)).isNotNull();
+
+        // The display is removed.
+        mFixedActivityService.launchIfNecessary();
+        assertThat(mFixedActivityService.getRunningFixedActivity(displayToBeRemoved)).isNull();
+    }
+
+    @Test
     public void testStartFixedActivityModeForDisplayAndUser_notAllowedUser() {
         int currentUserId = 10;
         int notAllowedUserId = 11;
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 6e8ac38..6b4254a 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
@@ -17,17 +17,27 @@
 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.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+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 android.os.PersistableBundle;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.car.CarLocalServices;
+import com.android.car.CarPropertyService;
 import com.android.car.systeminterface.SystemInterface;
+import com.android.car.systeminterface.SystemStateInterface;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -38,26 +48,45 @@
 import org.mockito.junit.MockitoJUnitRunner;
 import org.mockito.junit.MockitoRule;
 
+import java.io.ByteArrayOutputStream;
 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 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 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;
+    private MetricsConfigStore mMetricsConfigStore;
+    private ResultStore mResultStore;
 
     @Rule
     public MockitoRule mMockitoRule = MockitoJUnit.rule();
     @Mock
+    private CarPropertyService mMockCarPropertyService;
+    @Mock
     private Context mContext;
     @Mock
+    private ICarTelemetryServiceListener mMockListener;
+    @Mock
     private SystemInterface mMockSystemInterface;
+    @Mock
+    private SystemStateInterface mMockSystemStateInterface;
 
     @Before
     public void setUp() throws Exception {
@@ -66,78 +95,184 @@
 
         mTempSystemCarDir = Files.createTempDirectory("telemetry_test").toFile();
         when(mMockSystemInterface.getSystemCarDir()).thenReturn(mTempSystemCarDir);
+        when(mMockSystemInterface.getSystemStateInterface()).thenReturn(mMockSystemStateInterface);
 
-        mService = new CarTelemetryService(mContext);
+        mService = new CarTelemetryService(mContext, mMockCarPropertyService);
+        mService.init();
+        mService.setListener(mMockListener);
+
+        mTelemetryHandler = mService.getTelemetryHandler();
+        mTelemetryHandler.getLooper().getQueue().addIdleHandler(() -> {
+            mIdleHandlerLatch.countDown();
+            return true;
+        });
+        waitForHandlerThreadToFinish();
+
+        mMetricsConfigStore = mService.getMetricsConfigStore();
+        mResultStore = mService.getResultStore();
     }
 
     @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 config".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_shouldReplaceAndDeleteOldResult()
+            throws Exception {
+        mService.addMetricsConfig(KEY_V1, METRICS_CONFIG_V1.toByteArray());
+        mResultStore.putInterimResult(KEY_V1.getName(), new PersistableBundle());
 
-        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));
+        assertThat(mMetricsConfigStore.getActiveMetricsConfigs())
+                .containsExactly(METRICS_CONFIG_V2);
+        assertThat(mResultStore.getInterimResult(KEY_V1.getName())).isNull();
     }
 
     @Test
-    public void testRemoveManifest_manifestExists_shouldSucceed() {
-        mService.addManifest(mManifestKeyV1, mMetricsConfig.toByteArray());
+    public void testRemoveMetricsConfig_configExists_shouldDeleteScriptResult() throws Exception {
+        mService.addMetricsConfig(KEY_V1, METRICS_CONFIG_V1.toByteArray());
+        mResultStore.putInterimResult(KEY_V1.getName(), new PersistableBundle());
 
-        boolean result = mService.removeManifest(mManifestKeyV1);
+        mService.removeMetricsConfig(KEY_V1);
 
-        assertThat(result).isTrue();
+        waitForHandlerThreadToFinish();
+        verify(mMockListener).onRemoveMetricsConfigStatus(eq(KEY_V1), eq(true));
+        assertThat(mMetricsConfigStore.getActiveMetricsConfigs()).isEmpty();
+        assertThat(mResultStore.getInterimResult(KEY_V1.getName())).isNull();
     }
 
     @Test
-    public void testRemoveManifest_manifestDoesNotExist_shouldFail() {
-        boolean result = mService.removeManifest(mManifestKeyV1);
+    public void testRemoveMetricsConfig_configDoesNotExist_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());
+    public void testRemoveAllMetricsConfigs_shouldRemoveConfigsAndResults() throws Exception {
+        MetricsConfigKey key = new MetricsConfigKey("test config", 2);
+        TelemetryProto.MetricsConfig config =
+                TelemetryProto.MetricsConfig.newBuilder().setName(key.getName()).build();
+        mService.addMetricsConfig(key, config.toByteArray());
+        mService.addMetricsConfig(KEY_V1, METRICS_CONFIG_V1.toByteArray());
+        mResultStore.putInterimResult(KEY_V1.getName(), new PersistableBundle());
+        mResultStore.putFinalResult(key.getName(), new PersistableBundle());
 
-        mService.removeAllManifests();
+        mService.removeAllMetricsConfigs();
 
-        // 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);
+        waitForHandlerThreadToFinish();
+        assertThat(mMetricsConfigStore.getActiveMetricsConfigs()).isEmpty();
+        assertThat(mResultStore.getInterimResult(KEY_V1.getName())).isNull();
+        assertThat(mResultStore.getFinalResult(key.getName(), /* deleteResult = */ false)).isNull();
+    }
+
+    @Test
+    public void testSendFinishedReports_whenNoReport_shouldNotReceiveResponse() throws Exception {
+        mService.sendFinishedReports(KEY_V1);
+
+        waitForHandlerThreadToFinish();
+        verify(mMockListener, never()).onResult(any(), any());
+        verify(mMockListener, never()).onError(any(), any());
+    }
+
+    @Test
+    public void testSendFinishedReports_whenFinalResult_shouldReceiveResult() throws Exception {
+        PersistableBundle finalResult = new PersistableBundle();
+        finalResult.putBoolean("finished", true);
+        mResultStore.putFinalResult(KEY_V1.getName(), finalResult);
+
+        mService.sendFinishedReports(KEY_V1);
+
+        waitForHandlerThreadToFinish();
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        finalResult.writeToStream(bos);
+        verify(mMockListener).onResult(eq(KEY_V1), eq(bos.toByteArray()));
+        // result should have been deleted
+        assertThat(mResultStore.getFinalResult(KEY_V1.getName(), false)).isNull();
+    }
+
+    @Test
+    public void testSendFinishedReports_whenError_shouldReceiveError() throws Exception {
+        TelemetryProto.TelemetryError error = TelemetryProto.TelemetryError.newBuilder()
+                .setErrorType(TelemetryProto.TelemetryError.ErrorType.LUA_RUNTIME_ERROR)
+                .setMessage("test error")
+                .build();
+        mResultStore.putError(KEY_V1.getName(), error);
+
+        mService.sendFinishedReports(KEY_V1);
+
+        waitForHandlerThreadToFinish();
+        verify(mMockListener).onError(eq(KEY_V1), eq(error.toByteArray()));
+        // error should have been deleted
+        assertThat(mResultStore.getError(KEY_V1.getName(), false)).isNull();
+    }
+
+    @Test
+    public void testSendFinishedReports_whenListenerNotSet_shouldDoNothing() throws Exception {
+        PersistableBundle finalResult = new PersistableBundle();
+        finalResult.putBoolean("finished", true);
+        mResultStore.putFinalResult(KEY_V1.getName(), finalResult);
+        mService.clearListener(); // no listener = no way to send back results
+
+        mService.sendFinishedReports(KEY_V1);
+
+        waitForHandlerThreadToFinish();
+        // if listener is null, nothing should be done, result should still be in result store
+        assertThat(mResultStore.getFinalResult(KEY_V1.getName(), false).toString())
+                .isEqualTo(finalResult.toString());
+    }
+
+    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/MetricsConfigStoreTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/MetricsConfigStoreTest.java
index 8c546a0..daf83d8 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/MetricsConfigStoreTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/MetricsConfigStoreTest.java
@@ -18,6 +18,8 @@
 
 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;
@@ -53,6 +55,7 @@
     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();
 
@@ -61,14 +64,14 @@
 
     @Test
     public void testAddMetricsConfig_shouldWriteConfigToDisk() throws Exception {
-        boolean status = mMetricsConfigStore.addMetricsConfig(METRICS_CONFIG_FOO);
+        int status = mMetricsConfigStore.addMetricsConfig(METRICS_CONFIG_FOO);
 
-        assertThat(status).isTrue();
+        assertThat(status).isEqualTo(CarTelemetryManager.ERROR_METRICS_CONFIG_NONE);
         assertThat(readConfigFromFile(NAME_FOO)).isEqualTo(METRICS_CONFIG_FOO);
     }
 
     @Test
-    public void testDeleteMetricsConfig_whenNoConfig_shouldReturnFalse() throws Exception {
+    public void testDeleteMetricsConfig_whenNoConfig_shouldReturnFalse() {
         boolean status = mMetricsConfigStore.deleteMetricsConfig(NAME_BAR);
 
         assertThat(status).isFalse();
@@ -84,6 +87,16 @@
         assertThat(new File(mTestMetricsConfigDir, NAME_BAR).exists()).isFalse();
     }
 
+    @Test
+    public void testDeleteAllMetricsConfigs_shouldDeleteAll() throws Exception {
+        writeConfigToDisk(METRICS_CONFIG_FOO);
+        writeConfigToDisk(METRICS_CONFIG_BAR);
+
+        mMetricsConfigStore.deleteAllMetricsConfigs();
+
+        assertThat(mTestMetricsConfigDir.listFiles()).isEmpty();
+    }
+
     private void writeConfigToDisk(TelemetryProto.MetricsConfig config) throws Exception {
         File file = new File(mTestMetricsConfigDir, config.getName());
         Files.write(file.toPath(), config.toByteArray());
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
index d27aca4..6eb8fd6 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/ResultStoreTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/ResultStoreTest.java
@@ -19,20 +19,11 @@
 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.Mockito.eq;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.os.Handler;
 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;
@@ -46,35 +37,26 @@
 public class ResultStoreTest {
     private static final PersistableBundle TEST_INTERIM_BUNDLE = new PersistableBundle();
     private static final PersistableBundle TEST_FINAL_BUNDLE = new PersistableBundle();
+    private static final TelemetryProto.TelemetryError TEST_TELEMETRY_ERROR =
+            TelemetryProto.TelemetryError.newBuilder().setMessage("test error").build();
 
     private File mTestRootDir;
     private File mTestInterimResultDir;
+    private File mTestErrorResultDir;
     private File mTestFinalResultDir;
     private ResultStore mResultStore;
 
-    @Mock
-    private Handler mMockHandler;
-    @Mock
-    private ResultStore.FinalResultCallback mMockFinalResultCallback;
-    @Captor
-    private ArgumentCaptor<PersistableBundle> mBundleCaptor;
-
-
     @Before
     public void setUp() throws Exception {
-        // execute all handler posts immediately
-        when(mMockHandler.post(any())).thenAnswer(i -> {
-            ((Runnable) i.getArguments()[0]).run();
-            return true;
-        });
         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);
+        mTestErrorResultDir = new File(mTestRootDir, ResultStore.ERROR_RESULT_DIR);
         mTestFinalResultDir = new File(mTestRootDir, ResultStore.FINAL_RESULT_DIR);
 
-        mResultStore = new ResultStore(mMockHandler, mMockHandler, mTestRootDir);
+        mResultStore = new ResultStore(mTestRootDir);
     }
 
     @Test
@@ -89,7 +71,7 @@
         String testInterimFileName = "test_file_1";
         writeBundleToFile(mTestInterimResultDir, testInterimFileName, TEST_INTERIM_BUNDLE);
 
-        mResultStore = new ResultStore(mMockHandler, mMockHandler, mTestRootDir);
+        mResultStore = new ResultStore(mTestRootDir);
 
         // should compare value instead of reference
         assertThat(mResultStore.getInterimResult(testInterimFileName).toString())
@@ -141,11 +123,9 @@
     public void testGetFinalResult_whenNoData_shouldReceiveNull() throws Exception {
         String metricsConfigName = "my_metrics_config";
 
-        mResultStore.getFinalResult(metricsConfigName, true, mMockFinalResultCallback);
+        PersistableBundle bundle = mResultStore.getFinalResult(metricsConfigName, true);
 
-        verify(mMockFinalResultCallback).onFinalResult(eq(metricsConfigName),
-                mBundleCaptor.capture());
-        assertThat(mBundleCaptor.getValue()).isNull();
+        assertThat(bundle).isNull();
     }
 
     @Test
@@ -154,11 +134,9 @@
         Files.write(new File(mTestFinalResultDir, metricsConfigName).toPath(),
                 "not a bundle".getBytes(StandardCharsets.UTF_8));
 
-        mResultStore.getFinalResult(metricsConfigName, true, mMockFinalResultCallback);
+        PersistableBundle bundle = mResultStore.getFinalResult(metricsConfigName, true);
 
-        verify(mMockFinalResultCallback).onFinalResult(eq(metricsConfigName),
-                mBundleCaptor.capture());
-        assertThat(mBundleCaptor.getValue()).isNull();
+        assertThat(bundle).isNull();
     }
 
     @Test
@@ -166,12 +144,10 @@
         String testFinalFileName = "my_metrics_config";
         writeBundleToFile(mTestFinalResultDir, testFinalFileName, TEST_FINAL_BUNDLE);
 
-        mResultStore.getFinalResult(testFinalFileName, true, mMockFinalResultCallback);
+        PersistableBundle bundle = mResultStore.getFinalResult(testFinalFileName, true);
 
-        verify(mMockFinalResultCallback).onFinalResult(eq(testFinalFileName),
-                mBundleCaptor.capture());
         // should compare value instead of reference
-        assertThat(mBundleCaptor.getValue().toString()).isEqualTo(TEST_FINAL_BUNDLE.toString());
+        assertThat(bundle.toString()).isEqualTo(TEST_FINAL_BUNDLE.toString());
         assertThat(new File(mTestFinalResultDir, testFinalFileName).exists()).isFalse();
     }
 
@@ -180,23 +156,33 @@
         String testFinalFileName = "my_metrics_config";
         writeBundleToFile(mTestFinalResultDir, testFinalFileName, TEST_FINAL_BUNDLE);
 
-        mResultStore.getFinalResult(testFinalFileName, false, mMockFinalResultCallback);
+        PersistableBundle bundle = mResultStore.getFinalResult(testFinalFileName, false);
 
-        verify(mMockFinalResultCallback).onFinalResult(eq(testFinalFileName),
-                mBundleCaptor.capture());
         // should compare value instead of reference
-        assertThat(mBundleCaptor.getValue().toString()).isEqualTo(TEST_FINAL_BUNDLE.toString());
+        assertThat(bundle.toString()).isEqualTo(TEST_FINAL_BUNDLE.toString());
         assertThat(new File(mTestFinalResultDir, testFinalFileName).exists()).isTrue();
     }
 
     @Test
-    public void testPutFinalResult_shouldRemoveInterimResultFromMemory() throws Exception {
+    public void testGetError_whenNoError_shouldReceiveNull() {
         String metricsConfigName = "my_metrics_config";
-        mResultStore.putInterimResult(metricsConfigName, TEST_INTERIM_BUNDLE);
 
-        mResultStore.putFinalResult(metricsConfigName, TEST_FINAL_BUNDLE);
+        TelemetryProto.TelemetryError error = mResultStore.getError(metricsConfigName, true);
 
-        assertThat(mResultStore.getInterimResult(metricsConfigName)).isNull();
+        assertThat(error).isNull();
+    }
+
+    @Test
+    public void testGetError_shouldReceiveError() throws Exception {
+        String metricsConfigName = "my_metrics_config";
+        // write serialized error object to file
+        Files.write(
+                new File(mTestErrorResultDir, metricsConfigName).toPath(),
+                TEST_TELEMETRY_ERROR.toByteArray());
+
+        TelemetryProto.TelemetryError error = mResultStore.getError(metricsConfigName, true);
+
+        assertThat(error).isEqualTo(TEST_TELEMETRY_ERROR);
     }
 
     @Test
@@ -221,7 +207,7 @@
         File fileBar = new File(mTestInterimResultDir, "bar");
         writeBundleToFile(fileFoo, TEST_INTERIM_BUNDLE);
         writeBundleToFile(fileBar, TEST_INTERIM_BUNDLE);
-        mResultStore = new ResultStore(mMockHandler, mMockHandler, mTestRootDir); // re-load data
+        mResultStore = new ResultStore(mTestRootDir); // re-load data
         PersistableBundle newData = new PersistableBundle();
         newData.putDouble("pi", 3.1415926);
 
@@ -235,17 +221,48 @@
     }
 
     @Test
-    public void testPutFinalResultAndFlushToDisk_shouldRemoveInterimResultFile() throws Exception {
+    public void testPutFinalResult_shouldWriteResultAndRemoveInterimq() throws Exception {
         String metricsConfigName = "my_metrics_config";
         writeBundleToFile(mTestInterimResultDir, metricsConfigName, TEST_INTERIM_BUNDLE);
 
         mResultStore.putFinalResult(metricsConfigName, TEST_FINAL_BUNDLE);
-        mResultStore.flushToDisk();
 
+        assertThat(mResultStore.getInterimResult(metricsConfigName)).isNull();
         assertThat(new File(mTestInterimResultDir, metricsConfigName).exists()).isFalse();
         assertThat(new File(mTestFinalResultDir, metricsConfigName).exists()).isTrue();
     }
 
+    @Test
+    public void testPutError_shouldWriteErrorAndRemoveInterimResultFile() throws Exception {
+        String metricsConfigName = "my_metrics_config";
+        writeBundleToFile(mTestInterimResultDir, metricsConfigName, TEST_INTERIM_BUNDLE);
+
+        mResultStore.putError(metricsConfigName, TEST_TELEMETRY_ERROR);
+
+        assertThat(new File(mTestInterimResultDir, metricsConfigName).exists()).isFalse();
+        assertThat(new File(mTestErrorResultDir, metricsConfigName).exists()).isTrue();
+    }
+
+    @Test
+    public void testDeleteResult_whenInterimResult_shouldDelete() throws Exception {
+        String metricsConfigName = "my_metrics_config";
+        writeBundleToFile(mTestInterimResultDir, metricsConfigName, TEST_INTERIM_BUNDLE);
+
+        mResultStore.deleteResult(metricsConfigName);
+
+        assertThat(new File(mTestInterimResultDir, metricsConfigName).exists()).isFalse();
+    }
+
+    @Test
+    public void testDeleteResult_whenFinalResult_shouldDelete() throws Exception {
+        String metricsConfigName = "my_metrics_config";
+        writeBundleToFile(mTestFinalResultDir, metricsConfigName, TEST_FINAL_BUNDLE);
+
+        mResultStore.deleteResult(metricsConfigName);
+
+        assertThat(new File(mTestFinalResultDir, metricsConfigName).exists()).isFalse();
+    }
+
     private void writeBundleToFile(
             File dir, String fileName, PersistableBundle persistableBundle) throws Exception {
         writeBundleToFile(new File(dir, fileName), persistableBundle);
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/DataBrokerControllerTest.java
similarity index 83%
rename from tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerControllerUnitTest.java
rename to tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerControllerTest.java
index 89db3f6..43fe336 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/DataBrokerControllerTest.java
@@ -18,14 +18,21 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.os.Handler;
+
+import com.android.car.systeminterface.SystemStateInterface;
+import com.android.car.telemetry.MetricsConfigStore;
 import com.android.car.telemetry.TelemetryProto;
 import com.android.car.telemetry.systemmonitor.SystemMonitor;
 import com.android.car.telemetry.systemmonitor.SystemMonitorEvent;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -34,12 +41,16 @@
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import java.util.Arrays;
+
 @RunWith(MockitoJUnitRunner.class)
-public class DataBrokerControllerUnitTest {
+public class DataBrokerControllerTest {
 
     @Mock private DataBroker mMockDataBroker;
-
+    @Mock private Handler mMockHandler;
+    @Mock private MetricsConfigStore mMockMetricsConfigStore;
     @Mock private SystemMonitor mMockSystemMonitor;
+    @Mock private SystemStateInterface mMockSystemStateInterface;
 
     @Captor ArgumentCaptor<TelemetryProto.MetricsConfig> mConfigCaptor;
 
@@ -68,6 +79,15 @@
                           .addSubscribers(SUBSCRIBER)
                           .build();
 
+    @Before
+    public void setup() {
+        when(mMockHandler.post(any(Runnable.class))).thenAnswer(i -> {
+            Runnable runnable = i.getArgument(0);
+            runnable.run();
+            return true;
+        });
+    }
+
     @Test
     public void testOnNewConfig_configPassedToDataBroker() {
         mController.onNewMetricsConfig(CONFIG);
@@ -85,6 +105,18 @@
     }
 
     @Test
+    public void testOnBootCompleted_shouldStartMetricsCollection() {
+        when(mMockMetricsConfigStore.getActiveMetricsConfigs()).thenReturn(Arrays.asList(CONFIG));
+        ArgumentCaptor<Runnable> mRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+        verify(mMockSystemStateInterface).scheduleActionForBootCompleted(
+                mRunnableCaptor.capture(), any());
+
+        mRunnableCaptor.getValue().run(); // startMetricsCollection();
+
+        verify(mMockDataBroker).addMetricsConfiguration(eq(CONFIG));
+    }
+
+    @Test
     public void testOnSystemEvent_setDataBrokerPriorityCorrectlyForHighCpuUsage() {
         SystemMonitorEvent highCpuEvent = new SystemMonitorEvent();
         highCpuEvent.setCpuUsageLevel(SystemMonitorEvent.USAGE_LEVEL_HI);
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..0f37187
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerTest.java
@@ -0,0 +1,570 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.AbstractExtendedMockitoCarServiceTestCase;
+import android.car.hardware.CarPropertyConfig;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+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.invocation.InvocationOnMock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.PriorityBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DataBrokerTest extends AbstractExtendedMockitoCarServiceTestCase {
+    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 IBinder mMockScriptExecutorBinder;
+    @Mock
+    private ResultStore mMockResultStore;
+
+    @Before
+    public void setUp() throws Exception {
+        when(mMockCarPropertyService.getPropertyList())
+                .thenReturn(Collections.singletonList(PROP_CONFIG));
+        PublisherFactory factory = new PublisherFactory(
+                mMockCarPropertyService, mMockHandler, mMockStatsManager,
+                Files.createTempDirectory("telemetry_test").toFile());
+        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());
+    }
+
+    @Override
+    protected void onSessionBuilder(CustomMockitoSessionBuilder builder) {
+        builder.spyStatic(ParcelFileDescriptor.class);
+    }
+
+    @Test
+    public void testSetTaskExecutionPriority_whenNoTask_shouldNotInvokeScriptExecutor()
+            throws Exception {
+        mDataBroker.setTaskExecutionPriority(PRIORITY_HIGH);
+
+        waitForTelemetryThreadToFinish();
+        assertThat(mFakeScriptExecutor.getInvokeScriptCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testSetTaskExecutionPriority_whenNextTaskPriorityLow_shouldNotRunTask()
+            throws Exception {
+        mDataBroker.getTaskQueue().add(mLowPriorityTask);
+
+        mDataBroker.setTaskExecutionPriority(PRIORITY_HIGH);
+
+        waitForTelemetryThreadToFinish();
+        // task is not polled
+        assertThat(mDataBroker.getTaskQueue().peek()).isEqualTo(mLowPriorityTask);
+        assertThat(mFakeScriptExecutor.getInvokeScriptCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testSetTaskExecutionPriority_whenNextTaskPriorityHigh_shouldInvokeScriptExecutor()
+            throws Exception {
+        mDataBroker.getTaskQueue().add(mHighPriorityTask);
+
+        mDataBroker.setTaskExecutionPriority(PRIORITY_HIGH);
+
+        waitForTelemetryThreadToFinish();
+        // task is polled and run
+        assertThat(mDataBroker.getTaskQueue().peek()).isNull();
+        assertThat(mFakeScriptExecutor.getInvokeScriptCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void testScheduleNextTask_whenNoTask_shouldNotInvokeScriptExecutor() throws Exception {
+        mDataBroker.scheduleNextTask();
+
+        waitForTelemetryThreadToFinish();
+        assertThat(mFakeScriptExecutor.getInvokeScriptCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testScheduleNextTask_whenTaskInProgress_shouldNotInvokeScriptExecutorAgain()
+            throws Exception {
+        PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
+        taskQueue.add(mHighPriorityTask);
+        mDataBroker.scheduleNextTask(); // start a task
+        waitForTelemetryThreadToFinish();
+        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
+
+        waitForTelemetryThreadToFinish();
+        // verify task is not polled
+        assertThat(taskQueue.peek()).isEqualTo(mHighPriorityTask);
+        // expect one invocation for the task that is running
+        assertThat(mFakeScriptExecutor.getInvokeScriptCount()).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
+        waitForTelemetryThreadToFinish();
+        // end a task, should automatically schedule the next task
+        mFakeScriptExecutor.notifyScriptSuccess(mData); // posts to telemetry handler
+
+        waitForTelemetryThreadToFinish();
+        // verify queue is empty, both tasks are polled and executed
+        assertThat(taskQueue.peek()).isNull();
+        assertThat(mFakeScriptExecutor.getInvokeScriptCount()).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();
+        waitForTelemetryThreadToFinish();
+        mFakeScriptExecutor.notifyScriptSuccess(mData); // posts to telemetry handler
+
+        waitForTelemetryThreadToFinish();
+        assertThat(mFakeScriptExecutor.getInvokeScriptCount()).isEqualTo(1);
+        verify(mMockResultStore).putInterimResult(
+                eq(mHighPriorityTask.getMetricsConfig().getName()), eq(mData));
+    }
+
+    @Test
+    public void testScheduleNextTask_onScriptError_shouldStoreErrorObject() throws Exception {
+        mDataBroker.getTaskQueue().add(mHighPriorityTask);
+        TelemetryProto.TelemetryError.ErrorType errorType =
+                TelemetryProto.TelemetryError.ErrorType.LUA_RUNTIME_ERROR;
+        String errorMessage = "test onError";
+        TelemetryProto.TelemetryError expectedError = TelemetryProto.TelemetryError.newBuilder()
+                .setErrorType(errorType)
+                .setMessage(errorMessage)
+                .build();
+
+        mDataBroker.scheduleNextTask();
+        waitForTelemetryThreadToFinish();
+        mFakeScriptExecutor.notifyScriptError(errorType.getNumber(), errorMessage);
+
+        waitForTelemetryThreadToFinish();
+        assertThat(mFakeScriptExecutor.getInvokeScriptCount()).isEqualTo(1);
+        verify(mMockResultStore).putError(eq(METRICS_CONFIG_FOO.getName()), eq(expectedError));
+    }
+
+    @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();
+        waitForTelemetryThreadToFinish();
+        mFakeScriptExecutor.notifyScriptFinish(mData); // posts to telemetry handler
+
+        waitForTelemetryThreadToFinish();
+        assertThat(mFakeScriptExecutor.getInvokeScriptCount()).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();
+
+        waitForTelemetryThreadToFinish();
+        assertThat(mFakeScriptExecutor.getInvokeScriptCount()).isEqualTo(1);
+        assertThat(mFakeScriptExecutor.getSavedState()).isEqualTo(mData);
+    }
+
+    @Test
+    public void testScheduleNextTask_largeInput_shouldPipeData() throws Exception {
+        mData.putBooleanArray("1 MB Array", new boolean [1024 * 1024]);
+        mDataBroker.getTaskQueue().add(mHighPriorityTask);
+
+        mDataBroker.scheduleNextTask();
+
+        waitForTelemetryThreadToFinish();
+        assertThat(mFakeScriptExecutor.getInvokeScriptForLargeInputCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void testScheduleNextTask_largeInputPipeIOException_shouldIgnoreCurrentTask()
+            throws Exception {
+        mData.putBooleanArray("1 MB Array", new boolean [1024 * 1024]);
+        PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
+        taskQueue.add(mHighPriorityTask); // invokeScriptForLargeInput() path
+        taskQueue.add(new ScriptExecutionTask(
+                new DataSubscriber(mDataBroker, METRICS_CONFIG_FOO, SUBSCRIBER_FOO),
+                new PersistableBundle(),
+                SystemClock.elapsedRealtime())); // invokeScript() path
+        ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
+        when(ParcelFileDescriptor.createPipe()).thenReturn(fds);
+        fds[1].close(); // cause IO Exception in invokeScriptForLargeInput() path
+
+        mDataBroker.scheduleNextTask();
+
+        waitForTelemetryThreadToFinish();
+        assertThat(mFakeScriptExecutor.getInvokeScriptForLargeInputCount()).isEqualTo(1);
+        assertThat(mFakeScriptExecutor.getInvokeScriptCount()).isEqualTo(1);
+        assertThat(taskQueue).isEmpty();
+    }
+
+    @Test
+    public void testScheduleNextTask_bindScriptExecutorFailedOnce_shouldRebind()
+            throws Exception {
+        Mockito.reset(mMockContext);
+        when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(
+                new Answer() {
+                    private int mCount = 0;
+
+                    @Override
+                    public Object answer(InvocationOnMock invocation) {
+                        if (mCount++ == 1) {
+                            return false; // fail first attempt
+                        }
+                        ServiceConnection conn = invocation.getArgument(1);
+                        conn.onServiceConnected(null, mMockScriptExecutorBinder);
+                        return true; // second attempt should succeed
+                    }
+                });
+        mDataBroker.mBindScriptExecutorDelayMillis = 0L; // immediately rebind for testing purpose
+        mDataBroker.addMetricsConfiguration(METRICS_CONFIG_FOO);
+        PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
+        taskQueue.add(mHighPriorityTask);
+
+        // will rebind to ScriptExecutor if it is null
+        mDataBroker.scheduleNextTask();
+
+        waitForTelemetryThreadToFinish();
+        assertThat(taskQueue.peek()).isNull();
+        assertThat(mFakeScriptExecutor.getInvokeScriptCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void testScheduleNextTask_bindScriptExecutorFailedMultipleTimes_shouldDisableBroker()
+            throws Exception {
+        // fail 6 future attempts to bind to it
+        Mockito.reset(mMockContext);
+        when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any()))
+                .thenReturn(false, false, false, false, false, false);
+        mDataBroker.mBindScriptExecutorDelayMillis = 0L; // immediately rebind for testing purpose
+        mDataBroker.addMetricsConfiguration(METRICS_CONFIG_FOO);
+        PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
+        taskQueue.add(mHighPriorityTask);
+
+        // will rebind to ScriptExecutor if it is null
+        mDataBroker.scheduleNextTask();
+
+        waitForTelemetryThreadToFinish();
+        // broker disabled, all subscribers should have been removed
+        assertThat(mDataBroker.getSubscriptionMap()).hasSize(0);
+        assertThat(mFakeScriptExecutor.getInvokeScriptCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testScheduleNextTask_whenScriptExecutorThrowsException_shouldResetAndTryAgain()
+            throws Exception {
+        PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
+        taskQueue.add(mHighPriorityTask);
+        mFakeScriptExecutor.failNextApiCalls(1); // fail the next invokeScript() call
+
+        mDataBroker.scheduleNextTask();
+
+        waitForTelemetryThreadToFinish();
+        // invokeScript() failed, task is re-queued and re-run
+        assertThat(mFakeScriptExecutor.getInvokeScriptCount()).isEqualTo(2);
+        assertThat(taskQueue).isEmpty();
+    }
+
+    @Test
+    public void testAddTaskToQueue_shouldInvokeScriptExecutor() throws Exception {
+        mDataBroker.addTaskToQueue(mHighPriorityTask);
+
+        waitForTelemetryThreadToFinish();
+        assertThat(mFakeScriptExecutor.getInvokeScriptCount()).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 waitForTelemetryThreadToFinish() 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 mInvokeScriptCount = 0;
+        private int mInvokeScriptForLargeInputCount = 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 {
+            mInvokeScriptCount++;
+            mSavedState = savedState;
+            mListener = listener;
+            if (mFailApi > 0) {
+                mFailApi--;
+                throw new RemoteException("Simulated failure");
+            }
+        }
+
+        @Override
+        public void invokeScriptForLargeInput(String scriptBody, String functionName,
+                ParcelFileDescriptor publishedDataFileDescriptor,
+                @Nullable PersistableBundle savedState,
+                IScriptExecutorListener listener) throws RemoteException {
+            mInvokeScriptForLargeInputCount++;
+            mSavedState = savedState;
+            mListener = listener;
+            if (mFailApi > 0) {
+                mFailApi--;
+                throw new RemoteException("Simulated failure");
+            }
+            // Since DataBrokerImpl and FakeScriptExecutor are in the same process, they do not
+            // use real IPC and share the fd. When DataBroker closes the fd, it affects
+            // FakeScriptExecutor. Therefore FakeScriptExecutor must dup the fd before it is
+            // closed by DataBroker
+            ParcelFileDescriptor dup = null;
+            try {
+                dup = publishedDataFileDescriptor.dup();
+            } catch (IOException e) { }
+            final ParcelFileDescriptor fd = Objects.requireNonNull(dup);
+            // to prevent deadlock, read and write must happen on separate threads
+            Handler.getMain().post(() -> {
+                try (InputStream input = new ParcelFileDescriptor.AutoCloseInputStream(fd)) {
+                    PersistableBundle.readFromStream(input);
+                } catch (IOException e) { }
+            });
+        }
+
+        @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
+            }
+        }
+
+        /** Mocks script finished with error. */
+        public void notifyScriptError(int errorType, String errorMessage) {
+            try {
+                mListener.onError(errorType, errorMessage, null);
+            } catch (RemoteException e) {
+                // nothing to do
+            }
+        }
+
+        /** Fails the next N invokeScript() call. */
+        public void failNextApiCalls(int n) {
+            mFailApi = n;
+        }
+
+        /** Returns number of times invokeScript() was called. */
+        public int getInvokeScriptCount() {
+            return mInvokeScriptCount;
+        }
+
+        /** Returns number of times invokeScriptForLargeInput() was called. */
+        public int getInvokeScriptForLargeInputCount() {
+            return mInvokeScriptForLargeInputCount;
+        }
+
+        /** 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 4193ca5..0000000
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerUnitTest.java
+++ /dev/null
@@ -1,373 +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 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.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.util.Collections;
-import java.util.concurrent.PriorityBlockingQueue;
-
-@RunWith(MockitoJUnitRunner.class)
-public class DataBrokerUnitTest {
-    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 PersistableBundle DATA = new PersistableBundle();
-    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();
-
-    private DataBrokerImpl mDataBroker;
-    private FakeScriptExecutor mFakeScriptExecutor;
-    private Handler mHandler;
-    private ScriptExecutionTask mHighPriorityTask;
-    private ScriptExecutionTask mLowPriorityTask;
-
-    @Mock
-    private Context mMockContext;
-    @Mock
-    private CarPropertyService mMockCarPropertyService;
-    @Mock
-    private StatsManagerProxy mMockStatsManager;
-    @Mock
-    private SharedPreferences mMockSharedPreferences;
-    @Mock
-    private IBinder mMockScriptExecutorBinder;
-    @Mock
-    private ResultStore mMockResultStore;
-
-    @Captor
-    private ArgumentCaptor<ServiceConnection> mServiceConnectionCaptor;
-
-    @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, mMockStatsManager, mMockSharedPreferences);
-        mDataBroker = new DataBrokerImpl(mMockContext, factory, mMockResultStore);
-        mHandler = mDataBroker.getWorkerHandler();
-
-        mFakeScriptExecutor = new FakeScriptExecutor();
-        when(mMockScriptExecutorBinder.queryLocalInterface(anyString()))
-                .thenReturn(mFakeScriptExecutor);
-        // capture ServiceConnection and connect to fake ScriptExecutor
-        verify(mMockContext).bindServiceAsUser(
-                any(), mServiceConnectionCaptor.capture(), anyInt(), any());
-        mServiceConnectionCaptor.getValue().onServiceConnected(
-                null, mMockScriptExecutorBinder);
-
-        mHighPriorityTask = new ScriptExecutionTask(
-                new DataSubscriber(mDataBroker, METRICS_CONFIG_FOO, SUBSCRIBER_FOO),
-                DATA,
-                SystemClock.elapsedRealtime());
-        mLowPriorityTask = new ScriptExecutionTask(
-                new DataSubscriber(mDataBroker, METRICS_CONFIG_BAR, SUBSCRIBER_BAR),
-                DATA,
-                SystemClock.elapsedRealtime());
-    }
-
-    @Test
-    public void testSetTaskExecutionPriority_whenNoTask_shouldNotInvokeScriptExecutor() {
-        mDataBroker.setTaskExecutionPriority(PRIORITY_HIGH);
-
-        waitForHandlerThreadToFinish();
-        assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(0);
-    }
-
-    @Test
-    public void testSetTaskExecutionPriority_whenNextTaskPriorityLow_shouldNotRunTask() {
-        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() {
-        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() {
-        mDataBroker.scheduleNextTask();
-
-        waitForHandlerThreadToFinish();
-        assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(0);
-    }
-
-    @Test
-    public void testScheduleNextTask_whenTaskInProgress_shouldNotInvokeScriptExecutorAgain() {
-        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
-
-        // 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() {
-        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(DATA);
-
-        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() {
-        mDataBroker.getTaskQueue().add(mHighPriorityTask);
-        ArgumentCaptor<PersistableBundle> persistableBundleCaptor =
-                ArgumentCaptor.forClass(PersistableBundle.class);
-
-        mDataBroker.scheduleNextTask();
-        waitForHandlerThreadToFinish();
-        mFakeScriptExecutor.notifyScriptSuccess(new PersistableBundle()); // pass in empty bundle
-
-        assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(1);
-        verify(mMockResultStore).putInterimResult(eq(METRICS_CONFIG_FOO.getName()),
-                persistableBundleCaptor.capture());
-        assertThat(persistableBundleCaptor.getValue().toString()).isEqualTo(
-                new PersistableBundle().toString()); // expect empty persistable bundle
-    }
-
-    @Test
-    public void testScheduleNextTask_whenBindScriptExecutorFailed_shouldDisableBroker() {
-        mDataBroker.addMetricsConfiguration(METRICS_CONFIG_FOO);
-        PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
-        taskQueue.add(mHighPriorityTask);
-        // disconnect ScriptExecutor and fail all future attempts to bind to it
-        mServiceConnectionCaptor.getValue().onServiceDisconnected(null);
-        when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(false);
-
-        // 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_whenScriptExecutorFails_shouldRequeueTask() {
-        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() {
-        mDataBroker.addTaskToQueue(mHighPriorityTask);
-
-        waitForHandlerThreadToFinish();
-        assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(1);
-    }
-
-    @Test
-    public void testAddMetricsConfiguration_newMetricsConfig() {
-        mDataBroker.addMetricsConfiguration(METRICS_CONFIG_BAR);
-
-        waitForHandlerThreadToFinish();
-        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);
-
-        waitForHandlerThreadToFinish();
-        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),
-                DATA,
-                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);
-
-        waitForHandlerThreadToFinish();
-        assertThat(taskQueue).hasSize(1);
-        assertThat(taskQueue.poll()).isEqualTo(mLowPriorityTask);
-    }
-
-    @Test
-    public void testRemoveMetricsConfiguration_whenMetricsConfigNonExistent_shouldDoNothing() {
-        mDataBroker.removeMetricsConfiguration(METRICS_CONFIG_BAR);
-
-        waitForHandlerThreadToFinish();
-        assertThat(mDataBroker.getSubscriptionMap()).hasSize(0);
-    }
-
-    private void waitForHandlerThreadToFinish() {
-        assertWithMessage("handler not idle in %sms", TIMEOUT_MS)
-                .that(mHandler.runWithScissors(() -> {}, TIMEOUT_MS)).isTrue();
-    }
-
-    private static class FakeScriptExecutor implements IScriptExecutor {
-        private IScriptExecutorListener mListener;
-        private int mApiInvocationCount = 0;
-        private int mFailApi = 0;
-
-        @Override
-        public void invokeScript(String scriptBody, String functionName,
-                PersistableBundle publishedData, @Nullable PersistableBundle savedState,
-                IScriptExecutorListener listener)
-                throws RemoteException {
-            mApiInvocationCount++;
-            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
-            }
-        }
-
-        /** 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;
-        }
-    }
-}
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataSubscriberTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataSubscriberTest.java
new file mode 100644
index 0000000..69602f1
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataSubscriberTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.telemetry.databroker;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.car.telemetry.TelemetryProto;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DataSubscriberTest {
+
+    private static final TelemetryProto.VehiclePropertyPublisher
+            VEHICLE_PROPERTY_PUBLISHER_CONFIGURATION =
+            TelemetryProto.VehiclePropertyPublisher.newBuilder().setReadRate(
+                    1).setVehiclePropertyId(100).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(1).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(1).build();
+    private static final TelemetryProto.MetricsConfig METRICS_CONFIG_BAR =
+            TelemetryProto.MetricsConfig.newBuilder().setName("Bar").setVersion(
+                    1).addSubscribers(SUBSCRIBER_BAR).build();
+
+    @Mock
+    private DataBroker mMockDataBroker;
+
+    @Test
+    public void testEquals_whenSame_shouldBeEqual() {
+        DataSubscriber foo = new DataSubscriber(mMockDataBroker, METRICS_CONFIG_FOO,
+                SUBSCRIBER_FOO);
+        DataSubscriber bar = new DataSubscriber(mMockDataBroker, METRICS_CONFIG_FOO,
+                SUBSCRIBER_FOO);
+
+        assertThat(foo).isEqualTo(bar);
+    }
+
+    @Test
+    public void testEquals_whenDifferent_shouldNotBeEqual() {
+        DataSubscriber foo = new DataSubscriber(mMockDataBroker, METRICS_CONFIG_FOO,
+                SUBSCRIBER_FOO);
+        DataSubscriber bar = new DataSubscriber(mMockDataBroker, METRICS_CONFIG_BAR,
+                SUBSCRIBER_BAR);
+
+        assertThat(foo).isNotEqualTo(bar);
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/AtomDataConverterTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/AtomDataConverterTest.java
new file mode 100644
index 0000000..c61c106
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/AtomDataConverterTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 android.os.PersistableBundle;
+
+import com.android.car.telemetry.AtomsProto;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class AtomDataConverterTest {
+    @Test
+    public void testConvertPushedAtomsListWithUnsetFields_putsCorrectDataToPersistableBundle() {
+        List<AtomsProto.Atom> pushedAtomsList = Arrays.asList(
+                AtomsProto.Atom.newBuilder()
+                        .setAppStartMemoryStateCaptured(
+                                AtomsProto.AppStartMemoryStateCaptured.newBuilder()
+                                        .setUid(1000)
+                                        .setActivityName("activityName1")
+                                        .setRssInBytes(1234L))
+                        .build(),
+                AtomsProto.Atom.newBuilder()
+                        .setAppStartMemoryStateCaptured(
+                                AtomsProto.AppStartMemoryStateCaptured.newBuilder()
+                                        .setUid(1100)
+                                        .setActivityName("activityName2")
+                                        .setRssInBytes(2345L))
+                        .build()
+        );
+        PersistableBundle bundle = new PersistableBundle();
+
+        AtomDataConverter.convertAtomsList(pushedAtomsList, bundle);
+
+        assertThat(bundle.size()).isEqualTo(3);
+        assertThat(bundle.getIntArray(AtomDataConverter.UID))
+            .asList().containsExactly(1000, 1100).inOrder();
+        assertThat(Arrays.asList(bundle.getStringArray(AtomDataConverter.ACTIVITY_NAME)))
+            .containsExactly("activityName1", "activityName2").inOrder();
+        assertThat(bundle.getLongArray(AtomDataConverter.RSS_IN_BYTES))
+            .asList().containsExactly(1234L, 2345L).inOrder();
+    }
+
+    @Test
+    public void testConvertPulledAtomsListWithUnsetFields_putsCorrectDataToPersistableBundle() {
+        List<AtomsProto.Atom> pulledAtomsList = Arrays.asList(
+                AtomsProto.Atom.newBuilder()
+                        .setProcessMemoryState(AtomsProto.ProcessMemoryState.newBuilder()
+                                .setUid(1000)
+                                .setProcessName("processName1")
+                                .setRssInBytes(1234L))
+                        .build(),
+                AtomsProto.Atom.newBuilder()
+                        .setProcessMemoryState(AtomsProto.ProcessMemoryState.newBuilder()
+                                .setUid(1100)
+                                .setProcessName("processName2")
+                                .setRssInBytes(2345L))
+                        .build()
+        );
+        PersistableBundle bundle = new PersistableBundle();
+
+        AtomDataConverter.convertAtomsList(pulledAtomsList, bundle);
+
+        assertThat(bundle.size()).isEqualTo(3);
+        assertThat(bundle.getIntArray(AtomDataConverter.UID))
+            .asList().containsExactly(1000, 1100).inOrder();
+        assertThat(Arrays.asList(bundle.getStringArray(AtomDataConverter.PROCESS_NAME)))
+            .containsExactly("processName1", "processName2").inOrder();
+        assertThat(bundle.getLongArray(AtomDataConverter.RSS_IN_BYTES))
+            .asList().containsExactly(1234L, 2345L).inOrder();
+    }
+
+    @Test
+    public void testConvertAppStartMemoryStateCapturedAtoms_putsCorrectDataToPersistableBundle() {
+        List<AtomsProto.Atom> atomsList = Arrays.asList(
+                AtomsProto.Atom.newBuilder()
+                        .setAppStartMemoryStateCaptured(
+                                AtomsProto.AppStartMemoryStateCaptured.newBuilder()
+                                        .setUid(1000)
+                                        .setProcessName("processName")
+                                        .setActivityName("activityName")
+                                        .setPageFault(59L)
+                                        .setPageMajorFault(34L)
+                                        .setRssInBytes(1234L)
+                                        .setCacheInBytes(234L)
+                                        .setSwapInBytes(111L))
+                        .build()
+        );
+        PersistableBundle bundle = new PersistableBundle();
+
+        AtomDataConverter.convertAtomsList(atomsList, bundle);
+
+        assertThat(bundle.size()).isEqualTo(8);
+        assertThat(bundle.getIntArray(AtomDataConverter.UID)).asList().containsExactly(1000);
+        assertThat(Arrays.asList(bundle.getStringArray(AtomDataConverter.PROCESS_NAME)))
+            .containsExactly("processName");
+        assertThat(Arrays.asList(bundle.getStringArray(AtomDataConverter.ACTIVITY_NAME)))
+            .containsExactly("activityName");
+        assertThat(bundle.getLongArray(AtomDataConverter.PAGE_FAULT))
+            .asList().containsExactly(59L);
+        assertThat(bundle.getLongArray(AtomDataConverter.PAGE_MAJOR_FAULT))
+            .asList().containsExactly(34L);
+        assertThat(bundle.getLongArray(AtomDataConverter.RSS_IN_BYTES))
+            .asList().containsExactly(1234L);
+        assertThat(bundle.getLongArray(AtomDataConverter.CACHE_IN_BYTES))
+            .asList().containsExactly(234L);
+        assertThat(bundle.getLongArray(AtomDataConverter.SWAP_IN_BYTES))
+            .asList().containsExactly(111L);
+    }
+
+    @Test
+    public void testConvertProcessMemoryStateAtoms_putsCorrectDataToPersistableBundle() {
+        List<AtomsProto.Atom> atomsList = Arrays.asList(
+                AtomsProto.Atom.newBuilder()
+                        .setProcessMemoryState(AtomsProto.ProcessMemoryState.newBuilder()
+                                .setUid(1000)
+                                .setProcessName("processName")
+                                .setOomAdjScore(100)
+                                .setPageFault(59L)
+                                .setPageMajorFault(34L)
+                                .setRssInBytes(1234L)
+                                .setCacheInBytes(234L)
+                                .setSwapInBytes(111L))
+                        .build()
+        );
+        PersistableBundle bundle = new PersistableBundle();
+
+        AtomDataConverter.convertAtomsList(atomsList, bundle);
+
+        assertThat(bundle.size()).isEqualTo(8);
+        assertThat(bundle.getIntArray(AtomDataConverter.UID)).asList().containsExactly(1000);
+        assertThat(Arrays.asList(bundle.getStringArray(AtomDataConverter.PROCESS_NAME)))
+            .containsExactly("processName");
+        assertThat(bundle.getIntArray(AtomDataConverter.OOM_ADJ_SCORE))
+            .asList().containsExactly(100);
+        assertThat(bundle.getLongArray(AtomDataConverter.PAGE_FAULT))
+            .asList().containsExactly(59L);
+        assertThat(bundle.getLongArray(AtomDataConverter.PAGE_MAJOR_FAULT))
+            .asList().containsExactly(34L);
+        assertThat(bundle.getLongArray(AtomDataConverter.RSS_IN_BYTES))
+            .asList().containsExactly(1234L);
+        assertThat(bundle.getLongArray(AtomDataConverter.CACHE_IN_BYTES))
+            .asList().containsExactly(234L);
+        assertThat(bundle.getLongArray(AtomDataConverter.SWAP_IN_BYTES))
+            .asList().containsExactly(111L);
+    }
+}
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
index 221b6a0..bc3cd47 100644
--- 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
@@ -23,19 +23,21 @@
 
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
+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;
@@ -56,24 +58,26 @@
                             .setId(CAR_DATA_ID_1))
                     .build();
 
+    private final FakeHandlerWrapper mFakeHandlerWrapper =
+            new FakeHandlerWrapper(Looper.getMainLooper(), FakeHandlerWrapper.Mode.IMMEDIATE);
+
     @Mock private IBinder mMockBinder;
-    @Mock private ICarTelemetryInternal mMockCarTelemetryInternal;
     @Mock private DataSubscriber mMockDataSubscriber;
 
-    private final CarTelemetrydPublisher mPublisher = new CarTelemetrydPublisher();
+    @Captor private ArgumentCaptor<IBinder.DeathRecipient> mLinkToDeathCallbackCaptor;
 
-    // ICarTelemetryInternal side of the listener.
-    @Captor private ArgumentCaptor<ICarDataListener> mCarDataListenerCaptor;
+    @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);
-        doNothing().when(mMockCarTelemetryInternal).setListener(mCarDataListenerCaptor.capture());
-    }
-
-    private void mockICarTelemetryInternalBinder() {
-        when(mMockBinder.queryLocalInterface(any())).thenReturn(mMockCarTelemetryInternal);
-        when(mMockCarTelemetryInternal.asBinder()).thenReturn(mMockBinder);
+        when(mMockBinder.queryLocalInterface(any())).thenReturn(mFakeCarTelemetryInternal);
+        doNothing().when(mMockBinder).linkToDeath(mLinkToDeathCallbackCaptor.capture(), anyInt());
         doReturn(mMockBinder).when(() -> ServiceManager.checkService(SERVICE_NAME));
     }
 
@@ -84,18 +88,15 @@
 
     @Test
     public void testAddDataSubscriber_registersNewListener() {
-        mockICarTelemetryInternalBinder();
-
         mPublisher.addDataSubscriber(mMockDataSubscriber);
 
-        assertThat(mCarDataListenerCaptor.getAllValues()).hasSize(1);
+        assertThat(mFakeCarTelemetryInternal.mListener).isNotNull();
         assertThat(mPublisher.isConnectedToCarTelemetryd()).isTrue();
         assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isTrue();
     }
 
     @Test
     public void testAddDataSubscriber_withInvalidId_fails() {
-        mockICarTelemetryInternalBinder();
         DataSubscriber invalidDataSubscriber = Mockito.mock(DataSubscriber.class);
         when(invalidDataSubscriber.getPublisherParam()).thenReturn(
                 TelemetryProto.Publisher.newBuilder()
@@ -107,7 +108,7 @@
                 () -> mPublisher.addDataSubscriber(invalidDataSubscriber));
 
         assertThat(error).hasMessageThat().contains("Invalid CarData ID");
-        assertThat(mCarDataListenerCaptor.getAllValues()).hasSize(0);
+        assertThat(mFakeCarTelemetryInternal.mListener).isNull();
         assertThat(mPublisher.isConnectedToCarTelemetryd()).isFalse();
         assertThat(mPublisher.hasDataSubscriber(invalidDataSubscriber)).isFalse();
     }
@@ -118,8 +119,7 @@
     }
 
     @Test
-    public void testRemoveDataSubscriber_removesOnlySingleSubscriber() throws Exception {
-        mockICarTelemetryInternalBinder();
+    public void testRemoveDataSubscriber_removesOnlySingleSubscriber() {
         DataSubscriber subscriber2 = Mockito.mock(DataSubscriber.class);
         when(subscriber2.getPublisherParam()).thenReturn(PUBLISHER_PARAMS_1);
         mPublisher.addDataSubscriber(mMockDataSubscriber);
@@ -129,23 +129,21 @@
 
         assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isTrue();
         assertThat(mPublisher.hasDataSubscriber(subscriber2)).isFalse();
-        verify(mMockCarTelemetryInternal, never()).clearListener();
+        assertThat(mFakeCarTelemetryInternal.mListener).isNotNull();
     }
 
     @Test
-    public void testRemoveDataSubscriber_disconnectsFromICarTelemetry() throws Exception {
-        mockICarTelemetryInternalBinder();
+    public void testRemoveDataSubscriber_disconnectsFromICarTelemetry() {
         mPublisher.addDataSubscriber(mMockDataSubscriber);
 
         mPublisher.removeDataSubscriber(mMockDataSubscriber);
 
         assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isFalse();
-        verify(mMockCarTelemetryInternal, times(1)).clearListener();
+        assertThat(mFakeCarTelemetryInternal.mListener).isNull();
     }
 
     @Test
-    public void testRemoveAllDataSubscribers_succeeds() throws Exception {
-        mockICarTelemetryInternalBinder();
+    public void testRemoveAllDataSubscribers_succeeds() {
         DataSubscriber subscriber2 = Mockito.mock(DataSubscriber.class);
         when(subscriber2.getPublisherParam()).thenReturn(PUBLISHER_PARAMS_1);
         mPublisher.addDataSubscriber(mMockDataSubscriber);
@@ -155,8 +153,68 @@
 
         assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isFalse();
         assertThat(mPublisher.hasDataSubscriber(subscriber2)).isFalse();
-        verify(mMockCarTelemetryInternal, times(1)).clearListener();
+        assertThat(mFakeCarTelemetryInternal.mListener).isNull();
     }
 
-    // TODO(b/189142577): add test cases when connecting to cartelemetryd fails
+    @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/ConfigMetricsReportListConverterTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/ConfigMetricsReportListConverterTest.java
new file mode 100644
index 0000000..367fc5c
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/ConfigMetricsReportListConverterTest.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.telemetry.publisher;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.PersistableBundle;
+
+import com.android.car.telemetry.AtomsProto;
+import com.android.car.telemetry.StatsLogProto;
+import com.android.car.telemetry.StatsLogProto.ConfigMetricsReportList;
+import com.android.car.telemetry.StatsLogProto.StatsLogReport;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Map;
+
+@RunWith(JUnit4.class)
+public class ConfigMetricsReportListConverterTest {
+    @Test
+    public void testConvertMultipleReports_correctlyGroupsByMetricId() {
+        StatsLogProto.EventMetricData eventData = StatsLogProto.EventMetricData.newBuilder()
+                .setElapsedTimestampNanos(99999999L)
+                .setAtom(AtomsProto.Atom.newBuilder()
+                        .setAppStartMemoryStateCaptured(
+                                AtomsProto.AppStartMemoryStateCaptured.newBuilder()
+                                        .setUid(1000)
+                                        .setActivityName("activityName")
+                                        .setRssInBytes(1234L)))
+                .build();
+
+        StatsLogProto.GaugeMetricData gaugeData = StatsLogProto.GaugeMetricData.newBuilder()
+                .addBucketInfo(StatsLogProto.GaugeBucketInfo.newBuilder()
+                        .addAtom(AtomsProto.Atom.newBuilder()
+                                .setProcessMemoryState(AtomsProto.ProcessMemoryState.newBuilder()
+                                        .setUid(1300)
+                                        .setProcessName("processName")
+                                        .setRssInBytes(4567L)))
+                        .addElapsedTimestampNanos(445678901L))
+                .addDimensionLeafValuesInWhat(StatsLogProto.DimensionsValue.newBuilder()
+                        .setValueInt(234))
+                .addDimensionLeafValuesInWhat(StatsLogProto.DimensionsValue.newBuilder()
+                        .setValueStrHash(345678901L))
+                .build();
+
+        ConfigMetricsReportList reportList = ConfigMetricsReportList.newBuilder()
+                .addReports(StatsLogProto.ConfigMetricsReport.newBuilder()
+                        .addMetrics(StatsLogReport.newBuilder()
+                                .setMetricId(12345L)
+                                .setEventMetrics(
+                                        StatsLogReport.EventMetricDataWrapper.newBuilder()
+                                                .addData(eventData))))
+                .addReports(StatsLogProto.ConfigMetricsReport.newBuilder()
+                        .addMetrics(StatsLogProto.StatsLogReport.newBuilder()
+                                .setMetricId(23456L)
+                                .setGaugeMetrics(
+                                        StatsLogReport.GaugeMetricDataWrapper.newBuilder()
+                                                .addData(gaugeData))))
+                .build();
+
+        Map<Long, PersistableBundle> map = ConfigMetricsReportListConverter.convert(reportList);
+
+        PersistableBundle subBundle1 = map.get(12345L);
+        PersistableBundle subBundle2 = map.get(23456L);
+        PersistableBundle dimensionBundle = subBundle2.getPersistableBundle("234-345678901");
+        assertThat(new ArrayList<Long>(map.keySet())).containsExactly(12345L, 23456L);
+        assertThat(subBundle1.getLongArray(EventMetricDataConverter.ELAPSED_TIME_NANOS))
+            .asList().containsExactly(99999999L);
+        assertThat(subBundle1.getIntArray(AtomDataConverter.UID)).asList().containsExactly(1000);
+        assertThat(Arrays.asList(subBundle1.getStringArray(AtomDataConverter.ACTIVITY_NAME)))
+            .containsExactly("activityName");
+        assertThat(subBundle1.getLongArray(AtomDataConverter.RSS_IN_BYTES))
+            .asList().containsExactly(1234L);
+        // TODO(b/200064146) add checks for uid and process_name
+        assertThat(subBundle2.getLongArray(AtomDataConverter.RSS_IN_BYTES))
+            .asList().containsExactly(4567L);
+        assertThat(subBundle2.getLongArray(EventMetricDataConverter.ELAPSED_TIME_NANOS))
+            .asList().containsExactly(445678901L);
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/EventMetricDataConverterTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/EventMetricDataConverterTest.java
new file mode 100644
index 0000000..2f4f939
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/EventMetricDataConverterTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 android.os.PersistableBundle;
+
+import com.android.car.telemetry.AtomsProto;
+import com.android.car.telemetry.StatsLogProto;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class EventMetricDataConverterTest {
+    @Test
+    public void testConvertEventDataList_putsCorrectDataIntoPersistableBundle() {
+        List<StatsLogProto.EventMetricData> eventDataList = Arrays.asList(
+                StatsLogProto.EventMetricData.newBuilder()
+                        .setElapsedTimestampNanos(12345678L)
+                        .setAtom(AtomsProto.Atom.newBuilder()
+                                .setAppStartMemoryStateCaptured(
+                                        AtomsProto.AppStartMemoryStateCaptured.newBuilder()
+                                                .setUid(1000)
+                                                .setActivityName("activityName1")
+                                                .setRssInBytes(1234L)))
+                        .build(),
+                StatsLogProto.EventMetricData.newBuilder()
+                        .setElapsedTimestampNanos(23456789L)
+                        .setAtom(AtomsProto.Atom.newBuilder()
+                                .setAppStartMemoryStateCaptured(
+                                        AtomsProto.AppStartMemoryStateCaptured.newBuilder()
+                                                .setUid(1100)
+                                                .setActivityName("activityName2")
+                                                .setRssInBytes(2345L)))
+                        .build()
+        );
+        PersistableBundle bundle = new PersistableBundle();
+        EventMetricDataConverter.convertEventDataList(eventDataList, bundle);
+
+        assertThat(bundle.size()).isEqualTo(4);
+        assertThat(bundle.getLongArray(EventMetricDataConverter.ELAPSED_TIME_NANOS))
+            .asList().containsExactly(12345678L, 23456789L);
+        assertThat(bundle.getIntArray(AtomDataConverter.UID))
+            .asList().containsExactly(1000, 1100);
+        assertThat(Arrays.asList(bundle.getStringArray(AtomDataConverter.ACTIVITY_NAME)))
+            .containsExactly("activityName1", "activityName2");
+        assertThat(bundle.getLongArray(AtomDataConverter.RSS_IN_BYTES))
+            .asList().containsExactly(1234L, 2345L);
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/GaugeMetricDataConverterTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/GaugeMetricDataConverterTest.java
new file mode 100644
index 0000000..7ee19ce
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/GaugeMetricDataConverterTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 android.os.PersistableBundle;
+
+import com.android.car.telemetry.AtomsProto;
+import com.android.car.telemetry.StatsLogProto;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class GaugeMetricDataConverterTest {
+    @Test
+    public void testConvertGaugeDataList_putsCorrectDataIntoPersistableBundle() {
+        // TODO(b/200064146): handle process name and uid
+        List<StatsLogProto.GaugeMetricData> gaugeDataList = Arrays.asList(
+                StatsLogProto.GaugeMetricData.newBuilder()
+                        .addBucketInfo(StatsLogProto.GaugeBucketInfo.newBuilder()
+                                .addAtom(AtomsProto.Atom.newBuilder()
+                                        .setProcessMemoryState(
+                                                AtomsProto.ProcessMemoryState.newBuilder()
+                                                    .setUid(1000)
+                                                    .setProcessName("processName1")
+                                                    .setRssInBytes(1234L)))
+                                .addElapsedTimestampNanos(12345678L)
+                                .addAtom(AtomsProto.Atom.newBuilder()
+                                        .setProcessMemoryState(
+                                                AtomsProto.ProcessMemoryState.newBuilder()
+                                                    .setUid(1000)
+                                                    .setProcessName("processName1")
+                                                    .setRssInBytes(2345L)))
+                                .addElapsedTimestampNanos(23456789L))
+                        .addBucketInfo(StatsLogProto.GaugeBucketInfo.newBuilder()
+                                .addAtom(AtomsProto.Atom.newBuilder()
+                                        .setProcessMemoryState(
+                                                AtomsProto.ProcessMemoryState.newBuilder()
+                                                    .setUid(1200)
+                                                    .setProcessName("processName2")
+                                                    .setRssInBytes(3456L)))
+                                .addElapsedTimestampNanos(34567890L))
+                        .addDimensionLeafValuesInWhat(StatsLogProto.DimensionsValue.newBuilder()
+                                .setValueInt(123))
+                        .addDimensionLeafValuesInWhat(StatsLogProto.DimensionsValue.newBuilder()
+                                .setValueStrHash(234567890L))
+                        .build(),
+                StatsLogProto.GaugeMetricData.newBuilder()
+                        .addBucketInfo(StatsLogProto.GaugeBucketInfo.newBuilder()
+                                .addAtom(AtomsProto.Atom.newBuilder()
+                                        .setProcessMemoryState(
+                                                AtomsProto.ProcessMemoryState.newBuilder()
+                                                    .setUid(1300)
+                                                    .setProcessName("processName3")
+                                                    .setRssInBytes(4567L)))
+                                .addElapsedTimestampNanos(445678901L))
+                        .addDimensionLeafValuesInWhat(StatsLogProto.DimensionsValue.newBuilder()
+                                .setValueInt(234))
+                        .addDimensionLeafValuesInWhat(StatsLogProto.DimensionsValue.newBuilder()
+                                .setValueStrHash(345678901L))
+                        .build()
+        );
+        PersistableBundle bundle = new PersistableBundle();
+
+        GaugeMetricDataConverter.convertGaugeDataList(gaugeDataList, bundle);
+
+        assertThat(bundle.getLongArray(AtomDataConverter.RSS_IN_BYTES))
+            .asList().containsExactly(1234L, 2345L, 3456L, 4567L);
+        assertThat(bundle.getLongArray(EventMetricDataConverter.ELAPSED_TIME_NANOS))
+            .asList().containsExactly(12345678L, 23456789L, 34567890L, 445678901L);
+    }
+}
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
index c177a4d..5b7e21e 100644
--- 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
@@ -16,18 +16,26 @@
 
 package com.android.car.telemetry.publisher;
 
+import static com.android.car.telemetry.AtomsProto.Atom.APP_START_MEMORY_STATE_CAPTURED_FIELD_NUMBER;
+import static com.android.car.telemetry.AtomsProto.Atom.PROCESS_MEMORY_STATE_FIELD_NUMBER;
 import static com.android.car.telemetry.TelemetryProto.StatsPublisher.SystemMetric.APP_START_MEMORY_STATE_CAPTURED;
+import static com.android.car.telemetry.TelemetryProto.StatsPublisher.SystemMetric.PROCESS_MEMORY_STATE;
 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.android.car.telemetry.publisher.StatsPublisher.PROCESS_MEMORY_STATE_FIELDS_MATCHER;
+import static com.android.car.telemetry.publisher.StatsPublisher.PROCESS_MEMORY_STATE_GAUGE_METRIC_ID;
+import static com.android.car.telemetry.publisher.StatsPublisher.PROCESS_MEMORY_STATE_MATCHER_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;
@@ -38,7 +46,6 @@
 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;
 
@@ -48,8 +55,13 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.nio.file.Files;
+
 @RunWith(MockitoJUnitRunner.class)
 public class StatsPublisherTest {
     private static final TelemetryProto.Publisher STATS_PUBLISHER_PARAMS_1 =
@@ -57,19 +69,31 @@
                     .setStats(TelemetryProto.StatsPublisher.newBuilder()
                             .setSystemMetric(APP_START_MEMORY_STATE_CAPTURED))
                     .build();
+    private static final TelemetryProto.Publisher STATS_PUBLISHER_PARAMS_2 =
+            TelemetryProto.Publisher.newBuilder()
+                    .setStats(TelemetryProto.StatsPublisher.newBuilder()
+                            .setSystemMetric(PROCESS_MEMORY_STATE))
+                    .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.Subscriber SUBSCRIBER_2 =
+            TelemetryProto.Subscriber.newBuilder()
+                    .setHandler("handler_fn_2")
+                    .setPublisher(STATS_PUBLISHER_PARAMS_2)
+                    .build();
     private static final TelemetryProto.MetricsConfig METRICS_CONFIG =
             TelemetryProto.MetricsConfig.newBuilder()
                     .setName("myconfig")
                     .setVersion(1)
                     .addSubscribers(SUBSCRIBER_1)
+                    .addSubscribers(SUBSCRIBER_2)
                     .build();
 
     private static final long SUBSCRIBER_1_HASH = -8101507323446050791L;  // Used as ID.
+    private static final long SUBSCRIBER_2_HASH = 2778197004730583271L;  // Used as ID.
 
     private static final StatsdConfigProto.StatsdConfig STATSD_CONFIG_1 =
             StatsdConfigProto.StatsdConfig.newBuilder()
@@ -79,112 +103,161 @@
                             .setSimpleAtomMatcher(
                                     StatsdConfigProto.SimpleAtomMatcher.newBuilder()
                                             .setAtomId(
-                                                    ATOM_APP_START_MEMORY_STATE_CAPTURED_ID)))
+                                                    APP_START_MEMORY_STATE_CAPTURED_FIELD_NUMBER)))
                     .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 StatsdConfigProto.StatsdConfig STATSD_CONFIG_2 =
+            StatsdConfigProto.StatsdConfig.newBuilder()
+                    .setId(SUBSCRIBER_2_HASH)
+                    .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
+                            // The id must be unique within StatsdConfig/matchers
+                            .setId(PROCESS_MEMORY_STATE_MATCHER_ID)
+                            .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
+                                    .setAtomId(PROCESS_MEMORY_STATE_FIELD_NUMBER)))
+                    .addGaugeMetric(StatsdConfigProto.GaugeMetric.newBuilder()
+                            // The id must be unique within StatsdConfig/metrics
+                            .setId(PROCESS_MEMORY_STATE_GAUGE_METRIC_ID)
+                            .setWhat(PROCESS_MEMORY_STATE_MATCHER_ID)
+                            .setDimensionsInWhat(StatsdConfigProto.FieldMatcher.newBuilder()
+                                    .setField(PROCESS_MEMORY_STATE_FIELD_NUMBER)
+                                    .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
+                                            .setField(1))  // ProcessMemoryState.uid
+                                    .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
+                                            .setField(2))  // ProcessMemoryState.process_name
+                            )
+                            .setGaugeFieldsFilter(StatsdConfigProto.FieldFilter.newBuilder()
+                                    .setFields(PROCESS_MEMORY_STATE_FIELDS_MATCHER))
+                            .setSamplingType(
+                                    StatsdConfigProto.GaugeMetric.SamplingType.RANDOM_ONE_SAMPLE)
+                            .setBucket(StatsdConfigProto.TimeUnit.FIVE_MINUTES)
+                    )
+                    .addAllowedLogSource("AID_SYSTEM")
+                    .addPullAtomPackages(StatsdConfigProto.PullAtomPackages.newBuilder()
+                        .setAtomId(PROCESS_MEMORY_STATE_FIELD_NUMBER)
+                        .addPackages("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 static final DataSubscriber DATA_SUBSCRIBER_1 =
+            new DataSubscriber(null, METRICS_CONFIG, SUBSCRIBER_1);
+
     private final FakeHandlerWrapper mFakeHandlerWrapper =
             new FakeHandlerWrapper(Looper.getMainLooper(), FakeHandlerWrapper.Mode.QUEUEING);
-    @Mock private DataSubscriber mMockDataSubscriber;
+
+    private File mRootDirectory;
+    private StatsPublisher mPublisher;  // subject
+    private Throwable mPublisherFailure;
+
     @Mock private StatsManagerProxy mStatsManager;
 
     @Captor private ArgumentCaptor<PersistableBundle> mBundleCaptor;
 
     @Before
     public void setUp() throws Exception {
-        mPublisher = new StatsPublisher(
-                mStatsManager, mFakeSharedPref, mFakeHandlerWrapper.getMockHandler());
-        when(mMockDataSubscriber.getPublisherParam()).thenReturn(STATS_PUBLISHER_PARAMS_1);
-        when(mMockDataSubscriber.getMetricsConfig()).thenReturn(METRICS_CONFIG);
-        when(mMockDataSubscriber.getSubscriber()).thenReturn(SUBSCRIBER_1);
+        mRootDirectory = Files.createTempDirectory("telemetry_test").toFile();
+        mPublisher = createRestartedPublisher();
     }
 
     /**
-     * Emulates a restart by creating a new StatsPublisher. StatsManager and SharedPreference
+     * Emulates a restart by creating a new StatsPublisher. StatsManager and PersistableBundle
      * stays the same.
      */
-    private StatsPublisher createRestartedPublisher() {
+    private StatsPublisher createRestartedPublisher() throws Exception {
         return new StatsPublisher(
-                mStatsManager, mFakeSharedPref, mFakeHandlerWrapper.getMockHandler());
+                this::onPublisherFailure,
+                mStatsManager,
+                mRootDirectory,
+                mFakeHandlerWrapper.getMockHandler());
     }
 
     @Test
     public void testAddDataSubscriber_registersNewListener() throws Exception {
-        mPublisher.addDataSubscriber(mMockDataSubscriber);
+        mPublisher.addDataSubscriber(DATA_SUBSCRIBER_1);
 
         verify(mStatsManager, times(1))
                 .addConfig(SUBSCRIBER_1_HASH, STATSD_CONFIG_1.toByteArray());
-        assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isTrue();
+        assertThat(mPublisher.hasDataSubscriber(DATA_SUBSCRIBER_1)).isTrue();
     }
 
     @Test
     public void testAddDataSubscriber_sameVersion_addsToStatsdOnce() throws Exception {
-        mPublisher.addDataSubscriber(mMockDataSubscriber);
-        mPublisher.addDataSubscriber(mMockDataSubscriber);
+        mPublisher.addDataSubscriber(DATA_SUBSCRIBER_1);
+        mPublisher.addDataSubscriber(DATA_SUBSCRIBER_1);
 
         verify(mStatsManager, times(1))
                 .addConfig(SUBSCRIBER_1_HASH, STATSD_CONFIG_1.toByteArray());
-        assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isTrue();
+        assertThat(mPublisher.hasDataSubscriber(DATA_SUBSCRIBER_1)).isTrue();
     }
 
     @Test
     public void testAddDataSubscriber_whenRestarted_addsToStatsdOnce() throws Exception {
-        mPublisher.addDataSubscriber(mMockDataSubscriber);
+        mPublisher.addDataSubscriber(DATA_SUBSCRIBER_1);
         StatsPublisher publisher2 = createRestartedPublisher();
 
-        publisher2.addDataSubscriber(mMockDataSubscriber);
+        publisher2.addDataSubscriber(DATA_SUBSCRIBER_1);
 
         verify(mStatsManager, times(1))
                 .addConfig(SUBSCRIBER_1_HASH, STATSD_CONFIG_1.toByteArray());
-        assertThat(publisher2.hasDataSubscriber(mMockDataSubscriber)).isTrue();
+        assertThat(publisher2.hasDataSubscriber(DATA_SUBSCRIBER_1)).isTrue();
+    }
+
+    @Test
+    public void testAddDataSubscriber_forProcessMemoryState_generatesStatsdMetrics()
+            throws Exception {
+        DataSubscriber processMemoryStateSubscriber =
+                new DataSubscriber(null, METRICS_CONFIG, SUBSCRIBER_2);
+
+        mPublisher.addDataSubscriber(processMemoryStateSubscriber);
+
+        verify(mStatsManager, times(1))
+                .addConfig(SUBSCRIBER_2_HASH, STATSD_CONFIG_2.toByteArray());
+        assertThat(mPublisher.hasDataSubscriber(processMemoryStateSubscriber)).isTrue();
     }
 
     @Test
     public void testRemoveDataSubscriber_removesFromStatsd() throws Exception {
-        mPublisher.addDataSubscriber(mMockDataSubscriber);
+        mPublisher.addDataSubscriber(DATA_SUBSCRIBER_1);
 
-        mPublisher.removeDataSubscriber(mMockDataSubscriber);
+        mPublisher.removeDataSubscriber(DATA_SUBSCRIBER_1);
 
         verify(mStatsManager, times(1)).removeConfig(SUBSCRIBER_1_HASH);
-        assertThat(mFakeSharedPref.getAll().isEmpty()).isTrue();  // also removes from SharedPref.
-        assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isFalse();
+        assertThat(getSavedStatsConfigs().keySet()).isEmpty();
+        assertThat(mPublisher.hasDataSubscriber(DATA_SUBSCRIBER_1)).isFalse();
     }
 
     @Test
     public void testRemoveDataSubscriber_ifNotFound_nothingHappensButCallsStatsdRemove()
             throws Exception {
-        mPublisher.removeDataSubscriber(mMockDataSubscriber);
+        mPublisher.removeDataSubscriber(DATA_SUBSCRIBER_1);
 
         // 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();
+        assertThat(mPublisher.hasDataSubscriber(DATA_SUBSCRIBER_1)).isFalse();
     }
 
     @Test
     public void testRemoveAllDataSubscriber_whenRestarted_removesFromStatsdAndClears()
             throws Exception {
-        mPublisher.addDataSubscriber(mMockDataSubscriber);
+        mPublisher.addDataSubscriber(DATA_SUBSCRIBER_1);
         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();
+        assertThat(getSavedStatsConfigs().keySet()).isEmpty();
+        assertThat(publisher2.hasDataSubscriber(DATA_SUBSCRIBER_1)).isFalse();
     }
 
     @Test
     public void testAddDataSubscriber_queuesPeriodicTaskInTheHandler() {
-        mPublisher.addDataSubscriber(mMockDataSubscriber);
+        mPublisher.addDataSubscriber(DATA_SUBSCRIBER_1);
 
         assertThat(mFakeHandlerWrapper.getQueuedMessages()).hasSize(1);
         Message msg = mFakeHandlerWrapper.getQueuedMessages().get(0);
@@ -193,17 +266,27 @@
     }
 
     @Test
-    public void testRemoveDataSubscriber_removesPeriodicStatsdReportPull() {
-        mPublisher.addDataSubscriber(mMockDataSubscriber);
+    public void testAddDataSubscriber_whenFails_notifiesFailureConsumer() throws Exception {
+        doThrow(new StatsManager.StatsUnavailableException("fail"))
+                .when(mStatsManager).addConfig(anyLong(), any());
 
-        mPublisher.removeDataSubscriber(mMockDataSubscriber);
+        mPublisher.addDataSubscriber(DATA_SUBSCRIBER_1);
+
+        assertThat(mPublisherFailure).hasMessageThat().contains("Failed to add config");
+    }
+
+    @Test
+    public void testRemoveDataSubscriber_removesPeriodicStatsdReportPull() {
+        mPublisher.addDataSubscriber(DATA_SUBSCRIBER_1);
+
+        mPublisher.removeDataSubscriber(DATA_SUBSCRIBER_1);
 
         assertThat(mFakeHandlerWrapper.getQueuedMessages()).isEmpty();
     }
 
     @Test
     public void testRemoveAllDataSubscriber_removesPeriodicStatsdReportPull() {
-        mPublisher.addDataSubscriber(mMockDataSubscriber);
+        mPublisher.addDataSubscriber(DATA_SUBSCRIBER_1);
 
         mPublisher.removeAllDataSubscribers();
 
@@ -212,7 +295,7 @@
 
     @Test
     public void testAfterDispatchItSchedulesANewPullReportTask() throws Exception {
-        mPublisher.addDataSubscriber(mMockDataSubscriber);
+        mPublisher.addDataSubscriber(DATA_SUBSCRIBER_1);
         Message firstMessage = mFakeHandlerWrapper.getQueuedMessages().get(0);
         when(mStatsManager.getReports(anyLong())).thenReturn(EMPTY_STATS_REPORT.toByteArray());
 
@@ -227,7 +310,11 @@
 
     @Test
     public void testPullsStatsdReport() throws Exception {
-        mPublisher.addDataSubscriber(mMockDataSubscriber);
+        DataSubscriber subscriber = Mockito.mock(DataSubscriber.class);
+        when(subscriber.getSubscriber()).thenReturn(SUBSCRIBER_1);
+        when(subscriber.getMetricsConfig()).thenReturn(METRICS_CONFIG);
+        when(subscriber.getPublisherParam()).thenReturn(SUBSCRIBER_1.getPublisher());
+        mPublisher.addDataSubscriber(subscriber);
         when(mStatsManager.getReports(anyLong())).thenReturn(
                 StatsLogProto.ConfigMetricsReportList.newBuilder()
                         // add 2 empty reports
@@ -237,12 +324,20 @@
 
         mFakeHandlerWrapper.dispatchQueuedMessages();
 
-        verify(mMockDataSubscriber).push(mBundleCaptor.capture());
+        verify(subscriber).push(mBundleCaptor.capture());
         assertThat(mBundleCaptor.getValue().getInt("reportsCount")).isEqualTo(2);
     }
 
-    // TODO(b/189142577): add test cases when connecting to Statsd fails
-    // TODO(b/189142577): add test cases for handling config version upgrades
+    private PersistableBundle getSavedStatsConfigs() throws Exception {
+        File savedConfigsFile = new File(mRootDirectory, StatsPublisher.SAVED_STATS_CONFIGS_FILE);
+        try (FileInputStream fileInputStream = new FileInputStream(savedConfigsFile)) {
+            return PersistableBundle.readFromStream(fileInputStream);
+        }
+    }
+
+    private void onPublisherFailure(AbstractPublisher publisher, Throwable error) {
+        mPublisherFailure = error;
+    }
 
     private static void assertThatMessageIsScheduledWithGivenDelay(Message msg, long delayMillis) {
         long expectedTimeMillis = SystemClock.uptimeMillis() + delayMillis;
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 9951959..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
@@ -35,11 +35,13 @@
 import android.car.hardware.CarPropertyValue;
 import android.car.hardware.property.CarPropertyEvent;
 import android.car.hardware.property.ICarPropertyEventListener;
+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;
@@ -83,9 +85,11 @@
             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;
 
@@ -101,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
@@ -196,4 +203,6 @@
         // 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/SystemMonitorTest.java
similarity index 99%
rename from tests/carservice_unit_test/src/com/android/car/telemetry/systemmonitor/SystemMonitorUnitTest.java
rename to tests/carservice_unit_test/src/com/android/car/telemetry/systemmonitor/SystemMonitorTest.java
index 09591be..bb61bf8 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/systemmonitor/SystemMonitorUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/systemmonitor/SystemMonitorTest.java
@@ -46,7 +46,7 @@
 import java.io.IOException;
 
 @RunWith(MockitoJUnitRunner.class)
-public class SystemMonitorUnitTest {
+public class SystemMonitorTest {
 
     @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
 
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 f97965e..c1e3938 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
@@ -1211,10 +1211,6 @@
     @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),
@@ -1223,6 +1219,10 @@
                 constructPackageManagerPackageInfo("third_party_package", 1203456, null),
                 constructPackageManagerPackageInfo("vendor_package.critical.B", 1201278, null)));
 
+        List<android.automotive.watchdog.internal.ResourceOveruseConfiguration> configs =
+                sampleInternalResourceOveruseConfigurations();
+        injectResourceOveruseConfigsAndWait(configs);
+
         PackageKillableStateSubject.assertThat(
                 mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL))
                 .containsExactly(
@@ -1241,6 +1241,37 @@
     }
 
     @Test
+    public void testGetPackageKillableStatesAsUserWithVendorPackagePrefixes() throws Exception {
+        mockUmGetAliveUsers(mMockUserManager, 11);
+        /* Package names which start with "system" are constructed as system packages. */
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo("system_package_as_vendor", 1102459, null)));
+
+        android.automotive.watchdog.internal.ResourceOveruseConfiguration vendorConfig =
+                new android.automotive.watchdog.internal.ResourceOveruseConfiguration();
+        vendorConfig.componentType = ComponentType.VENDOR;
+        vendorConfig.safeToKillPackages = Collections.singletonList("system_package_as_vendor");
+        vendorConfig.vendorPackagePrefixes = Collections.singletonList(
+                "system_package_as_vendor");
+        injectResourceOveruseConfigsAndWait(Collections.singletonList(vendorConfig));
+
+        List<PackageKillableState> killableStates =
+                mCarWatchdogService.getPackageKillableStatesAsUser(new UserHandle(11));
+
+        /* When CarWatchdogService connects with the watchdog daemon, CarWatchdogService fetches
+         * resource overuse configs from watchdog daemon. The vendor package prefixes in the
+         * configs help identify vendor packages. The safe-to-kill list in the configs helps
+         * identify safe-to-kill vendor packages. |system_package_as_vendor| is a critical system
+         * package by default but with the latest resource overuse configs, this package should be
+         * classified as a safe-to-kill vendor package.
+         */
+        PackageKillableStateSubject.assertThat(killableStates)
+                .containsExactly(
+                        new PackageKillableState("system_package_as_vendor", 11,
+                                PackageKillableState.KILLABLE_STATE_YES));
+    }
+
+    @Test
     public void testGetPackageKillableStatesAsUserWithSharedUids() throws Exception {
         mockUmGetAliveUsers(mMockUserManager, 11, 12);
         injectPackageInfos(Arrays.asList(
@@ -1274,13 +1305,6 @@
     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"),
@@ -1293,6 +1317,14 @@
                 constructPackageManagerPackageInfo(
                         "third_party_package.D", 1105678, "third_party_shared_package")));
 
+        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");
+        vendorConfig.vendorPackagePrefixes = new ArrayList<>();
+        injectResourceOveruseConfigsAndWait(Collections.singletonList(vendorConfig));
+
         PackageKillableStateSubject.assertThat(
                 mCarWatchdogService.getPackageKillableStatesAsUser(new UserHandle(11)))
                 .containsExactly(
@@ -1312,13 +1344,6 @@
     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"),
@@ -1327,6 +1352,15 @@
                 constructPackageManagerPackageInfo(
                         "vendor_package.non_critical.B", 1103456, "vendor_shared_package.B")));
 
+        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");
+        vendorConfig.vendorPackagePrefixes = new ArrayList<>();
+        injectResourceOveruseConfigsAndWait(Collections.singletonList(vendorConfig));
+
+
         PackageKillableStateSubject.assertThat(
                 mCarWatchdogService.getPackageKillableStatesAsUser(new UserHandle(11)))
                 .containsExactly(
@@ -1991,25 +2025,97 @@
     }
 
     @Test
-    public void testResetResourceOveruseStats() throws Exception {
-        mGenericPackageNameByUid.put(Binder.getCallingUid(), mMockContext.getPackageName());
-        mGenericPackageNameByUid.put(1101278, "vendor_package.critical");
+    public void testResetResourceOveruseStatsResetsStats() throws Exception {
+        UserHandle user = UserHandle.getUserHandleForUid(10003346);
+        String packageName = mMockContext.getPackageName();
+        mGenericPackageNameByUid.put(10003346, packageName);
+        mGenericPackageNameByUid.put(10101278, "vendor_package.critical");
         injectIoOveruseStatsForPackages(
                 mGenericPackageNameByUid, /* killablePackages= */ new ArraySet<>(),
                 /* shouldNotifyPackages= */ new ArraySet<>());
 
         mWatchdogServiceForSystemImpl.resetResourceOveruseStats(
-                Collections.singletonList(mMockContext.getPackageName()));
+                Collections.singletonList(packageName));
 
-        ResourceOveruseStats actualStats = mCarWatchdogService.getResourceOveruseStats(
-                CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
-                CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
+        ResourceOveruseStats actualStats =
+                mCarWatchdogService.getResourceOveruseStatsForUserPackage(
+                        packageName, user,
+                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+                        CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
 
         ResourceOveruseStats expectedStats = new ResourceOveruseStats.Builder(
-                mMockContext.getPackageName(),
-                UserHandle.getUserHandleForUid(Binder.getCallingUid())).build();
+                packageName, user).build();
 
         ResourceOveruseStatsSubject.assertEquals(actualStats, expectedStats);
+
+        verify(mMockWatchdogStorage).deleteUserPackage(eq(user.getIdentifier()), eq(packageName));
+    }
+
+    @Test
+    public void testResetResourceOveruseStatsResetsUserPackageSettings() throws Exception {
+        mockUmGetAliveUsers(mMockUserManager, 100, 101);
+        injectPackageInfos(Arrays.asList(
+                constructPackageManagerPackageInfo("third_party_package.A", 10001278, null),
+                constructPackageManagerPackageInfo("third_party_package.A", 10101278, null),
+                constructPackageManagerPackageInfo("third_party_package.B", 10003346, null),
+                constructPackageManagerPackageInfo("third_party_package.B", 10103346, null)));
+        injectIoOveruseStatsForPackages(mGenericPackageNameByUid,
+                /* killablePackages= */ Set.of("third_party_package.A", "third_party_package.B"),
+                /* shouldNotifyPackages= */ new ArraySet<>());
+
+        mCarWatchdogService.setKillablePackageAsUser("third_party_package.A",
+                UserHandle.ALL, /* isKillable= */false);
+        mCarWatchdogService.setKillablePackageAsUser("third_party_package.B",
+                UserHandle.ALL, /* isKillable= */false);
+
+        mWatchdogServiceForSystemImpl.resetResourceOveruseStats(
+                Collections.singletonList("third_party_package.A"));
+
+        PackageKillableStateSubject.assertThat(
+                mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
+                new PackageKillableState("third_party_package.A", 100,
+                        PackageKillableState.KILLABLE_STATE_YES),
+                new PackageKillableState("third_party_package.A", 101,
+                        PackageKillableState.KILLABLE_STATE_YES),
+                new PackageKillableState("third_party_package.B", 100,
+                        PackageKillableState.KILLABLE_STATE_NO),
+                new PackageKillableState("third_party_package.B", 101,
+                        PackageKillableState.KILLABLE_STATE_NO)
+        );
+
+        verify(mMockWatchdogStorage, times(2)).deleteUserPackage(anyInt(),
+                eq("third_party_package.A"));
+    }
+
+    @Test
+    public void testSaveToStorageAfterResetResourceOveruseStats() throws Exception {
+        setDate(1);
+        mGenericPackageNameByUid.put(1011200, "system_package");
+        SparseArray<PackageIoOveruseStats> stats = injectIoOveruseStatsForPackages(
+                mGenericPackageNameByUid, /* killablePackages= */ new ArraySet<>(),
+                /* shouldNotifyPackages= */ new ArraySet<>());
+
+        mWatchdogServiceForSystemImpl.resetResourceOveruseStats(
+                Collections.singletonList("system_package"));
+
+        /* |resetResourceOveruseStats| sets the package's IoOveruseStats to null, packages with
+         * null I/O stats are not written to disk. Push new IoOveruseStats to the |system_package|
+         * so that the package can be written to the database when date changes.
+         */
+        pushLatestIoOveruseStatsAndWait(Collections.singletonList(stats.get(1011200)));
+
+        /* Force write to disk by changing the date and pushing new I/O overuse stats. */
+        setDate(0);
+        pushLatestIoOveruseStatsAndWait(Collections.singletonList(new PackageIoOveruseStats()));
+
+        WatchdogStorage.IoUsageStatsEntry expectedSavedEntries =
+                new WatchdogStorage.IoUsageStatsEntry(/* userId= */ 10, "system_package",
+                        new WatchdogPerfHandler.PackageIoUsage(stats.get(1011200).ioOveruseStats,
+                                /* forgivenWriteBytes= */ constructPerStateBytes(0, 0, 0),
+                                /* totalTimesKilled= */ 0));
+
+        IoUsageStatsEntrySubject.assertThat(mIoUsageStatsEntries)
+                .containsExactlyElementsIn(Collections.singletonList(expectedSavedEntries));
     }
 
     @Test
@@ -2432,6 +2538,11 @@
          */
         crashWatchdogDaemon();
         restartWatchdogDaemonAndAwait();
+
+        /* Method should be invoked 2 times. Once at test setup and once more after the daemon
+         * crashes and reconnects.
+         */
+        verify(mMockCarWatchdogDaemon, times(2)).getResourceOveruseConfigurations();
     }
 
     private SparseArray<PackageIoOveruseStats> injectIoOveruseStatsForPackages(
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
index 014cc23..3f16750 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogStorageUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogStorageUnitTest.java
@@ -272,6 +272,80 @@
     }
 
     @Test
+    public void testDeleteUserPackage() throws Exception {
+        ArrayList<WatchdogStorage.UserPackageSettingsEntry> settingsEntries = sampleSettings();
+        List<WatchdogStorage.IoUsageStatsEntry> ioUsageStatsEntries = sampleStatsForToday();
+
+        assertThat(mService.saveUserPackageSettings(settingsEntries)).isTrue();
+        assertThat(mService.saveIoUsageStats(ioUsageStatsEntries)).isTrue();
+
+        int deleteUserId = 100;
+        String deletePackageName = "system_package.non_critical.A";
+
+        mService.deleteUserPackage(deleteUserId, deletePackageName);
+
+        settingsEntries.removeIf(
+                (s) -> s.userId == deleteUserId && s.packageName.equals(deletePackageName));
+
+        UserPackageSettingsEntrySubject.assertThat(mService.getUserPackageSettings())
+                .containsExactlyElementsIn(settingsEntries);
+
+        ioUsageStatsEntries.removeIf(
+                (e) -> e.userId == deleteUserId && e.packageName.equals(deletePackageName));
+
+        IoUsageStatsEntrySubject.assertThat(mService.getTodayIoUsageStats())
+                .containsExactlyElementsIn(ioUsageStatsEntries);
+    }
+
+    @Test
+    public void testDeleteUserPackageWithNonexistentPackage() throws Exception {
+        injectSampleUserPackageSettings();
+        List<WatchdogStorage.IoUsageStatsEntry> ioUsageStatsEntries = sampleStatsForToday();
+
+        assertThat(mService.saveIoUsageStats(ioUsageStatsEntries)).isTrue();
+
+        int deleteUserId = 100;
+        String deletePackageName = "system_package.non_existent.A";
+
+        mService.deleteUserPackage(deleteUserId, deletePackageName);
+
+        UserPackageSettingsEntrySubject.assertThat(mService.getUserPackageSettings())
+                .containsExactlyElementsIn(sampleSettings());
+
+        ioUsageStatsEntries.removeIf(
+                (e) -> e.userId == deleteUserId && e.packageName.equals(deletePackageName));
+
+        IoUsageStatsEntrySubject.assertThat(mService.getTodayIoUsageStats())
+                .containsExactlyElementsIn(ioUsageStatsEntries);
+    }
+
+    @Test
+    public void testDeleteUserPackageWithHistoricalIoOveruseStats()
+            throws Exception {
+        ArrayList<WatchdogStorage.UserPackageSettingsEntry> settingsEntries = sampleSettings();
+
+        assertThat(mService.saveUserPackageSettings(settingsEntries)).isTrue();
+        assertThat(mService.saveIoUsageStats(sampleStatsBetweenDates(
+                /* includingStartDaysAgo= */ 1, /* excludingEndDaysAgo= */ 6))).isTrue();
+
+        int deleteUserId = 100;
+        String deletePackageName = "system_package.non_critical.A";
+
+        mService.deleteUserPackage(deleteUserId, deletePackageName);
+
+        settingsEntries.removeIf(
+                (s) -> s.userId == deleteUserId && s.packageName.equals(deletePackageName));
+
+        UserPackageSettingsEntrySubject.assertThat(mService.getUserPackageSettings())
+                .containsExactlyElementsIn(settingsEntries);
+
+        IoOveruseStats actual = mService.getHistoricalIoOveruseStats(
+                /* userId= */ 100, "system_package.non_critical.A", /* numDaysAgo= */ 7);
+
+        assertWithMessage("Fetched historical I/O overuse stats").that(actual).isNull();
+    }
+
+    @Test
     public void testTruncateStatsOutsideRetentionPeriodOnDateChange() throws Exception {
         injectSampleUserPackageSettings();
         setDate(/* numDaysAgo= */ 1);
@@ -341,8 +415,8 @@
         assertThat(mService.saveUserPackageSettings(expected)).isTrue();
     }
 
-    private static List<WatchdogStorage.UserPackageSettingsEntry> sampleSettings() {
-        return Arrays.asList(
+    private static ArrayList<WatchdogStorage.UserPackageSettingsEntry> sampleSettings() {
+        return new ArrayList<>(Arrays.asList(
                 new WatchdogStorage.UserPackageSettingsEntry(
                         /* userId= */ 100, "system_package.non_critical.A", KILLABLE_STATE_YES),
                 new WatchdogStorage.UserPackageSettingsEntry(
@@ -354,14 +428,14 @@
                 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));
+                        /* userId= */ 101, "vendor_package.critical.C", KILLABLE_STATE_NEVER)));
     }
 
-    private List<WatchdogStorage.IoUsageStatsEntry> sampleStatsBetweenDates(
+    private ArrayList<WatchdogStorage.IoUsageStatsEntry> sampleStatsBetweenDates(
             int includingStartDaysAgo, int excludingEndDaysAgo) {
         ZonedDateTime currentDate = mTimeSource.now().atZone(ZONE_OFFSET)
                 .truncatedTo(STATS_TEMPORAL_UNIT);
-        List<WatchdogStorage.IoUsageStatsEntry> entries = new ArrayList<>();
+        ArrayList<WatchdogStorage.IoUsageStatsEntry> entries = new ArrayList<>();
         for (int i = includingStartDaysAgo; i < excludingEndDaysAgo; ++i) {
             entries.addAll(sampleStatsForDate(
                     currentDate.minus(i, STATS_TEMPORAL_UNIT).toEpochSecond(),
@@ -370,9 +444,16 @@
         return entries;
     }
 
-    private static List<WatchdogStorage.IoUsageStatsEntry> sampleStatsForDate(
+    private ArrayList<WatchdogStorage.IoUsageStatsEntry> sampleStatsForToday() {
+        long currentTime = mTimeSource.now().atZone(ZONE_OFFSET)
+                .truncatedTo(STATS_TEMPORAL_UNIT).toEpochSecond();
+        long duration = mTimeSource.now().atZone(ZONE_OFFSET).toEpochSecond() - currentTime;
+        return sampleStatsForDate(currentTime, duration);
+    }
+
+    private static ArrayList<WatchdogStorage.IoUsageStatsEntry> sampleStatsForDate(
             long statsDateEpoch, long duration) {
-        List<WatchdogStorage.IoUsageStatsEntry> entries = new ArrayList<>();
+        ArrayList<WatchdogStorage.IoUsageStatsEntry> entries = new ArrayList<>();
         for (int i = 100; i < 101; ++i) {
             entries.add(constructIoUsageStatsEntry(
                     /* userId= */ i, "system_package.non_critical.A", statsDateEpoch, duration,