1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.car.hardware;
18 
19 import static android.car.feature.Flags.FLAG_CAR_PROPERTY_VALUE_PROPERTY_STATUS;
20 
21 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
22 
23 import static java.lang.Integer.toHexString;
24 
25 import android.annotation.FlaggedApi;
26 import android.annotation.IntDef;
27 import android.annotation.NonNull;
28 import android.car.VehiclePropertyIds;
29 import android.car.builtin.os.ParcelHelper;
30 import android.os.Parcel;
31 import android.os.Parcelable;
32 
33 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
34 
35 import java.lang.annotation.Retention;
36 import java.lang.annotation.RetentionPolicy;
37 import java.nio.charset.Charset;
38 import java.nio.charset.StandardCharsets;
39 import java.util.Arrays;
40 import java.util.Objects;
41 
42 /**
43  * Stores a value for a vehicle property ID and area ID combination.
44  *
45  * Client should use {@code android.car.*} types when dealing with property ID, area ID or property
46  * value and MUST NOT use {@code android.hardware.automotive.vehicle.*} types directly.
47  *
48  * @param <T> refer to {@link Parcel#writeValue(java.lang.Object)} to get a list of all supported
49  *            types. The class should be visible to framework as default class loader is being used
50  *            here.
51  */
52 public final class CarPropertyValue<T> implements Parcelable {
53     private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
54 
55     private final int mPropertyId;
56     private final int mAreaId;
57     private final int mStatus;
58     private final long mTimestampNanos;
59     private final T mValue;
60 
61     /** @removed accidentally exposed previously */
62     @IntDef({
63         STATUS_AVAILABLE,
64         STATUS_UNAVAILABLE,
65         STATUS_ERROR
66     })
67     @Retention(RetentionPolicy.SOURCE)
68     public @interface PropertyStatus {}
69 
70     /**
71      * {@code CarPropertyValue} is available.
72      */
73     public static final int STATUS_AVAILABLE = 0;
74 
75     /**
76      * {@code CarPropertyValue} is unavailable.
77      */
78     public static final int STATUS_UNAVAILABLE = 1;
79 
80     /**
81      * {@code CarPropertyValue} has an error.
82      */
83     public static final int STATUS_ERROR = 2;
84 
85     /**
86      * Creates an instance of {@code CarPropertyValue}.
87      *
88      * @param propertyId The property identifier, see constants in
89      *                   {@link android.car.VehiclePropertyIds} for system defined property IDs.
90      * @param areaId     The area identifier. Must be {@code 0} if property is
91      *                   {@link android.car.VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}. Otherwise, it
92      *                   must be one or more OR'd together constants of this property's
93      *                   {@link android.car.VehicleAreaType}:
94      *                     <ul>
95      *                       <li>{@code VehicleAreaWindow}</li>
96      *                       <li>{@code VehicleAreaDoor}</li>
97      *                       <li>{@link android.car.VehicleAreaSeat}</li>
98      *                       <li>{@code VehicleAreaMirror}</li>
99      *                       <li>{@link android.car.VehicleAreaWheel}</li>
100      *                     </ul>
101      * @param value Value of Property
102      * @hide
103      */
CarPropertyValue(int propertyId, int areaId, T value)104     public CarPropertyValue(int propertyId, int areaId, T value) {
105         this(propertyId, areaId, /* timestampNanos= */ 0, value);
106     }
107 
108     /**
109      * Creates an instance of {@code CarPropertyValue}. The {@code timestampNanos} is the time in
110      * nanoseconds at which the event happened. For a given car property, each new {@code
111      * CarPropertyValue} should be monotonically increasing using the same time base as
112      * {@link android.os.SystemClock#elapsedRealtimeNanos()}.
113      *
114      *
115      * @param propertyId The property identifier, see constants in
116      *                   {@link android.car.VehiclePropertyIds} for system defined property IDs.
117      * @param areaId     The area identifier. Must be {@code 0} if property is
118      *                   {@link android.car.VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}. Otherwise, it
119      *                   must be one or more OR'd together constants of this property's
120      *                   {@link android.car.VehicleAreaType}:
121      *                     <ul>
122      *                       <li>{@code VehicleAreaWindow}</li>
123      *                       <li>{@code VehicleAreaDoor}</li>
124      *                       <li>{@link android.car.VehicleAreaSeat}</li>
125      *                       <li>{@code VehicleAreaMirror}</li>
126      *                       <li>{@link android.car.VehicleAreaWheel}</li>
127      *                     </ul>
128      * @param timestampNanos  Elapsed time in nanoseconds since boot
129      * @param value      Value of Property
130      * @hide
131      */
CarPropertyValue(int propertyId, int areaId, long timestampNanos, T value)132     public CarPropertyValue(int propertyId, int areaId, long timestampNanos, T value) {
133         this(propertyId, areaId, CarPropertyValue.STATUS_AVAILABLE, timestampNanos, value);
134     }
135 
136     /**
137      * @hide
138      *
139      * @deprecated use {@link CarPropertyValue#CarPropertyValue(int, int, long, T)} instead
140      */
141     @Deprecated
CarPropertyValue(int propertyId, int areaId, int status, long timestampNanos, T value)142     public CarPropertyValue(int propertyId, int areaId, int status, long timestampNanos, T value) {
143         mPropertyId = propertyId;
144         mAreaId = areaId;
145         mStatus = status;
146         mTimestampNanos = timestampNanos;
147         mValue = value;
148     }
149 
150     /**
151      * Creates an instance of {@code CarPropertyValue}.
152      *
153      * @param in Parcel to read
154      * @hide
155      */
156     @SuppressWarnings("unchecked")
CarPropertyValue(Parcel in)157     public CarPropertyValue(Parcel in) {
158         mPropertyId = in.readInt();
159         mAreaId = in.readInt();
160         mStatus = in.readInt();
161         mTimestampNanos = in.readLong();
162         String valueClassName = in.readString();
163         Class<?> valueClass;
164         try {
165             valueClass = Class.forName(valueClassName);
166         } catch (ClassNotFoundException e) {
167             throw new IllegalArgumentException("Class not found: " + valueClassName, e);
168         }
169 
170         if (String.class.equals(valueClass)) {
171             byte[] bytes = ParcelHelper.readBlob(in);
172             mValue = (T) new String(bytes, DEFAULT_CHARSET);
173         } else if (byte[].class.equals(valueClass)) {
174             mValue = (T) ParcelHelper.readBlob(in);
175         } else {
176             mValue = (T) in.readValue(valueClass.getClassLoader());
177         }
178     }
179 
180     public static final Creator<CarPropertyValue> CREATOR = new Creator<CarPropertyValue>() {
181         @Override
182         public CarPropertyValue createFromParcel(Parcel in) {
183             return new CarPropertyValue(in);
184         }
185 
186         @Override
187         public CarPropertyValue[] newArray(int size) {
188             return new CarPropertyValue[size];
189         }
190     };
191 
192     @Override
193     @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
describeContents()194     public int describeContents() {
195         return 0;
196     }
197 
198     @Override
writeToParcel(Parcel dest, int flags)199     public void writeToParcel(Parcel dest, int flags) {
200         dest.writeInt(mPropertyId);
201         dest.writeInt(mAreaId);
202         dest.writeInt(mStatus);
203         dest.writeLong(mTimestampNanos);
204 
205         Class<?> valueClass = mValue == null ? null : mValue.getClass();
206         dest.writeString(valueClass == null ? null : valueClass.getName());
207 
208         // Special handling for String and byte[] to mitigate transaction buffer limitations.
209         if (String.class.equals(valueClass)) {
210             ParcelHelper.writeBlob(dest, ((String) mValue).getBytes(DEFAULT_CHARSET));
211         } else if (byte[].class.equals(valueClass)) {
212             ParcelHelper.writeBlob(dest, (byte[]) mValue);
213         } else {
214             dest.writeValue(mValue);
215         }
216     }
217 
218     /**
219      * Returns the property identifier.
220      *
221      * @return The property identifier of {@code CarPropertyValue}. See constants in
222      *         {@link android.car.VehiclePropertyIds} for some system defined possible values.
223      */
getPropertyId()224     public int getPropertyId() {
225         return mPropertyId;
226     }
227 
228     /**
229      * Returns the area identifier.
230      *
231      * @return The area identifier of {@code CarPropertyValue}, If property is
232      *         {@link android.car.VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}, it will be {@code 0}.
233      *         Otherwise, it will be on or more OR'd together constants of this property's
234      *         {@link android.car.VehicleAreaType}:
235      *           <ul>
236      *             <li>{@code VehicleAreaWindow}</li>
237      *             <li>{@code VehicleAreaDoor}</li>
238      *             <li>{@link android.car.VehicleAreaSeat}</li>
239      *             <li>{@code VehicleAreaMirror}</li>
240      *             <li>{@link android.car.VehicleAreaWheel}</li>
241      *           </ul>
242      */
getAreaId()243     public int getAreaId() {
244         return mAreaId;
245     }
246 
247     /**
248      * @return The property status of {@code CarPropertyValue}
249      */
250     @FlaggedApi(FLAG_CAR_PROPERTY_VALUE_PROPERTY_STATUS)
251     @PropertyStatus
getPropertyStatus()252     public int getPropertyStatus() {
253         return mStatus;
254     }
255 
256     /**
257      * @return Status of {@code CarPropertyValue}
258      * @deprecated Use a new API that communicates the property status.
259      */
260     @Deprecated
261     @PropertyStatus
getStatus()262     public int getStatus() {
263         return mStatus;
264     }
265 
266     /**
267      * Returns the timestamp in nanoseconds at which the {@code CarPropertyValue} happened. For a
268      * given car property, each new {@code CarPropertyValue} should be monotonically increasing
269      * using the same time base as {@link android.os.SystemClock#elapsedRealtimeNanos()}.
270      *
271      * <p>NOTE: Timestamp should be synchronized with other signals from the platform (e.g.
272      * {@link android.location.Location} and {@link android.hardware.SensorEvent} instances).
273      * Ideally, timestamp synchronization error should be below 1 millisecond.
274      */
getTimestamp()275     public long getTimestamp() {
276         return mTimestampNanos;
277     }
278 
279     /**
280      * Returns the value for {@code CarPropertyValue}.
281      *
282      * <p>
283      * <b>Note:</b>Caller must check the value of {@link #getStatus()}. Only use
284      * {@link #getValue()} when {@link #getStatus()} is {@link #STATUS_AVAILABLE}. If not,
285      * {@link #getValue()} is meaningless.
286      */
287     @NonNull
getValue()288     public T getValue() {
289         return mValue;
290     }
291 
292     /** @hide */
293     @Override
toString()294     public String toString() {
295         return "CarPropertyValue{"
296                 + "mPropertyId=0x" + toHexString(mPropertyId)
297                 + ", propertyName=" + VehiclePropertyIds.toString(mPropertyId)
298                 + ", mAreaId=0x" + toHexString(mAreaId)
299                 + ", mStatus=" + mStatus
300                 + ", mTimestampNanos=" + mTimestampNanos
301                 + ", mValue=" + mValue
302                 + '}';
303     }
304 
305     /** Generates hash code for this instance. */
306     @Override
hashCode()307     public int hashCode() {
308         return Arrays.deepHashCode(new Object[]{
309                 mPropertyId, mAreaId, mStatus, mTimestampNanos, mValue});
310     }
311 
312     /** Checks equality with passed {@code object}. */
313     @Override
equals(Object object)314     public boolean equals(Object object) {
315         if (this == object) {
316             return true;
317         }
318         if (!(object instanceof CarPropertyValue<?>)) {
319             return false;
320         }
321         CarPropertyValue<?> carPropertyValue = (CarPropertyValue<?>) object;
322         return mPropertyId == carPropertyValue.mPropertyId && mAreaId == carPropertyValue.mAreaId
323                 && mStatus == carPropertyValue.mStatus
324                 && mTimestampNanos == carPropertyValue.mTimestampNanos
325                 && Objects.deepEquals(mValue, carPropertyValue.mValue);
326     }
327 }
328