Merge "Use epoll to monitor sysfs in carpowerpolicyd" 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/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 73%
rename from car-lib/src/android/car/telemetry/ManifestKey.java
rename to car-lib/src/android/car/telemetry/MetricsConfigKey.java
index b0a69c2..6e4614b 100644
--- a/car-lib/src/android/car/telemetry/ManifestKey.java
+++ b/car-lib/src/android/car/telemetry/MetricsConfigKey.java
@@ -25,7 +25,7 @@
  *
  * @hide
  */
-public final class ManifestKey implements Parcelable {
+public final class MetricsConfigKey implements Parcelable {
 
     @NonNull
     private String mName;
@@ -46,12 +46,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 +61,16 @@
         return 0;
     }
 
-    public static final @NonNull Parcelable.Creator<ManifestKey> CREATOR =
-            new Parcelable.Creator<ManifestKey>() {
+    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/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/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/fan_speed_seek_bar_thumb_off.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_off.xml
deleted file mode 100644
index b6c1cb9..0000000
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_thumb_off.xml
+++ /dev/null
@@ -1,30 +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.
-  -->
-
-<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>
-    </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/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_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/layout/car_bottom_system_bar.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_bottom_system_bar.xml
index 974cec2..557547a 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
@@ -93,4 +93,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..8792680 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
@@ -38,6 +38,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..fa95f56 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
@@ -29,5 +29,5 @@
         <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>
 </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..8ed622b 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
@@ -49,20 +49,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/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/integers.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/integers.xml
new file mode 100644
index 0000000..282373c
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/integers.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <integer name="hvac_seat_heat_level_count">3</integer>
+</resources>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/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/rro/CarEvsCameraPreviewAppRRO/Android.bp b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/Android.bp
new file mode 100644
index 0000000..2a4357c
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+android_app {
+    name: "CarEvsCameraPreviewAppRRO",
+    resource_dirs: ["res"],
+    platform_apis: true,
+    certificate: "platform",
+    aaptflags: [
+        "--no-resource-deduping",
+        "--no-resource-removal"
+    ],
+    static_libs: [
+        "androidx-constraintlayout_constraintlayout",
+        "androidx-constraintlayout_constraintlayout-solver",
+    ],
+}
diff --git a/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/AndroidManifest.xml b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/AndroidManifest.xml
new file mode 100644
index 0000000..c878ee1
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.google.android.car.evs.caruiportrait.rro">
+    <application android:hasCode="false"/>
+    <overlay android:priority="20"
+             android:targetName="CarEvsCameraPreviewApp"
+             android:targetPackage="com.google.android.car.evs"
+             android:resourcesMap="@xml/overlays"
+             android:isStatic="true"/>
+</manifest>
diff --git a/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/drawable/close_bg.xml b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/drawable/close_bg.xml
new file mode 100644
index 0000000..9a4596b
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/drawable/close_bg.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="?android:attr/colorBackground"/>
+    <corners android:radius="@dimen/close_button_radius"/>
+</shape>
diff --git a/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/drawable/ic_close.xml b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/drawable/ic_close.xml
new file mode 100644
index 0000000..5874541
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/drawable/ic_close.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/close_icon_dimen"
+        android:height="@dimen/close_icon_dimen"
+        android:viewportWidth="26"
+        android:viewportHeight="26">
+    <path
+        android:pathData="M25.8327 2.75199L23.2477 0.166992L12.9994 10.4153L2.75102 0.166992L0.166016 2.75199L10.4144 13.0003L0.166016 23.2487L2.75102 25.8337L12.9994 15.5853L23.2477 25.8337L25.8327 23.2487L15.5844 13.0003L25.8327 2.75199Z"
+        android:fillColor="?android:attr/textColorPrimary"/>
+</vector>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/layout/evs_preview_activity.xml b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/layout/evs_preview_activity.xml
new file mode 100644
index 0000000..bfd6370
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/layout/evs_preview_activity.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+          http://www.apache.org/licenses/LICENSE-2.0
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@android:color/transparent">
+    <LinearLayout
+        android:id="@+id/evs_preview_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@android:color/transparent"
+        android:orientation="vertical"/>
+
+    <ImageButton
+        android:id="@+id/close_button"
+        android:layout_width="@dimen/close_button_dimen"
+        android:layout_height="@dimen/close_button_dimen"
+        android:layout_marginLeft="@dimen/close_button_margin"
+        android:layout_marginTop="@dimen/close_button_margin"
+        android:background="@drawable/close_bg"
+        android:scaleType="center"
+        android:alpha="0.5"
+        android:src="@drawable/ic_close"/>
+</FrameLayout>
diff --git a/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/config.xml b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/config.xml
new file mode 100644
index 0000000..935c096
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/config.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<resources>
+    <!-- Shade of the background behind the camera window. 1.0 for fully opaque, 0.0 for fully
+         transparent. -->
+    <item name="config_cameraBackgroundScrim" format="float" type="dimen">0.7</item>
+</resources>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/dimens.xml b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/dimens.xml
new file mode 100644
index 0000000..ea3ce97
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/dimens.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<resources>
+    <!-- dimensions for evs camera preview in the system window -->
+    <dimen name="camera_preview_width">1224dp</dimen>
+    <dimen name="camera_preview_height">720dp</dimen>
+
+    <dimen name="close_icon_dimen">28dp</dimen>
+    <dimen name="close_button_dimen">80dp</dimen>
+    <dimen name="close_button_margin">24dp</dimen>
+    <dimen name="close_button_radius">20dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/strings.xml b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/strings.xml
new file mode 100644
index 0000000..77cc4d1
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <string name="app_name">Rearview Camera</string>
+</resources>
diff --git a/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/xml/overlays.xml
new file mode 100644
index 0000000..681ef74
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarEvsCameraPreviewAppRRO/res/xml/overlays.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<overlay>
+    <item target="layout/evs_preview_activity" value="@layout/evs_preview_activity"/>
+
+    <item target="string/app_name" value="@string/app_name"/>
+
+    <item target="dimen/camera_preview_width" value="@dimen/camera_preview_width"/>
+    <item target="dimen/camera_preview_height" value="@dimen/camera_preview_height"/>
+
+    <item target="id/evs_preview_container" value="@id/evs_preview_container"/>
+    <item target="id/close_button" value="@id/close_button"/>
+
+    <item target="dimen/config_cameraBackgroundScrim" value="@dimen/config_cameraBackgroundScrim"/>
+</overlay>
diff --git a/car_product/car_ui_portrait/rro/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/xml/overlays.xml b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/xml/overlays.xml
index 8b153fa..9cc1245 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
@@ -54,5 +54,5 @@
     <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/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/android/res/values/colors.xml b/car_product/car_ui_portrait/rro/android/res/values/colors.xml
index 8fc21cc..16f1e2b 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
@@ -65,4 +65,5 @@
   <color name="error_color_device_default_dark">#ec928e</color> <!-- Material Red 300 -->
   <color name="error_color_device_default_light">#b3261e</color> <!-- Material Red 600 -->
 
+  <color name="list_divider_color">#2E3134</color>
 </resources>
diff --git a/car_product/car_ui_portrait/rro/android/res/values/themes_device_defaults.xml b/car_product/car_ui_portrait/rro/android/res/values/themes_device_defaults.xml
index e1671e4..dc0198b 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
@@ -45,6 +45,8 @@
         <item name="android:colorButtonNormal">@color/btn_device_default_dark</item>
         <item name="android:colorControlHighlight">@color/btn_device_default_dark</item>
         <item name="android:colorControlNormal">@color/btn_device_default_dark</item>
+
+        <item name="android:listDivider">@color/list_divider_color</item>
     </style>
 
     <style name="Theme.DeviceDefault.Dialog" parent="android:Theme.Material.Dialog">
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..39d910c 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
@@ -162,7 +162,7 @@
             <View
                 android:layout_width="match_parent"
                 android:layout_height="2dp"
-                android:background="#F1F3F4"
+                android:background="?android:attr/listDivider"
                 app:layout_constraintBottom_toBottomOf="parent"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent" />
@@ -175,6 +175,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">
@@ -189,10 +190,11 @@
     <!-- Hairline to the right of the tabs -->
     <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:layout_height="match_parent"
+        android:background="?android:attr/listDivider"
+        android:focusable="false"
+        app:layout_constraintStart_toEndOf="@id/left_part_of_toolbar_focus_area"
+        app:layout_constraintTop_toBottomOf="@id/top_part_of_toolbar_focus_area"
+        app:layout_constraintBottom_toBottomOf="parent"/>
 
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/car_product/car_ui_portrait/rro/car_ui_portrait_rro.mk b/car_product/car_ui_portrait/rro/car_ui_portrait_rro.mk
index 5029fcd..5a4082b 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,13 @@
 
 # All RROs to be included in car_ui_portrait builds.
 PRODUCT_PACKAGES += \
+    CarEvsCameraPreviewAppRRO \
     CarUiPortraitDialerRRO \
     CarUiPortraitMediaRRO \
     CarUiPortraitLauncherRRO \
     CarUiPortraitNotificationRRO \
     CarUiPortraitFrameworkResRRO \
-    CarUiPortraitFrameworkResRROTest \
+    CarUiPortraitFrameworkResRROTest
 
 ifneq ($(INCLUDE_SEAHAWK_ONLY_RROS),)
 PRODUCT_PACKAGES += \
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..6db644f 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-da/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-da/strings.xml
@@ -18,6 +18,6 @@
 <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="permlab_accessCoarseLocation" msgid="2494909511737161237">"få kun adgang til omtrentlig lokation i forgrunden"</string>
     <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2093486820466005919">"Aktivér mikrofon"</string>
 </resources>
diff --git a/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/packages/ScriptExecutor/src/LuaEngine.cpp b/packages/ScriptExecutor/src/LuaEngine.cpp
index 39aa38e..cf418bf 100644
--- a/packages/ScriptExecutor/src/LuaEngine.cpp
+++ b/packages/ScriptExecutor/src/LuaEngine.cpp
@@ -42,6 +42,40 @@
     ZERO_RETURNED_RESULTS = 0,
 };
 
+// Helper method that goes over Lua table fields one by one and populates PersistableBundle
+// object wrapped in BundleWrapper.
+// It is assumed that Lua table is located on top of the Lua stack.
+void convertLuaTableToBundle(lua_State* lua, BundleWrapper* bundleWrapper) {
+    // Iterate over Lua table which is expected to be at the top of Lua stack.
+    // lua_next call pops the key from the top of the stack and finds the next
+    // key-value pair. It returns 0 if the next pair was not found.
+    // More on lua_next in: https://www.lua.org/manual/5.3/manual.html#lua_next
+    lua_pushnil(lua);  // First key is a null value.
+    while (lua_next(lua, /* index = */ -2) != 0) {
+        //  'key' is at index -2 and 'value' is at index -1
+        // -1 index is the top of the stack.
+        // remove 'value' and keep 'key' for next iteration
+        // Process each key-value depending on a type and push it to Java PersistableBundle.
+        const char* key = lua_tostring(lua, /* index = */ -2);
+        if (lua_isboolean(lua, /* index = */ -1)) {
+            bundleWrapper->putBoolean(key, static_cast<bool>(lua_toboolean(lua, /* index = */ -1)));
+        } else if (lua_isinteger(lua, /* index = */ -1)) {
+            bundleWrapper->putInteger(key, static_cast<int>(lua_tointeger(lua, /* index = */ -1)));
+        } else if (lua_isnumber(lua, /* index = */ -1)) {
+            bundleWrapper->putDouble(key, static_cast<double>(lua_tonumber(lua, /* index = */ -1)));
+        } else if (lua_isstring(lua, /* index = */ -1)) {
+            bundleWrapper->putString(key, lua_tostring(lua, /* index = */ -1));
+        } else {
+            // not supported yet...
+            LOG(WARNING) << "key=" << key << " has a Lua type which is not supported yet. "
+                         << "The bundle object will not have this key-value pair.";
+        }
+        // Pop value from the stack, keep the key for the next iteration.
+        lua_pop(lua, 1);
+        // The key is at index -1, the table is at index -2 now.
+    }
+}
+
 }  // namespace
 
 ScriptExecutorListener* LuaEngine::sListener = nullptr;
@@ -87,6 +121,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;
 }
@@ -118,40 +153,15 @@
 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.
-    }
+    convertLuaTableToBundle(lua, &bundleWrapper);
 
     // Forward the populated Bundle object to Java callback.
     sListener->onSuccess(bundleWrapper.getBundle());
@@ -160,6 +170,27 @@
     return ZERO_RETURNED_RESULTS;
 }
 
+int LuaEngine::onScriptFinished(lua_State* lua) {
+    // Any script we run can call on_success only with a single argument of Lua table type.
+    if (lua_gettop(lua) != 1 || !lua_istable(lua, /* index =*/-1)) {
+        sListener->onError(IScriptExecutorConstants::ERROR_TYPE_LUA_SCRIPT_ERROR,
+                           "on_script_finished can push only a single parameter from Lua - a Lua "
+                           "table",
+                           "");
+        return ZERO_RETURNED_RESULTS;
+    }
+
+    // Helper object to create and populate Java PersistableBundle object.
+    BundleWrapper bundleWrapper(sListener->getCurrentJNIEnv());
+    convertLuaTableToBundle(lua, &bundleWrapper);
+
+    // Forward the populated Bundle object to Java callback.
+    sListener->onScriptFinished(bundleWrapper.getBundle());
+    // We explicitly must tell Lua how many results we return, which is 0 in this case.
+    // More on the topic: https://www.lua.org/manual/5.3/manual.html#lua_CFunction
+    return ZERO_RETURNED_RESULTS;
+}
+
 int LuaEngine::onError(lua_State* lua) {
     // Any script we run can call on_error only with a single argument of Lua string type.
     if (lua_gettop(lua) != 1 || !lua_isstring(lua, /* index = */ -1)) {
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/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/tests/unit/src/com/android/car/scriptexecutor/ScriptExecutorTest.java b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutor/ScriptExecutorTest.java
index 144e7b9..37760f0 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
@@ -53,20 +53,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,7 +76,7 @@
             mErrorType = errorType;
             mMessage = message;
             mStackTrace = stackTrace;
-            mErrorLatch.countDown();
+            mResponseLatch.countDown();
         }
     }
 
@@ -96,8 +98,7 @@
     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 +115,16 @@
                 }
             };
 
-    // Helper method to invoke the script and wait for it to complete and return the result.
-    private void runScriptAndWaitForResult(String script, String function,
+    // Helper method to invoke the script and wait for it to complete and return a response.
+    private void runScriptAndWaitForResponse(String script, String function,
             PersistableBundle previousState)
             throws RemoteException {
         mScriptExecutor.invokeScript(script, function, mPublishedData, previousState,
                 mFakeScriptExecutorListener);
         try {
-            if (!mFakeScriptExecutorListener.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();
@@ -131,18 +132,9 @@
         }
     }
 
+
     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());
     }
 
     @Before
@@ -183,7 +175,7 @@
                         + "end\n";
 
 
-        runScriptAndWaitForResult(returnResultScript, "hello", mSavedState);
+        runScriptAndWaitForResponse(returnResultScript, "hello", mSavedState);
 
         // Expect to get back a bundle with a single string key: string value pair:
         // {"hello": "world"}
@@ -200,7 +192,7 @@
                         + "end\n";
 
 
-        runScriptAndWaitForResult(script, "knows", mSavedState);
+        runScriptAndWaitForResponse(script, "knows", mSavedState);
 
         // Expect to get back a bundle with 4 keys, each corresponding to a distinct supported type.
         assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(4);
@@ -220,7 +212,7 @@
                         + "end\n";
 
 
-        runScriptAndWaitForResult(script, "nested", mSavedState);
+        runScriptAndWaitForResponse(script, "nested", mSavedState);
 
         // Bundle does not contain any value under "nested_table" key, because nested tables are
         // not supported yet.
@@ -237,7 +229,7 @@
                         + "end\n";
 
 
-        runScriptAndWaitForResult(script, "empty", mSavedState);
+        runScriptAndWaitForResponse(script, "empty", mSavedState);
 
         // If a script returns empty table as the result, we get an empty bundle.
         assertThat(mFakeScriptExecutorListener.mSavedBundle).isNotNull();
@@ -258,7 +250,7 @@
         previousState.putInt("x", 1);
 
 
-        runScriptAndWaitForResult(script, "update", previousState);
+        runScriptAndWaitForResponse(script, "update", previousState);
 
         // Verify that y = 2, because y = x + 1 and x = 1.
         assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(1);
@@ -287,7 +279,7 @@
         previousState.putString("string", "ABRA");
 
 
-        runScriptAndWaitForResult(script, "update_all", previousState);
+        runScriptAndWaitForResponse(script, "update_all", previousState);
 
         // Verify that keys are preserved but the values are modified as expected.
         assertThat(mFakeScriptExecutorListener.mSavedBundle.size()).isEqualTo(4);
@@ -350,5 +342,151 @@
         assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
                 "on_error can push only a single string parameter from Lua");
     }
+
+    @Test
+    public void invokeScript_returnsFinalResult() throws RemoteException {
+        String returnFinalResultScript =
+                "function script_finishes(state)\n"
+                        + "    result = {data = state.input + 1}\n"
+                        + "    on_script_finished(result)\n"
+                        + "end\n";
+        PersistableBundle previousState = new PersistableBundle();
+        previousState.putInt("input", 1);
+
+        runScriptAndWaitForResponse(returnFinalResultScript, "script_finishes", previousState);
+
+        // Expect to get back a bundle with a single key-value pair {"data": 2}
+        // because data = state.input + 1 as in the script body above.
+        assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(1);
+        assertThat(mFakeScriptExecutorListener.mFinalResult.getInt("data")).isEqualTo(2);
+    }
+
+    @Test
+    public void invokeScript_allSupportedTypesForReturningFinalResult()
+            throws RemoteException {
+        // Here we verify that all supported types are present in the returned final result
+        // bundle are present.
+        // TODO(b/189241508): update function signatures.
+        String script =
+                "function finalize_all(state)\n"
+                        + "    result = {}\n"
+                        + "    result.integer = state.integer + 1\n"
+                        + "    result.number = state.number + 0.1\n"
+                        + "    result.boolean = not state.boolean\n"
+                        + "    result.string = state.string .. \"CADABRA\"\n"
+                        + "    on_script_finished(result)\n"
+                        + "end\n";
+        PersistableBundle previousState = new PersistableBundle();
+        previousState.putInt("integer", 1);
+        previousState.putDouble("number", 0.1);
+        previousState.putBoolean("boolean", false);
+        previousState.putString("string", "ABRA");
+
+
+        runScriptAndWaitForResponse(script, "finalize_all", previousState);
+
+        // Verify that keys are preserved but the values are modified as expected.
+        assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(4);
+        assertThat(mFakeScriptExecutorListener.mFinalResult.getInt("integer")).isEqualTo(2);
+        assertThat(mFakeScriptExecutorListener.mFinalResult.getDouble("number")).isEqualTo(0.2);
+        assertThat(mFakeScriptExecutorListener.mFinalResult.getBoolean("boolean")).isEqualTo(true);
+        assertThat(mFakeScriptExecutorListener.mFinalResult.getString("string")).isEqualTo(
+                "ABRACADABRA");
+    }
+
+    @Test
+    public void invokeScript_emptyFinalResultBundle() throws RemoteException {
+        String script =
+                "function empty_final_result(state)\n"
+                        + "    result = {}\n"
+                        + "    on_script_finished(result)\n"
+                        + "end\n";
+
+
+        runScriptAndWaitForResponse(script, "empty_final_result", mSavedState);
+
+        // If a script returns empty table as the final result, we get an empty bundle.
+        assertThat(mFakeScriptExecutorListener.mFinalResult).isNotNull();
+        assertThat(mFakeScriptExecutorListener.mFinalResult.size()).isEqualTo(0);
+    }
+
+    @Test
+    public void invokeScript_wrongNumberOfCallbackInputsInOnScriptFinished()
+            throws RemoteException {
+        String script =
+                "function wrong_number_of_outputs_in_on_script_finished(state)\n"
+                        + "    result = {}\n"
+                        + "    extra = 1\n"
+                        + "    on_script_finished(result, extra)\n"
+                        + "end\n";
+
+
+        runScriptAndWaitForResponse(script, "wrong_number_of_outputs_in_on_script_finished",
+                mSavedState);
+
+        // We expect to get an error here because we expect only 1 input parameter in
+        // on_script_finished.
+        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
+                IScriptExecutorConstants.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
+                "on_script_finished can push only a single parameter from Lua - a Lua table");
+    }
+
+    @Test
+    public void invokeScript_wrongNumberOfCallbackInputsInOnSuccess() throws RemoteException {
+        String script =
+                "function wrong_number_of_outputs_in_on_success(state)\n"
+                        + "    result = {}\n"
+                        + "    extra = 1\n"
+                        + "    on_success(result, extra)\n"
+                        + "end\n";
+
+
+        runScriptAndWaitForResponse(script, "wrong_number_of_outputs_in_on_success", mSavedState);
+
+        // We expect to get an error here because we expect only 1 input parameter in on_success.
+        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
+                IScriptExecutorConstants.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
+                "on_success can push only a single parameter from Lua - a Lua table");
+    }
+
+    @Test
+    public void invokeScript_wrongTypeInOnSuccess() throws RemoteException {
+        String script =
+                "function wrong_type_in_on_success(state)\n"
+                        + "    result = 1\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+
+        runScriptAndWaitForResponse(script, "wrong_type_in_on_success", mSavedState);
+
+        // We expect to get an error here because the type of the input parameter for on_success
+        // must be a Lua table.
+        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
+                IScriptExecutorConstants.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
+                "on_success can push only a single parameter from Lua - a Lua table");
+    }
+
+    @Test
+    public void invokeScript_wrongTypeInOnScriptFinished() throws RemoteException {
+        String script =
+                "function wrong_type_in_on_script_finished(state)\n"
+                        + "    result = 1\n"
+                        + "    on_success(result)\n"
+                        + "end\n";
+
+
+        runScriptAndWaitForResponse(script, "wrong_type_in_on_script_finished", mSavedState);
+
+        // We expect to get an error here because the type of the input parameter for
+        // on_script_finished must be a Lua table.
+        assertThat(mFakeScriptExecutorListener.mErrorType).isEqualTo(
+                IScriptExecutorConstants.ERROR_TYPE_LUA_SCRIPT_ERROR);
+        assertThat(mFakeScriptExecutorListener.mMessage).isEqualTo(
+                "on_success can push only a single parameter from Lua - a Lua table");
+    }
 }
 
diff --git a/service/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/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/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/telemetry/CarTelemetryService.java b/service/src/com/android/car/telemetry/CarTelemetryService.java
index 07ae879..537ce45 100644
--- a/service/src/com/android/car/telemetry/CarTelemetryService.java
+++ b/service/src/com/android/car/telemetry/CarTelemetryService.java
@@ -15,34 +15,43 @@
  */
 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.content.SharedPreferences;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.RemoteException;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
 import com.android.car.CarLocalServices;
+import com.android.car.CarLog;
+import com.android.car.CarPropertyService;
 import com.android.car.CarServiceBase;
 import com.android.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.File;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.List;
 
 /**
  * CarTelemetryService manages OEM telemetry collection, processing and communication
@@ -50,28 +59,29 @@
  */
 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 CarPropertyService mCarPropertyService;
     private final File mRootDirectory;
     private final HandlerThread mTelemetryThread = CarServiceUtils.getHandlerThread(
             CarTelemetryService.class.getSimpleName());
     private final Handler mTelemetryHandler = new Handler(mTelemetryThread.getLooper());
-    @GuardedBy("mLock")
-    private 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 SharedPreferences mSharedPrefs;
+    private StatsManagerProxy mStatsManagerProxy;
+    private SystemMonitor mSystemMonitor;
 
-    public CarTelemetryService(Context context) {
+    public CarTelemetryService(Context context, CarPropertyService carPropertyService) {
         mContext = context;
+        mCarPropertyService = carPropertyService;
         SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class);
         // full root directory path is /data/system/car/telemetry
         mRootDirectory = new File(systemInterface.getSystemCarDir(), TELEMETRY_DIR);
@@ -80,15 +90,31 @@
     @Override
     public void init() {
         mTelemetryHandler.post(() -> {
+            // initialize all necessary components
             mMetricsConfigStore = new MetricsConfigStore(mRootDirectory);
-            mMetricsConfigStore.getActiveMetricsConfigs();
-            // TODO(b/197343030): mDataBroker.addMetricsConfig to start metrics collection
+            mResultStore = new ResultStore(mRootDirectory);
+            mStatsManagerProxy = new StatsManagerImpl(
+                    mContext.getSystemService(StatsManager.class));
+            // TODO(b/197968695): delay initialization of stats publisher
+            mPublisherFactory = new PublisherFactory(mCarPropertyService, mTelemetryHandler,
+                    mStatsManagerProxy, null);
+            mDataBroker = new DataBrokerImpl(mContext, mPublisherFactory, mResultStore);
+            mSystemMonitor = SystemMonitor.create(mContext, mTelemetryHandler);
+            mDataBrokerController = new DataBrokerController(mDataBroker, mSystemMonitor);
+
+            // start collecting data. once data is sent by publisher, scripts will be able to run
+            List<TelemetryProto.MetricsConfig> activeConfigs =
+                    mMetricsConfigStore.getActiveMetricsConfigs();
+            for (TelemetryProto.MetricsConfig config : activeConfigs) {
+                mDataBroker.addMetricsConfiguration(config);
+            }
         });
     }
 
     @Override
     public void release() {
-        // nothing to do
+        // TODO(b/197969149): prevent threading issue, block main thread
+        mTelemetryHandler.post(() -> mResultStore.flushToDisk());
     }
 
     @Override
@@ -104,9 +130,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,50 +144,91 @@
     @Override
     public void clearListener() {
         mContext.enforceCallingOrSelfPermission(
-                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
-        synchronized (mLock) {
-            clearListenerLocked();
-        }
+                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "clearListener");
+        mTelemetryHandler.post(() -> {
+            if (DEBUG) {
+                Slog.d(CarLog.TAG_TELEMETRY, "Clearing the listener for car telemetry service");
+            }
+            mListener = null;
+        });
     }
 
     /**
-     * Allows client to send telemetry manifests.
+     * Send a telemetry metrics config to the service.
      *
-     * @param key    the unique key to identify the manifest.
-     * @param config the serialized bytes of a Manifest object.
-     * @return {@link AddManifestError} the error code.
+     * @param key    the unique key to identify the MetricsConfig.
+     * @param config the serialized bytes of a MetricsConfig object.
      */
     @Override
-    public @AddManifestError int addManifest(@NonNull ManifestKey key, @NonNull byte[] config) {
+    public void addMetricsConfig(@NonNull MetricsConfigKey key, @NonNull byte[] config) {
         mContext.enforceCallingOrSelfPermission(
-                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
-        synchronized (mLock) {
-            return addManifestLocked(key, config);
-        }
+                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "addMetricsConfig");
+        mTelemetryHandler.post(() -> {
+            Slog.d(CarLog.TAG_TELEMETRY, "Adding metrics config " + key.getName()
+                    + " to car telemetry service");
+            // TODO(b/199540952): If config is active, add it to DataBroker
+            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);
+            }
+            // 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.d(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.
      */
     @Override
-    public boolean removeManifest(@NonNull ManifestKey key) {
+    public void removeMetricsConfig(@NonNull MetricsConfigKey key) {
         mContext.enforceCallingOrSelfPermission(
-                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
-        synchronized (mLock) {
-            return removeManifestLocked(key);
-        }
+                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "removeMetricsConfig");
+        mTelemetryHandler.post(() -> {
+            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.d(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();
+        });
     }
 
     /**
@@ -166,99 +236,40 @@
      * {@link ICarTelemetryServiceListener}.
      */
     @Override
-    public void sendFinishedReports(@NonNull ManifestKey key) {
+    public void sendFinishedReports(@NonNull MetricsConfigKey key) {
         // TODO(b/184087869): Implement
         mContext.enforceCallingOrSelfPermission(
-                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
+                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "sendFinishedReports");
         if (DEBUG) {
-            Slog.d(TAG, "Flushing reports for a manifest");
+            Slog.d(CarLog.TAG_TELEMETRY, "Flushing reports for a metrics config");
         }
     }
 
     /**
-     * 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");
-        }
+    @VisibleForTesting
+    Handler getTelemetryHandler() {
+        return mTelemetryHandler;
     }
 
-    @GuardedBy("mLock")
-    private void setListenerLocked(@NonNull ICarTelemetryServiceListener listener) {
-        if (DEBUG) {
-            Slog.d(TAG, "Setting the listener for car telemetry service");
-        }
-        mListener = listener;
+    @VisibleForTesting
+    ResultStore getResultStore() {
+        return mResultStore;
     }
 
-    @GuardedBy("mLock")
-    private void clearListenerLocked() {
-        if (DEBUG) {
-            Slog.d(TAG, "Clearing listener");
-        }
-        mListener = null;
-    }
-
-    @GuardedBy("mLock")
-    private @AddManifestError int addManifestLocked(ManifestKey key, byte[] configProto) {
-        if (DEBUG) {
-            Slog.d(TAG, "Adding MetricsConfig to car telemetry service");
-        }
-        int currentVersion = mNameVersionMap.getOrDefault(key.getName(), DEFAULT_VERSION);
-        if (currentVersion > key.getVersion()) {
-            return ERROR_NEWER_MANIFEST_EXISTS;
-        } else if (currentVersion == key.getVersion()) {
-            return ERROR_SAME_MANIFEST_EXISTS;
-        }
-
-        TelemetryProto.MetricsConfig metricsConfig;
-        try {
-            metricsConfig = TelemetryProto.MetricsConfig.parseFrom(configProto);
-        } catch (InvalidProtocolBufferException e) {
-            Slog.e(TAG, "Failed to parse MetricsConfig.", e);
-            return ERROR_PARSE_MANIFEST_FAILED;
-        }
-        mNameVersionMap.put(key.getName(), key.getVersion());
-
-        // TODO(b/186047142): Store the MetricsConfig to disk
-        // TODO(b/186047142): Send metricsConfig to a script manager or a queue
-        return ERROR_NONE;
-    }
-
-    @GuardedBy("mLock")
-    private boolean removeManifestLocked(@NonNull ManifestKey key) {
-        Integer version = mNameVersionMap.remove(key.getName());
-        if (version == null) {
-            return false;
-        }
-        // TODO(b/186047142): Delete manifest from disk and remove it from queue
-        return true;
-    }
-
-    @GuardedBy("mLock")
-    private void removeAllManifestsLocked() {
-        if (DEBUG) {
-            Slog.d(TAG, "Removing all manifest from car telemetry service");
-        }
-        mNameVersionMap.clear();
-        // TODO(b/186047142): Delete all manifests from disk & queue
+    @VisibleForTesting
+    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..7a5512a 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 {
     @VisibleForTesting
     static final String METRICS_CONFIG_DIR = "metrics_configs";
 
     private final File mConfigDirectory;
+    private Map<String, TelemetryProto.MetricsConfig> mActiveConfigs;
+    private Map<String, Integer> mNameVersionMap;
 
     MetricsConfigStore(File rootDirectory) {
         mConfigDirectory = new File(rootDirectory, METRICS_CONFIG_DIR);
         mConfigDirectory.mkdirs();
+        mActiveConfigs = new ArrayMap<>();
+        mNameVersionMap = new ArrayMap<>();
+        // TODO(b/197336485): Add expiration date check for MetricsConfig
+        for (File file : mConfigDirectory.listFiles()) {
+            try {
+                byte[] serializedConfig = Files.readAllBytes(file.toPath());
+                TelemetryProto.MetricsConfig config =
+                        TelemetryProto.MetricsConfig.parseFrom(serializedConfig);
+                mActiveConfigs.put(config.getName(), config);
+                mNameVersionMap.put(config.getName(), config.getVersion());
+            } catch (IOException e) {
+                // TODO(b/197336655): record failure
+                file.delete();
+            }
+        }
     }
 
     /**
      * Returns all active {@link TelemetryProto.MetricsConfig} from disk.
      */
     List<TelemetryProto.MetricsConfig> getActiveMetricsConfigs() {
-        // 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));
-            } catch (IOException e) {
-                // TODO(b/197336655): record failure
-                file.delete();
-            }
-        }
-        return activeConfigs;
+        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
+    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,21 @@
         } 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) {
+        mActiveConfigs.remove(metricsConfigName);
         return new File(mConfigDirectory, metricsConfigName).delete();
     }
+
+    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..ffa3d09 100644
--- a/service/src/com/android/car/telemetry/ResultStore.java
+++ b/service/src/com/android/car/telemetry/ResultStore.java
@@ -16,25 +16,23 @@
 
 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;
 import java.util.concurrent.TimeUnit;
 
 /**
  * Disk storage for interim and final metrics statistics.
+ * All methods in this class should be invoked from the telemetry thread.
  */
 public class ResultStore {
 
@@ -45,32 +43,23 @@
     @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 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);
         mFinalResultDirectory = new File(rootDirectory, FINAL_RESULT_DIR);
         mInterimResultDirectory.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(
@@ -88,12 +77,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 +88,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 +97,23 @@
      *
      * @param metricsConfigName name of the MetricsConfig.
      * @param deleteResult      if true, the final result will be deleted from disk.
-     * @param callback          for receiving the metrics output. If result does not exist, it will
-     *                          receive a null value.
      */
-    public void getFinalResult(
-            String metricsConfigName, boolean deleteResult, FinalResultCallback callback) {
-        // 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 +121,52 @@
      * {@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);
-            });
-            mInterimResultCache.remove(metricsConfigName);
-        }
+        writePersistableBundleToFile(mFinalResultDirectory, metricsConfigName, result);
+        deleteFileInDirectory(mInterimResultDirectory, metricsConfigName);
+        mInterimResultCache.remove(metricsConfigName);
     }
 
     /** 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,13 +181,10 @@
     /**
      * 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
@@ -216,16 +192,11 @@
     }
 
     /** 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;
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/DataBrokerImpl.java b/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java
index 94df370..55c7685 100644
--- a/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java
+++ b/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java
@@ -41,7 +41,6 @@
 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.lang.ref.WeakReference;
@@ -49,9 +48,6 @@
 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 +57,7 @@
 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;
 
     private static final String SCRIPT_EXECUTOR_PACKAGE = "com.android.car.scriptexecutor";
     private static final String SCRIPT_EXECUTOR_CLASS =
@@ -72,26 +67,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 +79,62 @@
      * 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;
+
+    /** Priority of current system to determine if a {@link ScriptExecutionTask} can run. */
+    private int mPriority = 1;
+
+    /**
+     * Name of the script that's currently running. If no script is running, value is null.
+     * A non-null script name indicates a script is running, which means DataBroker should not
+     * make another ScriptExecutor binder call.
+     */
+    private String mCurrentScriptName;
+    private IScriptExecutor mScriptExecutor;
+    private ScriptFinishedCallback mScriptFinishedCallback;
+
     private final ServiceConnection mServiceConnection = new ServiceConnection() {
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
-            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();
@@ -149,8 +151,13 @@
         }
     }
 
-    /** 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) {
@@ -159,53 +166,26 @@
         }
     }
 
-    /**
-     * Cleans up the state after ScriptExecutor is killed by the system & service disconnects.
-     */
-    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 +210,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 +242,7 @@
 
     @Override
     public void addTaskToQueue(ScriptExecutionTask task) {
-        if (mDisabled.get()) {
+        if (mDisabled) {
             return;
         }
         mTaskQueue.add(task);
@@ -292,20 +256,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 +276,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 +305,65 @@
      * 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) {
-            return;
-        }
-        MetricsConfig metricsConfig = task.getMetricsConfig();
-        mCurrentScriptName.set(metricsConfig.getName()); // signal the start of script execution
+        mTaskQueue.poll(); // remove task from queue
         try {
-            scriptExecutor.invokeScript(
-                    metricsConfig.getScript(),
-                    task.getHandlerName(),
-                    task.getData(),
-                    null, // TODO(b/197027637): PersistableBundle cannot be converted into Bundle
-                    mScriptExecutorListener);
+            if (mScriptExecutor == null) {
+                Slog.w(CarLog.TAG_TELEMETRY,
+                        "script executor is null, cannot execute task");
+                mTaskQueue.add(task);
+                // upon successful binding, a task will be scheduled to run if there are any
+                mTelemetryHandler.sendEmptyMessage(MSG_BIND_TO_SCRIPT_EXECUTOR);
+            } else {
+                // update current name because a script is currently running
+                mCurrentScriptName = task.getMetricsConfig().getName();
+                mScriptExecutor.invokeScript(
+                        task.getMetricsConfig().getScript(),
+                        task.getHandlerName(),
+                        task.getData(),
+                        mResultStore.getInterimResult(mCurrentScriptName),
+                        mScriptExecutorListener);
+            }
         } catch (RemoteException e) {
             Slog.d(CarLog.TAG_TELEMETRY, "remote exception occurred invoking script", e);
             mTaskQueue.add(task); // will not trigger scheduleNextTask()
-            mCurrentScriptName.set(null);
+            mCurrentScriptName = null;
         }
     }
 
     /** 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(() -> {
+            // TODO(b/197005294): create error object
+            mCurrentScriptName = null;
+            scheduleNextTask();
+        });
     }
 
     /** Listens for script execution status. Methods are called on the binder thread. */
@@ -426,7 +375,7 @@
         }
 
         @Override
-        public void onScriptFinished(byte[] result) {
+        public void onScriptFinished(PersistableBundle result) {
             DataBrokerImpl dataBroker = mWeakDataBroker.get();
             if (dataBroker == null) {
                 return;
@@ -472,6 +421,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..8af2c6f 100644
--- a/service/src/com/android/car/telemetry/databroker/DataSubscriber.java
+++ b/service/src/com/android/car/telemetry/databroker/DataSubscriber.java
@@ -23,12 +23,7 @@
 
 /**
  * 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.
+ * All methods of this class must be accessed on telemetry thread.
  *
  * <p>TODO(b/187743369): implement equals() and hash() functions, as they are used in publishers
  *                       to check equality of subscribers.
@@ -64,13 +59,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}. */
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/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/PublisherFactory.java b/service/src/com/android/car/telemetry/publisher/PublisherFactory.java
index fb32742..1009d25 100644
--- a/service/src/com/android/car/telemetry/publisher/PublisherFactory.java
+++ b/service/src/com/android/car/telemetry/publisher/PublisherFactory.java
@@ -17,48 +17,66 @@
 package com.android.car.telemetry.publisher;
 
 import android.content.SharedPreferences;
+import android.os.Handler;
 
 import com.android.car.CarPropertyService;
 import com.android.car.telemetry.TelemetryProto;
 
+import java.util.function.BiConsumer;
+
 /**
- * Factory class for Publishers. It's expected to have a single factory instance.
+ * Lazy factory class for Publishers. It's expected to have a single factory instance.
+ * Must be called from the telemetry thread.
+ *
+ * <p>It doesn't instantiate all the publishers right away, as in some cases some publishers are
+ * not needed.
  *
  * <p>Thread-safe.
  */
 public class PublisherFactory {
     private final Object mLock = new Object();
     private final CarPropertyService mCarPropertyService;
+    private final Handler mTelemetryHandler;
     private final StatsManagerProxy mStatsManager;
     private final SharedPreferences mSharedPreferences;
     private VehiclePropertyPublisher mVehiclePropertyPublisher;
+    private CarTelemetrydPublisher mCarTelemetrydPublisher;
     private StatsPublisher mStatsPublisher;
 
+    private BiConsumer<AbstractPublisher, Throwable> mFailureConsumer;
+
     public PublisherFactory(
             CarPropertyService carPropertyService,
+            Handler handler,
             StatsManagerProxy statsManager,
             SharedPreferences sharedPreferences) {
         mCarPropertyService = carPropertyService;
+        mTelemetryHandler = handler;
         mStatsManager = statsManager;
         mSharedPreferences = sharedPreferences;
     }
 
-    /** Returns publisher by given type. */
-    public AbstractPublisher getPublisher(
-            TelemetryProto.Publisher.PublisherCase type) {
+    /** Returns the publisher by given type. */
+    public AbstractPublisher getPublisher(TelemetryProto.Publisher.PublisherCase type) {
         // No need to optimize locks, as this method is infrequently called.
         synchronized (mLock) {
             switch (type.getNumber()) {
                 case TelemetryProto.Publisher.VEHICLE_PROPERTY_FIELD_NUMBER:
                     if (mVehiclePropertyPublisher == null) {
                         mVehiclePropertyPublisher = new VehiclePropertyPublisher(
-                                mCarPropertyService);
+                                mCarPropertyService, mFailureConsumer, mTelemetryHandler);
                     }
                     return mVehiclePropertyPublisher;
-                // 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, mSharedPreferences);
                     }
                     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..8d5f7ea 100644
--- a/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
@@ -41,15 +41,14 @@
 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.
@@ -73,7 +72,7 @@
 
     private final StatsManagerProxy mStatsManager;
     private final SharedPreferences mSharedPreferences;
-    private final Handler mHandler;
+    private final Handler mTelemetryHandler;
 
     // True if the publisher is periodically pulling reports from StatsD.
     private final AtomicBoolean mIsPullingReports = new AtomicBoolean(false);
@@ -88,16 +87,24 @@
     @GuardedBy("mLock")
     private final LongSparseArray<DataSubscriber> mConfigKeyToSubscribers = new LongSparseArray<>();
 
-    StatsPublisher(StatsManagerProxy statsManager, SharedPreferences sharedPreferences) {
-        this(statsManager, sharedPreferences, new Handler(Looper.myLooper()));
+    // TODO(b/198331078): Use telemetry thread
+    StatsPublisher(
+            BiConsumer<AbstractPublisher, Throwable> failureConsumer,
+            StatsManagerProxy statsManager,
+            SharedPreferences sharedPreferences) {
+        this(failureConsumer, statsManager, sharedPreferences, new Handler(Looper.myLooper()));
     }
 
     @VisibleForTesting
     StatsPublisher(
-            StatsManagerProxy statsManager, SharedPreferences sharedPreferences, Handler handler) {
+            BiConsumer<AbstractPublisher, Throwable> failureConsumer,
+            StatsManagerProxy statsManager,
+            SharedPreferences sharedPreferences,
+            Handler handler) {
+        super(failureConsumer);
         mStatsManager = statsManager;
         mSharedPreferences = sharedPreferences;
-        mHandler = handler;
+        mTelemetryHandler = handler;
     }
 
     @Override
@@ -113,26 +120,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,7 +154,7 @@
         }
 
         if (mIsPullingReports.get()) {
-            mHandler.postDelayed(mPullReportsPeriodically, PULL_REPORTS_PERIOD.toMillis());
+            mTelemetryHandler.postDelayed(mPullReportsPeriodically, PULL_REPORTS_PERIOD.toMillis());
         }
     }
 
@@ -180,7 +193,7 @@
 
         if (mConfigKeyToSubscribers.size() == 0) {
             mIsPullingReports.set(false);
-            mHandler.removeCallbacks(mPullReportsPeriodically);
+            mTelemetryHandler.removeCallbacks(mPullReportsPeriodically);
         }
     }
 
@@ -199,16 +212,19 @@
                     String sharedPrefVersion = buildSharedPrefConfigVersionKey(configKey);
                     editor.remove(key).remove(sharedPrefVersion);
                 } 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();
         }
         mIsPullingReports.set(false);
-        mHandler.removeCallbacks(mPullReportsPeriodically);
+        mTelemetryHandler.removeCallbacks(mPullReportsPeriodically);
     }
 
     @Override
@@ -223,6 +239,13 @@
         }
     }
 
+    /** Returns a subscriber for the given statsd config key. Returns null if not found. */
+    private DataSubscriber getSubscriberByConfigKey(long configKey) {
+        synchronized (mLock) {
+            return mConfigKeyToSubscribers.get(configKey);
+        }
+    }
+
     /**
      * Returns the key for SharedPreferences to store/retrieve configKey associated with the
      * subscriber.
@@ -247,21 +270,18 @@
      */
     @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.
+            if (currentVersion >= subscriber.getMetricsConfig().getVersion()) {
+                // It's trying to add current or older MetricsConfig version, just ignore it.
                 return configKey;
-            }
+            }  // if the subscriber's MetricsConfig version is newer, it will replace the old one.
         }
+        String sharedPrefConfigKey = buildSharedPrefConfigKey(subscriber);
+        StatsdConfig config = buildStatsdConfig(subscriber, configKey);
         try {
             // It doesn't throw exception if the StatsdConfig is invalid. But it shouldn't happen,
             // as we generate well-tested StatsdConfig in this service.
@@ -274,6 +294,9 @@
             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;
     }
@@ -289,9 +312,12 @@
             mStatsManager.removeConfig(configKey);
             mSharedPreferences.edit().remove(sharedPrefVersion).remove(sharedPrefConfigKey).apply();
         } 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;
     }
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/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/tests/CarEvsCameraPreviewApp/res/values/config.xml b/tests/CarEvsCameraPreviewApp/res/values/config.xml
new file mode 100644
index 0000000..935c096
--- /dev/null
+++ b/tests/CarEvsCameraPreviewApp/res/values/config.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<resources>
+    <!-- Shade of the background behind the camera window. 1.0 for fully opaque, 0.0 for fully
+         transparent. -->
+    <item name="config_cameraBackgroundScrim" format="float" type="dimen">0.7</item>
+</resources>
\ No newline at end of file
diff --git a/tests/CarEvsCameraPreviewApp/res/values/overlayable.xml b/tests/CarEvsCameraPreviewApp/res/values/overlayable.xml
new file mode 100644
index 0000000..0f77eab
--- /dev/null
+++ b/tests/CarEvsCameraPreviewApp/res/values/overlayable.xml
@@ -0,0 +1,33 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+  http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.-->
+<!--
+THIS FILE WAS AUTO GENERATED, DO NOT EDIT MANUALLY.
+REGENERATE USING packages/apps/Car/tests/tools/rro/generate-overlayable.py
+-->
+<resources>
+  <overlayable name="CarEvsCameraPreviewApp">
+    <policy type="system|product|signature">
+      <item type="color" name="button_background"/>
+      <item type="color" name="button_text"/>
+      <item type="dimen" name="camera_preview_height"/>
+      <item type="dimen" name="camera_preview_width"/>
+      <item type="dimen" name="close_button_text_size"/>
+      <item type="dimen" name="config_cameraBackgroundScrim"/>
+      <item type="id" name="close_button"/>
+      <item type="id" name="evs_preview_container"/>
+      <item type="layout" name="evs_preview_activity"/>
+      <item type="string" name="app_name"/>
+      <item type="string" name="close_button_text"/>
+      <item type="style" name="Theme.Transparent"/>
+    </policy>
+  </overlayable>
+</resources>
diff --git a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraPreviewActivity.java b/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraPreviewActivity.java
index 737d918..6ff7940 100644
--- a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraPreviewActivity.java
+++ b/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraPreviewActivity.java
@@ -34,8 +34,8 @@
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.WindowManager;
-import android.widget.Button;
 import android.widget.LinearLayout;
 
 import java.util.ArrayList;
@@ -55,6 +55,7 @@
 
     /** GL backed surface view to render the camera preview */
     private CarEvsCameraGLSurfaceView mEvsView;
+    private ViewGroup mRootView;
     private LinearLayout mPreviewContainer;
 
     /** Display manager to monitor the display's state */
@@ -164,8 +165,9 @@
                 Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, mCarServiceLifecycleListener);
 
         mEvsView = new CarEvsCameraGLSurfaceView(getApplication(), this);
-        mPreviewContainer = (LinearLayout) LayoutInflater.from(this).inflate(
+        mRootView = (ViewGroup) LayoutInflater.from(this).inflate(
                 R.layout.evs_preview_activity, /* root= */ null);
+        mPreviewContainer = mRootView.findViewById(R.id.evs_preview_container);
         LinearLayout.LayoutParams viewParam = new LinearLayout.LayoutParams(
                 LinearLayout.LayoutParams.MATCH_PARENT,
                 LinearLayout.LayoutParams.MATCH_PARENT,
@@ -173,31 +175,31 @@
         );
         mEvsView.setLayoutParams(viewParam);
         mPreviewContainer.addView(mEvsView, 0);
-        Button closeButton = mPreviewContainer.findViewById(R.id.close_button);
-        closeButton.setOnClickListener((v) -> finish());
+        View closeButton = mRootView.findViewById(R.id.close_button);
+        if (closeButton != null) {
+            closeButton.setOnClickListener(v -> finish());
+        }
 
         int width = WindowManager.LayoutParams.MATCH_PARENT;
         int height = WindowManager.LayoutParams.MATCH_PARENT;
-        int x = 0;
-        int y = 0;
         if (mUseSystemWindow) {
             width = getResources().getDimensionPixelOffset(R.dimen.camera_preview_width);
             height = getResources().getDimensionPixelOffset(R.dimen.camera_preview_height);
-            x = (getResources().getDisplayMetrics().widthPixels - width) / 2;
-            y = (getResources().getDisplayMetrics().heightPixels - height) / 2;
         }
         WindowManager.LayoutParams params = new WindowManager.LayoutParams(
-                width, height, x, y,
+                width, height,
                 2020 /* WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY */,
-                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                WindowManager.LayoutParams.FLAG_DIM_BEHIND
                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
                 PixelFormat.TRANSLUCENT);
-        params.gravity = Gravity.LEFT | Gravity.TOP;
+        params.gravity = Gravity.CENTER;
+        params.dimAmount = getResources().getFloat(R.dimen.config_cameraBackgroundScrim);
+
         if (mUseSystemWindow) {
             WindowManager wm = getSystemService(WindowManager.class);
-            wm.addView(mPreviewContainer, params);
+            wm.addView(mRootView, params);
         } else {
-            setContentView(mPreviewContainer, params);
+            setContentView(mRootView, params);
         }
     }
 
@@ -252,7 +254,7 @@
         mDisplayManager.unregisterDisplayListener(mDisplayListener);
         if (mUseSystemWindow) {
             WindowManager wm = getSystemService(WindowManager.class);
-            wm.removeView(mPreviewContainer);
+            wm.removeView(mRootView);
         }
     }
 
diff --git a/tests/CarLibTests/src/android/car/CarTelemetryManagerTest.java b/tests/CarLibTests/src/android/car/CarTelemetryManagerTest.java
deleted file mode 100644
index 906662d..0000000
--- a/tests/CarLibTests/src/android/car/CarTelemetryManagerTest.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.car;
-
-import static android.car.telemetry.CarTelemetryManager.ERROR_NONE;
-import static android.car.telemetry.CarTelemetryManager.ERROR_SAME_MANIFEST_EXISTS;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.verify;
-
-import android.app.Application;
-import android.car.telemetry.CarTelemetryManager;
-import android.car.telemetry.ManifestKey;
-import android.car.testapi.CarTelemetryController;
-import android.car.testapi.FakeCar;
-
-import androidx.test.core.app.ApplicationProvider;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.internal.DoNotInstrument;
-
-import java.util.concurrent.Executor;
-
-@RunWith(RobolectricTestRunner.class)
-@DoNotInstrument
-public class CarTelemetryManagerTest {
-    @Rule
-    public MockitoRule rule = MockitoJUnit.rule();
-
-    private static final byte[] ERROR_BYTES = "ERROR".getBytes();
-    private static final byte[] MANIFEST_BYTES = "MANIFEST".getBytes();
-    private static final byte[] SCRIPT_RESULT_BYTES = "SCRIPT RESULT".getBytes();
-    private static final ManifestKey DEFAULT_MANIFEST_KEY =
-            new ManifestKey("NAME", 1);
-    private static final Executor DIRECT_EXECUTOR = Runnable::run;
-
-    private CarTelemetryController mCarTelemetryController;
-    private CarTelemetryManager mCarTelemetryManager;
-
-    @Mock
-    private CarTelemetryManager.CarTelemetryResultsListener mListener;
-
-
-
-    @Before
-    public void setUp() {
-        Application context = ApplicationProvider.getApplicationContext();
-        FakeCar fakeCar = FakeCar.createFakeCar(context);
-        Car carApi = fakeCar.getCar();
-
-        mCarTelemetryManager =
-                (CarTelemetryManager) carApi.getCarManager(Car.CAR_TELEMETRY_SERVICE);
-        mCarTelemetryController = fakeCar.getCarTelemetryController();
-    }
-
-    @Test
-    public void setListener_shouldSucceed() {
-        mCarTelemetryManager.setListener(DIRECT_EXECUTOR, mListener);
-
-        assertThat(mCarTelemetryController.isListenerSet()).isTrue();
-    }
-
-    @Test
-    public void clearListener_shouldSucceed() {
-        mCarTelemetryManager.setListener(DIRECT_EXECUTOR, mListener);
-        mCarTelemetryManager.clearListener();
-
-        assertThat(mCarTelemetryController.isListenerSet()).isFalse();
-    }
-
-    @Test
-    public void addManifest_whenNew_shouldSucceed() {
-        int result = mCarTelemetryManager.addManifest(DEFAULT_MANIFEST_KEY, MANIFEST_BYTES);
-
-        assertThat(result).isEqualTo(ERROR_NONE);
-        assertThat(mCarTelemetryController.getValidManifestsCount()).isEqualTo(1);
-    }
-
-    @Test
-    public void addManifest_whenDuplicate_shouldIgnore() {
-        int firstResult =
-                mCarTelemetryManager.addManifest(DEFAULT_MANIFEST_KEY, MANIFEST_BYTES);
-        int secondResult =
-                mCarTelemetryManager.addManifest(DEFAULT_MANIFEST_KEY, MANIFEST_BYTES);
-
-        assertThat(firstResult).isEqualTo(ERROR_NONE);
-        assertThat(secondResult).isEqualTo(ERROR_SAME_MANIFEST_EXISTS);
-        assertThat(mCarTelemetryController.getValidManifestsCount()).isEqualTo(1);
-    }
-
-    @Test
-    public void removeManifest_whenValid_shouldSucceed() {
-        mCarTelemetryManager.addManifest(DEFAULT_MANIFEST_KEY, MANIFEST_BYTES);
-
-        boolean result = mCarTelemetryManager.removeManifest(DEFAULT_MANIFEST_KEY);
-
-        assertThat(result).isTrue();
-        assertThat(mCarTelemetryController.getValidManifestsCount()).isEqualTo(0);
-    }
-
-    @Test
-    public void removeManifest_whenInvalid_shouldIgnore() {
-        mCarTelemetryManager.addManifest(DEFAULT_MANIFEST_KEY, MANIFEST_BYTES);
-
-        boolean result = mCarTelemetryManager.removeManifest(new ManifestKey("NAME", 100));
-
-        assertThat(result).isFalse();
-        assertThat(mCarTelemetryController.getValidManifestsCount()).isEqualTo(1);
-    }
-
-    @Test
-    public void removeAllManifests_shouldSucceed() {
-        mCarTelemetryManager.addManifest(DEFAULT_MANIFEST_KEY, MANIFEST_BYTES);
-        mCarTelemetryManager.addManifest(new ManifestKey("NAME", 100), MANIFEST_BYTES);
-
-        mCarTelemetryManager.removeAllManifests();
-
-        assertThat(mCarTelemetryController.getValidManifestsCount()).isEqualTo(0);
-    }
-
-    @Test
-    public void sendFinishedReports_shouldSucceed() {
-        mCarTelemetryManager.setListener(DIRECT_EXECUTOR, mListener);
-        mCarTelemetryController.addDataForKey(DEFAULT_MANIFEST_KEY, SCRIPT_RESULT_BYTES);
-
-        mCarTelemetryManager.sendFinishedReports(DEFAULT_MANIFEST_KEY);
-
-        verify(mListener).onResult(DEFAULT_MANIFEST_KEY, SCRIPT_RESULT_BYTES);
-    }
-
-    @Test
-    public void sendAllFinishedReports_shouldSucceed() {
-        mCarTelemetryManager.setListener(DIRECT_EXECUTOR, mListener);
-        mCarTelemetryController.addDataForKey(DEFAULT_MANIFEST_KEY, SCRIPT_RESULT_BYTES);
-        ManifestKey key2 = new ManifestKey("key name", 1);
-        mCarTelemetryController.addDataForKey(key2, SCRIPT_RESULT_BYTES);
-
-        mCarTelemetryManager.sendAllFinishedReports();
-
-        verify(mListener).onResult(DEFAULT_MANIFEST_KEY, SCRIPT_RESULT_BYTES);
-        verify(mListener).onResult(key2, SCRIPT_RESULT_BYTES);
-    }
-
-    @Test
-    public void sendScriptExecutionErrors_shouldSucceed() {
-        mCarTelemetryManager.setListener(DIRECT_EXECUTOR, mListener);
-        mCarTelemetryController.setErrorData(ERROR_BYTES);
-
-        mCarTelemetryManager.sendScriptExecutionErrors();
-
-        verify(mListener).onError(ERROR_BYTES);
-    }
-}
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/telemetry/CarTelemetryManagerPermissionTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/telemetry/CarTelemetryManagerPermissionTest.java
index 509c931..84409cf 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/telemetry/CarTelemetryManagerPermissionTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/telemetry/CarTelemetryManagerPermissionTest.java
@@ -22,7 +22,7 @@
 
 import android.car.Car;
 import android.car.telemetry.CarTelemetryManager;
-import android.car.telemetry.ManifestKey;
+import android.car.telemetry.MetricsConfigKey;
 import android.content.Context;
 
 import androidx.annotation.NonNull;
@@ -45,8 +45,8 @@
 public class CarTelemetryManagerPermissionTest {
     private final Context mContext =
             InstrumentationRegistry.getInstrumentation().getTargetContext();
-    private final ManifestKey mManifestKey = new ManifestKey("name", 1);
-    private final byte[] mManifestBytes = "manifest".getBytes();
+    private final MetricsConfigKey mMetricsConfigKey = new MetricsConfigKey("name", 1);
+    private final byte[] mMetricsConfigBytes = "manifest".getBytes();
 
     private Car mCar;
     private CarTelemetryManager mCarTelemetryManager;
@@ -83,25 +83,26 @@
     }
 
     @Test
-    public void testAddManifest() throws Exception {
+    public void testAddMetricsConfig() throws Exception {
         Exception e = expectThrows(SecurityException.class,
-                () -> mCarTelemetryManager.addManifest(mManifestKey, mManifestBytes));
+                () -> mCarTelemetryManager.addMetricsConfig(mMetricsConfigKey,
+                        mMetricsConfigBytes));
 
         assertThat(e.getMessage()).contains(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE);
     }
 
     @Test
-    public void testRemoveManifest() throws Exception {
+    public void testRemoveMetricsConfig() throws Exception {
         Exception e = expectThrows(SecurityException.class,
-                () -> mCarTelemetryManager.removeManifest(mManifestKey));
+                () -> mCarTelemetryManager.removeMetricsConfig(mMetricsConfigKey));
 
         assertThat(e.getMessage()).contains(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE);
     }
 
     @Test
-    public void testRemoveAllManifests() throws Exception {
+    public void testRemoveAllMetricsConfigs() throws Exception {
         Exception e = expectThrows(SecurityException.class,
-                () -> mCarTelemetryManager.removeAllManifests());
+                () -> mCarTelemetryManager.removeAllMetricsConfigs());
 
         assertThat(e.getMessage()).contains(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE);
     }
@@ -109,7 +110,7 @@
     @Test
     public void testSendFinishedReports() throws Exception {
         Exception e = expectThrows(SecurityException.class,
-                () -> mCarTelemetryManager.sendFinishedReports(mManifestKey));
+                () -> mCarTelemetryManager.sendFinishedReports(mMetricsConfigKey));
 
         assertThat(e.getMessage()).contains(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE);
     }
@@ -122,23 +123,22 @@
         assertThat(e.getMessage()).contains(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE);
     }
 
-    @Test
-    public void testSendScriptExecutionErrors() throws Exception {
-        Exception e = expectThrows(SecurityException.class,
-                () -> mCarTelemetryManager.sendScriptExecutionErrors());
-
-        assertThat(e.getMessage()).contains(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE);
-    }
-
     private class FakeCarTelemetryResultsListener implements
             CarTelemetryManager.CarTelemetryResultsListener {
         @Override
-        public void onResult(@NonNull ManifestKey key, @NonNull byte[] result) {
+        public void onResult(@NonNull MetricsConfigKey key, @NonNull byte[] result) {
         }
 
         @Override
-        public void onError(@NonNull byte[] error) {
+        public void onError(@NonNull MetricsConfigKey key, @NonNull byte[] error) {
+        }
+
+        @Override
+        public void onAddMetricsConfigStatus(@NonNull MetricsConfigKey key, int statusCode) {
+        }
+
+        @Override
+        public void onRemoveMetricsConfigStatus(@NonNull MetricsConfigKey key, boolean success) {
         }
     }
-
 }
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
index af573f7..ea527fa 100644
--- a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -50,6 +50,8 @@
     <uses-permission android:name="android.car.permission.READ_CAR_STEERING"/>
     <uses-permission android:name="android.car.permission.STORAGE_MONITORING"/>
     <uses-permission android:name="android.car.permission.CAR_DYNAMICS_STATE"/>
+    <!-- use for CarServiceTest -->
+    <uses-permission android:name="android.car.permission.USE_CAR_TELEMETRY_SERVICE"/>
     <!-- Allow querying and writing to any property -->
     <uses-permission android:name="android.car.permission.CAR_ENERGY_PORTS" />
     <uses-permission android:name="android.car.permission.PERMISSION_CONTROL_ENERGY_PORTS" />
diff --git a/tests/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_unit_test/src/com/android/car/telemetry/CarTelemetryServiceTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/CarTelemetryServiceTest.java
index 6e8ac38..39389d2 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,16 +17,23 @@
 package com.android.car.telemetry;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.car.telemetry.CarTelemetryManager;
-import android.car.telemetry.ManifestKey;
+import android.car.telemetry.ICarTelemetryServiceListener;
+import android.car.telemetry.MetricsConfigKey;
 import android.content.Context;
+import android.os.Handler;
+import 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 org.junit.Before;
@@ -40,23 +47,39 @@
 
 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;
 
     @Before
@@ -67,77 +90,126 @@
         mTempSystemCarDir = Files.createTempDirectory("telemetry_test").toFile();
         when(mMockSystemInterface.getSystemCarDir()).thenReturn(mTempSystemCarDir);
 
-        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();
+    }
+
+    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..ed59675 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;
@@ -52,21 +43,8 @@
     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");
 
@@ -74,7 +52,7 @@
         mTestInterimResultDir = new File(mTestRootDir, ResultStore.INTERIM_RESULT_DIR);
         mTestFinalResultDir = new File(mTestRootDir, ResultStore.FINAL_RESULT_DIR);
 
-        mResultStore = new ResultStore(mMockHandler, mMockHandler, mTestRootDir);
+        mResultStore = new ResultStore(mTestRootDir);
     }
 
     @Test
@@ -89,7 +67,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 +119,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 +130,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 +140,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,12 +152,10 @@
         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();
     }
 
@@ -221,7 +191,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);
 
@@ -246,6 +216,26 @@
         assertThat(new File(mTestFinalResultDir, 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/DataBrokerUnitTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerTest.java
similarity index 76%
rename from tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerUnitTest.java
rename to tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerTest.java
index 4193ca5..6c41e4b 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerTest.java
@@ -48,16 +48,17 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.Collections;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.PriorityBlockingQueue;
+import java.util.concurrent.TimeUnit;
 
 @RunWith(MockitoJUnitRunner.class)
-public class DataBrokerUnitTest {
+public class DataBrokerTest {
     private static final int PROP_ID = 100;
     private static final int PROP_AREA = 200;
     private static final int PRIORITY_HIGH = 1;
@@ -66,7 +67,6 @@
     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(
@@ -87,9 +87,12 @@
             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 Handler mHandler;
     private ScriptExecutionTask mHighPriorityTask;
     private ScriptExecutionTask mLowPriorityTask;
 
@@ -98,6 +101,8 @@
     @Mock
     private CarPropertyService mMockCarPropertyService;
     @Mock
+    private Handler mMockHandler;
+    @Mock
     private StatsManagerProxy mMockStatsManager;
     @Mock
     private SharedPreferences mMockSharedPreferences;
@@ -106,9 +111,6 @@
     @Mock
     private ResultStore mMockResultStore;
 
-    @Captor
-    private ArgumentCaptor<ServiceConnection> mServiceConnectionCaptor;
-
     @Before
     public void setUp() {
         when(mMockCarPropertyService.getPropertyList())
@@ -116,31 +118,36 @@
         // 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);
+                mMockCarPropertyService, mMockHandler, mMockStatsManager, mMockSharedPreferences);
         mDataBroker = new DataBrokerImpl(mMockContext, factory, mMockResultStore);
-        mHandler = mDataBroker.getWorkerHandler();
+        // 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);
-        // capture ServiceConnection and connect to fake ScriptExecutor
-        verify(mMockContext).bindServiceAsUser(
-                any(), mServiceConnectionCaptor.capture(), anyInt(), any());
-        mServiceConnectionCaptor.getValue().onServiceConnected(
-                null, mMockScriptExecutorBinder);
+        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),
-                DATA,
+                mData,
                 SystemClock.elapsedRealtime());
         mLowPriorityTask = new ScriptExecutionTask(
                 new DataSubscriber(mDataBroker, METRICS_CONFIG_BAR, SUBSCRIBER_BAR),
-                DATA,
+                mData,
                 SystemClock.elapsedRealtime());
     }
 
     @Test
-    public void testSetTaskExecutionPriority_whenNoTask_shouldNotInvokeScriptExecutor() {
+    public void testSetTaskExecutionPriority_whenNoTask_shouldNotInvokeScriptExecutor()
+            throws Exception {
         mDataBroker.setTaskExecutionPriority(PRIORITY_HIGH);
 
         waitForHandlerThreadToFinish();
@@ -148,7 +155,8 @@
     }
 
     @Test
-    public void testSetTaskExecutionPriority_whenNextTaskPriorityLow_shouldNotRunTask() {
+    public void testSetTaskExecutionPriority_whenNextTaskPriorityLow_shouldNotRunTask()
+            throws Exception {
         mDataBroker.getTaskQueue().add(mLowPriorityTask);
 
         mDataBroker.setTaskExecutionPriority(PRIORITY_HIGH);
@@ -160,7 +168,8 @@
     }
 
     @Test
-    public void testSetTaskExecutionPriority_whenNextTaskPriorityHigh_shouldInvokeScriptExecutor() {
+    public void testSetTaskExecutionPriority_whenNextTaskPriorityHigh_shouldInvokeScriptExecutor()
+            throws Exception {
         mDataBroker.getTaskQueue().add(mHighPriorityTask);
 
         mDataBroker.setTaskExecutionPriority(PRIORITY_HIGH);
@@ -172,7 +181,7 @@
     }
 
     @Test
-    public void testScheduleNextTask_whenNoTask_shouldNotInvokeScriptExecutor() {
+    public void testScheduleNextTask_whenNoTask_shouldNotInvokeScriptExecutor() throws Exception {
         mDataBroker.scheduleNextTask();
 
         waitForHandlerThreadToFinish();
@@ -180,7 +189,8 @@
     }
 
     @Test
-    public void testScheduleNextTask_whenTaskInProgress_shouldNotInvokeScriptExecutorAgain() {
+    public void testScheduleNextTask_whenTaskInProgress_shouldNotInvokeScriptExecutorAgain()
+            throws Exception {
         PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
         taskQueue.add(mHighPriorityTask);
         mDataBroker.scheduleNextTask(); // start a task
@@ -190,6 +200,7 @@
 
         mDataBroker.scheduleNextTask(); // schedule next task while the last task is in progress
 
+        waitForHandlerThreadToFinish();
         // verify task is not polled
         assertThat(taskQueue.peek()).isEqualTo(mHighPriorityTask);
         // expect one invocation for the task that is running
@@ -197,7 +208,8 @@
     }
 
     @Test
-    public void testScheduleNextTask_whenTaskCompletes_shouldAutomaticallyScheduleNextTask() {
+    public void testScheduleNextTask_whenTaskCompletes_shouldAutomaticallyScheduleNextTask()
+            throws Exception {
         PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
         // add two tasks into the queue for execution
         taskQueue.add(mHighPriorityTask);
@@ -206,7 +218,7 @@
         mDataBroker.scheduleNextTask(); // start a task
         waitForHandlerThreadToFinish();
         // end a task, should automatically schedule the next task
-        mFakeScriptExecutor.notifyScriptSuccess(DATA);
+        mFakeScriptExecutor.notifyScriptSuccess(mData); // posts to telemetry handler
 
         waitForHandlerThreadToFinish();
         // verify queue is empty, both tasks are polled and executed
@@ -215,30 +227,62 @@
     }
 
     @Test
-    public void testScheduleNextTask_onScriptSuccess_shouldStoreInterimResult() {
+    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);
-        ArgumentCaptor<PersistableBundle> persistableBundleCaptor =
-                ArgumentCaptor.forClass(PersistableBundle.class);
 
         mDataBroker.scheduleNextTask();
         waitForHandlerThreadToFinish();
-        mFakeScriptExecutor.notifyScriptSuccess(new PersistableBundle()); // pass in empty bundle
+        mFakeScriptExecutor.notifyScriptSuccess(mData); // posts to telemetry handler
 
+        waitForHandlerThreadToFinish();
         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
+        verify(mMockResultStore).putInterimResult(
+                eq(mHighPriorityTask.getMetricsConfig().getName()), eq(mData));
     }
 
     @Test
-    public void testScheduleNextTask_whenBindScriptExecutorFailed_shouldDisableBroker() {
+    public void testScheduleNextTask_whenScriptFinishes_shouldStoreFinalResult()
+            throws Exception {
+        mData.putBoolean("script is finished", true);
+        mData.putDouble("value of pi", 3.14159265359);
+        mDataBroker.getTaskQueue().add(mHighPriorityTask);
+
+        mDataBroker.scheduleNextTask();
+        waitForHandlerThreadToFinish();
+        mFakeScriptExecutor.notifyScriptFinish(mData); // posts to telemetry handler
+
+        waitForHandlerThreadToFinish();
+        assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(1);
+        verify(mMockResultStore).putFinalResult(
+                eq(mHighPriorityTask.getMetricsConfig().getName()), eq(mData));
+    }
+
+    @Test
+    public void testScheduleNextTask_whenInterimDataExists_shouldPassToScriptExecutor()
+            throws Exception {
+        mData.putDouble("value of golden ratio", 1.618033);
+        mDataBroker.getTaskQueue().add(mHighPriorityTask);
+        when(mMockResultStore.getInterimResult(mHighPriorityTask.getMetricsConfig().getName()))
+                .thenReturn(mData);
+
+        mDataBroker.scheduleNextTask();
+
+        waitForHandlerThreadToFinish();
+        assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(1);
+        assertThat(mFakeScriptExecutor.getSavedState()).isEqualTo(mData);
+    }
+
+    @Test
+    public void testScheduleNextTask_whenBindScriptExecutorFailed_shouldDisableBroker()
+            throws Exception {
+        // fail all future attempts to bind to it
+        Mockito.reset(mMockContext);
+        when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(false);
         mDataBroker.addMetricsConfiguration(METRICS_CONFIG_FOO);
         PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
         taskQueue.add(mHighPriorityTask);
-        // 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();
@@ -250,7 +294,8 @@
     }
 
     @Test
-    public void testScheduleNextTask_whenScriptExecutorFails_shouldRequeueTask() {
+    public void testScheduleNextTask_whenScriptExecutorThrowsException_shouldRequeueTask()
+            throws Exception {
         PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
         taskQueue.add(mHighPriorityTask);
         mFakeScriptExecutor.failNextApiCalls(1); // fail the next invokeScript() call
@@ -264,7 +309,7 @@
     }
 
     @Test
-    public void testAddTaskToQueue_shouldInvokeScriptExecutor() {
+    public void testAddTaskToQueue_shouldInvokeScriptExecutor() throws Exception {
         mDataBroker.addTaskToQueue(mHighPriorityTask);
 
         waitForHandlerThreadToFinish();
@@ -275,7 +320,6 @@
     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
@@ -288,7 +332,6 @@
         // 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);
@@ -300,7 +343,7 @@
         mDataBroker.addMetricsConfiguration(METRICS_CONFIG_BAR);
         ScriptExecutionTask taskWithMetricsConfigFoo = new ScriptExecutionTask(
                 new DataSubscriber(mDataBroker, METRICS_CONFIG_FOO, SUBSCRIBER_FOO),
-                DATA,
+                mData,
                 SystemClock.elapsedRealtime());
         PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
         taskQueue.add(mHighPriorityTask); // associated with METRICS_CONFIG_FOO
@@ -310,7 +353,6 @@
 
         mDataBroker.removeMetricsConfiguration(METRICS_CONFIG_FOO);
 
-        waitForHandlerThreadToFinish();
         assertThat(taskQueue).hasSize(1);
         assertThat(taskQueue.poll()).isEqualTo(mLowPriorityTask);
     }
@@ -319,19 +361,20 @@
     public void testRemoveMetricsConfiguration_whenMetricsConfigNonExistent_shouldDoNothing() {
         mDataBroker.removeMetricsConfiguration(METRICS_CONFIG_BAR);
 
-        waitForHandlerThreadToFinish();
         assertThat(mDataBroker.getSubscriptionMap()).hasSize(0);
     }
 
-    private void waitForHandlerThreadToFinish() {
+    private void waitForHandlerThreadToFinish() throws Exception {
         assertWithMessage("handler not idle in %sms", TIMEOUT_MS)
-                .that(mHandler.runWithScissors(() -> {}, TIMEOUT_MS)).isTrue();
+                .that(mIdleHandlerLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+        mIdleHandlerLatch = new CountDownLatch(1); // reset idle handler condition
     }
 
     private static class FakeScriptExecutor implements IScriptExecutor {
         private IScriptExecutorListener mListener;
         private int mApiInvocationCount = 0;
         private int mFailApi = 0;
+        private PersistableBundle mSavedState = null;
 
         @Override
         public void invokeScript(String scriptBody, String functionName,
@@ -339,6 +382,7 @@
                 IScriptExecutorListener listener)
                 throws RemoteException {
             mApiInvocationCount++;
+            mSavedState = savedState;
             mListener = listener;
             if (mFailApi > 0) {
                 mFailApi--;
@@ -360,6 +404,15 @@
             }
         }
 
+        /** Mocks script producing final result. */
+        public void notifyScriptFinish(PersistableBundle bundle) {
+            try {
+                mListener.onScriptFinished(bundle);
+            } catch (RemoteException e) {
+                // nothing to do
+            }
+        }
+
         /** Fails the next N invokeScript() call. */
         public void failNextApiCalls(int n) {
             mFailApi = n;
@@ -369,5 +422,10 @@
         public int getApiInvocationCount() {
             return mApiInvocationCount;
         }
+
+        /** Returns the interim data passed in invokeScript(). */
+        public PersistableBundle getSavedState() {
+            return mSavedState;
+        }
     }
 }
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/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/StatsPublisherTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/StatsPublisherTest.java
index c177a4d..a672bd5 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
@@ -23,11 +23,14 @@
 
 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;
@@ -93,6 +96,8 @@
     private final FakeSharedPreferences mFakeSharedPref = new FakeSharedPreferences();
     private final FakeHandlerWrapper mFakeHandlerWrapper =
             new FakeHandlerWrapper(Looper.getMainLooper(), FakeHandlerWrapper.Mode.QUEUEING);
+    private Throwable mPublisherFailure;
+
     @Mock private DataSubscriber mMockDataSubscriber;
     @Mock private StatsManagerProxy mStatsManager;
 
@@ -100,8 +105,7 @@
 
     @Before
     public void setUp() throws Exception {
-        mPublisher = new StatsPublisher(
-                mStatsManager, mFakeSharedPref, mFakeHandlerWrapper.getMockHandler());
+        mPublisher = createRestartedPublisher();
         when(mMockDataSubscriber.getPublisherParam()).thenReturn(STATS_PUBLISHER_PARAMS_1);
         when(mMockDataSubscriber.getMetricsConfig()).thenReturn(METRICS_CONFIG);
         when(mMockDataSubscriber.getSubscriber()).thenReturn(SUBSCRIBER_1);
@@ -113,7 +117,10 @@
      */
     private StatsPublisher createRestartedPublisher() {
         return new StatsPublisher(
-                mStatsManager, mFakeSharedPref, mFakeHandlerWrapper.getMockHandler());
+                this::onPublisherFailure,
+                mStatsManager,
+                mFakeSharedPref,
+                mFakeHandlerWrapper.getMockHandler());
     }
 
     @Test
@@ -193,6 +200,16 @@
     }
 
     @Test
+    public void testAddDataSubscriber_whenFails_notifiesFailureConsumer() throws Exception {
+        doThrow(new StatsManager.StatsUnavailableException("fail"))
+                .when(mStatsManager).addConfig(anyLong(), any());
+
+        mPublisher.addDataSubscriber(mMockDataSubscriber);
+
+        assertThat(mPublisherFailure).hasMessageThat().contains("Failed to add config");
+    }
+
+    @Test
     public void testRemoveDataSubscriber_removesPeriodicStatsdReportPull() {
         mPublisher.addDataSubscriber(mMockDataSubscriber);
 
@@ -241,8 +258,12 @@
         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
+    // TODO(b/189143813): add test cases when connecting to Statsd fails
+    // TODO(b/189143813): add test cases for handling config version upgrades
+
+    private void onPublisherFailure(AbstractPublisher publisher, Throwable error) {
+        mPublisherFailure = error;
+    }
 
     private static void assertThatMessageIsScheduledWithGivenDelay(Message msg, long delayMillis) {
         long expectedTimeMillis = SystemClock.uptimeMillis() + delayMillis;
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) { }
 }