[go: nahoru, domu]

Register/Unregister implementation of AtomotiveCarClimate.

The CL only covers a basic successful response unit test
for AutomotiveCarClimate APIs. Thorough testing will be performed
in follow-up CLs.

Relnote: Adding register/unregister APIs for AutomotiveCarClimate.

Bug: 215225317
Test: ./gradlew :car:app:app-automotive:build
Change-Id: If2dd39d94a58566240d761d75a9265f82cb3e430
diff --git a/car/app/app-automotive/build.gradle b/car/app/app-automotive/build.gradle
index 4314d26..27a74b0 100644
--- a/car/app/app-automotive/build.gradle
+++ b/car/app/app-automotive/build.gradle
@@ -34,6 +34,7 @@
     // There is an implicit compile-only dep due to :annotation-experimental
     // Build will complain without this manual declaration.
     api(libs.kotlinStdlib)
+    implementation project(path: ':annotation:annotation-experimental')
 
     annotationProcessor(libs.nullaway)
     annotationProcessor(libs.autoValue)
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/AutomotiveCarHardwareManager.java b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/AutomotiveCarHardwareManager.java
index acd7164..445bfbb 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/AutomotiveCarHardwareManager.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/AutomotiveCarHardwareManager.java
@@ -47,9 +47,10 @@
 
     public AutomotiveCarHardwareManager(@NonNull Context context) {
         Context appContext = requireNonNull(context.getApplicationContext());
-        mCarInfo = new AutomotiveCarInfo(new PropertyManager(appContext));
+        PropertyManager mPropertyManager = new PropertyManager(appContext);
+        mCarInfo = new AutomotiveCarInfo(mPropertyManager);
         mCarSensors = new AutomotiveCarSensors();
-        mCarClimate = new AutomotiveCarClimate();
+        mCarClimate = new AutomotiveCarClimate(mPropertyManager);
     }
 
     /** @hide */
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/climate/AutomotiveCarClimate.java b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/climate/AutomotiveCarClimate.java
index 68caf91..6af4dfd 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/climate/AutomotiveCarClimate.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/climate/AutomotiveCarClimate.java
@@ -16,13 +16,61 @@
 
 package androidx.car.app.hardware.climate;
 
+import static android.car.VehiclePropertyIds.HVAC_AC_ON;
+import static android.car.VehiclePropertyIds.HVAC_AUTO_ON;
+import static android.car.VehiclePropertyIds.HVAC_AUTO_RECIRC_ON;
+import static android.car.VehiclePropertyIds.HVAC_DEFROSTER;
+import static android.car.VehiclePropertyIds.HVAC_DUAL_ON;
+import static android.car.VehiclePropertyIds.HVAC_FAN_DIRECTION;
+import static android.car.VehiclePropertyIds.HVAC_FAN_SPEED;
+import static android.car.VehiclePropertyIds.HVAC_MAX_AC_ON;
+import static android.car.VehiclePropertyIds.HVAC_MAX_DEFROST_ON;
+import static android.car.VehiclePropertyIds.HVAC_POWER_ON;
+import static android.car.VehiclePropertyIds.HVAC_RECIRC_ON;
+import static android.car.VehiclePropertyIds.HVAC_SEAT_TEMPERATURE;
+import static android.car.VehiclePropertyIds.HVAC_SEAT_VENTILATION;
+import static android.car.VehiclePropertyIds.HVAC_STEERING_WHEEL_HEAT;
+import static android.car.VehiclePropertyIds.HVAC_TEMPERATURE_SET;
+
 import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+import static androidx.car.app.hardware.climate.ClimateProfileRequest.FEATURE_CABIN_TEMPERATURE;
+import static androidx.car.app.hardware.climate.ClimateProfileRequest.FEATURE_FAN_DIRECTION;
+import static androidx.car.app.hardware.climate.ClimateProfileRequest.FEATURE_FAN_SPEED;
+import static androidx.car.app.hardware.climate.ClimateProfileRequest.FEATURE_HVAC_AC;
+import static androidx.car.app.hardware.climate.ClimateProfileRequest.FEATURE_HVAC_AUTO_MODE;
+import static androidx.car.app.hardware.climate.ClimateProfileRequest.FEATURE_HVAC_AUTO_RECIRCULATION;
+import static androidx.car.app.hardware.climate.ClimateProfileRequest.FEATURE_HVAC_DEFROSTER;
+import static androidx.car.app.hardware.climate.ClimateProfileRequest.FEATURE_HVAC_DUAL_MODE;
+import static androidx.car.app.hardware.climate.ClimateProfileRequest.FEATURE_HVAC_MAX_AC;
+import static androidx.car.app.hardware.climate.ClimateProfileRequest.FEATURE_HVAC_MAX_DEFROSTER;
+import static androidx.car.app.hardware.climate.ClimateProfileRequest.FEATURE_HVAC_POWER;
+import static androidx.car.app.hardware.climate.ClimateProfileRequest.FEATURE_HVAC_RECIRCULATION;
+import static androidx.car.app.hardware.climate.ClimateProfileRequest.FEATURE_SEAT_TEMPERATURE_LEVEL;
+import static androidx.car.app.hardware.climate.ClimateProfileRequest.FEATURE_SEAT_VENTILATION_LEVEL;
+import static androidx.car.app.hardware.climate.ClimateProfileRequest.FEATURE_STEERING_WHEEL_HEAT;
+import static androidx.car.app.hardware.common.CarValueUtils.getCarValue;
+
+import static java.util.Objects.requireNonNull;
+
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
 import androidx.car.app.annotations.ExperimentalCarApi;
+import androidx.car.app.hardware.common.CarPropertyResponse;
 import androidx.car.app.hardware.common.CarSetOperationStatusCallback;
+import androidx.car.app.hardware.common.CarValue;
+import androidx.car.app.hardware.common.CarZone;
+import androidx.car.app.hardware.common.OnCarPropertyResponseListener;
+import androidx.car.app.hardware.common.PropertyManager;
+import androidx.car.app.utils.LogTags;
 
+import com.google.common.collect.ImmutableBiMap;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.concurrent.Executor;
 
 /**
@@ -34,19 +82,60 @@
 @ExperimentalCarApi
 public class AutomotiveCarClimate implements CarClimate {
 
-    public AutomotiveCarClimate() {
+    @VisibleForTesting
+    static final float DEFAULT_SAMPLE_RATE_HZ = 5f;
+    static ImmutableBiMap<Integer, Integer> sFeatureToPropertyId =
+            new ImmutableBiMap.Builder<Integer,
+            Integer>()
+                    .put(FEATURE_HVAC_POWER, HVAC_POWER_ON)
+                    .put(FEATURE_HVAC_AC, HVAC_AC_ON)
+                    .put(FEATURE_HVAC_MAX_AC, HVAC_MAX_AC_ON)
+                    .put(FEATURE_CABIN_TEMPERATURE, HVAC_TEMPERATURE_SET)
+                    .put(FEATURE_FAN_SPEED, HVAC_FAN_SPEED)
+                    .put(FEATURE_FAN_DIRECTION, HVAC_FAN_DIRECTION)
+                    .put(FEATURE_SEAT_TEMPERATURE_LEVEL, HVAC_SEAT_TEMPERATURE)
+                    .put(FEATURE_SEAT_VENTILATION_LEVEL, HVAC_SEAT_VENTILATION)
+                    .put(FEATURE_STEERING_WHEEL_HEAT, HVAC_STEERING_WHEEL_HEAT)
+                    .put(FEATURE_HVAC_RECIRCULATION, HVAC_RECIRC_ON)
+                    .put(FEATURE_HVAC_AUTO_RECIRCULATION, HVAC_AUTO_RECIRC_ON)
+                    .put(FEATURE_HVAC_AUTO_MODE, HVAC_AUTO_ON)
+                    .put(FEATURE_HVAC_DUAL_MODE, HVAC_DUAL_ON)
+                    .put(FEATURE_HVAC_DEFROSTER, HVAC_DEFROSTER)
+                    .put(FEATURE_HVAC_MAX_DEFROSTER, HVAC_MAX_DEFROST_ON)
+            .buildOrThrow();
+
+    private final Map<CarClimateStateCallback, OnCarPropertyResponseListener> mListenerMap =
+            new HashMap<>();
+
+    private final PropertyManager mPropertyManager;
+
+    public AutomotiveCarClimate(@NonNull PropertyManager manager) {
+        mPropertyManager = requireNonNull(manager);
     }
 
     @Override
     public void registerClimateStateCallback(@NonNull Executor executor,
             @NonNull RegisterClimateStateRequest request,
             @NonNull CarClimateStateCallback callback) {
-
+        Map<Integer, List<CarZone>> propertyIdsWithCarZones = new HashMap<>();
+        for (CarClimateFeature feature : request.getClimateRegisterFeatures()) {
+            int propertyId = requireNonNull(sFeatureToPropertyId.get(feature.getFeature()));
+            propertyIdsWithCarZones.put(propertyId, feature.getCarZones());
+        }
+        PropertyListener listener = new PropertyListener(callback, executor);
+        mPropertyManager.submitRegisterListenerRequest(propertyIdsWithCarZones,
+                DEFAULT_SAMPLE_RATE_HZ,
+                listener,
+                executor);
+        mListenerMap.put(callback, listener);
     }
 
     @Override
     public void unregisterClimateStateCallback(@NonNull CarClimateStateCallback callback) {
-
+        OnCarPropertyResponseListener responseListener = mListenerMap.remove(callback);
+        if (responseListener != null) {
+            mPropertyManager.submitUnregisterListenerRequest(responseListener);
+        }
     }
 
     @Override
@@ -62,4 +151,98 @@
             @NonNull CarSetOperationStatusCallback callback) {
 
     }
+
+    private static class PropertyListener implements OnCarPropertyResponseListener {
+        private final Executor mExecutor;
+        private final CarClimateStateCallback mCarClimateStateCallback;
+
+        PropertyListener(CarClimateStateCallback callback, Executor executor) {
+            mCarClimateStateCallback = callback;
+            mExecutor = executor;
+        }
+
+        @Override
+        @SuppressWarnings({"unchecked", "unsafe"})
+        public void onCarPropertyResponses(
+                @NonNull List<CarPropertyResponse<?>> carPropertyResponses) {
+            mExecutor.execute(() -> {
+                for (CarPropertyResponse<?> response : carPropertyResponses) {
+                    Integer mFeature = sFeatureToPropertyId.inverse().get(response.getPropertyId());
+                    if (mFeature == null) {
+                        Log.e(LogTags.TAG_CAR_HARDWARE, "Feature not found for property Id "
+                                + response.getPropertyId());
+                        continue;
+                    }
+                    CarValue<?> mCarValue = getCarValue(response, response.getValue());
+                    switch (mFeature) {
+                        case FEATURE_HVAC_POWER:
+                            mCarClimateStateCallback.onHvacPowerStateAvailable(
+                                    (CarValue<Boolean>) mCarValue);
+                            break;
+                        case FEATURE_HVAC_AC:
+                            mCarClimateStateCallback.onHvacAcStateAvailable(
+                                    (CarValue<Boolean>) mCarValue);
+                            break;
+                        case FEATURE_HVAC_MAX_AC:
+                            mCarClimateStateCallback.onHvacMaxAcModeStateAvailable(
+                                    (CarValue<Boolean>) mCarValue);
+                            break;
+                        case FEATURE_CABIN_TEMPERATURE:
+                            mCarClimateStateCallback.onCabinTemperatureStateAvailable(
+                                    (CarValue<Float>) mCarValue);
+                            break;
+                        case FEATURE_FAN_SPEED:
+                            mCarClimateStateCallback.onFanSpeedLevelStateAvailable(
+                                    (CarValue<Integer>) mCarValue);
+                            break;
+                        case FEATURE_FAN_DIRECTION:
+                            mCarClimateStateCallback.onFanDirectionStateAvailable(
+                                    (CarValue<Integer>) mCarValue);
+                            break;
+                        case FEATURE_SEAT_TEMPERATURE_LEVEL:
+                            mCarClimateStateCallback.onSeatTemperatureLevelStateAvailable(
+                                    (CarValue<Integer>) mCarValue);
+                            break;
+                        case FEATURE_SEAT_VENTILATION_LEVEL:
+                            mCarClimateStateCallback.onSeatVentilationLevelStateAvailable(
+                                    (CarValue<Integer>) mCarValue);
+                            break;
+                        case FEATURE_STEERING_WHEEL_HEAT:
+                            mCarClimateStateCallback.onSteeringWheelHeatStateAvailable(
+                                    (CarValue<Boolean>) mCarValue);
+                            break;
+                        case FEATURE_HVAC_RECIRCULATION:
+                            mCarClimateStateCallback.onHvacRecirculationStateAvailable(
+                                    (CarValue<Boolean>) mCarValue);
+                            break;
+                        case FEATURE_HVAC_AUTO_RECIRCULATION:
+                            mCarClimateStateCallback.onHvacAutoRecirculationStateAvailable(
+                                    (CarValue<Boolean>) mCarValue);
+                            break;
+                        case FEATURE_HVAC_AUTO_MODE:
+                            mCarClimateStateCallback.onHvacAutoModeStateAvailable(
+                                    (CarValue<Boolean>) mCarValue);
+                            break;
+                        case FEATURE_HVAC_DUAL_MODE:
+                            mCarClimateStateCallback.onHvacDualModeStateAvailable(
+                                    (CarValue<Boolean>) mCarValue);
+                            break;
+                        case FEATURE_HVAC_DEFROSTER:
+                            mCarClimateStateCallback.onDefrosterStateAvailable(
+                                    (CarValue<Boolean>) mCarValue);
+                            break;
+                        case FEATURE_HVAC_MAX_DEFROSTER:
+                            mCarClimateStateCallback.onMaxDefrosterStateAvailable(
+                                    (CarValue<Boolean>) mCarValue);
+                            break;
+                        default:
+                            Log.e(LogTags.TAG_CAR_HARDWARE,
+                                    "Invalid response callback in PropertyListener with "
+                                            + "feature value: " + mFeature);
+                            break;
+                    }
+                }
+            });
+        }
+    }
 }
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/CarValueUtils.java b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/CarValueUtils.java
new file mode 100644
index 0000000..381c855
--- /dev/null
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/CarValueUtils.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.hardware.common;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
+import androidx.annotation.RestrictTo;
+import androidx.car.app.annotations.ExperimentalCarApi;
+
+import java.util.List;
+
+/**
+ * Utility functions to work with {@link androidx.car.app.hardware.info.AutomotiveCarInfo} and
+ * {@link androidx.car.app.hardware.climate.AutomotiveCarClimate}
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public final class CarValueUtils {
+    /**
+     * Gets a {@link androidx.car.app.hardware.common.CarValue} object from
+     * {@link androidx.car.app.hardware.common.CarPropertyResponse}
+     */
+    @NonNull
+    @OptIn(markerClass = ExperimentalCarApi.class)
+    public static <T> CarValue<T> getCarValue(@NonNull CarPropertyResponse<?> response,
+            @Nullable T value) {
+        long timestampMillis = response.getTimestampMillis();
+        int status = response.getStatus();
+        List<CarZone> zones =  response.getCarZones();
+        return new CarValue<>(value, timestampMillis, status, zones);
+    }
+
+    private CarValueUtils() {
+    }
+}
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyIdAreaId.java b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyIdAreaId.java
new file mode 100644
index 0000000..93d568b
--- /dev/null
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyIdAreaId.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.hardware.common;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.OptIn;
+import androidx.annotation.RestrictTo;
+import androidx.car.app.annotations.ExperimentalCarApi;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * Container class for information about property Ids and area Ids.
+ *
+ * <p>The hash code generated by the auto class is used as uId.
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+@AutoValue
+public abstract class PropertyIdAreaId {
+
+    /** Returns one of the property Ids in {@link android.car.VehiclePropertyIds}. */
+    public abstract int getPropertyId();
+
+    /** Returns one of area Ids in {@link android.car.VehicleAreaSeat}. */
+    public abstract int getAreaId();
+
+    /** Get a builder class for {@link PropertyIdAreaId}*/
+    @NonNull
+    @OptIn(markerClass = ExperimentalCarApi.class)
+    public static PropertyIdAreaId.Builder builder() {
+        return new AutoValue_PropertyIdAreaId.Builder().setAreaId(0);
+    }
+
+    /**
+     * A builder for {@link PropertyIdAreaId}
+     */
+    @AutoValue.Builder
+    public abstract static class Builder {
+        /** Sets a property ID for the {@link PropertyIdAreaId}. */
+        @NonNull
+        public abstract PropertyIdAreaId.Builder setPropertyId(int propertyId);
+
+        /** Sets an area Id for the {@link PropertyIdAreaId}. */
+        @NonNull
+        public abstract PropertyIdAreaId.Builder setAreaId(int areaId);
+
+        /** Create an instance of {@link PropertyIdAreaId}. */
+        @NonNull
+        public abstract PropertyIdAreaId build();
+    }
+
+}
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyManager.java b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyManager.java
index 59d7a1c..47ddc93 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyManager.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyManager.java
@@ -18,7 +18,6 @@
 
 import static androidx.annotation.RestrictTo.Scope.LIBRARY;
 
-import android.car.VehicleAreaType;
 import android.car.hardware.CarPropertyValue;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -92,20 +91,25 @@
     /**
      * Submits a request for registering the listener to get property updates.
      *
-     * @param propertyIds           a list of property id in {@link android.car.VehiclePropertyIds}
+     * @param propertyIdsToCarZones a map of property id in {@link android.car.VehiclePropertyIds}
+     *                              to their CarZones
      * @param sampleRate            sample rate in Hz
      * @param listener              {@link OnCarPropertyResponseListener}
      * @param executor              execute the task for registering properties
-     * @throws SecurityException    if the application did not grant permissions for
-     *                              registering properties
+     * @throws SecurityException if the application did not grant permissions for
+     *                           registering properties
      */
     @SuppressWarnings("FutureReturnValueIgnored")
-    public void submitRegisterListenerRequest(@NonNull List<Integer> propertyIds, float sampleRate,
+    public void submitRegisterListenerRequest(
+            @NonNull Map<Integer, List<CarZone>> propertyIdsToCarZones, float sampleRate,
             @NonNull OnCarPropertyResponseListener listener, @NonNull Executor executor) {
+        List<PropertyIdAreaId> propertyIdWithAreaIds =
+                PropertyUtils.getPropertyIdWithAreaIds(propertyIdsToCarZones);
+        List<Integer> propertyIds = new ArrayList<>(propertyIdsToCarZones.keySet());
         checkPermissions(propertyIds);
         long samplingIntervalMs;
         synchronized (mLock) {
-            mListenerAndResponseCache.putListenerAndPropertyIds(listener, propertyIds);
+            mListenerAndResponseCache.putListenerAndUIds(listener, propertyIdWithAreaIds);
             if (sampleRate == 0) {
                 throw new IllegalArgumentException("Sample rate cannot be zero.");
             }
@@ -115,66 +119,70 @@
 
         // register properties
         executor.execute(() -> {
-            for (int propertyId : propertyIds) {
+            for (PropertyIdAreaId propertyIdAndAreadId : propertyIdWithAreaIds) {
                 try {
-                    mPropertyRequestProcessor.registerProperty(propertyId, sampleRate);
+                    mPropertyRequestProcessor.registerProperty(propertyIdAndAreadId.getPropertyId(),
+                            sampleRate);
                 } catch (IllegalArgumentException e) {
                     // the property is not implemented
                     Log.e(LogTags.TAG_CAR_HARDWARE,
-                            "Failed to register for property: " + propertyId, e);
-                    mPropertyProcessorCallback.onErrorEvent(CarInternalError.create(propertyId,
-                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
-                            CarValue.STATUS_UNIMPLEMENTED));
+                            "Failed to register for property: "
+                                    + propertyIdAndAreadId.getPropertyId(), e);
+                    mPropertyProcessorCallback.onErrorEvent(
+                            CarInternalError.create(propertyIdAndAreadId.getPropertyId(),
+                                    propertyIdAndAreadId.getAreaId(),
+                                    CarValue.STATUS_UNIMPLEMENTED));
                 } catch (Exception e) {
                     Log.e(LogTags.TAG_CAR_HARDWARE,
-                            "Failed to register for property: " + propertyId, e);
-                    mPropertyProcessorCallback.onErrorEvent(CarInternalError.create(propertyId,
-                            VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, CarValue.STATUS_UNAVAILABLE));
+                            "Failed to register for property: "
+                                    + propertyIdAndAreadId.getPropertyId(), e);
+                    mPropertyProcessorCallback.onErrorEvent(
+                            CarInternalError.create(propertyIdAndAreadId.getPropertyId(),
+                                    propertyIdAndAreadId.getAreaId(), CarValue.STATUS_UNAVAILABLE));
                 }
             }
         });
-        mScheduledExecutorService.schedule(()-> dispatchResponseWithDelay(listener),
+        mScheduledExecutorService.schedule(() -> dispatchResponseWithDelay(listener),
                 samplingIntervalMs, TimeUnit.MILLISECONDS);
     }
 
     /**
      * Submits a request for unregistering the listener to get property updates.
      *
-     * @param listener                  {@link OnCarPropertyResponseListener} to be unregistered.
-     * @throws IllegalStateException    if the listener was not registered yet
-     * @throws SecurityException        if the application did not grant permissions for
-     *                                  unregistering properties
+     * @param listener {@link OnCarPropertyResponseListener} to be unregistered.
+     * @throws IllegalStateException if the listener was not registered yet
+     * @throws SecurityException     if the application did not grant permissions for
+     *                               unregistering properties
      */
     public void submitUnregisterListenerRequest(@NonNull OnCarPropertyResponseListener listener) {
-        List<Integer> propertyIds;
-        List<Integer> propertyIdsToBeUnregistered;
+        List<PropertyIdAreaId> uIdsToBeUnregistered;
         synchronized (mLock) {
-            propertyIds = mListenerAndResponseCache.getPropertyIdsByListener(listener);
-            if (propertyIds == null) {
+            if (mListenerAndResponseCache.getUIdsByListener(listener).isEmpty()) {
                 throw new IllegalStateException("Listener was not registered yet.");
             }
-            propertyIdsToBeUnregistered = mListenerAndResponseCache.removeListener(listener);
+            uIdsToBeUnregistered = mListenerAndResponseCache.removeListener(listener);
+            if (uIdsToBeUnregistered.size() == 0) {
+                Log.w(LogTags.TAG_CAR_HARDWARE, "No property was unregistered.");
+                return;
+            }
             mListenerToSamplingIntervalMap.remove(listener);
         }
-        if (propertyIdsToBeUnregistered.size() != 0) {
-            mScheduledExecutorService.execute(() -> {
-                for (int propertyId : propertyIdsToBeUnregistered) {
-                    mPropertyRequestProcessor.unregisterProperty(propertyId);
-                }
-            });
-        }
+        mScheduledExecutorService.execute(() -> {
+            for (PropertyIdAreaId uId : uIdsToBeUnregistered) {
+                mPropertyRequestProcessor.unregisterProperty(uId.getPropertyId());
+            }
+        });
     }
 
     /**
      * Submits {@link CarPropertyResponse} for getting property values.
      *
-     * @param rawRequests           a list of {@link GetPropertyRequest}
-     * @param executor              executes the expensive operation such as fetching property
-     *                              values from cars
-     * @throws SecurityException    if the application did not grant permissions for getting
-     *                              property
-     *
+     * @param rawRequests a list of {@link GetPropertyRequest}
+     * @param executor    executes the expensive operation such as fetching property
+     *                    values from cars
      * @return {@link ListenableFuture} contains a list of {@link CarPropertyResponse}
+     * @throws SecurityException if the application did not grant permissions for getting
+     *                           property
      */
     @NonNull
     public ListenableFuture<List<CarPropertyResponse<?>>> submitGetPropertyRequest(
@@ -187,9 +195,8 @@
         List<Pair<Integer, Integer>> requests = parseRawRequest(rawRequests);
         return CallbackToFutureAdapter.getFuture(completer -> {
             // Getting properties' value is expensive operation.
-            executor.execute(() ->
-                    mPropertyRequestProcessor.fetchCarPropertyValues(requests, (values, errors) ->
-                                    completer.set(createResponses(values, errors))));
+            executor.execute(() -> mPropertyRequestProcessor.fetchCarPropertyValues(requests,
+                    (values, errors) -> completer.set(createResponses(values, errors))));
             return "Get property values done";
         });
     }
@@ -199,12 +206,12 @@
      *
      * <p>For on_change properties and error events, dispatches them to listeners without delay.
      *
-     * @param propertyId property id in {@link android.car.VehiclePropertyIds}
+     * @param uId PropertyIdAreaId Class object.
      */
-    void dispatchResponsesWithoutDelay(int propertyId) {
+    void dispatchResponsesWithoutDelay(PropertyIdAreaId uId) {
         synchronized (mLock) {
-            Set<OnCarPropertyResponseListener> listeners =
-                    mListenerAndResponseCache.getListenersByPropertyId(propertyId);
+            List<OnCarPropertyResponseListener> listeners =
+                    mListenerAndResponseCache.getListenersByUId(uId);
             if (listeners == null) {
                 return;
             }
@@ -232,7 +239,7 @@
                 propertyResponses = mListenerAndResponseCache.getResponsesByListener(listener);
 
                 //Schedules for next dispatch
-                mScheduledExecutorService.schedule(()-> dispatchResponseWithDelay(listener),
+                mScheduledExecutorService.schedule(() -> dispatchResponseWithDelay(listener),
                         delayTime, TimeUnit.MILLISECONDS);
             }
         }
@@ -255,8 +262,9 @@
                 if (mListenerAndResponseCache.updateResponseIfNeeded(carPropertyValue)) {
                     int propertyId = carPropertyValue.getPropertyId();
                     if (PropertyUtils.isOnChangeProperty(propertyId)) {
-                        mScheduledExecutorService.execute(() ->
-                                dispatchResponsesWithoutDelay(propertyId));
+                        mScheduledExecutorService.execute(() -> dispatchResponsesWithoutDelay(
+                                PropertyIdAreaId.builder().setPropertyId(propertyId)
+                                        .setAreaId(carPropertyValue.getAreaId()).build()));
                     }
                 }
             }
@@ -266,8 +274,9 @@
         public void onErrorEvent(CarInternalError carInternalError) {
             synchronized (mLock) {
                 mListenerAndResponseCache.updateInternalError(carInternalError);
-                mScheduledExecutorService.execute(() ->
-                        dispatchResponsesWithoutDelay(carInternalError.getPropertyId()));
+                mScheduledExecutorService.execute(() -> dispatchResponsesWithoutDelay(
+                        PropertyIdAreaId.builder().setPropertyId(carInternalError.getPropertyId())
+                                .setAreaId(carInternalError.getAreaId()).build()));
             }
         }
     }
@@ -279,10 +288,9 @@
         for (CarPropertyValue<?> value : propertyValues) {
             carResponses.add(PropertyUtils.convertPropertyValueToPropertyResponse(value));
         }
-        for (CarInternalError error: propertyErrors) {
-            carResponses.add(CarPropertyResponse.builder()
-                    .setPropertyId(error.getPropertyId())
-                    .setStatus(error.getErrorCode()).build());
+        for (CarInternalError error : propertyErrors) {
+            carResponses.add(CarPropertyResponse.builder().setPropertyId(
+                    error.getPropertyId()).setStatus(error.getErrorCode()).build());
         }
         return carResponses;
     }
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyResponseCache.java b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyResponseCache.java
index c87cd94..8bc9dc7 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyResponseCache.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyResponseCache.java
@@ -17,20 +17,20 @@
 package androidx.car.app.hardware.common;
 
 import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+import static androidx.car.app.hardware.common.PropertyUtils.CAR_ZONE_TO_AREA_ID;
 
 import android.car.hardware.CarPropertyValue;
-import android.util.SparseArray;
 
 import androidx.annotation.GuardedBy;
+import androidx.annotation.OptIn;
 import androidx.annotation.RestrictTo;
+import androidx.car.app.annotations.ExperimentalCarApi;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -45,71 +45,70 @@
 
     // key: property Id, value: listener which registered for the key value
     @GuardedBy("mLock")
-    private final SparseArray<Set<OnCarPropertyResponseListener>> mPropertyIdToListeners =
-            new SparseArray<>();
+    private final Map<PropertyIdAreaId, List<OnCarPropertyResponseListener>> mUIdToListeners =
+            new HashMap<>();
 
     // key: listener, value: properties
     @GuardedBy("mLock")
-    private final Map<OnCarPropertyResponseListener, List<Integer>> mListenerToPropertyIds =
+    private final Map<OnCarPropertyResponseListener, List<PropertyIdAreaId>> mListenerToUIds =
             new HashMap<>();
 
     // cache for car property values.
     @GuardedBy("mLock")
-    private final SparseArray<CarPropertyResponse<?>> mPropertyIdToResponse = new SparseArray<>();
+    private final Map<PropertyIdAreaId, CarPropertyResponse<?>> mUIdToResponse = new HashMap<>();
 
     /**
      * Puts the listener and a list of properties that are registered by the listener into cache.
      */
-    void putListenerAndPropertyIds(OnCarPropertyResponseListener listener,
-            List<Integer> propertyIds) {
+    void putListenerAndUIds(OnCarPropertyResponseListener listener, List<PropertyIdAreaId> uIds) {
         synchronized (mLock) {
-            mListenerToPropertyIds.put(listener, propertyIds);
-            for (int propertyId : propertyIds) {
-                Set<OnCarPropertyResponseListener> listenerSet =
-                        mPropertyIdToListeners.get(propertyId, new HashSet<>());
-                listenerSet.add(listener);
-                mPropertyIdToListeners.put(propertyId, listenerSet);
+            mListenerToUIds.put(listener, uIds);
+            for (PropertyIdAreaId uId : uIds) {
+                List<OnCarPropertyResponseListener> listenerList =
+                        mUIdToListeners.getOrDefault(uId, new ArrayList<>());
+                listenerList.add(listener);
+                mUIdToListeners.put(uId, listenerList);
 
                 // add an init value if needed
-                if (mPropertyIdToResponse.get(propertyId) == null) {
-                    mPropertyIdToResponse.put(propertyId,
-                            CarPropertyResponse.builder().setPropertyId(propertyId)
-                                    .setStatus(CarValue.STATUS_UNKNOWN).build());
+                if (mUIdToResponse.get(uId) == null) {
+                    mUIdToResponse.put(uId, CarPropertyResponse.builder()
+                            .setPropertyId(uId.getPropertyId())
+                            .setStatus(CarValue.STATUS_UNKNOWN).build());
                 }
             }
         }
     }
 
     /** Returns a list of properties that are registered by the listener. */
-    List<Integer> getPropertyIdsByListener(OnCarPropertyResponseListener listener) {
+    List<PropertyIdAreaId> getUIdsByListener(OnCarPropertyResponseListener listener) {
         synchronized (mLock) {
-            return mListenerToPropertyIds.getOrDefault(listener, Collections.emptyList());
+            return mListenerToUIds.getOrDefault(listener, Collections.emptyList());
         }
     }
 
     /**
-     * Returns a {@link Set} containing all {@link OnCarPropertyResponseListener} registered the
+     * Returns a {@link List} containing all {@link OnCarPropertyResponseListener} registered the
      * property.
      */
-    Set<OnCarPropertyResponseListener> getListenersByPropertyId(int propertyId) {
+    List<OnCarPropertyResponseListener> getListenersByUId(PropertyIdAreaId uId) {
         synchronized (mLock) {
-            return mPropertyIdToListeners.get(propertyId);
+            return mUIdToListeners.getOrDefault(uId, new ArrayList<>());
         }
     }
 
     /** Gets a list of {@link CarPropertyResponse} that need to be dispatched to the listener. */
-    List<CarPropertyResponse<?>> getResponsesByListener(
-            OnCarPropertyResponseListener listener) {
+    List<CarPropertyResponse<?>> getResponsesByListener(OnCarPropertyResponseListener listener) {
         List<CarPropertyResponse<?>> values = new ArrayList<>();
         synchronized (mLock) {
-            List<Integer> propertyIds = mListenerToPropertyIds.get(listener);
-            if (propertyIds == null) {
+            List<PropertyIdAreaId> uIds = mListenerToUIds.get(listener);
+            if (uIds == null) {
                 return values;
             }
-            for (int propertyId : propertyIds) {
-                // return a response with unknown status if can not find in cache
-                CarPropertyResponse<?> propertyResponse = mPropertyIdToResponse.get(propertyId,
-                        CarPropertyResponse.builder().setPropertyId(propertyId)
+
+            for (PropertyIdAreaId uId : uIds) {
+                // Return a response with unknown status if cannot find in cache.
+                CarPropertyResponse<?> propertyResponse = mUIdToResponse.getOrDefault(uId,
+                        CarPropertyResponse.builder().setPropertyId(uId.getPropertyId())
                                 .setStatus(CarValue.STATUS_UNKNOWN).build());
                 values.add(propertyResponse);
             }
@@ -122,22 +121,22 @@
      *
      * @return a list of property ids that are not registered by any other listener
      */
-    List<Integer> removeListener(OnCarPropertyResponseListener listener) {
-        List<Integer> propertyWithOutListener = new ArrayList<>();
+    List<PropertyIdAreaId> removeListener(OnCarPropertyResponseListener listener) {
+        List<PropertyIdAreaId> propertyWithOutListener = new ArrayList<>();
         synchronized (mLock) {
-            List<Integer> propertyIds = mListenerToPropertyIds.get(listener);
-            mListenerToPropertyIds.remove(listener);
-            if (propertyIds == null) {
+            List<PropertyIdAreaId> uIds = mListenerToUIds.get(listener);
+            mListenerToUIds.remove(listener);
+            if (uIds == null) {
                 throw new IllegalStateException("Listener is not registered yet");
             }
-            for (int propertyId : propertyIds) {
-                Set<OnCarPropertyResponseListener> listenerSet =
-                        mPropertyIdToListeners.get(propertyId);
-                listenerSet.remove(listener);
-                if (listenerSet.isEmpty()) {
-                    propertyWithOutListener.add(propertyId);
-                    mPropertyIdToListeners.remove(propertyId);
-                    mPropertyIdToResponse.remove(propertyId);
+            for (PropertyIdAreaId uId : uIds) {
+                List<OnCarPropertyResponseListener> listenerList = mUIdToListeners.getOrDefault(uId,
+                                new ArrayList<>());
+                listenerList.remove(listener);
+                if (listenerList.isEmpty()) {
+                    propertyWithOutListener.add(uId);
+                    mUIdToListeners.remove(uId);
+                    mUIdToResponse.remove(uId);
                 }
             }
         }
@@ -147,20 +146,24 @@
     /** Returns {@code true} if the value in cache is updated. */
     boolean updateResponseIfNeeded(CarPropertyValue<?> propertyValue) {
         synchronized (mLock) {
-            int propertyId = propertyValue.getPropertyId();
-            CarPropertyResponse<?> responseInCache = mPropertyIdToResponse.get(propertyId);
+            PropertyIdAreaId uId = PropertyIdAreaId.builder()
+                    .setPropertyId(propertyValue.getPropertyId())
+                    .setAreaId(propertyValue.getAreaId()).build();
+
+            CarPropertyResponse<?> responseInCache = mUIdToResponse.get(uId);
+
             if (responseInCache == null) {
                 // the property is unregistered
                 return false;
             }
+
             long timestampMs = TimeUnit.MILLISECONDS.convert(propertyValue.getTimestamp(),
                     TimeUnit.NANOSECONDS);
             // CarService can not guarantee the order of events.
             if (responseInCache.getTimestampMillis() <= timestampMs) {
-                // In V1.1, all properties are global properties.
                 CarPropertyResponse<?> response =
                         PropertyUtils.convertPropertyValueToPropertyResponse(propertyValue);
-                mPropertyIdToResponse.put(propertyId, response);
+                mUIdToResponse.put(uId, response);
                 return true;
             }
             return false;
@@ -168,12 +171,18 @@
     }
 
     /** Updates the error event in cache */
+    @OptIn(markerClass = ExperimentalCarApi.class)
     void updateInternalError(CarInternalError internalError) {
-        CarPropertyResponse<?> response = CarPropertyResponse.builder()
+        List<CarZone> carZones = new ArrayList<CarZone>();
+        carZones.add(CAR_ZONE_TO_AREA_ID.inverse().get(internalError.getAreaId()));
+        CarPropertyResponse<?> response = CarPropertyResponse.builder().setPropertyId(
+                internalError.getPropertyId()).setCarZones(carZones).setStatus(
+                internalError.getErrorCode()).build();
+        PropertyIdAreaId uId = PropertyIdAreaId.builder()
                 .setPropertyId(internalError.getPropertyId())
-                .setStatus(internalError.getErrorCode()).build();
+                .setAreaId(internalError.getAreaId()).build();
         synchronized (mLock) {
-            mPropertyIdToResponse.put(internalError.getPropertyId(), response);
+            mUIdToResponse.put(uId, response);
         }
     }
 }
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyUtils.java b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyUtils.java
index 9e5e0c5..8d833bb 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyUtils.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyUtils.java
@@ -27,6 +27,7 @@
 import android.car.VehicleAreaType;
 import android.car.VehiclePropertyIds;
 import android.car.hardware.CarPropertyValue;
+import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
 
@@ -36,12 +37,16 @@
 import androidx.car.app.annotations.ExperimentalCarApi;
 import androidx.car.app.hardware.info.AutomotiveCarInfo;
 import androidx.car.app.hardware.info.EnergyProfile;
+import androidx.car.app.utils.LogTags;
+
+import com.google.common.collect.ImmutableBiMap;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
@@ -109,37 +114,36 @@
     };
 
     @ExperimentalCarApi
-    private static final SparseArray<CarZone> AREAID_TO_CARZONE = new SparseArray<CarZone>() {
-        {
-            append(VehicleAreaSeat.SEAT_ROW_1_LEFT,
-                    new CarZone.Builder().setRow(CarZone.CAR_ZONE_ROW_FIRST)
-                            .setColumn(CarZone.CAR_ZONE_COLUMN_LEFT).build());
-            append(VehicleAreaSeat.SEAT_ROW_1_CENTER,
-                    new CarZone.Builder().setRow(CarZone.CAR_ZONE_ROW_FIRST)
-                            .setColumn(CarZone.CAR_ZONE_COLUMN_CENTER).build());
-            append(VehicleAreaSeat.SEAT_ROW_1_RIGHT,
-                    new CarZone.Builder().setRow(CarZone.CAR_ZONE_ROW_FIRST)
-                            .setColumn(CarZone.CAR_ZONE_COLUMN_RIGHT).build());
-            append(VehicleAreaSeat.SEAT_ROW_2_LEFT,
-                    new CarZone.Builder().setRow(CarZone.CAR_ZONE_ROW_SECOND)
-                            .setColumn(CarZone.CAR_ZONE_COLUMN_LEFT).build());
-            append(VehicleAreaSeat.SEAT_ROW_2_CENTER,
-                    new CarZone.Builder().setRow(CarZone.CAR_ZONE_ROW_SECOND)
-                            .setColumn(CarZone.CAR_ZONE_COLUMN_CENTER).build());
-            append(VehicleAreaSeat.SEAT_ROW_2_RIGHT,
-                    new CarZone.Builder().setRow(CarZone.CAR_ZONE_ROW_SECOND)
-                            .setColumn(CarZone.CAR_ZONE_COLUMN_RIGHT).build());
-            append(VehicleAreaSeat.SEAT_ROW_3_LEFT,
-                    new CarZone.Builder().setRow(CarZone.CAR_ZONE_ROW_THIRD)
-                            .setColumn(CarZone.CAR_ZONE_COLUMN_LEFT).build());
-            append(VehicleAreaSeat.SEAT_ROW_3_CENTER,
-                    new CarZone.Builder().setRow(CarZone.CAR_ZONE_ROW_THIRD)
-                            .setColumn(CarZone.CAR_ZONE_COLUMN_CENTER).build());
-            append(VehicleAreaSeat.SEAT_ROW_3_RIGHT,
-                    new CarZone.Builder().setRow(CarZone.CAR_ZONE_ROW_THIRD)
-                            .setColumn(CarZone.CAR_ZONE_COLUMN_RIGHT).build());
-        }
-    };
+    static final ImmutableBiMap<CarZone, Integer> CAR_ZONE_TO_AREA_ID =
+            new ImmutableBiMap.Builder<CarZone, Integer>()
+                    .put(new CarZone.Builder().setRow(CarZone.CAR_ZONE_ROW_FIRST)
+                                    .setColumn(CarZone.CAR_ZONE_COLUMN_LEFT).build(),
+                            VehicleAreaSeat.SEAT_ROW_1_LEFT)
+                    .put(new CarZone.Builder().setRow(CarZone.CAR_ZONE_ROW_FIRST)
+                                    .setColumn(CarZone.CAR_ZONE_COLUMN_CENTER).build(),
+                            VehicleAreaSeat.SEAT_ROW_1_CENTER)
+                    .put(new CarZone.Builder().setRow(CarZone.CAR_ZONE_ROW_FIRST)
+                                    .setColumn(CarZone.CAR_ZONE_COLUMN_RIGHT).build(),
+                            VehicleAreaSeat.SEAT_ROW_1_RIGHT)
+                    .put(new CarZone.Builder().setRow(CarZone.CAR_ZONE_ROW_SECOND)
+                                    .setColumn(CarZone.CAR_ZONE_COLUMN_LEFT).build(),
+                            VehicleAreaSeat.SEAT_ROW_2_LEFT)
+                    .put(new CarZone.Builder().setRow(CarZone.CAR_ZONE_ROW_SECOND)
+                                    .setColumn(CarZone.CAR_ZONE_COLUMN_CENTER).build(),
+                            VehicleAreaSeat.SEAT_ROW_2_CENTER)
+                    .put(new CarZone.Builder().setRow(CarZone.CAR_ZONE_ROW_SECOND)
+                                    .setColumn(CarZone.CAR_ZONE_COLUMN_RIGHT).build(),
+                            VehicleAreaSeat.SEAT_ROW_2_RIGHT)
+                    .put(new CarZone.Builder().setRow(CarZone.CAR_ZONE_ROW_THIRD)
+                                    .setColumn(CarZone.CAR_ZONE_COLUMN_LEFT).build(),
+                            VehicleAreaSeat.SEAT_ROW_3_LEFT)
+                    .put(new CarZone.Builder().setRow(CarZone.CAR_ZONE_ROW_THIRD)
+                                    .setColumn(CarZone.CAR_ZONE_COLUMN_CENTER).build(),
+                            VehicleAreaSeat.SEAT_ROW_3_CENTER)
+                    .put(new CarZone.Builder().setRow(CarZone.CAR_ZONE_ROW_THIRD)
+                                    .setColumn(CarZone.CAR_ZONE_COLUMN_RIGHT).build(),
+                            VehicleAreaSeat.SEAT_ROW_3_RIGHT)
+                    .buildOrThrow();
 
     // Permissions for writing properties. They are system level permissions.
     private static final SparseArray<String> PERMISSION_WRITE_PROPERTY = new SparseArray<String>() {
@@ -300,7 +304,8 @@
         if (propertyValue.getAreaId() == VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL) {
             carZones = Collections.singletonList(CarZone.CAR_ZONE_GLOBAL);
         } else {
-            carZones = mapAreaIdToCarZones(propertyValue.getAreaId());
+            carZones = Collections.singletonList(CAR_ZONE_TO_AREA_ID.inverse().get(
+                    propertyValue.getAreaId()));
         }
         return CarPropertyResponse.builder().setValue(propertyValue.getValue())
                 .setPropertyId(propertyValue.getPropertyId())
@@ -382,19 +387,30 @@
         }
     }
 
-    /**
-     * Builds a list of {@link CarZone}s from the {@link CarPropertyValue#getAreaId()}.
-     */
     @OptIn(markerClass = ExperimentalCarApi.class)
-    static List<CarZone> mapAreaIdToCarZones(int areaId) {
-        List<CarZone> carZones = new ArrayList<>();
-        for (int i = 0; i < AREAID_TO_CARZONE.size(); i++) {
-            int key = AREAID_TO_CARZONE.keyAt(i);
-            if ((key & areaId) != key) {
-                carZones.add(AREAID_TO_CARZONE.valueAt(i));
+    static List<PropertyIdAreaId> getPropertyIdWithAreaIds(Map<Integer, List<CarZone>>
+            propertyIdToCarZones) {
+        List<PropertyIdAreaId> propertyIdWithAreaIds = new ArrayList<>();
+        for (Map.Entry<Integer, List<CarZone>> propertyIdWithCarZones :
+                propertyIdToCarZones.entrySet()) {
+            for (CarZone carZone : propertyIdWithCarZones.getValue()) {
+                if (CAR_ZONE_TO_AREA_ID.containsKey(carZone)) {
+                    propertyIdWithAreaIds.add(PropertyIdAreaId.builder()
+                            .setAreaId(CAR_ZONE_TO_AREA_ID.get(carZone))
+                            .setPropertyId(propertyIdWithCarZones.getKey())
+                            .build());
+                } else {
+                    Log.w(LogTags.TAG_CAR_HARDWARE,
+                            "Could not find area Id for car zone: " + carZone.toString()
+                                    +  " for property: " + propertyIdWithCarZones.getKey());
+                }
             }
         }
-        return carZones;
+        if (propertyIdWithAreaIds.isEmpty()) {
+            throw new IllegalStateException("Could not create uIds for the given property Ids and "
+                    + "their corresponding car zones.");
+        }
+        return propertyIdWithAreaIds;
     }
 
     private PropertyUtils() {
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/info/AutomotiveCarInfo.java b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/info/AutomotiveCarInfo.java
index 0a7f1a0..852387e 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/info/AutomotiveCarInfo.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/info/AutomotiveCarInfo.java
@@ -30,19 +30,20 @@
 import static android.car.VehiclePropertyIds.INFO_MODEL;
 import static android.car.VehiclePropertyIds.INFO_MODEL_YEAR;
 import static android.car.VehiclePropertyIds.PERF_ODOMETER;
+import static android.car.VehiclePropertyIds.PERF_VEHICLE_SPEED;
+import static android.car.VehiclePropertyIds.PERF_VEHICLE_SPEED_DISPLAY;
 import static android.car.VehiclePropertyIds.RANGE_REMAINING;
 
 import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+import static androidx.car.app.hardware.common.CarValueUtils.getCarValue;
 
 import static java.util.Objects.requireNonNull;
 
-import android.car.VehiclePropertyIds;
 import android.os.Build;
 import android.util.Log;
 
 import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
@@ -50,6 +51,7 @@
 import androidx.car.app.annotations.ExperimentalCarApi;
 import androidx.car.app.hardware.common.CarPropertyResponse;
 import androidx.car.app.hardware.common.CarValue;
+import androidx.car.app.hardware.common.CarZone;
 import androidx.car.app.hardware.common.GetPropertyRequest;
 import androidx.car.app.hardware.common.OnCarDataAvailableListener;
 import androidx.car.app.hardware.common.OnCarPropertyResponseListener;
@@ -57,11 +59,11 @@
 import androidx.car.app.hardware.common.PropertyUtils;
 import androidx.car.app.utils.LogTags;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.util.concurrent.ListenableFuture;
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -76,14 +78,16 @@
  */
 @RestrictTo(LIBRARY)
 public class AutomotiveCarInfo implements CarInfo {
-    @VisibleForTesting
-    static final List<Integer> ENERGY_LEVEL_REQUEST =
-            Arrays.asList(EV_BATTERY_LEVEL, FUEL_LEVEL,
-                    FUEL_LEVEL_LOW, RANGE_REMAINING,
-                    DISTANCE_DISPLAY_UNITS, FUEL_VOLUME_DISPLAY_UNITS);
+    static final List<CarZone> GLOBAL_CAR_ZONE = Arrays.asList(getGlobalCarZone());
+
     @VisibleForTesting
     static final float DEFAULT_SAMPLE_RATE = 5f;
 
+    @OptIn(markerClass = ExperimentalCarApi.class)
+    private static CarZone getGlobalCarZone() {
+        return CarZone.CAR_ZONE_GLOBAL;
+    }
+
     /*
      * ELECTRONIC_TOLL_COLLECTION_CARD_STATUS in VehiclePropertyIds. The property is added after
      * Android Q.
@@ -94,15 +98,37 @@
     public static final int SPEED_DISPLAY_UNIT_ID = 289408516;
 
     private static final float UNKNOWN_CAPACITY = Float.NEGATIVE_INFINITY;
-    private static final List<Integer> MILEAGE_REQUEST =
-            Arrays.asList(PERF_ODOMETER, DISTANCE_DISPLAY_UNITS);
-    static final List<Integer> TOLL_REQUEST =
-            Collections.singletonList(TOLL_CARD_STATUS_ID);
-    private static final List<Integer> SPEED_REQUEST =
-            Arrays.asList(VehiclePropertyIds.PERF_VEHICLE_SPEED,
-                    VehiclePropertyIds.PERF_VEHICLE_SPEED_DISPLAY, SPEED_DISPLAY_UNIT_ID);
-    private static final List<Integer> EV_STATUS_REQUEST = Arrays.asList(EV_CHARGE_PORT_OPEN,
-            EV_CHARGE_PORT_CONNECTED);
+    static final ImmutableMap<Integer, List<CarZone>> ENERGY_LEVEL_REQUEST = ImmutableMap.<Integer,
+                    List<CarZone>>builder()
+            .put(EV_BATTERY_LEVEL, GLOBAL_CAR_ZONE)
+            .put(FUEL_LEVEL, GLOBAL_CAR_ZONE)
+            .put(FUEL_LEVEL_LOW, GLOBAL_CAR_ZONE)
+            .put(RANGE_REMAINING, GLOBAL_CAR_ZONE)
+            .put(DISTANCE_DISPLAY_UNITS, GLOBAL_CAR_ZONE)
+            .put(FUEL_VOLUME_DISPLAY_UNITS, GLOBAL_CAR_ZONE)
+            .buildKeepingLast();
+    private static final ImmutableMap<Integer, List<CarZone>> MILEAGE_REQUEST =
+            ImmutableMap.<Integer,
+                            List<CarZone>>builder()
+                    .put(PERF_ODOMETER, GLOBAL_CAR_ZONE)
+                    .put(DISTANCE_DISPLAY_UNITS, GLOBAL_CAR_ZONE)
+                    .buildKeepingLast();
+    static final ImmutableMap<Integer, List<CarZone>> TOLL_REQUEST = ImmutableMap.<Integer,
+                    List<CarZone>>builder()
+            .put(TOLL_CARD_STATUS_ID, GLOBAL_CAR_ZONE)
+            .buildKeepingLast();
+    private static final ImmutableMap<Integer, List<CarZone>> SPEED_REQUEST = ImmutableMap.<Integer,
+                    List<CarZone>>builder()
+            .put(PERF_VEHICLE_SPEED, GLOBAL_CAR_ZONE)
+            .put(PERF_VEHICLE_SPEED_DISPLAY, GLOBAL_CAR_ZONE)
+            .put(SPEED_DISPLAY_UNIT_ID, GLOBAL_CAR_ZONE)
+            .buildKeepingLast();
+    private static final ImmutableMap<Integer, List<CarZone>> EV_STATUS_REQUEST =
+            ImmutableMap.<Integer,
+                            List<CarZone>>builder()
+                    .put(EV_CHARGE_PORT_OPEN, GLOBAL_CAR_ZONE)
+                    .put(EV_CHARGE_PORT_CONNECTED, GLOBAL_CAR_ZONE)
+                    .buildKeepingLast();
     private final Map<OnCarDataAvailableListener<?>, OnCarPropertyResponseListener> mListenerMap =
             new HashMap<>();
     private final PropertyManager mPropertyManager;
@@ -227,12 +253,6 @@
         removeListenerImpl(listener);
     }
 
-    static <T> CarValue<T> getCarValue(CarPropertyResponse<?> response, @Nullable T value) {
-        long timestampMillis = response.getTimestampMillis();
-        int status = response.getStatus();
-        return new CarValue<>(value, timestampMillis, status);
-    }
-
     void getCapacitiesThenEnergyLevel(@NonNull Executor executor,
             @NonNull OnCarDataAvailableListener<EnergyLevel> listener) {
         // Prepare request GetPropertyRequest for battery and fuel capacities.
@@ -438,10 +458,10 @@
                 CarValue<Integer> displayUnitValue = CarValue.UNKNOWN_INTEGER;
                 for (CarPropertyResponse<?> response : carPropertyResponses) {
                     switch (response.getPropertyId()) {
-                        case VehiclePropertyIds.PERF_VEHICLE_SPEED:
+                        case PERF_VEHICLE_SPEED:
                             rawSpeedValue = getCarValue(response, (Float) response.getValue());
                             break;
-                        case VehiclePropertyIds.PERF_VEHICLE_SPEED_DISPLAY:
+                        case PERF_VEHICLE_SPEED_DISPLAY:
                             displaySpeedValue = getCarValue(response, (Float) response.getValue());
                             break;
                         case SPEED_DISPLAY_UNIT_ID:
diff --git a/car/app/app-automotive/src/test/java/androidx/car/app/hardware/climate/AutomotiveCarClimateTest.java b/car/app/app-automotive/src/test/java/androidx/car/app/hardware/climate/AutomotiveCarClimateTest.java
new file mode 100644
index 0000000..c09f1e7
--- /dev/null
+++ b/car/app/app-automotive/src/test/java/androidx/car/app/hardware/climate/AutomotiveCarClimateTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.hardware.climate;
+
+import static android.car.VehiclePropertyIds.HVAC_POWER_ON;
+
+import static androidx.car.app.hardware.climate.AutomotiveCarClimate.DEFAULT_SAMPLE_RATE_HZ;
+import static androidx.car.app.hardware.climate.ClimateProfileRequest.FEATURE_HVAC_POWER;
+import static androidx.car.app.hardware.common.CarValue.STATUS_SUCCESS;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.car.Car;
+import android.car.hardware.property.CarPropertyManager;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.hardware.common.CarPropertyResponse;
+import androidx.car.app.hardware.common.CarValue;
+import androidx.car.app.hardware.common.CarZone;
+import androidx.car.app.hardware.common.OnCarPropertyResponseListener;
+import androidx.car.app.hardware.common.PropertyManager;
+import androidx.car.app.shadows.car.ShadowCar;
+
+import com.google.common.collect.ImmutableMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = Config.NONE, shadows = {ShadowCar.class})
+@DoNotInstrument
+public class AutomotiveCarClimateTest {
+    private List<CarPropertyResponse<?>> mResponse;
+    private CountDownLatch mCountDownLatch;
+    private final Executor mExecutor = directExecutor();
+    private AutomotiveCarClimate mAutomotiveCarClimate;
+    private CarZone mCarZone;
+    @Mock
+    private Car mCarMock;
+    @Mock
+    private CarPropertyManager mCarPropertyManagerMock;
+    @Mock
+    private PropertyManager mPropertyManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowCar.setCar(mCarMock);
+        when(mCarMock.getCarManager(anyString())).thenReturn(mCarPropertyManagerMock);
+        mAutomotiveCarClimate = new AutomotiveCarClimate(mPropertyManager);
+        mCountDownLatch = new CountDownLatch(1);
+        mResponse = new ArrayList<>();
+        mCarZone = new CarZone.Builder().build();
+    }
+
+    @Test
+    public void getHvacPower_verifyResponse() throws InterruptedException {
+        CarClimateFeature.Builder mCarClimateBuilder = new CarClimateFeature.Builder(
+                FEATURE_HVAC_POWER);
+        mCarClimateBuilder.addCarZones(mCarZone);
+        CarClimateFeature mCarClimateFeature = new CarClimateFeature(mCarClimateBuilder);
+        RegisterClimateStateRequest.Builder builder =
+                new RegisterClimateStateRequest.Builder(false);
+        builder.addClimateRegisterFeatures(mCarClimateFeature);
+
+        AtomicReference<CarValue<Boolean>> loadedResult = new AtomicReference<>();
+        CarClimateStateCallback listener = new CarClimateStateCallback() {
+            @Override
+            public void onHvacPowerStateAvailable(@NonNull CarValue<Boolean> hvacPowerState) {
+                loadedResult.set(hvacPowerState);
+                mCountDownLatch.countDown();
+            }
+        };
+
+        mAutomotiveCarClimate.registerClimateStateCallback(mExecutor, builder.build(), listener);
+
+        Map<Integer, List<CarZone>> propertyIdsWithCarZones =
+                ImmutableMap.<Integer, List<CarZone>>builder().put(HVAC_POWER_ON,
+                        Collections.singletonList(mCarZone)).buildKeepingLast();
+
+        ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
+                OnCarPropertyResponseListener.class);
+        verify(mPropertyManager).submitRegisterListenerRequest(eq(propertyIdsWithCarZones),
+                eq(DEFAULT_SAMPLE_RATE_HZ), captor.capture(), eq(mExecutor));
+
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(HVAC_POWER_ON).setCarZones(
+                Collections.singletonList(mCarZone)).setValue(true).setStatus(
+                STATUS_SUCCESS).build());
+
+        captor.getValue().onCarPropertyResponses(mResponse);
+        mCountDownLatch.await();
+
+        CarValue<Boolean> carValue = loadedResult.get();
+        assertThat(carValue.getValue()).isEqualTo(true);
+        assertThat(carValue.getCarZones()).isEqualTo(Collections.singletonList(mCarZone));
+        assertThat(carValue.getStatus()).isEqualTo(STATUS_SUCCESS);
+    }
+}
diff --git a/car/app/app-automotive/src/test/java/androidx/car/app/hardware/common/PropertyResponseCacheTest.java b/car/app/app-automotive/src/test/java/androidx/car/app/hardware/common/PropertyResponseCacheTest.java
index 6ebff47..9ab708d 100644
--- a/car/app/app-automotive/src/test/java/androidx/car/app/hardware/common/PropertyResponseCacheTest.java
+++ b/car/app/app-automotive/src/test/java/androidx/car/app/hardware/common/PropertyResponseCacheTest.java
@@ -37,9 +37,13 @@
     @Test
     public void getResponsesByListener_returnsUnknownResponseIfNotInCache() {
         Integer testPropertyId = 1;
+        PropertyIdAreaId testPropertyIdAreaIds = PropertyIdAreaId.builder()
+                .setAreaId(0)
+                .setPropertyId(testPropertyId)
+                .build();
         PropertyResponseCache propertyResponseCache = new PropertyResponseCache();
-        propertyResponseCache.putListenerAndPropertyIds(mOnCarPropertyResponseListener,
-                ImmutableList.of(testPropertyId));
+        propertyResponseCache.putListenerAndUIds(mOnCarPropertyResponseListener,
+                ImmutableList.of(testPropertyIdAreaIds));
         List<CarPropertyResponse<?>> carPropertyResponses =
                 propertyResponseCache.getResponsesByListener(mOnCarPropertyResponseListener);
         assertThat(carPropertyResponses).containsExactly(
diff --git a/car/app/app-automotive/src/test/java/androidx/car/app/hardware/info/AutomotiveCarInfoTest.java b/car/app/app-automotive/src/test/java/androidx/car/app/hardware/info/AutomotiveCarInfoTest.java
index dd78771..5f26962 100644
--- a/car/app/app-automotive/src/test/java/androidx/car/app/hardware/info/AutomotiveCarInfoTest.java
+++ b/car/app/app-automotive/src/test/java/androidx/car/app/hardware/info/AutomotiveCarInfoTest.java
@@ -67,6 +67,7 @@
 import androidx.car.app.hardware.common.PropertyManager;
 import androidx.car.app.shadows.car.ShadowCar;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -81,8 +82,10 @@
 import org.robolectric.annotation.internal.DoNotInstrument;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicReference;
@@ -100,6 +103,7 @@
     private CountDownLatch mCountDownLatch;
     private final Executor mExecutor = directExecutor();
     private AutomotiveCarInfo mAutomotiveCarInfo;
+    private List<CarZone> mCarZones;
     @Mock
     private Car mCarMock;
     @Mock
@@ -119,6 +123,7 @@
         mGetPropertyRequests = new ArrayList<>();
         mPropertyIds = new ArrayList<>();
         mResponse = new ArrayList<>();
+        mCarZones = Arrays.asList(CarZone.CAR_ZONE_GLOBAL);
     }
 
     @Test
@@ -153,33 +158,24 @@
         mGetPropertyRequests.add(GetPropertyRequest.create(INFO_MODEL_YEAR));
 
         // Add "make", "model", "year" values to the response.
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(INFO_MAKE)
-                .setStatus(STATUS_SUCCESS)
-                .setValue("Toy Vehicle")
-                .setTimestampMillis(1L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(INFO_MODEL)
-                .setStatus(STATUS_SUCCESS)
-                .setValue("Speedy Model")
-                .setTimestampMillis(2L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(INFO_MODEL_YEAR)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(2020)
-                .setTimestampMillis(3L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(INFO_MAKE).setStatus(
+                STATUS_SUCCESS).setValue("Toy Vehicle").setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(INFO_MODEL).setStatus(
+                STATUS_SUCCESS).setValue("Speedy Model").setTimestampMillis(2L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(INFO_MODEL_YEAR).setStatus(
+                STATUS_SUCCESS).setValue(2020).setTimestampMillis(3L).build());
         ListenableFuture<List<CarPropertyResponse<?>>> listenableCarPropertyResponse =
                 Futures.immediateFuture(mResponse);
-        when(mPropertyManager.submitGetPropertyRequest(
-                eq(mGetPropertyRequests), eq(mExecutor))).thenReturn(listenableCarPropertyResponse);
+        when(mPropertyManager.submitGetPropertyRequest(eq(mGetPropertyRequests),
+                eq(mExecutor))).thenReturn(listenableCarPropertyResponse);
         AtomicReference<Model> loadedResult = new AtomicReference<>();
         OnCarDataAvailableListener<Model> listener = (data) -> {
             loadedResult.set(data);
             mCountDownLatch.countDown();
         };
         mAutomotiveCarInfo.fetchModel(mExecutor, listener);
-        verify(mPropertyManager, times(1)).submitGetPropertyRequest(
-                eq(mGetPropertyRequests), eq(mExecutor));
+        verify(mPropertyManager, times(1)).submitGetPropertyRequest(eq(mGetPropertyRequests),
+                eq(mExecutor));
         mCountDownLatch.await();
         Model mModel = loadedResult.get();
         assertThat(mModel.getName().getValue()).isEqualTo("Speedy Model");
@@ -201,21 +197,12 @@
         mGetPropertyRequests.add(GetPropertyRequest.create(INFO_MODEL));
 
         // Add "make", "model", "year" values to the response.
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(INFO_MAKE)
-                .setStatus(STATUS_SUCCESS)
-                .setValue("Toy Vehicle")
-                .setTimestampMillis(1L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(INFO_MODEL)
-                .setStatus(STATUS_SUCCESS)
-                .setValue("Speedy Model")
-                .setTimestampMillis(2L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(INFO_MODEL_YEAR)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(2020)
-                .setTimestampMillis(3L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(INFO_MAKE).setStatus(
+                STATUS_SUCCESS).setValue("Toy Vehicle").setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(INFO_MODEL).setStatus(
+                STATUS_SUCCESS).setValue("Speedy Model").setTimestampMillis(2L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(INFO_MODEL_YEAR).setStatus(
+                STATUS_SUCCESS).setValue(2020).setTimestampMillis(3L).build());
         ListenableFuture<List<CarPropertyResponse<?>>> listenableCarPropertyResponse =
                 Futures.immediateFuture(mResponse);
         when(mPropertyManager.submitGetPropertyRequest(
@@ -225,8 +212,8 @@
 
         // Given that the number of values in the response is more(3) than what was requested(2),
         // there should be null pointer exception.
-        assertThrows(NullPointerException.class, () ->
-                mAutomotiveCarInfo.fetchModel(mExecutor, listener));
+        assertThrows(NullPointerException.class,
+                () -> mAutomotiveCarInfo.fetchModel(mExecutor, listener));
     }
 
     @Test
@@ -239,36 +226,31 @@
         mGetPropertyRequests.add(GetPropertyRequest.create(INFO_FUEL_TYPE));
 
         // Add "evConnector" and "fuel" type of the vehicle to the response.
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(INFO_EV_CONNECTOR_TYPE)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(new Integer[]{chademoInVehicle})
-                .setTimestampMillis(1L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(INFO_FUEL_TYPE)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(new Integer[]{FUEL_TYPE_UNLEADED})
-                .setTimestampMillis(2L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(INFO_EV_CONNECTOR_TYPE).setStatus(
+                STATUS_SUCCESS).setValue(new Integer[]{chademoInVehicle}).setTimestampMillis(
+                1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(INFO_FUEL_TYPE).setStatus(
+                STATUS_SUCCESS).setValue(new Integer[]{FUEL_TYPE_UNLEADED}).setTimestampMillis(
+                2L).build());
         ListenableFuture<List<CarPropertyResponse<?>>> listenableCarPropertyResponse =
                 Futures.immediateFuture(mResponse);
-        when(mPropertyManager.submitGetPropertyRequest(
-                eq(mGetPropertyRequests), eq(mExecutor))).thenReturn(listenableCarPropertyResponse);
+        when(mPropertyManager.submitGetPropertyRequest(eq(mGetPropertyRequests),
+                eq(mExecutor))).thenReturn(listenableCarPropertyResponse);
         AtomicReference<EnergyProfile> loadedResult = new AtomicReference<>();
         OnCarDataAvailableListener<EnergyProfile> listener = (data) -> {
             loadedResult.set(data);
             mCountDownLatch.countDown();
         };
         mAutomotiveCarInfo.fetchEnergyProfile(mExecutor, listener);
-        verify(mPropertyManager, times(1)).submitGetPropertyRequest(
-                eq(mGetPropertyRequests), eq(mExecutor));
+        verify(mPropertyManager, times(1)).submitGetPropertyRequest(eq(mGetPropertyRequests),
+                eq(mExecutor));
         mCountDownLatch.await();
         EnergyProfile energyProfile = loadedResult.get();
         List<Integer> evConnector = new ArrayList<Integer>();
         evConnector.add(EVCONNECTOR_TYPE_CHADEMO);
         List<Integer> fuel = new ArrayList<Integer>();
         fuel.add(FUEL_TYPE_UNLEADED);
-        assertThat(energyProfile.getEvConnectorTypes().getValue()).isEqualTo(
-                evConnector);
+        assertThat(energyProfile.getEvConnectorTypes().getValue()).isEqualTo(evConnector);
         assertThat(energyProfile.getFuelTypes().getValue()).isEqualTo(fuel);
     }
 
@@ -301,9 +283,12 @@
         // VehicleUnit.METER in car service
         int meterUnit = 0x21;
 
-        // Create "PERF_ODOMETER" and "DISTANCE_DISPLAY_UNITS" property IDs list.
-        mPropertyIds.add(PERF_ODOMETER);
-        mPropertyIds.add(DISTANCE_DISPLAY_UNITS);
+        // Create "PERF_ODOMETER" and "DISTANCE_DISPLAY_UNITS" property IDs list with car zones.
+        Map<Integer, List<CarZone>> propertyIdsWithCarZones =
+                ImmutableMap.<Integer, List<CarZone>>builder()
+                        .put(PERF_ODOMETER, mCarZones)
+                        .put(DISTANCE_DISPLAY_UNITS, mCarZones)
+                        .buildKeepingLast();
 
         AtomicReference<Mileage> loadedResult = new AtomicReference<>();
         OnCarDataAvailableListener<Mileage> listener = (data) -> {
@@ -315,19 +300,13 @@
 
         ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
                 OnCarPropertyResponseListener.class);
-        verify(mPropertyManager).submitRegisterListenerRequest(eq(mPropertyIds),
+        verify(mPropertyManager).submitRegisterListenerRequest(eq(propertyIdsWithCarZones),
                 eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
 
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(PERF_ODOMETER)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(1f)
-                .setTimestampMillis(1L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(DISTANCE_DISPLAY_UNITS)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(meterUnit)
-                .setTimestampMillis(2L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(PERF_ODOMETER).setStatus(
+                STATUS_SUCCESS).setValue(1f).setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(DISTANCE_DISPLAY_UNITS).setStatus(
+                STATUS_SUCCESS).setValue(meterUnit).setTimestampMillis(2L).build());
 
         captor.getValue().onCarPropertyResponses(mResponse);
         mCountDownLatch.await();
@@ -340,9 +319,12 @@
     @Test
     public void addMileageListener_returnsMileageWithUnknownValuesIfNoResponses()
             throws InterruptedException {
-        // Create "PERF_ODOMETER" and "DISTANCE_DISPLAY_UNITS" property IDs list.
-        mPropertyIds.add(PERF_ODOMETER);
-        mPropertyIds.add(DISTANCE_DISPLAY_UNITS);
+        // Create "PERF_ODOMETER" and "DISTANCE_DISPLAY_UNITS" property IDs list with car zones.
+        Map<Integer, List<CarZone>> propertyIdsWithCarZones =
+                ImmutableMap.<Integer, List<CarZone>>builder()
+                        .put(PERF_ODOMETER, mCarZones)
+                        .put(DISTANCE_DISPLAY_UNITS, mCarZones)
+                        .buildKeepingLast();
         AtomicReference<Mileage> loadedResult = new AtomicReference<>();
         OnCarDataAvailableListener<Mileage> listener = (data) -> {
             loadedResult.set(data);
@@ -353,7 +335,7 @@
 
         ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
                 OnCarPropertyResponseListener.class);
-        verify(mPropertyManager).submitRegisterListenerRequest(eq(mPropertyIds),
+        verify(mPropertyManager).submitRegisterListenerRequest(eq(propertyIdsWithCarZones),
                 eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
         captor.getValue().onCarPropertyResponses(mResponse);
         mCountDownLatch.await();
@@ -366,9 +348,12 @@
         // VehicleUnit.METER in car service
         int meterUnit = 0x21;
 
-        // Create "PERF_ODOMETER" and "DISTANCE_DISPLAY_UNITS" property IDs list.
-        mPropertyIds.add(PERF_ODOMETER);
-        mPropertyIds.add(DISTANCE_DISPLAY_UNITS);
+        // Create "PERF_ODOMETER" and "DISTANCE_DISPLAY_UNITS" property IDs list with car zones.
+        Map<Integer, List<CarZone>> propertyIdsWithCarZones =
+                ImmutableMap.<Integer, List<CarZone>>builder()
+                        .put(PERF_ODOMETER, mCarZones)
+                        .put(DISTANCE_DISPLAY_UNITS, mCarZones)
+                        .buildKeepingLast();
 
         AtomicReference<Mileage> loadedResult = new AtomicReference<>();
         OnCarDataAvailableListener<Mileage> listener = (data) -> {
@@ -382,18 +367,13 @@
         ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
                 OnCarPropertyResponseListener.class);
         verify(mPropertyManager, times(2)).submitRegisterListenerRequest(
-                eq(mPropertyIds), eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
+                eq(propertyIdsWithCarZones), eq(DEFAULT_SAMPLE_RATE), captor.capture(),
+                eq(mExecutor));
 
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(PERF_ODOMETER)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(1f)
-                .setTimestampMillis(1L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(DISTANCE_DISPLAY_UNITS)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(meterUnit)
-                .setTimestampMillis(2L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(PERF_ODOMETER).setStatus(
+                STATUS_SUCCESS).setValue(1f).setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(DISTANCE_DISPLAY_UNITS).setStatus(
+                STATUS_SUCCESS).setValue(meterUnit).setTimestampMillis(2L).build());
 
         captor.getValue().onCarPropertyResponses(mResponse);
         mCountDownLatch.await();
@@ -413,9 +393,12 @@
         Executor firstExecutor = directExecutor();
         Executor secondExecutor = directExecutor();
 
-        // Create "PERF_ODOMETER" and "DISTANCE_DISPLAY_UNITS" property IDs list.
-        mPropertyIds.add(PERF_ODOMETER);
-        mPropertyIds.add(DISTANCE_DISPLAY_UNITS);
+        // Create "PERF_ODOMETER" and "DISTANCE_DISPLAY_UNITS" property IDs list with car zones.
+        Map<Integer, List<CarZone>> propertyIdsWithCarZones =
+                ImmutableMap.<Integer, List<CarZone>>builder()
+                        .put(PERF_ODOMETER, mCarZones)
+                        .put(DISTANCE_DISPLAY_UNITS, mCarZones)
+                        .buildKeepingLast();
 
         AtomicReference<Mileage> loadedFirstResult = new AtomicReference<>();
         AtomicReference<Mileage> loadedSecondResult = new AtomicReference<>();
@@ -436,19 +419,13 @@
                 OnCarPropertyResponseListener.class);
 
         verify(mPropertyManager, times(1)).submitRegisterListenerRequest(
-                eq(mPropertyIds), eq(DEFAULT_SAMPLE_RATE), firstCaptor.capture(),
+                eq(propertyIdsWithCarZones), eq(DEFAULT_SAMPLE_RATE), firstCaptor.capture(),
                 eq(firstExecutor));
 
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(PERF_ODOMETER)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(1f)
-                .setTimestampMillis(1L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(DISTANCE_DISPLAY_UNITS)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(meterUnit)
-                .setTimestampMillis(2L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(PERF_ODOMETER).setStatus(
+                STATUS_SUCCESS).setValue(1f).setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(DISTANCE_DISPLAY_UNITS).setStatus(
+                STATUS_SUCCESS).setValue(meterUnit).setTimestampMillis(2L).build());
 
         firstCaptor.getValue().onCarPropertyResponses(mResponse);
 
@@ -459,9 +436,9 @@
                 OnCarPropertyResponseListener.class);
 
         // Listener request would be submitted twice by now.
-        verify(mPropertyManager, times(2)).submitRegisterListenerRequest(eq(mPropertyIds),
-                eq(DEFAULT_SAMPLE_RATE),
-                secondCaptor.capture(), eq(secondExecutor));
+        verify(mPropertyManager, times(2)).submitRegisterListenerRequest(
+                eq(propertyIdsWithCarZones), eq(DEFAULT_SAMPLE_RATE), secondCaptor.capture(),
+                eq(secondExecutor));
         secondCaptor.getValue().onCarPropertyResponses(mResponse);
 
         firstCountDownLatch.await();
@@ -477,9 +454,13 @@
 
     @Test
     public void getEvStatus_verifyResponse() throws InterruptedException {
-        // Create "EV_CHARGE_PORT_OPEN" and "EV_CHARGE_PORT_CONNECTED" property IDs list.
-        mPropertyIds.add(EV_CHARGE_PORT_OPEN);
-        mPropertyIds.add(EV_CHARGE_PORT_CONNECTED);
+        // Create "EV_CHARGE_PORT_OPEN" and "EV_CHARGE_PORT_CONNECTED" property IDs list with car
+        // zones.
+        Map<Integer, List<CarZone>> propertyIdsWithCarZones =
+                ImmutableMap.<Integer, List<CarZone>>builder()
+                        .put(EV_CHARGE_PORT_OPEN, mCarZones)
+                        .put(EV_CHARGE_PORT_CONNECTED, mCarZones)
+                        .buildKeepingLast();
 
         AtomicReference<EvStatus> loadedResult = new AtomicReference<>();
         OnCarDataAvailableListener<EvStatus> listener = (data) -> {
@@ -491,19 +472,14 @@
 
         ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
                 OnCarPropertyResponseListener.class);
-        verify(mPropertyManager).submitRegisterListenerRequest(eq(mPropertyIds),
+        verify(mPropertyManager).submitRegisterListenerRequest(eq(propertyIdsWithCarZones),
                 eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
 
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(EV_CHARGE_PORT_OPEN)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(true)
-                .setTimestampMillis(1L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(EV_CHARGE_PORT_CONNECTED)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(false)
-                .setTimestampMillis(2L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(EV_CHARGE_PORT_OPEN).setStatus(
+                STATUS_SUCCESS).setValue(true).setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(
+                EV_CHARGE_PORT_CONNECTED).setStatus(STATUS_SUCCESS).setValue(
+                false).setTimestampMillis(2L).build());
 
         captor.getValue().onCarPropertyResponses(mResponse);
         mCountDownLatch.await();
@@ -516,9 +492,13 @@
     @Test
     public void addEvStatusListener_returnsEvStatusWithUnknownValuesIfNoResponses()
             throws InterruptedException {
-        // Create "EV_CHARGE_PORT_OPEN" and "EV_CHARGE_PORT_CONNECTED" property IDs list.
-        mPropertyIds.add(EV_CHARGE_PORT_OPEN);
-        mPropertyIds.add(EV_CHARGE_PORT_CONNECTED);
+        // Create "EV_CHARGE_PORT_OPEN" and "EV_CHARGE_PORT_CONNECTED" property IDs list with car
+        // zones.
+        Map<Integer, List<CarZone>> propertyIdsWithCarZones =
+                ImmutableMap.<Integer, List<CarZone>>builder()
+                        .put(EV_CHARGE_PORT_OPEN, mCarZones)
+                        .put(EV_CHARGE_PORT_CONNECTED, mCarZones)
+                        .buildKeepingLast();
         AtomicReference<EvStatus> loadedResult = new AtomicReference<>();
         OnCarDataAvailableListener<EvStatus> listener = (data) -> {
             loadedResult.set(data);
@@ -529,7 +509,7 @@
 
         ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
                 OnCarPropertyResponseListener.class);
-        verify(mPropertyManager).submitRegisterListenerRequest(eq(mPropertyIds),
+        verify(mPropertyManager).submitRegisterListenerRequest(eq(propertyIdsWithCarZones),
                 eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
         captor.getValue().onCarPropertyResponses(mResponse);
         mCountDownLatch.await();
@@ -539,9 +519,13 @@
 
     @Test
     public void getEvStatus_withInvalidResponse_verifyResponse() throws InterruptedException {
-        // Create "EV_CHARGE_PORT_OPEN" and "EV_CHARGE_PORT_CONNECTED" property IDs list.
-        mPropertyIds.add(EV_CHARGE_PORT_OPEN);
-        mPropertyIds.add(EV_CHARGE_PORT_CONNECTED);
+        // Create "EV_CHARGE_PORT_OPEN" and "EV_CHARGE_PORT_CONNECTED" property IDs list with car
+        // zones.
+        Map<Integer, List<CarZone>> propertyIdsWithCarZones =
+                ImmutableMap.<Integer, List<CarZone>>builder()
+                        .put(EV_CHARGE_PORT_OPEN, mCarZones)
+                        .put(EV_CHARGE_PORT_CONNECTED, mCarZones)
+                        .buildKeepingLast();
 
         AtomicReference<EvStatus> loadedResult = new AtomicReference<>();
         OnCarDataAvailableListener<EvStatus> listener = (data) -> {
@@ -553,7 +537,7 @@
 
         ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
                 OnCarPropertyResponseListener.class);
-        verify(mPropertyManager).submitRegisterListenerRequest(eq(mPropertyIds),
+        verify(mPropertyManager).submitRegisterListenerRequest(eq(propertyIdsWithCarZones),
                 eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
 
         mResponse.add(CarPropertyResponse.builder()
@@ -578,8 +562,11 @@
     @Config(minSdk = 31)
     @Test
     public void getTollCard_verifyResponseApi31() throws InterruptedException {
-        // Create "TOLL_CARD_STATUS_ID" request property IDs list.
-        mPropertyIds.add(TOLL_CARD_STATUS_ID);
+        // Create "TOLL_CARD_STATUS_ID" request property IDs list with car zones.
+        Map<Integer, List<CarZone>> propertyIdsWithCarZones =
+                ImmutableMap.<Integer, List<CarZone>>builder()
+                        .put(TOLL_CARD_STATUS_ID, mCarZones)
+                        .buildKeepingLast();
 
         AtomicReference<TollCard> loadedResult = new AtomicReference<>();
         OnCarDataAvailableListener<TollCard> listener = (data) -> {
@@ -591,14 +578,12 @@
 
         ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
                 OnCarPropertyResponseListener.class);
-        verify(mPropertyManager).submitRegisterListenerRequest(eq(mPropertyIds),
+        verify(mPropertyManager).submitRegisterListenerRequest(eq(propertyIdsWithCarZones),
                 eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
 
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(TOLL_CARD_STATUS_ID)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(TollCard.TOLLCARD_STATE_VALID)
-                .setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(TOLL_CARD_STATUS_ID).setStatus(
+                STATUS_SUCCESS).setValue(TollCard.TOLLCARD_STATE_VALID).setTimestampMillis(
+                1L).build());
 
         captor.getValue().onCarPropertyResponses(mResponse);
         mCountDownLatch.await();
@@ -626,10 +611,13 @@
     @Test
     public void getSpeed_verifyResponse() throws InterruptedException {
         // Create "PERF_VEHICLE_SPEED", "PERF_VEHICLE_SPEED_DISPLAY" and "SPEED_DISPLAY_UNIT_ID"
-        // property IDs list.
-        mPropertyIds.add(PERF_VEHICLE_SPEED);
-        mPropertyIds.add(PERF_VEHICLE_SPEED_DISPLAY);
-        mPropertyIds.add(SPEED_DISPLAY_UNIT_ID);
+        // property IDs list with car zones.
+        Map<Integer, List<CarZone>> propertyIdsWithCarZones =
+                ImmutableMap.<Integer, List<CarZone>>builder()
+                        .put(PERF_VEHICLE_SPEED, mCarZones)
+                        .put(PERF_VEHICLE_SPEED_DISPLAY, mCarZones)
+                        .put(SPEED_DISPLAY_UNIT_ID, mCarZones)
+                        .buildKeepingLast();
 
         float defaultSpeed = 20f;
         float defaultRawSpeed = 20.5f;
@@ -647,24 +635,16 @@
 
         ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
                 OnCarPropertyResponseListener.class);
-        verify(mPropertyManager).submitRegisterListenerRequest(eq(mPropertyIds),
+        verify(mPropertyManager).submitRegisterListenerRequest(eq(propertyIdsWithCarZones),
                 eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
 
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(SPEED_DISPLAY_UNIT_ID)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(metersPerSec)
-                .setTimestampMillis(1L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(PERF_VEHICLE_SPEED)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(defaultRawSpeed)
-                .setTimestampMillis(2L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(PERF_VEHICLE_SPEED_DISPLAY)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(defaultSpeed)
-                .setTimestampMillis(3L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(SPEED_DISPLAY_UNIT_ID).setStatus(
+                STATUS_SUCCESS).setValue(metersPerSec).setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(PERF_VEHICLE_SPEED).setStatus(
+                STATUS_SUCCESS).setValue(defaultRawSpeed).setTimestampMillis(2L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(
+                PERF_VEHICLE_SPEED_DISPLAY).setStatus(STATUS_SUCCESS).setValue(
+                defaultSpeed).setTimestampMillis(3L).build());
 
         captor.getValue().onCarPropertyResponses(mResponse);
         mCountDownLatch.await();
@@ -679,10 +659,13 @@
     public void addSpeedListener_returnsSpeedWithUnknownValuesIfNoResponses()
             throws InterruptedException {
         // Create "PERF_VEHICLE_SPEED", "PERF_VEHICLE_SPEED_DISPLAY" and "SPEED_DISPLAY_UNIT_ID"
-        // property IDs list.
-        mPropertyIds.add(PERF_VEHICLE_SPEED);
-        mPropertyIds.add(PERF_VEHICLE_SPEED_DISPLAY);
-        mPropertyIds.add(SPEED_DISPLAY_UNIT_ID);
+        // property IDs list with car zones.
+        Map<Integer, List<CarZone>> propertyIdsWithCarZones =
+                ImmutableMap.<Integer, List<CarZone>>builder()
+                        .put(PERF_VEHICLE_SPEED, mCarZones)
+                        .put(PERF_VEHICLE_SPEED_DISPLAY, mCarZones)
+                        .put(SPEED_DISPLAY_UNIT_ID, mCarZones)
+                        .buildKeepingLast();
         AtomicReference<Speed> loadedResult = new AtomicReference<>();
         OnCarDataAvailableListener<Speed> listener = (data) -> {
             loadedResult.set(data);
@@ -693,7 +676,7 @@
 
         ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
                 OnCarPropertyResponseListener.class);
-        verify(mPropertyManager).submitRegisterListenerRequest(eq(mPropertyIds),
+        verify(mPropertyManager).submitRegisterListenerRequest(eq(propertyIdsWithCarZones),
                 eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
         captor.getValue().onCarPropertyResponses(mResponse);
         mCountDownLatch.await();
@@ -716,20 +699,14 @@
         float fuelCapacity = 120f;
         float fuelLevelValue = 50f;
         List<CarPropertyResponse<?>> capacities = new ArrayList<>();
-        capacities.add(CarPropertyResponse.builder()
-                .setPropertyId(INFO_EV_BATTERY_CAPACITY)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(evBatteryCapacity)
-                .setTimestampMillis(1L).build());
-        capacities.add(CarPropertyResponse.builder()
-                .setPropertyId(INFO_FUEL_CAPACITY)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(fuelCapacity)
-                .setTimestampMillis(1L).build());
-        ListenableFuture<List<CarPropertyResponse<?>>> future =
-                Futures.immediateFuture(capacities);
-        when(mPropertyManager.submitGetPropertyRequest(eq(mGetPropertyRequests), any()))
-                .thenReturn(future);
+        capacities.add(CarPropertyResponse.builder().setPropertyId(
+                INFO_EV_BATTERY_CAPACITY).setStatus(STATUS_SUCCESS).setValue(
+                evBatteryCapacity).setTimestampMillis(1L).build());
+        capacities.add(CarPropertyResponse.builder().setPropertyId(INFO_FUEL_CAPACITY).setStatus(
+                STATUS_SUCCESS).setValue(fuelCapacity).setTimestampMillis(1L).build());
+        ListenableFuture<List<CarPropertyResponse<?>>> future = Futures.immediateFuture(capacities);
+        when(mPropertyManager.submitGetPropertyRequest(eq(mGetPropertyRequests), any())).thenReturn(
+                future);
 
         AtomicReference<EnergyLevel> loadedResult = new AtomicReference<>();
         OnCarDataAvailableListener<EnergyLevel> listener = (data) -> {
@@ -740,49 +717,37 @@
         mAutomotiveCarInfo.addEnergyLevelListener(mExecutor, listener);
 
         // Create "EV_BATTERY_LEVEL", "FUEL_LEVEL", "FUEL_LEVEL_LOW", "RANGE_REMAINING",
-        // "DISTANCE_DISPLAY_UNITS" and "FUEL_VOLUME_DISPLAY_UNITS" property IDs list.
-        mPropertyIds.add(EV_BATTERY_LEVEL);
-        mPropertyIds.add(FUEL_LEVEL);
-        mPropertyIds.add(FUEL_LEVEL_LOW);
-        mPropertyIds.add(RANGE_REMAINING);
-        mPropertyIds.add(DISTANCE_DISPLAY_UNITS);
-        mPropertyIds.add(FUEL_VOLUME_DISPLAY_UNITS);
+        // "DISTANCE_DISPLAY_UNITS" and "FUEL_VOLUME_DISPLAY_UNITS" property IDs list with car
+        // zones.
+        Map<Integer, List<CarZone>> propertyIdsWithCarZones =
+                ImmutableMap.<Integer, List<CarZone>>builder()
+                        .put(EV_BATTERY_LEVEL, mCarZones)
+                        .put(FUEL_LEVEL, mCarZones)
+                        .put(FUEL_LEVEL_LOW, mCarZones)
+                        .put(RANGE_REMAINING, mCarZones)
+                        .put(DISTANCE_DISPLAY_UNITS, mCarZones)
+                        .put(FUEL_VOLUME_DISPLAY_UNITS, mCarZones)
+                        .buildKeepingLast();
 
-        verify(mPropertyManager, times(1)).submitGetPropertyRequest(
-                eq(mGetPropertyRequests), eq(mExecutor));
+        verify(mPropertyManager, times(1)).submitGetPropertyRequest(eq(mGetPropertyRequests),
+                eq(mExecutor));
         verify(mPropertyManager, times(1)).submitRegisterListenerRequest(
-                eq(mPropertyIds), eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
+                eq(propertyIdsWithCarZones), eq(DEFAULT_SAMPLE_RATE), captor.capture(),
+                eq(mExecutor));
 
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(EV_BATTERY_LEVEL)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(evBatteryLevelValue)
-                .setTimestampMillis(1L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(FUEL_LEVEL)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(fuelLevelValue)
-                .setTimestampMillis(1L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(FUEL_LEVEL_LOW)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(true)
-                .setTimestampMillis(1L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(RANGE_REMAINING)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(5f)
-                .setTimestampMillis(1L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(DISTANCE_DISPLAY_UNITS)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(meterDistanceUnit)
-                .setTimestampMillis(1L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(FUEL_VOLUME_DISPLAY_UNITS)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(meterVolumeUnit)
-                .setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(EV_BATTERY_LEVEL).setStatus(
+                STATUS_SUCCESS).setValue(evBatteryLevelValue).setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(FUEL_LEVEL).setStatus(
+                STATUS_SUCCESS).setValue(fuelLevelValue).setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(FUEL_LEVEL_LOW).setStatus(
+                STATUS_SUCCESS).setValue(true).setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(RANGE_REMAINING).setStatus(
+                STATUS_SUCCESS).setValue(5f).setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(DISTANCE_DISPLAY_UNITS).setStatus(
+                STATUS_SUCCESS).setValue(meterDistanceUnit).setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(
+                FUEL_VOLUME_DISPLAY_UNITS).setStatus(STATUS_SUCCESS).setValue(
+                meterVolumeUnit).setTimestampMillis(1L).build());
 
         captor.getValue().onCarPropertyResponses(mResponse);
         mCountDownLatch.await();
@@ -792,10 +757,8 @@
                 evBatteryLevelValue / evBatteryCapacity * 100);
         assertThat(energyLevel.getFuelPercent().getValue()).isEqualTo(
                 fuelLevelValue / fuelCapacity * 100);
-        assertThat(energyLevel.getEnergyIsLow().getValue()).isEqualTo(
-                true);
-        assertThat(energyLevel.getRangeRemainingMeters().getValue()).isEqualTo(
-                5f);
+        assertThat(energyLevel.getEnergyIsLow().getValue()).isEqualTo(true);
+        assertThat(energyLevel.getRangeRemainingMeters().getValue()).isEqualTo(5f);
         assertThat(energyLevel.getDistanceDisplayUnit().getValue()).isEqualTo(2);
         assertThat(energyLevel.getFuelVolumeDisplayUnit().getValue()).isEqualTo(201);
     }
@@ -820,17 +783,22 @@
         verify(mPropertyManager, times(1)).submitGetPropertyRequest(
                 eq(mGetPropertyRequests), eq(mExecutor));
         // Create "EV_BATTERY_LEVEL", "FUEL_LEVEL", "FUEL_LEVEL_LOW", "RANGE_REMAINING",
-        // "DISTANCE_DISPLAY_UNITS" and "FUEL_VOLUME_DISPLAY_UNITS" property IDs list.
-        mPropertyIds.add(EV_BATTERY_LEVEL);
-        mPropertyIds.add(FUEL_LEVEL);
-        mPropertyIds.add(FUEL_LEVEL_LOW);
-        mPropertyIds.add(RANGE_REMAINING);
-        mPropertyIds.add(DISTANCE_DISPLAY_UNITS);
-        mPropertyIds.add(FUEL_VOLUME_DISPLAY_UNITS);
+        // "DISTANCE_DISPLAY_UNITS" and "FUEL_VOLUME_DISPLAY_UNITS" property IDs list with car
+        // zones.
+        Map<Integer, List<CarZone>> propertyIdsWithCarZones =
+                ImmutableMap.<Integer, List<CarZone>>builder()
+                        .put(EV_BATTERY_LEVEL, mCarZones)
+                        .put(FUEL_LEVEL, mCarZones)
+                        .put(FUEL_LEVEL_LOW, mCarZones)
+                        .put(RANGE_REMAINING, mCarZones)
+                        .put(DISTANCE_DISPLAY_UNITS, mCarZones)
+                        .put(FUEL_VOLUME_DISPLAY_UNITS, mCarZones)
+                        .buildKeepingLast();
         ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
                 OnCarPropertyResponseListener.class);
         verify(mPropertyManager, times(1)).submitRegisterListenerRequest(
-                eq(mPropertyIds), eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
+                eq(propertyIdsWithCarZones), eq(DEFAULT_SAMPLE_RATE), captor.capture(),
+                eq(mExecutor));
         captor.getValue().onCarPropertyResponses(mResponse);
         mCountDownLatch.await();
 
@@ -852,20 +820,14 @@
         float fuelCapacity = 120f;
         float fuelLevelValue = 50f;
         List<CarPropertyResponse<?>> capacities = new ArrayList<>();
-        capacities.add(CarPropertyResponse.builder()
-                .setPropertyId(INFO_EV_BATTERY_CAPACITY)
-                .setStatus(STATUS_UNAVAILABLE)
-                .setValue(evBatteryCapacity)
-                .setTimestampMillis(1L).build());
-        capacities.add(CarPropertyResponse.builder()
-                .setPropertyId(INFO_FUEL_CAPACITY)
-                .setStatus(STATUS_UNAVAILABLE)
-                .setValue(fuelCapacity)
-                .setTimestampMillis(1L).build());
-        ListenableFuture<List<CarPropertyResponse<?>>> future =
-                Futures.immediateFuture(capacities);
-        when(mPropertyManager.submitGetPropertyRequest(
-                eq(mGetPropertyRequests), any())).thenReturn(future);
+        capacities.add(CarPropertyResponse.builder().setPropertyId(
+                INFO_EV_BATTERY_CAPACITY).setStatus(STATUS_UNAVAILABLE).setValue(
+                evBatteryCapacity).setTimestampMillis(1L).build());
+        capacities.add(CarPropertyResponse.builder().setPropertyId(INFO_FUEL_CAPACITY).setStatus(
+                STATUS_UNAVAILABLE).setValue(fuelCapacity).setTimestampMillis(1L).build());
+        ListenableFuture<List<CarPropertyResponse<?>>> future = Futures.immediateFuture(capacities);
+        when(mPropertyManager.submitGetPropertyRequest(eq(mGetPropertyRequests), any())).thenReturn(
+                future);
 
         AtomicReference<EnergyLevel> loadedResult = new AtomicReference<>();
         OnCarDataAvailableListener<EnergyLevel> listener = (data) -> {
@@ -876,49 +838,38 @@
         mAutomotiveCarInfo.addEnergyLevelListener(mExecutor, listener);
 
         // Create "EV_BATTERY_LEVEL", "FUEL_LEVEL", "FUEL_LEVEL_LOW", "RANGE_REMAINING",
-        // "DISTANCE_DISPLAY_UNITS" and "FUEL_VOLUME_DISPLAY_UNITS" property IDs list.
-        mPropertyIds.add(EV_BATTERY_LEVEL);
-        mPropertyIds.add(FUEL_LEVEL);
-        mPropertyIds.add(FUEL_LEVEL_LOW);
-        mPropertyIds.add(RANGE_REMAINING);
-        mPropertyIds.add(DISTANCE_DISPLAY_UNITS);
-        mPropertyIds.add(FUEL_VOLUME_DISPLAY_UNITS);
+        // "DISTANCE_DISPLAY_UNITS" and "FUEL_VOLUME_DISPLAY_UNITS" property IDs list with car
+        // zones.
+        Map<Integer, List<CarZone>> propertyIdsWithCarZones =
+                ImmutableMap.<Integer, List<CarZone>>builder()
+                        .put(EV_BATTERY_LEVEL, mCarZones)
+                        .put(FUEL_LEVEL, mCarZones)
+                        .put(FUEL_LEVEL_LOW, mCarZones)
+                        .put(RANGE_REMAINING, mCarZones)
+                        .put(DISTANCE_DISPLAY_UNITS, mCarZones)
+                        .put(FUEL_VOLUME_DISPLAY_UNITS, mCarZones)
+                        .buildKeepingLast();
 
         verify(mPropertyManager, times(1)).submitGetPropertyRequest(
-                eq(mGetPropertyRequests), eq(mExecutor));
+                eq(mGetPropertyRequests),
+                eq(mExecutor));
         verify(mPropertyManager, times(1)).submitRegisterListenerRequest(
-                eq(mPropertyIds), eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
+                eq(propertyIdsWithCarZones), eq(DEFAULT_SAMPLE_RATE), captor.capture(),
+                eq(mExecutor));
 
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(EV_BATTERY_LEVEL)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(evBatteryLevelValue)
-                .setTimestampMillis(1L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(FUEL_LEVEL)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(fuelLevelValue)
-                .setTimestampMillis(1L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(FUEL_LEVEL_LOW)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(true)
-                .setTimestampMillis(1L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(RANGE_REMAINING)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(5f)
-                .setTimestampMillis(1L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(DISTANCE_DISPLAY_UNITS)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(meterDistanceUnit)
-                .setTimestampMillis(1L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(FUEL_VOLUME_DISPLAY_UNITS)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(meterVolumeUnit)
-                .setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(EV_BATTERY_LEVEL).setStatus(
+                STATUS_SUCCESS).setValue(evBatteryLevelValue).setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(FUEL_LEVEL).setStatus(
+                STATUS_SUCCESS).setValue(fuelLevelValue).setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(FUEL_LEVEL_LOW).setStatus(
+                STATUS_SUCCESS).setValue(true).setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(RANGE_REMAINING).setStatus(
+                STATUS_SUCCESS).setValue(5f).setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(DISTANCE_DISPLAY_UNITS).setStatus(
+                STATUS_SUCCESS).setValue(meterDistanceUnit).setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(
+                FUEL_VOLUME_DISPLAY_UNITS).setStatus(STATUS_SUCCESS).setValue(
+                meterVolumeUnit).setTimestampMillis(1L).build());
         captor.getValue().onCarPropertyResponses(mResponse);
         mCountDownLatch.await();
 
@@ -932,10 +883,8 @@
                 CarValue.UNKNOWN_FLOAT.getValue());
 
         // The other properties should still work without capacity values
-        assertThat(energyLevel.getEnergyIsLow().getValue()).isEqualTo(
-                true);
-        assertThat(energyLevel.getRangeRemainingMeters().getValue()).isEqualTo(
-                5f);
+        assertThat(energyLevel.getEnergyIsLow().getValue()).isEqualTo(true);
+        assertThat(energyLevel.getRangeRemainingMeters().getValue()).isEqualTo(5f);
         assertThat(energyLevel.getDistanceDisplayUnit().getValue()).isEqualTo(2);
         assertThat(energyLevel.getFuelVolumeDisplayUnit().getValue()).isEqualTo(201);
     }
@@ -954,20 +903,14 @@
         float fuelCapacity = 120f;
         float fuelLevelValue = 50f;
         List<CarPropertyResponse<?>> capacities = new ArrayList<>();
-        capacities.add(CarPropertyResponse.builder()
-                .setPropertyId(INFO_EV_BATTERY_CAPACITY)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(evBatteryCapacity)
-                .setTimestampMillis(1L).build());
-        capacities.add(CarPropertyResponse.builder()
-                .setPropertyId(INFO_FUEL_CAPACITY)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(fuelCapacity)
-                .setTimestampMillis(1L).build());
-        ListenableFuture<List<CarPropertyResponse<?>>> future =
-                Futures.immediateFuture(capacities);
-        when(mPropertyManager.submitGetPropertyRequest(
-                eq(mGetPropertyRequests), any())).thenReturn(future);
+        capacities.add(CarPropertyResponse.builder().setPropertyId(
+                INFO_EV_BATTERY_CAPACITY).setStatus(STATUS_SUCCESS).setValue(
+                evBatteryCapacity).setTimestampMillis(1L).build());
+        capacities.add(CarPropertyResponse.builder().setPropertyId(INFO_FUEL_CAPACITY).setStatus(
+                STATUS_SUCCESS).setValue(fuelCapacity).setTimestampMillis(1L).build());
+        ListenableFuture<List<CarPropertyResponse<?>>> future = Futures.immediateFuture(capacities);
+        when(mPropertyManager.submitGetPropertyRequest(eq(mGetPropertyRequests), any())).thenReturn(
+                future);
 
         AtomicReference<EnergyLevel> loadedResult = new AtomicReference<>();
         OnCarDataAvailableListener<EnergyLevel> listener = (data) -> {
@@ -978,45 +921,35 @@
         mAutomotiveCarInfo.addEnergyLevelListener(mExecutor, listener);
 
         // Create "EV_BATTERY_LEVEL", "FUEL_LEVEL", "FUEL_LEVEL_LOW", "RANGE_REMAINING",
-        // "DISTANCE_DISPLAY_UNITS" and "FUEL_VOLUME_DISPLAY_UNITS" property IDs list.
-        mPropertyIds.add(EV_BATTERY_LEVEL);
-        mPropertyIds.add(FUEL_LEVEL);
-        mPropertyIds.add(FUEL_LEVEL_LOW);
-        mPropertyIds.add(RANGE_REMAINING);
-        mPropertyIds.add(DISTANCE_DISPLAY_UNITS);
-        mPropertyIds.add(FUEL_VOLUME_DISPLAY_UNITS);
+        // "DISTANCE_DISPLAY_UNITS" and "FUEL_VOLUME_DISPLAY_UNITS" property IDs list with car
+        // zones.
+        Map<Integer, List<CarZone>> propertyIdsWithCarZones =
+                ImmutableMap.<Integer, List<CarZone>>builder()
+                        .put(EV_BATTERY_LEVEL, mCarZones)
+                        .put(FUEL_LEVEL, mCarZones)
+                        .put(FUEL_LEVEL_LOW, mCarZones)
+                        .put(RANGE_REMAINING, mCarZones)
+                        .put(DISTANCE_DISPLAY_UNITS, mCarZones)
+                        .put(FUEL_VOLUME_DISPLAY_UNITS, mCarZones)
+                        .buildKeepingLast();
 
-        verify(mPropertyManager, times(1)).submitGetPropertyRequest(
-                eq(mGetPropertyRequests), eq(mExecutor));
+        verify(mPropertyManager, times(1)).submitGetPropertyRequest(eq(mGetPropertyRequests),
+                eq(mExecutor));
         verify(mPropertyManager, times(1)).submitRegisterListenerRequest(
-                eq(mPropertyIds), eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
+                eq(propertyIdsWithCarZones), eq(DEFAULT_SAMPLE_RATE), captor.capture(),
+                eq(mExecutor));
 
         // Missing response for FUEL_VOLUME_DISPLAY_UNITS.
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(EV_BATTERY_LEVEL)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(evBatteryLevelValue)
-                .setTimestampMillis(1L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(FUEL_LEVEL)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(fuelLevelValue)
-                .setTimestampMillis(1L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(FUEL_LEVEL_LOW)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(true)
-                .setTimestampMillis(1L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(RANGE_REMAINING)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(5f)
-                .setTimestampMillis(1L).build());
-        mResponse.add(CarPropertyResponse.builder()
-                .setPropertyId(DISTANCE_DISPLAY_UNITS)
-                .setStatus(STATUS_SUCCESS)
-                .setValue(meterDistanceUnit)
-                .setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(EV_BATTERY_LEVEL).setStatus(
+                STATUS_SUCCESS).setValue(evBatteryLevelValue).setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(FUEL_LEVEL).setStatus(
+                STATUS_SUCCESS).setValue(fuelLevelValue).setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(FUEL_LEVEL_LOW).setStatus(
+                STATUS_SUCCESS).setValue(true).setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(RANGE_REMAINING).setStatus(
+                STATUS_SUCCESS).setValue(5f).setTimestampMillis(1L).build());
+        mResponse.add(CarPropertyResponse.builder().setPropertyId(DISTANCE_DISPLAY_UNITS).setStatus(
+                STATUS_SUCCESS).setValue(meterDistanceUnit).setTimestampMillis(1L).build());
 
         captor.getValue().onCarPropertyResponses(mResponse);
         mCountDownLatch.await();
@@ -1027,10 +960,8 @@
                 evBatteryLevelValue / evBatteryCapacity * 100);
         assertThat(energyLevel.getFuelPercent().getValue()).isEqualTo(
                 fuelLevelValue / fuelCapacity * 100);
-        assertThat(energyLevel.getEnergyIsLow().getValue()).isEqualTo(
-                true);
-        assertThat(energyLevel.getRangeRemainingMeters().getValue()).isEqualTo(
-                5f);
+        assertThat(energyLevel.getEnergyIsLow().getValue()).isEqualTo(true);
+        assertThat(energyLevel.getRangeRemainingMeters().getValue()).isEqualTo(5f);
         assertThat(energyLevel.getDistanceDisplayUnit().getValue()).isEqualTo(2);
     }
 }