[go: nahoru, domu]

Enable CarZone in the GetPropertyRequest/response

Enable the CarZone in the GetPropertyRequest and CarPropertyResponse.
Set the default value for  the property with global areaId. Added some
tests cases for CarZone in CarInfo APIs

Bug: 190869722
Test: :car:app:app:build
Change-Id: I981973aec099b8e95519f1dc1a0f715f927dc358
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/CarPropertyResponse.java b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/CarPropertyResponse.java
index 75a19c4..87abaf1 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/CarPropertyResponse.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/CarPropertyResponse.java
@@ -18,15 +18,17 @@
 
 import static androidx.annotation.RestrictTo.Scope.LIBRARY;
 
-import android.car.hardware.CarPropertyValue;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
 import androidx.annotation.RestrictTo;
+import androidx.car.app.annotations.ExperimentalCarApi;
 
 import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
 
-import java.util.concurrent.TimeUnit;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * Container class for information about property value and status.
@@ -40,49 +42,6 @@
 @RestrictTo(LIBRARY)
 @AutoValue
 public abstract class CarPropertyResponse<T> {
-    /**
-     * Creates a response for {@link androidx.car.app.hardware.info.AutomotiveCarInfo}.
-     *
-     * @param propertyId        one of the values in {@link android.car.VehiclePropertyIds}.
-     * @param status            one of the values in {@link CarValue.StatusCode}
-     * @param timestampMillis   timestamp in milliseconds
-     * @param value             the same value in {@link CarPropertyValue#getValue()}
-     * @param <T>               the value type of {@link CarPropertyResponse}
-     */
-    @NonNull
-    public static <T> CarPropertyResponse<T> create(int propertyId,
-            @CarValue.StatusCode int status, long timestampMillis, @Nullable T value) {
-        return new AutoValue_CarPropertyResponse<>(propertyId, status, timestampMillis,
-                value);
-    }
-
-    /**
-     * Creates a response from {@link CarPropertyValue}.
-     *
-     * @see #create(int, int, long, Object)
-     */
-    @SuppressWarnings("unchecked")
-    @NonNull
-    public static <T> CarPropertyResponse<T> createFromPropertyValue(
-            @NonNull CarPropertyValue<T> propertyValue) {
-        int status = PropertyUtils.mapToStatusCodeInCarValue(propertyValue.getStatus());
-        long timestamp = TimeUnit.MILLISECONDS.convert(propertyValue.getTimestamp(),
-                TimeUnit.NANOSECONDS);
-        return create(propertyValue.getPropertyId(), status, timestamp,
-                propertyValue.getValue());
-    }
-
-
-    /**
-     * Creates an error response. The timestamp is always 0 and the value is always {@code null}.
-     *
-     * @see #create(int, int, long, Object)
-     */
-    @NonNull
-    public static <T> CarPropertyResponse<T> createErrorResponse(int propertyId,
-            @CarValue.StatusCode int status) {
-        return create(propertyId, status, 0, null);
-    }
 
     /** Returns one of the values in {@link android.car.VehiclePropertyIds}. */
     public abstract int getPropertyId();
@@ -101,4 +60,51 @@
     /** Returns response's value. */
     @Nullable
     public abstract T getValue();
+
+
+    /** Returns a list of {@link CarZone}s. */
+    @NonNull
+    public abstract ImmutableList<CarZone> getCarZones();
+
+    /** Get a builder class for {@link CarPropertyResponse}*/
+    @NonNull
+    @OptIn(markerClass = ExperimentalCarApi.class)
+    public static <T> Builder<T> builder() {
+        return new AutoValue_CarPropertyResponse.Builder<T>()
+                .setCarZones(Collections.singletonList(CarZone.CAR_ZONE_GLOBAL))
+                .setValue(null)
+                .setTimestampMillis(0);
+    }
+
+    /**
+     * A builder for {@link CarPropertyResponse}
+     *
+     * @param <T> is the value type of {@link CarPropertyResponse#getValue()}
+     */
+    @AutoValue.Builder
+    public abstract static class Builder<T> {
+        /** Sets a property ID for the {@link CarPropertyResponse}. */
+        @NonNull
+        public abstract Builder<T> setPropertyId(int propertyId);
+
+        /** Sets a timestamp for the {@link CarPropertyResponse}. */
+        @NonNull
+        public abstract Builder<T> setTimestampMillis(long timestampMillis);
+
+        /** Sets a value for the {@link CarPropertyResponse}. */
+        @NonNull
+        public abstract Builder<T> setValue(@Nullable T value);
+
+        /** Sets a status code for the {@link CarPropertyResponse}. */
+        @NonNull
+        public abstract Builder<T> setStatus(@CarValue.StatusCode int status);
+
+        /** Sets the list of {@link CarZone}s for the {@link CarPropertyResponse}. */
+        @NonNull
+        public abstract Builder<T> setCarZones(@NonNull List<CarZone> carZones);
+
+        /** Create an instance of {@link CarPropertyResponse}. */
+        @NonNull
+        public abstract CarPropertyResponse<T> build();
+    }
 }
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/GetPropertyRequest.java b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/GetPropertyRequest.java
index 7d59e28..78b4195 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/GetPropertyRequest.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/GetPropertyRequest.java
@@ -19,9 +19,15 @@
 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;
+import com.google.common.collect.ImmutableList;
+
+import java.util.Collections;
+import java.util.List;
 
 /**
  * Container class for information about getting property values.
@@ -34,14 +40,44 @@
 @RestrictTo(LIBRARY)
 public abstract class GetPropertyRequest {
     /**
+     * Creates a request with {@link CarZone#CAR_ZONE_GLOBAL}.
+     *
      * @param propertyId    one of the values in {@link android.car.VehiclePropertyIds}
      */
     @NonNull
     public static GetPropertyRequest create(int propertyId) {
-        return new AutoValue_GetPropertyRequest(propertyId);
+        return builder().setPropertyId(propertyId).build();
     }
 
     /** Returns one of the values in {@link android.car.VehiclePropertyIds}. */
     public abstract int getPropertyId();
 
+    /** Returns a list of {@link CarZone}s associated with this request.  */
+    @NonNull
+    public abstract ImmutableList<CarZone> getCarZones();
+
+    /** Get a {@link Builder} for creating the {@link GetPropertyRequest}. */
+    @NonNull
+    @OptIn(markerClass = ExperimentalCarApi.class)
+    public static Builder builder() {
+        return new AutoValue_GetPropertyRequest.Builder()
+                .setCarZones(Collections.singletonList(CarZone.CAR_ZONE_GLOBAL));
+    }
+
+    /** A builder for the {@link GetPropertyRequest}. */
+    @AutoValue.Builder
+    public abstract static class Builder {
+        /** Sets a property ID for the {@link GetPropertyRequest}. */
+        @NonNull
+        public abstract Builder setPropertyId(int propertyId);
+
+        /** Sets a list of {@link CarZone}s for the {@link GetPropertyRequest}. */
+        @NonNull
+        public abstract Builder setCarZones(@NonNull List<CarZone> carZones);
+
+        /** Creates an instance of {@link GetPropertyRequest}. */
+        @NonNull
+        public abstract GetPropertyRequest 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 fd8170a..59d7a1c 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
@@ -277,15 +277,12 @@
         // TODO(b/190869722): handle AreaId to VehicleZone map in V1.2
         List<CarPropertyResponse<?>> carResponses = new ArrayList<>();
         for (CarPropertyValue<?> value : propertyValues) {
-            int statusCode = PropertyUtils.mapToStatusCodeInCarValue(value.getStatus());
-            long timeInMillis = TimeUnit.MILLISECONDS.convert(value.getTimestamp(),
-                    TimeUnit.NANOSECONDS);
-            carResponses.add(CarPropertyResponse.create(
-                    value.getPropertyId(), statusCode, timeInMillis, value.getValue()));
+            carResponses.add(PropertyUtils.convertPropertyValueToPropertyResponse(value));
         }
         for (CarInternalError error: propertyErrors) {
-            carResponses.add(CarPropertyResponse.createErrorResponse(error.getPropertyId(),
-                    error.getErrorCode()));
+            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 b3c7c55..c43e584 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
@@ -73,8 +73,8 @@
                 // add an init value if needed
                 if (mPropertyIdToResponse.get(propertyId) == null) {
                     mPropertyIdToResponse.put(propertyId,
-                            CarPropertyResponse.createErrorResponse(propertyId,
-                                    CarValue.STATUS_UNAVAILABLE));
+                            CarPropertyResponse.builder().setPropertyId(propertyId)
+                                    .setValue(CarValue.STATUS_SUCCESS).build());
                 }
             }
         }
@@ -109,8 +109,8 @@
             for (int propertyId : propertyIds) {
                 // return a response with unavailable status if can not find in cache
                 CarPropertyResponse<?> propertyResponse = mPropertyIdToResponse.get(propertyId,
-                        CarPropertyResponse.createErrorResponse(propertyId,
-                                CarValue.STATUS_UNAVAILABLE));
+                        CarPropertyResponse.builder().setPropertyId(propertyId)
+                                .setValue(CarValue.STATUS_SUCCESS).build());
                 values.add(propertyResponse);
             }
         }
@@ -159,7 +159,7 @@
             if (responseInCache.getTimestampMillis() <= timestampMs) {
                 // In V1.1, all properties are global properties.
                 CarPropertyResponse<?> response =
-                        CarPropertyResponse.createFromPropertyValue(propertyValue);
+                        PropertyUtils.convertPropertyValueToPropertyResponse(propertyValue);
                 mPropertyIdToResponse.put(propertyId, response);
                 return true;
             }
@@ -169,8 +169,9 @@
 
     /** Updates the error event in cache */
     void updateInternalError(CarInternalError internalError) {
-        CarPropertyResponse<?> response = CarPropertyResponse.createErrorResponse(
-                internalError.getPropertyId(), internalError.getErrorCode());
+        CarPropertyResponse<?> response = CarPropertyResponse.builder()
+                .setPropertyId(internalError.getPropertyId())
+                .setStatus(internalError.getErrorCode()).build();
         synchronized (mLock) {
             mPropertyIdToResponse.put(internalError.getPropertyId(), 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 8dc560a..9e5e0c5 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
@@ -23,21 +23,27 @@
 import static androidx.car.app.hardware.common.CarUnit.US_GALLON;
 
 import android.car.Car;
+import android.car.VehicleAreaSeat;
+import android.car.VehicleAreaType;
 import android.car.VehiclePropertyIds;
 import android.car.hardware.CarPropertyValue;
 import android.util.Pair;
 import android.util.SparseArray;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.OptIn;
 import androidx.annotation.RestrictTo;
 import androidx.car.app.annotations.ExperimentalCarApi;
 import androidx.car.app.hardware.info.AutomotiveCarInfo;
 import androidx.car.app.hardware.info.EnergyProfile;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Utility functions to work with {@link android.car.hardware.CarPropertyValue}
@@ -102,6 +108,39 @@
         }
     };
 
+    @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());
+        }
+    };
+
     // Permissions for writing properties. They are system level permissions.
     private static final SparseArray<String> PERMISSION_WRITE_PROPERTY = new SparseArray<String>() {
         {
@@ -247,6 +286,30 @@
     }
 
     /**
+     * Creates a response from {@link CarPropertyValue}.
+     */
+    @SuppressWarnings("unchecked")
+    @NonNull
+    @OptIn(markerClass = ExperimentalCarApi.class)
+    public static CarPropertyResponse<?> convertPropertyValueToPropertyResponse(
+            @NonNull CarPropertyValue<?> propertyValue) {
+        int status = mapToStatusCodeInCarValue(propertyValue.getStatus());
+        long timestamp = TimeUnit.MILLISECONDS.convert(propertyValue.getTimestamp(),
+                TimeUnit.NANOSECONDS);
+        List<CarZone> carZones;
+        if (propertyValue.getAreaId() == VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL) {
+            carZones = Collections.singletonList(CarZone.CAR_ZONE_GLOBAL);
+        } else {
+            carZones = mapAreaIdToCarZones(propertyValue.getAreaId());
+        }
+        return CarPropertyResponse.builder().setValue(propertyValue.getValue())
+                .setPropertyId(propertyValue.getPropertyId())
+                .setCarZones(carZones)
+                .setStatus(status)
+                .setTimestampMillis(timestamp).build();
+    }
+
+    /**
      * Returns a {@link Set<String>} that contains permissions for reading properties.
      *
      * @throws SecurityException if android application cannot access the property
@@ -319,6 +382,21 @@
         }
     }
 
+    /**
+     * 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));
+            }
+        }
+        return carZones;
+    }
+
     private PropertyUtils() {
     }
 }
diff --git a/car/app/app-automotive/src/test/java/androidx/car/app/hardware/common/PropertyManagerTest.java b/car/app/app-automotive/src/test/java/androidx/car/app/hardware/common/PropertyManagerTest.java
index ff5ac31..01eeb85 100644
--- a/car/app/app-automotive/src/test/java/androidx/car/app/hardware/common/PropertyManagerTest.java
+++ b/car/app/app-automotive/src/test/java/androidx/car/app/hardware/common/PropertyManagerTest.java
@@ -39,6 +39,7 @@
 import org.robolectric.shadows.ShadowApplication;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
@@ -47,6 +48,8 @@
 @DoNotInstrument
 public class PropertyManagerTest extends MockedCarTestBase {
     private final Executor mExecutor = Executors.newSingleThreadExecutor();
+    private static final List<CarZone> CAR_ZONE_GLOBAL =
+            Collections.singletonList(CarZone.CAR_ZONE_GLOBAL);
     private PropertyManager mPropertyManager;
 
     @Before
@@ -78,6 +81,7 @@
         assertThat(infoYearResponse.getPropertyId()).isEqualTo(VehiclePropertyIds.INFO_MODEL_YEAR);
         assertThat(infoYearResponse.getValue()).isEqualTo(MODEL_YEAR);
         assertThat(infoYearResponse.getStatus()).isEqualTo(CarValue.STATUS_SUCCESS);
+        assertThat(infoYearResponse.getCarZones()).isEqualTo(CAR_ZONE_GLOBAL);
     }
 
     /**
@@ -104,8 +108,10 @@
         assertThat(infoMakerResponse.getStatus()).isEqualTo(CarValue.STATUS_UNAVAILABLE);
         assertThat(infoModelResponse.getValue()).isEqualTo(MODEL_NAME);
         assertThat(infoModelResponse.getStatus()).isEqualTo(CarValue.STATUS_UNKNOWN);
+        assertThat(infoModelResponse.getCarZones()).isEqualTo(CAR_ZONE_GLOBAL);
         assertThat(infoYearResponse.getValue()).isEqualTo(MODEL_YEAR);
         assertThat(infoYearResponse.getStatus()).isEqualTo(CarValue.STATUS_SUCCESS);
+        assertThat(infoYearResponse.getCarZones()).isEqualTo(CAR_ZONE_GLOBAL);
     }
 
     /**
@@ -137,6 +143,7 @@
         assertThat(responses.size()).isEqualTo(requests.size());
         assertThat(response.getValue()).isNull();
         assertThat(response.getStatus()).isEqualTo(CarValue.STATUS_UNIMPLEMENTED);
+        assertThat(response.getCarZones()).isEqualTo(CAR_ZONE_GLOBAL);
     }
 
     private static SparseArray<CarPropertyResponse<?>> getCarPropertyResponseMap(
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 23c5c8e..d43df1a 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
@@ -36,6 +36,7 @@
 
 import static androidx.car.app.hardware.common.CarValue.STATUS_SUCCESS;
 import static androidx.car.app.hardware.common.CarValue.STATUS_UNAVAILABLE;
+import static androidx.car.app.hardware.common.CarValue.STATUS_UNKNOWN;
 import static androidx.car.app.hardware.info.AutomotiveCarInfo.DEFAULT_SAMPLE_RATE;
 import static androidx.car.app.hardware.info.AutomotiveCarInfo.SPEED_DISPLAY_UNIT_ID;
 import static androidx.car.app.hardware.info.AutomotiveCarInfo.TOLL_CARD_STATUS_ID;
@@ -59,6 +60,7 @@
 import androidx.car.app.hardware.common.CarPropertyResponse;
 import androidx.car.app.hardware.common.CarUnit;
 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;
@@ -79,6 +81,7 @@
 import org.robolectric.annotation.internal.DoNotInstrument;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
@@ -103,6 +106,8 @@
     private CarPropertyManager mCarPropertyManagerMock;
     @Mock
     private PropertyManager mPropertyManager;
+    private static final List<CarZone> GLOBAL_ZONE = Collections.singletonList(
+            CarZone.CAR_ZONE_GLOBAL);
 
     @Before
     public void setUp() {
@@ -124,12 +129,21 @@
         mGetPropertyRequests.add(GetPropertyRequest.create(INFO_MODEL_YEAR));
 
         // Add "make", "model", "year" values to the response.
-        mResponse.add(CarPropertyResponse.create(INFO_MAKE,
-                STATUS_SUCCESS, 1, "Toy Vehicle"));
-        mResponse.add(CarPropertyResponse.create(INFO_MODEL,
-                STATUS_SUCCESS, 2, "Speedy Model"));
-        mResponse.add(CarPropertyResponse.create(INFO_MODEL_YEAR,
-                STATUS_SUCCESS, 3, 2020));
+        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(
@@ -149,6 +163,10 @@
         assertThat(mModel.getYear().getValue()).isEqualTo(2020);
         assertThat(mModel.getManufacturer().getTimestampMillis()).isEqualTo(1);
         assertThat(mModel.getName().getTimestampMillis()).isEqualTo(2);
+        // test CarZone
+        assertThat(mModel.getName().getCarZones()).isEqualTo(GLOBAL_ZONE);
+        assertThat(mModel.getManufacturer().getCarZones()).isEqualTo(GLOBAL_ZONE);
+        assertThat(mModel.getYear().getCarZones()).isEqualTo(GLOBAL_ZONE);
         assertThat(mModel.getYear().getTimestampMillis()).isEqualTo(3);
     }
 
@@ -159,12 +177,21 @@
         mGetPropertyRequests.add(GetPropertyRequest.create(INFO_MODEL));
 
         // Add "make", "model", "year" values to the response.
-        mResponse.add(CarPropertyResponse.create(INFO_MAKE,
-                STATUS_SUCCESS, 1, "Toy Vehicle"));
-        mResponse.add(CarPropertyResponse.create(INFO_MODEL,
-                STATUS_SUCCESS, 2, "Speedy Model"));
-        mResponse.add(CarPropertyResponse.create(INFO_MODEL_YEAR,
-                STATUS_SUCCESS, 3, 2020));
+        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(
@@ -187,10 +214,16 @@
         mGetPropertyRequests.add(GetPropertyRequest.create(INFO_FUEL_TYPE));
 
         // Add "evConnector" and "fuel" type of the vehicle to the response.
-        mResponse.add(CarPropertyResponse.create(INFO_EV_CONNECTOR_TYPE,
-                STATUS_SUCCESS, 1, new Integer[]{chademoInVehicle}));
-        mResponse.add(CarPropertyResponse.create(INFO_FUEL_TYPE,
-                STATUS_SUCCESS, 2, new Integer[]{FUEL_TYPE_UNLEADED}));
+        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(
@@ -262,9 +295,16 @@
         verify(mPropertyManager).submitRegisterListenerRequest(eq(mPropertyIds),
                 eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
 
-        mResponse.add(CarPropertyResponse.create(PERF_ODOMETER, STATUS_SUCCESS, 1, 1f));
-        mResponse.add(CarPropertyResponse.create(DISTANCE_DISPLAY_UNITS, STATUS_SUCCESS, 2,
-                meterUnit));
+        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();
@@ -297,9 +337,16 @@
         verify(mPropertyManager, times(2)).submitRegisterListenerRequest(
                 eq(mPropertyIds), eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
 
-        mResponse.add(CarPropertyResponse.create(PERF_ODOMETER, STATUS_SUCCESS, 1, 1f));
-        mResponse.add(CarPropertyResponse.create(DISTANCE_DISPLAY_UNITS, STATUS_SUCCESS, 2,
-                meterUnit));
+        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();
@@ -345,9 +392,16 @@
                 eq(mPropertyIds), eq(DEFAULT_SAMPLE_RATE), firstCaptor.capture(),
                 eq(firstExecutor));
 
-        mResponse.add(CarPropertyResponse.create(PERF_ODOMETER, STATUS_SUCCESS, 1, 1f));
-        mResponse.add(CarPropertyResponse.create(DISTANCE_DISPLAY_UNITS, STATUS_SUCCESS, 2,
-                meterUnit));
+        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);
 
@@ -393,9 +447,16 @@
         verify(mPropertyManager).submitRegisterListenerRequest(eq(mPropertyIds),
                 eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
 
-        mResponse.add(CarPropertyResponse.create(EV_CHARGE_PORT_OPEN, STATUS_SUCCESS, 1, true));
-        mResponse.add(
-                CarPropertyResponse.create(EV_CHARGE_PORT_CONNECTED, STATUS_SUCCESS, 2, false));
+        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();
@@ -424,10 +485,16 @@
         verify(mPropertyManager).submitRegisterListenerRequest(eq(mPropertyIds),
                 eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
 
-        mResponse.add(CarPropertyResponse.create(EV_CHARGE_PORT_OPEN, STATUS_SUCCESS, 1, true));
-        mResponse.add(
-                CarPropertyResponse.create(EV_CHARGE_PORT_CONNECTED, CarValue.STATUS_UNKNOWN, 1,
-                        null));
+        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_UNKNOWN)
+                .setValue(null)
+                .setTimestampMillis(1L).build());
 
         captor.getValue().onCarPropertyResponses(mResponse);
         mCountDownLatch.await();
@@ -457,14 +524,18 @@
         verify(mPropertyManager).submitRegisterListenerRequest(eq(mPropertyIds),
                 eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
 
-        mResponse.add(CarPropertyResponse.create(TOLL_CARD_STATUS_ID,
-                STATUS_SUCCESS, 1, TollCard.TOLLCARD_STATE_VALID));
+        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();
 
         TollCard tollCard = loadedResult.get();
         assertThat(tollCard.getCardState().getValue()).isEqualTo(TollCard.TOLLCARD_STATE_VALID);
+        assertThat(tollCard.getCardState().getCarZones()).isEqualTo(GLOBAL_ZONE);
     }
 
     @Config(maxSdk = 30)
@@ -479,6 +550,7 @@
 
         TollCard tollCard = loadedResult.get();
         assertThat(tollCard.getCardState().getStatus()).isEqualTo(CarValue.STATUS_UNIMPLEMENTED);
+        assertThat(tollCard.getCardState().getCarZones().isEmpty()).isTrue();
     }
 
     @Test
@@ -508,12 +580,21 @@
         verify(mPropertyManager).submitRegisterListenerRequest(eq(mPropertyIds),
                 eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
 
-        mResponse.add(CarPropertyResponse.create(SPEED_DISPLAY_UNIT_ID, STATUS_SUCCESS, 1,
-                metersPerSec));
-        mResponse.add(CarPropertyResponse.create(PERF_VEHICLE_SPEED,
-                STATUS_SUCCESS, 2, defaultRawSpeed));
-        mResponse.add(CarPropertyResponse.create(PERF_VEHICLE_SPEED_DISPLAY,
-                STATUS_SUCCESS, 3, defaultSpeed));
+        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();
@@ -539,10 +620,16 @@
         float fuelCapacity = 120f;
         float fuelLevelValue = 50f;
         List<CarPropertyResponse<?>> capacities = new ArrayList<>();
-        capacities.add(CarPropertyResponse.create(INFO_EV_BATTERY_CAPACITY,
-                STATUS_SUCCESS, 1, evBatteryCapacity));
-        capacities.add(CarPropertyResponse.create(INFO_FUEL_CAPACITY,
-                STATUS_SUCCESS, 1, fuelCapacity));
+        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()))
@@ -570,18 +657,37 @@
         verify(mPropertyManager, times(1)).submitRegisterListenerRequest(
                 eq(mPropertyIds), eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
 
-        mResponse.add(CarPropertyResponse.create(EV_BATTERY_LEVEL,
-                STATUS_SUCCESS, 1, evBatteryLevelValue));
-        mResponse.add(CarPropertyResponse.create(FUEL_LEVEL,
-                STATUS_SUCCESS, 1, fuelLevelValue));
-        mResponse.add(CarPropertyResponse.create(FUEL_LEVEL_LOW,
-                STATUS_SUCCESS, 1, true));
-        mResponse.add(CarPropertyResponse.create(RANGE_REMAINING,
-                STATUS_SUCCESS, 1, 5f));
-        mResponse.add(CarPropertyResponse.create(DISTANCE_DISPLAY_UNITS,
-                STATUS_SUCCESS, 1, meterDistanceUnit));
-        mResponse.add(CarPropertyResponse.create(FUEL_VOLUME_DISPLAY_UNITS,
-                STATUS_SUCCESS, 2, meterVolumeUnit));
+        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();
 
@@ -613,10 +719,16 @@
         float fuelCapacity = 120f;
         float fuelLevelValue = 50f;
         List<CarPropertyResponse<?>> capacities = new ArrayList<>();
-        capacities.add(CarPropertyResponse.create(INFO_EV_BATTERY_CAPACITY,
-                STATUS_UNAVAILABLE, 1, evBatteryCapacity));
-        capacities.add(CarPropertyResponse.create(INFO_FUEL_CAPACITY,
-                STATUS_UNAVAILABLE, 1, fuelCapacity));
+        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(
@@ -644,18 +756,36 @@
         verify(mPropertyManager, times(1)).submitRegisterListenerRequest(
                 eq(mPropertyIds), eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
 
-        mResponse.add(CarPropertyResponse.create(EV_BATTERY_LEVEL,
-                STATUS_SUCCESS, 1, evBatteryLevelValue));
-        mResponse.add(CarPropertyResponse.create(FUEL_LEVEL,
-                STATUS_SUCCESS, 1, fuelLevelValue));
-        mResponse.add(CarPropertyResponse.create(FUEL_LEVEL_LOW,
-                STATUS_SUCCESS, 1, true));
-        mResponse.add(CarPropertyResponse.create(RANGE_REMAINING,
-                STATUS_SUCCESS, 1, 5f));
-        mResponse.add(CarPropertyResponse.create(DISTANCE_DISPLAY_UNITS,
-                STATUS_SUCCESS, 1, meterDistanceUnit));
-        mResponse.add(CarPropertyResponse.create(FUEL_VOLUME_DISPLAY_UNITS,
-                STATUS_SUCCESS, 2, meterVolumeUnit));
+        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();
 
@@ -691,10 +821,16 @@
         float fuelCapacity = 120f;
         float fuelLevelValue = 50f;
         List<CarPropertyResponse<?>> capacities = new ArrayList<>();
-        capacities.add(CarPropertyResponse.create(INFO_EV_BATTERY_CAPACITY,
-                STATUS_SUCCESS, 1, evBatteryCapacity));
-        capacities.add(CarPropertyResponse.create(INFO_FUEL_CAPACITY,
-                STATUS_SUCCESS, 1, fuelCapacity));
+        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(
@@ -723,16 +859,31 @@
                 eq(mPropertyIds), eq(DEFAULT_SAMPLE_RATE), captor.capture(), eq(mExecutor));
 
         // Missing response for FUEL_VOLUME_DISPLAY_UNITS.
-        mResponse.add(CarPropertyResponse.create(EV_BATTERY_LEVEL,
-                STATUS_SUCCESS, 1, evBatteryLevelValue));
-        mResponse.add(CarPropertyResponse.create(FUEL_LEVEL,
-                STATUS_SUCCESS, 1, fuelLevelValue));
-        mResponse.add(CarPropertyResponse.create(FUEL_LEVEL_LOW,
-                STATUS_SUCCESS, 1, true));
-        mResponse.add(CarPropertyResponse.create(RANGE_REMAINING,
-                STATUS_SUCCESS, 1, 5f));
-        mResponse.add(CarPropertyResponse.create(DISTANCE_DISPLAY_UNITS,
-                STATUS_SUCCESS, 1, meterDistanceUnit));
+        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();