• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.property;
18 
19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
20 import static com.android.car.internal.property.CarPropertyHelper.STATUS_OK;
21 import static com.android.car.internal.property.CarPropertyHelper.SYNC_OP_LIMIT_TRY_AGAIN;
22 import static com.android.car.internal.util.VersionUtils.assertPlatformVersionAtLeastU;
23 
24 import static java.lang.Integer.toHexString;
25 import static java.util.Objects.requireNonNull;
26 
27 import android.annotation.FloatRange;
28 import android.annotation.IntDef;
29 import android.annotation.NonNull;
30 import android.annotation.Nullable;
31 import android.annotation.SuppressLint;
32 import android.annotation.SystemApi;
33 import android.car.Car;
34 import android.car.CarManagerBase;
35 import android.car.VehiclePropertyIds;
36 import android.car.annotation.AddedInOrBefore;
37 import android.car.annotation.ApiRequirements;
38 import android.car.hardware.CarPropertyConfig;
39 import android.car.hardware.CarPropertyValue;
40 import android.os.Build;
41 import android.os.CancellationSignal;
42 import android.os.Handler;
43 import android.os.IBinder;
44 import android.os.RemoteException;
45 import android.os.ServiceSpecificException;
46 import android.os.SystemClock;
47 import android.os.Trace;
48 import android.util.ArraySet;
49 import android.util.Log;
50 import android.util.SparseArray;
51 
52 import com.android.car.internal.CarPropertyEventCallbackController;
53 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
54 import com.android.car.internal.SingleMessageHandler;
55 import com.android.car.internal.os.HandlerExecutor;
56 import com.android.car.internal.property.AsyncPropertyServiceRequest;
57 import com.android.car.internal.property.AsyncPropertyServiceRequestList;
58 import com.android.car.internal.property.CarPropertyHelper;
59 import com.android.car.internal.property.GetSetValueResult;
60 import com.android.car.internal.property.GetSetValueResultList;
61 import com.android.car.internal.property.IAsyncPropertyResultCallback;
62 import com.android.car.internal.property.InputSanitizationUtils;
63 import com.android.internal.annotations.GuardedBy;
64 
65 import java.lang.annotation.Retention;
66 import java.lang.annotation.RetentionPolicy;
67 import java.lang.ref.WeakReference;
68 import java.util.ArrayList;
69 import java.util.List;
70 import java.util.concurrent.Executor;
71 import java.util.concurrent.atomic.AtomicInteger;
72 
73 /**
74  * Provides an application interface for interacting with the Vehicle specific properties.
75  * For details about the individual properties, see the descriptions in {@link VehiclePropertyIds}
76  */
77 public class CarPropertyManager extends CarManagerBase {
78     private static final String TAG = "CarPropertyManager";
79     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
80     private static final int MSG_GENERIC_EVENT = 0;
81     private static final int SYNC_OP_RETRY_SLEEP_IN_MS = 10;
82     private static final int SYNC_OP_RETRY_MAX_COUNT = 10;
83     /**
84      * The default timeout in MS for {@link CarPropertyManager#getPropertiesAsync}.
85      */
86     @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
87             minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
88     public static final long ASYNC_GET_DEFAULT_TIMEOUT_MS = 10_000;
89 
90     private final SingleMessageHandler<CarPropertyEvent> mHandler;
91     private final ICarProperty mService;
92     private final int mAppTargetSdk;
93     private final AtomicInteger mRequestIdCounter = new AtomicInteger(0);
94     @GuardedBy("mLock")
95     private final SparseArray<AsyncPropertyRequestInfo<?, ?>> mRequestIdToAsyncRequestInfo =
96             new SparseArray<>();
97     private final AsyncPropertyResultCallback mAsyncPropertyResultCallback =
98             new AsyncPropertyResultCallback();
99 
100     private final CarPropertyEventListenerToService mCarPropertyEventToService =
101             new CarPropertyEventListenerToService(this);
102 
103     // This lock is shared with all CarPropertyEventCallbackController instances to prevent
104     // potential deadlock.
105     private final Object mLock = new Object();
106     @GuardedBy("mLock")
107     private final SparseArray<CarPropertyEventCallbackController>
108             mPropertyIdToCarPropertyEventCallbackController = new SparseArray<>();
109 
110     private final CarPropertyEventCallbackController.RegistrationUpdateCallback
111             mRegistrationUpdateCallback =
112             new CarPropertyEventCallbackController.RegistrationUpdateCallback() {
113                 @Override
114                 public boolean register(int propertyId, float updateRateHz) {
115                     try {
116                         mService.registerListener(propertyId, updateRateHz,
117                                 mCarPropertyEventToService);
118                     } catch (RemoteException e) {
119                         handleRemoteExceptionFromCarService(e);
120                         return false;
121                     } catch (IllegalArgumentException e) {
122                         Log.w(TAG, "register: propertyId: "
123                                 + VehiclePropertyIds.toString(propertyId) + ", updateRateHz: "
124                                 + updateRateHz + ", exception: ", e);
125                         return false;
126                     }
127                     return true;
128                 }
129 
130                 @Override
131                 public boolean unregister(int propertyId) {
132                     try {
133                         mService.unregisterListener(propertyId, mCarPropertyEventToService);
134                     } catch (RemoteException e) {
135                         handleRemoteExceptionFromCarService(e);
136                         return false;
137                     } catch (IllegalArgumentException e) {
138                         Log.w(TAG, "unregister: propertyId: "
139                                 + VehiclePropertyIds.toString(propertyId) + ", exception: ", e);
140                         return false;
141                     }
142                     return true;
143                 }
144             };
145 
146     private final GetPropertyResultCallback mGetPropertyResultCallback =
147             new GetPropertyResultCallback();
148 
149     private final SetPropertyResultCallback mSetPropertyResultCallback =
150             new SetPropertyResultCallback();
151 
152     /**
153      * Application registers {@link CarPropertyEventCallback} object to receive updates and changes
154      * to subscribed Vehicle specific properties.
155      */
156     public interface CarPropertyEventCallback {
157         /**
158          * Called when a property is updated
159          *
160          * @param value the new value of the property
161          */
162         @AddedInOrBefore(majorVersion = 33)
onChangeEvent(CarPropertyValue value)163         void onChangeEvent(CarPropertyValue value);
164 
165         /**
166          * Called when an error is detected when setting a property.
167          *
168          * @param propertyId the property ID which has detected an error
169          * @param areaId the area ID which has detected an error
170          *
171          * @see CarPropertyEventCallback#onErrorEvent(int, int, int)
172          */
173         @AddedInOrBefore(majorVersion = 33)
onErrorEvent(int propertyId, int areaId)174         void onErrorEvent(int propertyId, int areaId);
175 
176         /**
177          * Called when an error is detected when setting a property.
178          *
179          * <p>Clients which changed the property value in the areaId most recently will receive
180          * this callback. If multiple clients set a property for the same area ID simultaneously,
181          * which one takes precedence is undefined. Typically, the last set operation
182          * (in the order that they are issued to car's ECU) overrides the previous set operations.
183          * The delivered error reflects the error happened in the last set operation.
184          *
185          * @param propertyId the property ID which is detected an error
186          * @param areaId the area ID which is detected an error
187          * @param errorCode the error code is raised in the car
188          */
189         @AddedInOrBefore(majorVersion = 33)
onErrorEvent(int propertyId, int areaId, @CarSetPropertyErrorCode int errorCode)190         default void onErrorEvent(int propertyId, int areaId,
191                 @CarSetPropertyErrorCode int errorCode) {
192             if (DBG) {
193                 Log.d(TAG, "onErrorEvent propertyId: " + VehiclePropertyIds.toString(propertyId)
194                         + " areaId: 0x" + toHexString(areaId) + " ErrorCode: " + errorCode);
195             }
196             onErrorEvent(propertyId, areaId);
197         }
198     }
199 
200     /**
201      * A callback {@link CarPropertyManager#getPropertiesAsync} when succeeded or failed.
202      */
203     @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
204             minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
205     public interface GetPropertyCallback {
206         /**
207          * Method called when {@link GetPropertyRequest} successfully gets a result.
208          */
209         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
210                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
onSuccess(@onNull GetPropertyResult<?> getPropertyResult)211         void onSuccess(@NonNull GetPropertyResult<?> getPropertyResult);
212 
213         /**
214          * Method called when {@link GetPropertyRequest} returns an error.
215          */
216         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
217                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
onFailure(@onNull PropertyAsyncError propertyAsyncError)218         void onFailure(@NonNull PropertyAsyncError propertyAsyncError);
219     }
220 
221     /**
222      * A callback {@link CarPropertyManager#setPropertiesAsync} when succeeded or failed.
223      */
224     @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
225             minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
226     public interface SetPropertyCallback {
227         /**
228          * Method called when the {@link SetPropertyRequest} successfully set the value.
229          *
230          * <p>This means: the set value request is successfully sent to vehicle
231          *
232          * <p>and
233          *
234          * <p>either the current property value is already the target value, or we have received a
235          * property update event indicating the value is updated to the target value.
236          *
237          * <p>If multiple clients set a property for the same area ID simultaneously with different
238          * values, the order is undefined. One possible case is that both requests are sent to the
239          * vehicle bus, which causes two property update events. As a result, the success callback
240          * would be called for both clients, but in an undefined order. This means that even if
241          * the success callback is called, it doesn't necessarily mean getting the property would
242          * return the same value you just set. Another client might have changed the value after you
243          * set it.
244          *
245          * <p>If only one requests is successfully processed by the vehicle bus, overwriting the
246          * other request, then only one success callback would be called for one client. The other
247          * client would get the failure callback with
248          * {@link CarPropertyManager#STATUS_ERROR_TIMEOUT} error code.
249          *
250          * <p>If multiple clients set a property for the same area ID simultaneously with the same
251          * value. The success callback for both clients would be called in an undefined order.
252          */
253         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
254                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
onSuccess(@onNull SetPropertyResult setPropertyResult)255         void onSuccess(@NonNull SetPropertyResult setPropertyResult);
256 
257         /**
258          * Method called when {@link SetPropertyRequest} returns an error.
259          */
260         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
261                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
onFailure(@onNull PropertyAsyncError propertyAsyncError)262         void onFailure(@NonNull PropertyAsyncError propertyAsyncError);
263     }
264 
265     /**
266      * An async get/set property request.
267      */
268     @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
269                      minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
270     public interface AsyncPropertyRequest {
271         /**
272          * Returns the unique ID for this request.
273          *
274          * <p>Each request must have a unique request ID so the responses can be differentiated.
275          */
276         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
277                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getRequestId()278         int getRequestId();
279 
280         /**
281          * Returns the ID for the property of this request.
282          *
283          * <p>The ID must be one of the {@link VehiclePropertyIds} or vendor property IDs.
284          */
285         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
286                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getPropertyId()287         int getPropertyId();
288 
289         /**
290          * Returns the area ID for the property of this request.
291          */
292         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
293                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getAreaId()294         int getAreaId();
295     }
296 
297     /**
298      * A request for {@link CarPropertyManager#getPropertiesAsync(List, long, CancellationSignal,
299      * Executor, GetPropertyCallback)}.
300      */
301     @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
302                      minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
303     public static final class GetPropertyRequest implements AsyncPropertyRequest {
304         private final int mRequestId;
305         private final int mPropertyId;
306         private final int mAreaId;
307 
308         /**
309          * @see AsyncPropertyRequest#getRequestId
310          */
311         @Override
312         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
313                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getRequestId()314         public int getRequestId() {
315             return mRequestId;
316         }
317 
318         /**
319          * @see AsyncPropertyRequest#getPropertyId
320          */
321         @Override
322         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
323                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getPropertyId()324         public int getPropertyId() {
325             return mPropertyId;
326         }
327 
328         /**
329          * @see AsyncPropertyRequest#getAreaId
330          */
331         @Override
332         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
333                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getAreaId()334         public int getAreaId() {
335             return mAreaId;
336         }
337 
338         /**
339          * Internal use only. Users should use {@link #generateGetPropertyRequest(int, int)}
340          * instead.
341          */
GetPropertyRequest(int requestId, int propertyId, int areaId)342         private GetPropertyRequest(int requestId, int propertyId, int areaId) {
343             mRequestId = requestId;
344             mPropertyId = propertyId;
345             mAreaId = areaId;
346         }
347 
348         /**
349          * Prints out debug message.
350          */
351         @Override
352         @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
toString()353         public String toString() {
354             return new StringBuilder()
355                     .append("GetPropertyRequest{request ID: ")
356                     .append(mRequestId)
357                     .append(", property ID: ")
358                     .append(VehiclePropertyIds.toString(mPropertyId))
359                     .append(", area ID: ")
360                     .append(mAreaId)
361                     .append("}").toString();
362         }
363     }
364 
365     /**
366      * A request for {@link CarPropertyManager#setPropertiesAsync(List, long, CancellationSignal,
367      * Executor, SetPropertyCallback)}.
368      *
369      * @param <T> the type for the property value, must be one of Object, Boolean, Float, Integer,
370      *      Long, Float[], Integer[], Long[], String, byte[], Object[]
371      */
372     @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
373             minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
374     public static final class SetPropertyRequest<T> implements AsyncPropertyRequest {
375         private final int mRequestId;
376         private final int mPropertyId;
377         private final int mAreaId;
378         private float mUpdateRateHz = 0.f;
379         // By default, the async set operation will wait for the property to be updated to the
380         // target value before calling the success callback (or when the target value is the
381         // same as the current value).
382         private boolean mWaitForPropertyUpdate = true;
383 
384         /**
385          * The value to set.
386          */
387         private final T mValue;
388 
389         /**
390          * Sets the update rate in Hz for listening for property updates for continuous property.
391          *
392          * <p>If {@code waitForPropertyUpdate} is set to {@code true} (by default) and if the
393          * property is set to a different value than its current value, the success callback will be
394          * called when a property update event for the new value arrived. This option controls how
395          * frequent the property update event should be reported for continuous property. This is
396          * similar to {@code updateRateHz} in {@link CarPropertyManager#registerCallback}.
397          *
398          * <p>This is ignored for non-continuous properties.
399          *
400          * <p>This is ignored if {@code waitForPropertyUpdate} is set to {@code false}.
401          */
402         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
403                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
setUpdateRateHz(float updateRateHz)404         public void setUpdateRateHz(float updateRateHz) {
405             mUpdateRateHz = updateRateHz;
406         }
407 
408         /**
409          * Sets whether to wait for the property update event before calling success callback.
410          *
411          * <p>This arguments controls under what conditions the operation is considered succeeded
412          * and the success callback will be called.
413          *
414          * <p>If this is set to {@code true} (by default), the success callback will be called when
415          * both of the following coniditions are met:
416          *
417          * <ul>
418          * <li>the set operation is successfully delivered to vehicle bus.
419          * <li>the {@code mPropertyId}+{@code mAreaId}'s value already equal to {@code mValue} or
420          * is successfully updated to the {@code mValue} through the set operation.
421          * </ul>
422          *
423          * <p>Even if the target value is the same as the current value, we will still send the set
424          * operation to the vehicle bus. If caller wants to reduce unnecessary overhead, caller must
425          * check existing values before issuing the requests.
426          *
427          * <p>If the first condition fails, the error callback will be called. If the second
428          * condition fails, which means we don't see the property updated to the target value within
429          * a specified timeout, the error callback will be called with {@link
430          * #STATUS_ERROR_TIMEOUT}.
431          *
432          * <p>If this is set to {@code false}, the success callback will be called after the
433          * set operation is successfully delivered to vehicle bus.
434          *
435          * <p>Under most cases, client should wait for the property update to verify that the set
436          * operation actually succeeded.
437          *
438          * <p>For cases when the property is write-only (no way to get property update event) or
439          * when the property represents some action, instead of an actual state, e.g. key stroke
440          * where the property's current value is not meaningful, caller should set
441          * {@code waitForPropertyUpdate} to {@code false}.
442          *
443          * <p>For {@code HVAC_TEMPERATURE_VALUE_SUGGESTION}, this must be set to {@code false}
444          * because the updated property value will not be the same as the value to be set.
445          *
446          * <p>Note that even if this is set to {@code true}, it is only guaranteed that the property
447          * value is the target value after the success callback is called if no other clients are
448          * changing the property at the same time. It is always possible that another client changes
449          * the property value after the property is updated to the target value, but before the
450          * client success callback runs. We only guarantee that at some point during the period
451          * after the client issues the request and before the success callback is called, the
452          * property value was set to the target value.
453          */
454         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
455                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
setWaitForPropertyUpdate(boolean waitForPropertyUpdate)456         public void setWaitForPropertyUpdate(boolean waitForPropertyUpdate) {
457             mWaitForPropertyUpdate = waitForPropertyUpdate;
458         }
459 
460         /**
461          * @see AsyncPropertyRequest#getRequestId
462          */
463         @Override
464         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
465                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getRequestId()466         public int getRequestId() {
467             return mRequestId;
468         }
469 
470         /**
471          * @see AsyncPropertyRequest#getPropertyId
472          */
473         @Override
474         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
475                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getPropertyId()476         public int getPropertyId() {
477             return mPropertyId;
478         }
479 
480         /**
481          * @see AsyncPropertyRequest#getAreaId
482          */
483         @Override
484         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
485                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getAreaId()486         public int getAreaId() {
487             return mAreaId;
488         }
489 
490         /**
491          * Get the property value to set.
492          */
493         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
494                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getValue()495         public T getValue() {
496             return mValue;
497         }
498 
499         /**
500          * Gets the update rate for listening for property updates.
501          */
502         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
503                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getUpdateRateHz()504         public float getUpdateRateHz() {
505             return mUpdateRateHz;
506         }
507 
508         /**
509          * Gets whether to wait for property update event before calling success callback.
510          */
511         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
512                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
isWaitForPropertyUpdate()513         public boolean isWaitForPropertyUpdate() {
514             return mWaitForPropertyUpdate;
515         }
516 
517         /**
518          * Internal use only. Users should use {@link #generateSetPropertyRequest(int, int, T)}
519          * instead.
520          */
SetPropertyRequest(int requestId, int propertyId, int areaId, T value)521         private SetPropertyRequest(int requestId, int propertyId, int areaId, T value) {
522             mRequestId = requestId;
523             mPropertyId = propertyId;
524             mAreaId = areaId;
525             mValue = value;
526         }
527 
528         /**
529          * Prints out debug message.
530          */
531         @Override
532         @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
toString()533         public String toString() {
534             return new StringBuilder()
535                     .append("SetPropertyRequest{request ID: ")
536                     .append(mRequestId)
537                     .append(", property ID: ")
538                     .append(VehiclePropertyIds.toString(mPropertyId))
539                     .append(", area ID: ")
540                     .append(mAreaId)
541                     .append(", value: ")
542                     .append(mValue)
543                     .append(", waitForPropertyUpdate: ")
544                     .append(mWaitForPropertyUpdate)
545                     .append(", mUpdateRateHz: ")
546                     .append(mUpdateRateHz)
547                     .append("}").toString();
548         }
549     }
550 
551     /**
552      * An error result for {@link GetPropertyCallback} or {@link SetPropertyCallback}.
553      */
554     @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
555             minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
556     public static final class PropertyAsyncError {
557         private final int mRequestId;
558         private final int mPropertyId;
559         private final int mAreaId;
560         private final @CarPropertyAsyncErrorCode int mErrorCode;
561         private final int mVendorErrorCode;
562 
563         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
564                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getRequestId()565         public int getRequestId() {
566             return mRequestId;
567         }
568 
569         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
570                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getPropertyId()571         public int getPropertyId() {
572             return mPropertyId;
573         }
574 
575         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
576                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getAreaId()577         public int getAreaId() {
578             return mAreaId;
579         }
580 
581         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
582                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getErrorCode()583         public @CarPropertyAsyncErrorCode int getErrorCode() {
584             return mErrorCode;
585         }
586 
587         /**
588          * Gets the vendor error codes to allow for more detailed error codes.
589          *
590          * @return the vendor error code if it is set, otherwise 0. A vendor error code will have a
591          * range from 0x0000 to 0xffff.
592          *
593          * @hide
594          */
595         @SystemApi
596         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
597                 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0)
getVendorErrorCode()598         public int getVendorErrorCode() {
599             assertPlatformVersionAtLeastU();
600             return mVendorErrorCode;
601         }
602 
603         /**
604          * Creates a new error result for async property request.
605          *
606          * @param requestId the request ID
607          * @param propertyId the property ID in the request
608          * @param areaId the area ID for the property in the request
609          * @param errorCode the code indicating the error
610          */
PropertyAsyncError(int requestId, int propertyId, int areaId, @CarPropertyAsyncErrorCode int errorCode, int vendorErrorCode)611         PropertyAsyncError(int requestId, int propertyId, int areaId,
612                 @CarPropertyAsyncErrorCode int errorCode,
613                 int vendorErrorCode) {
614             mRequestId = requestId;
615             mPropertyId = propertyId;
616             mAreaId = areaId;
617             mErrorCode = errorCode;
618             mVendorErrorCode = vendorErrorCode;
619         }
620 
621         /**
622          * Prints out debug message.
623          */
624         @Override
625         @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
toString()626         public String toString() {
627             return new StringBuilder()
628                     .append("PropertyAsyncError{request ID: ")
629                     .append(mRequestId)
630                     .append(", property: ")
631                     .append(VehiclePropertyIds.toString(mPropertyId))
632                     .append(", areaId: ")
633                     .append(mAreaId)
634                     .append(", error code: ")
635                     .append(mErrorCode)
636                     .append(", vendor error code: ")
637                     .append(mVendorErrorCode)
638                     .append("}").toString();
639         }
640     }
641 
642     /**
643      * A successful result for {@link GetPropertyCallback}.
644      *
645      * @param <T> the type for the property value, must be one of Object, Boolean, Float, Integer,
646      *      Long, Float[], Integer[], Long[], String, byte[], Object[]
647      */
648     @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
649             minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
650     public static final class GetPropertyResult<T> {
651         private final int mRequestId;
652         private final int mPropertyId;
653         private final int mAreaId;
654         private final long mTimestampNanos;
655         private final T mValue;
656 
657         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
658                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getRequestId()659         public int getRequestId() {
660             return mRequestId;
661         }
662 
663         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
664                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getPropertyId()665         public int getPropertyId() {
666             return mPropertyId;
667         }
668 
669         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
670                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getAreaId()671         public int getAreaId() {
672             return mAreaId;
673         }
674 
675         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
676                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
677         @NonNull
getValue()678         public T getValue() {
679             return mValue;
680         }
681 
682         /**
683          * Returns the timestamp in nanoseconds at which the value for the vehicle property
684          * happened. For a given vehicle property, each new timestamp should be monotonically
685          * increasing using the same time base as {@link SystemClock#elapsedRealtimeNanos()}.
686          *
687          * <p>NOTE: Timestamp should be synchronized with other signals from the platform (e.g.
688          * {@link android.location.Location} and {@link android.hardware.SensorEvent} instances).
689          * Ideally, timestamp synchronization error should be below 1 millisecond.
690          */
691         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
692                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getTimestampNanos()693         public long getTimestampNanos() {
694             return mTimestampNanos;
695         }
696 
697         /**
698          * Creates a new value result for async GetProperty request.
699          *
700          * @param requestId the request ID
701          * @param propertyId the property ID in the request
702          * @param areaId the area ID for the property in the request
703          * @param timestampNanos the timestamp in nanoseconds when this property is updated
704          * @param value the property's value
705          */
GetPropertyResult(int requestId, int propertyId, int areaId, long timestampNanos, @NonNull T value)706         GetPropertyResult(int requestId, int propertyId, int areaId, long timestampNanos,
707                  @NonNull T value) {
708             mRequestId = requestId;
709             mPropertyId = propertyId;
710             mAreaId = areaId;
711             mTimestampNanos = timestampNanos;
712             mValue = value;
713         }
714 
715         /**
716          * Prints out debug message.
717          */
718         @Override
719         @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
toString()720         public String toString() {
721             return new StringBuilder()
722                     .append("GetPropertyResult{type: ")
723                     .append(mValue.getClass())
724                     .append(", request ID: ")
725                     .append(mRequestId)
726                     .append(", property: ")
727                     .append(VehiclePropertyIds.toString(mPropertyId))
728                     .append(", areaId: ")
729                     .append(mAreaId)
730                     .append(", value: ")
731                     .append(mValue)
732                     .append(", timestamp: ")
733                     .append(mTimestampNanos).append("ns")
734                     .append("}").toString();
735         }
736     }
737 
738     /**
739      * A successful result for {@link SetPropertyCallback}.
740      */
741     @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
742             minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
743     public static final class SetPropertyResult {
744         private final int mRequestId;
745         private final int mPropertyId;
746         private final int mAreaId;
747         private final long mUpdateTimestampNanos;
748 
749         /**
750          * Gets the ID for the request this result is for.
751          */
752         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
753                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getRequestId()754         public int getRequestId() {
755             return mRequestId;
756         }
757 
758         /**
759          * Gets the property ID this result is for.
760          */
761         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
762                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getPropertyId()763         public int getPropertyId() {
764             return mPropertyId;
765         }
766 
767         /**
768          * Gets the area ID this result is for.
769          */
770         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
771                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getAreaId()772         public int getAreaId() {
773             return mAreaId;
774         }
775 
776         /**
777          * Gets the timestamp in nanoseconds at which the property was updated to the desired value.
778          *
779          * <p>The timestamp will use the same time base as
780          * {@link SystemClock#elapsedRealtimeNanos()}.
781          *
782          * <p>NOTE: If {@code waitForPropertyUpdate} is set to {@code false} for the request, then
783          * this value will be the time when the async set request is successfully sent to the
784          * vehicle bus, not when the property is updated since we have no way of knowing that.
785          *
786          * <p>NOTE: Timestamp should be synchronized with other signals from the platform (e.g.
787          * {@link android.location.Location} and {@link android.hardware.SensorEvent} instances).
788          * Ideally, timestamp synchronization error should be below 1 millisecond.
789          */
790         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
791                          minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getUpdateTimestampNanos()792         public long getUpdateTimestampNanos() {
793             return mUpdateTimestampNanos;
794         }
795 
SetPropertyResult(int requestId, int propertyId, int areaId, long updateTimestampNanos)796         SetPropertyResult(int requestId, int propertyId, int areaId, long updateTimestampNanos) {
797             mRequestId = requestId;
798             mPropertyId = propertyId;
799             mAreaId = areaId;
800             mUpdateTimestampNanos = updateTimestampNanos;
801         }
802 
803         /**
804          * Prints out debug message.
805          */
806         @Override
807         @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
toString()808         public String toString() {
809             return new StringBuilder()
810                     .append("SetPropertyResult{request ID: ")
811                     .append(mRequestId)
812                     .append(", property: ")
813                     .append(VehiclePropertyIds.toString(mPropertyId))
814                     .append(", areaId: ")
815                     .append(mAreaId)
816                     .append(", updated timestamp: ")
817                     .append(mUpdateTimestampNanos).append("ns")
818                     .append("}").toString();
819         }
820     }
821 
822     /**
823      * An abstract interface for converting async get/set result and calling client callbacks.
824      */
825     private interface PropertyResultCallback<CallbackType, ResultType> {
build(int requestId, int propertyId, int areaId, long timestampNanos, @Nullable Object value)826         ResultType build(int requestId, int propertyId, int areaId, long timestampNanos,
827                 @Nullable Object value);
onSuccess(CallbackType callback, ResultType result)828         void onSuccess(CallbackType callback, ResultType result);
onFailure(CallbackType callback, PropertyAsyncError error)829         void onFailure(CallbackType callback, PropertyAsyncError error);
830     }
831 
832     /**
833      * Class to hide implementation detail for get/set callbacks.
834      */
835     private static final class GetPropertyResultCallback implements
836             PropertyResultCallback<GetPropertyCallback, GetPropertyResult> {
build(int requestId, int propertyId, int areaId, long timestampNanos, @Nullable Object value)837         public GetPropertyResult build(int requestId, int propertyId, int areaId,
838                 long timestampNanos, @Nullable Object value) {
839             return new GetPropertyResult(requestId, propertyId, areaId, timestampNanos, value);
840         }
841 
onSuccess(GetPropertyCallback callback, GetPropertyResult result)842         public void onSuccess(GetPropertyCallback callback, GetPropertyResult result) {
843             if (DBG) {
844                 Log.d(TAG, "delivering success get property result: " + result);
845             }
846             callback.onSuccess(result);
847         }
848 
onFailure(GetPropertyCallback callback, PropertyAsyncError error)849         public void onFailure(GetPropertyCallback callback, PropertyAsyncError error) {
850             if (DBG) {
851                 Log.d(TAG, "delivering error get property result: " + error);
852             }
853             callback.onFailure(error);
854         }
855     }
856 
857     /**
858      * Class to hide implementation detail for get/set callbacks.
859      */
860     private static final class SetPropertyResultCallback implements
861             PropertyResultCallback<SetPropertyCallback, SetPropertyResult> {
build(int requestId, int propertyId, int areaId, long timestampNanos, @Nullable Object value)862         public  SetPropertyResult build(int requestId, int propertyId, int areaId,
863                 long timestampNanos, @Nullable Object value) {
864             return new SetPropertyResult(requestId, propertyId, areaId, timestampNanos);
865         }
866 
onSuccess(SetPropertyCallback callback, SetPropertyResult result)867         public void onSuccess(SetPropertyCallback callback, SetPropertyResult result) {
868             if (DBG) {
869                 Log.d(TAG, "delivering success set property result: " + result);
870             }
871             callback.onSuccess(result);
872         }
873 
onFailure(SetPropertyCallback callback, PropertyAsyncError error)874         public void onFailure(SetPropertyCallback callback, PropertyAsyncError error) {
875             if (DBG) {
876                 Log.d(TAG, "delivering error set property result: " + error);
877             }
878             callback.onFailure(error);
879         }
880     }
881 
882     /**
883      * A class for delivering {@link GetPropertyCallback} or {@link SetPropertyCallback} client
884      * callback when {@code IAsyncPropertyResultCallback} returns results.
885      */
886     private class AsyncPropertyResultCallback extends IAsyncPropertyResultCallback.Stub {
887 
888         @Override
asBinder()889         public IBinder asBinder() {
890             return this;
891         }
892 
893         @Override
onGetValueResults(GetSetValueResultList getValueResults)894         public void onGetValueResults(GetSetValueResultList getValueResults) {
895             this.<GetPropertyRequest, GetPropertyCallback, GetPropertyResult>onResults(
896                     getValueResults.getList(), mGetPropertyResultCallback);
897         }
898 
899         @Override
onSetValueResults(GetSetValueResultList setValueResults)900         public void onSetValueResults(GetSetValueResultList setValueResults) {
901             this.<SetPropertyRequest<?>, SetPropertyCallback, SetPropertyResult>onResults(
902                     setValueResults.getList(), mSetPropertyResultCallback);
903         }
904 
905         @SuppressLint("WrongConstant")
onResults( List<GetSetValueResult> results, PropertyResultCallback<CallbackType, ResultType> propertyResultCallback)906         private <RequestType extends AsyncPropertyRequest, CallbackType, ResultType> void onResults(
907                 List<GetSetValueResult> results,
908                 PropertyResultCallback<CallbackType, ResultType> propertyResultCallback) {
909             for (int i = 0; i < results.size(); i++) {
910                 GetSetValueResult result = results.get(i);
911                 int requestId = result.getRequestId();
912                 AsyncPropertyRequestInfo<RequestType, CallbackType> requestInfo;
913                 synchronized (mLock) {
914                     requestInfo =
915                             (AsyncPropertyRequestInfo<RequestType, CallbackType>)
916                             mRequestIdToAsyncRequestInfo.get(requestId);
917                     mRequestIdToAsyncRequestInfo.remove(requestId);
918                 }
919                 if (requestInfo == null) {
920                     Log.w(TAG, "onResults: Request ID: " + requestId
921                             + " might have been completed, cancelled or an exception might have "
922                             + "been thrown");
923                     continue;
924                 }
925                 Executor callbackExecutor = requestInfo.getCallbackExecutor();
926                 CallbackType clientCallback = requestInfo.getCallback();
927                 @CarPropertyAsyncErrorCode int errorCode = result.getErrorCode();
928                 int propertyId = requestInfo.getRequest().getPropertyId();
929                 String propertyName = VehiclePropertyIds.toString(propertyId);
930                 int areaId = requestInfo.getRequest().getAreaId();
931                 if (errorCode == STATUS_OK) {
932                     CarPropertyValue<?> carPropertyValue = result.getCarPropertyValue();
933                     long timestampNanos;
934                     if (carPropertyValue != null) {
935                         // This is a get result.
936                         int valuePropertyId = carPropertyValue.getPropertyId();
937                         if (propertyId  != valuePropertyId) {
938                             Log.e(TAG, "onResults: Request ID: " + requestId + " received get "
939                                     + "property value result, but has mismatch property ID, "
940                                     + " expect: " + propertyName + ", got: "
941                                     + VehiclePropertyIds.toString(valuePropertyId));
942                         }
943                         int valueAreaId = carPropertyValue.getAreaId();
944                         if (areaId  != valueAreaId) {
945                             Log.e(TAG, "onResults: Property: " + propertyName + " Request ID: "
946                                     + requestId + " received get property value result, but has "
947                                     + "mismatch area ID, expect: " + areaId + ", got: "
948                                     + valueAreaId);
949                         }
950                         timestampNanos = carPropertyValue.getTimestamp();
951                     } else {
952                         // This is a set result.
953                         timestampNanos = result.getUpdateTimestampNanos();
954                     }
955 
956                     ResultType clientResult = propertyResultCallback.build(
957                             requestId, propertyId, areaId, timestampNanos,
958                             carPropertyValue == null ? null : carPropertyValue.getValue());
959                     callbackExecutor.execute(() -> propertyResultCallback.onSuccess(
960                             clientCallback, clientResult));
961                 } else {
962                     callbackExecutor.execute(() -> propertyResultCallback.onFailure(clientCallback,
963                             new PropertyAsyncError(requestId, propertyId, areaId, errorCode,
964                                     result.getVendorErrorCode())));
965                 }
966             }
967         }
968     }
969 
970     /**
971      * A class to store async get/set property request info.
972      */
973     private static final class AsyncPropertyRequestInfo<RequestType, CallbackType> {
974         private final RequestType mRequest;
975         private final Executor mCallbackExecutor;
976         private final CallbackType mCallback;
977 
getRequest()978         public RequestType getRequest() {
979             return mRequest;
980         }
981 
getCallbackExecutor()982         public Executor getCallbackExecutor() {
983             return mCallbackExecutor;
984         }
985 
getCallback()986         public CallbackType getCallback() {
987             return mCallback;
988         }
989 
AsyncPropertyRequestInfo(RequestType request, Executor callbackExecutor, CallbackType callback)990         private AsyncPropertyRequestInfo(RequestType request, Executor callbackExecutor,
991                 CallbackType callback) {
992             mRequest = request;
993             mCallbackExecutor = callbackExecutor;
994             mCallback = callback;
995         }
996     }
997 
998     /** Read ONCHANGE sensors. */
999     @AddedInOrBefore(majorVersion = 33)
1000     public static final float SENSOR_RATE_ONCHANGE = 0f;
1001     /** Read sensors at the rate of  1 hertz */
1002     @AddedInOrBefore(majorVersion = 33)
1003     public static final float SENSOR_RATE_NORMAL = 1f;
1004     /** Read sensors at the rate of 5 hertz */
1005     @AddedInOrBefore(majorVersion = 33)
1006     public static final float SENSOR_RATE_UI = 5f;
1007     /** Read sensors at the rate of 10 hertz */
1008     @AddedInOrBefore(majorVersion = 33)
1009     public static final float SENSOR_RATE_FAST = 10f;
1010     /** Read sensors at the rate of 100 hertz */
1011     @AddedInOrBefore(majorVersion = 33)
1012     public static final float SENSOR_RATE_FASTEST = 100f;
1013 
1014 
1015 
1016     /**
1017      * Status to indicate that set operation failed. Try it again.
1018      */
1019     @AddedInOrBefore(majorVersion = 33)
1020     public static final int CAR_SET_PROPERTY_ERROR_CODE_TRY_AGAIN = 1;
1021 
1022     /**
1023      * Status to indicate that set operation failed because of an invalid argument.
1024      */
1025     @AddedInOrBefore(majorVersion = 33)
1026     public static final int CAR_SET_PROPERTY_ERROR_CODE_INVALID_ARG = 2;
1027 
1028     /**
1029      * Status to indicate that set operation failed because the property is not available.
1030      */
1031     @AddedInOrBefore(majorVersion = 33)
1032     public static final int CAR_SET_PROPERTY_ERROR_CODE_PROPERTY_NOT_AVAILABLE = 3;
1033 
1034     /**
1035      * Status to indicate that set operation failed because car denied access to the property.
1036      */
1037     @AddedInOrBefore(majorVersion = 33)
1038     public static final int CAR_SET_PROPERTY_ERROR_CODE_ACCESS_DENIED = 4;
1039 
1040     /**
1041      * Status to indicate that set operation failed because of a general error in cars.
1042      */
1043     @AddedInOrBefore(majorVersion = 33)
1044     public static final int CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN = 5;
1045 
1046     /** @hide */
1047     @IntDef(prefix = {"CAR_SET_PROPERTY_ERROR_CODE_"}, value = {
1048             CAR_SET_PROPERTY_ERROR_CODE_TRY_AGAIN,
1049             CAR_SET_PROPERTY_ERROR_CODE_INVALID_ARG,
1050             CAR_SET_PROPERTY_ERROR_CODE_PROPERTY_NOT_AVAILABLE,
1051             CAR_SET_PROPERTY_ERROR_CODE_ACCESS_DENIED,
1052             CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN,
1053     })
1054     @Retention(RetentionPolicy.SOURCE)
1055     public @interface CarSetPropertyErrorCode {}
1056 
1057     /**
1058      * Error indicating that there is an error detected in cars.
1059      */
1060     @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
1061             minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
1062     public static final int STATUS_ERROR_INTERNAL_ERROR = 1;
1063     /**
1064      * Error indicating that the property is temporarily not available.
1065      */
1066     @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
1067             minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
1068     public static final int STATUS_ERROR_NOT_AVAILABLE = 2;
1069     /**
1070      * Error indicating the operation has timed-out.
1071      */
1072     @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
1073             minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
1074     public static final int STATUS_ERROR_TIMEOUT = 3;
1075 
1076     /** @hide */
1077     @IntDef(prefix = {"STATUS_"}, value = {
1078             STATUS_OK,
1079             STATUS_ERROR_INTERNAL_ERROR,
1080             STATUS_ERROR_NOT_AVAILABLE,
1081             STATUS_ERROR_TIMEOUT
1082     })
1083     @Retention(RetentionPolicy.SOURCE)
1084     public @interface CarPropertyAsyncErrorCode {}
1085 
1086     /**
1087      * Get an instance of the CarPropertyManager.
1088      *
1089      * Should not be obtained directly by clients, use {@link Car#getCarManager(String)} instead.
1090      *
1091      * @param car the Car instance
1092      * @param service the ICarProperty instance
1093      * @hide
1094      */
CarPropertyManager(Car car, @NonNull ICarProperty service)1095     public CarPropertyManager(Car car, @NonNull ICarProperty service) {
1096         super(car);
1097         mService = service;
1098         mAppTargetSdk = getContext().getApplicationInfo().targetSdkVersion;
1099 
1100         Handler eventHandler = getEventHandler();
1101         if (eventHandler == null) {
1102             mHandler = null;
1103             return;
1104         }
1105         mHandler = new SingleMessageHandler<CarPropertyEvent>(eventHandler.getLooper(),
1106                 MSG_GENERIC_EVENT) {
1107             @Override
1108             protected void handleEvent(CarPropertyEvent carPropertyEvent) {
1109                 CarPropertyEventCallbackController carPropertyEventCallbackController;
1110                 synchronized (mLock) {
1111                     carPropertyEventCallbackController =
1112                             mPropertyIdToCarPropertyEventCallbackController.get(
1113                                     carPropertyEvent.getCarPropertyValue().getPropertyId());
1114                 }
1115                 if (carPropertyEventCallbackController == null) {
1116                     return;
1117                 }
1118                 switch (carPropertyEvent.getEventType()) {
1119                     case CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE:
1120                         carPropertyEventCallbackController.forwardPropertyChanged(carPropertyEvent);
1121                         break;
1122                     case CarPropertyEvent.PROPERTY_EVENT_ERROR:
1123                         carPropertyEventCallbackController.forwardErrorEvent(carPropertyEvent);
1124                         break;
1125                     default:
1126                         throw new IllegalArgumentException();
1127                 }
1128             }
1129         };
1130     }
1131 
1132     /**
1133      * Register {@link CarPropertyEventCallback} to get property updates. Multiple callbacks
1134      * can be registered for a single property or the same callback can be used for different
1135      * properties. If the same callback is registered again for the same property, it will be
1136      * updated to new {@code updateRateHz}.
1137      *
1138      * <p>Rate could be one of the following:
1139      * <ul>
1140      *   <li>{@link CarPropertyManager#SENSOR_RATE_ONCHANGE}</li>
1141      *   <li>{@link CarPropertyManager#SENSOR_RATE_NORMAL}</li>
1142      *   <li>{@link CarPropertyManager#SENSOR_RATE_UI}</li>
1143      *   <li>{@link CarPropertyManager#SENSOR_RATE_FAST}</li>
1144      *   <li>{@link CarPropertyManager#SENSOR_RATE_FASTEST}</li>
1145      * </ul>
1146      *
1147      * <p>
1148      * <b>Note:</b>Rate has no effect if the property has one of the following change modes:
1149      * <ul>
1150      *   <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_STATIC}</li>
1151      *   <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}</li>
1152      * </ul>
1153      *
1154      * <p>
1155      * <b>Note:</b> If listener registers a callback for updates for a property for the first time,
1156      * it will receive the property's current value via a change event upon registration if the
1157      * property's value is currently available for reading. If the property is currently not
1158      * available for reading or in error state, a property change event with a unavailable or
1159      * error status will be generated.
1160      *
1161      * <p>For properties that might be unavailable for reading because their power state
1162      * is off, property change events containing the property's initial value will be generated
1163      * once their power state is on.
1164      *
1165      * <p>If {@code updateRateHz} is higher than {@link CarPropertyConfig#getMaxSampleRate()}, it
1166      * will be registered with max sample {@code updateRateHz}.
1167      *
1168      * <p>If {@code updateRateHz} is lower than {@link CarPropertyConfig#getMinSampleRate()}, it
1169      * will be registered with min sample {@code updateRateHz}.
1170      *
1171      * <p>
1172      * <b>Note:</b>Caller must check the value of {@link CarPropertyValue#getStatus()} for property
1173      * change events and only use {@link CarPropertyValue#getValue()} when
1174      * {@link CarPropertyValue#getStatus()} is {@link CarPropertyValue#STATUS_AVAILABLE}. If not,
1175      * the {@link CarPropertyValue#getValue()} is meaningless.
1176      *
1177      * <p>
1178      * <b>Note:</b>A property change event may/may not happen when the property's status
1179      * changes. Caller should not depend on the change event to check property's status. For
1180      * properties that might be unavailable because they depend on certain power state, caller
1181      * should subscribe to the power state property (e.g.
1182      * {@link VehiclePropertyIds#HVAC_POWER_ON} for hvac power dependent properties) to decide this
1183      * property's availability.
1184      *
1185      * @param carPropertyEventCallback the CarPropertyEventCallback to be registered
1186      * @param propertyId               the property ID to subscribe
1187      * @param updateRateHz             how fast the property events are delivered in Hz
1188      * @return {@code true} if the listener is successfully registered
1189      * @throws SecurityException if missing the appropriate permission.
1190      */
1191     @AddedInOrBefore(majorVersion = 33)
1192     @SuppressWarnings("FormatString")
registerCallback(@onNull CarPropertyEventCallback carPropertyEventCallback, int propertyId, @FloatRange(from = 0.0, to = 100.0) float updateRateHz)1193     public boolean registerCallback(@NonNull CarPropertyEventCallback carPropertyEventCallback,
1194             int propertyId, @FloatRange(from = 0.0, to = 100.0) float updateRateHz) {
1195         if (DBG) {
1196             Log.d(TAG, String.format("registerCallback, callback: %s propertyId: %s, "
1197                     + "updateRateHz: %f", carPropertyEventCallback,
1198                     VehiclePropertyIds.toString(propertyId), updateRateHz));
1199         }
1200         requireNonNull(carPropertyEventCallback);
1201         CarPropertyConfig<?> carPropertyConfig = getCarPropertyConfig(propertyId);
1202         if (carPropertyConfig == null) {
1203             Log.e(TAG, "registerCallback:  propertyId is not in carPropertyConfig list:  "
1204                     + VehiclePropertyIds.toString(propertyId));
1205             return false;
1206         }
1207 
1208         float sanitizedUpdateRateHz = InputSanitizationUtils.sanitizeUpdateRateHz(carPropertyConfig,
1209                 updateRateHz);
1210 
1211         boolean registerSuccessful;
1212         synchronized (mLock) {
1213             boolean isNewInstance = false;
1214             CarPropertyEventCallbackController carPropertyEventCallbackController =
1215                     mPropertyIdToCarPropertyEventCallbackController.get(propertyId);
1216             if (carPropertyEventCallbackController == null) {
1217                 carPropertyEventCallbackController = new CarPropertyEventCallbackController(
1218                         propertyId, mLock, mRegistrationUpdateCallback);
1219                 isNewInstance = true;
1220             }
1221 
1222             registerSuccessful = carPropertyEventCallbackController.add(carPropertyEventCallback,
1223                     sanitizedUpdateRateHz);
1224             if (registerSuccessful && isNewInstance) {
1225                 mPropertyIdToCarPropertyEventCallbackController.put(propertyId,
1226                         carPropertyEventCallbackController);
1227             }
1228         }
1229         return registerSuccessful;
1230     }
1231 
1232     private static class CarPropertyEventListenerToService extends ICarPropertyEventListener.Stub {
1233         private final WeakReference<CarPropertyManager> mCarPropertyManager;
1234 
CarPropertyEventListenerToService(CarPropertyManager carPropertyManager)1235         CarPropertyEventListenerToService(CarPropertyManager carPropertyManager) {
1236             mCarPropertyManager = new WeakReference<>(carPropertyManager);
1237         }
1238 
1239         @Override
onEvent(List<CarPropertyEvent> carPropertyEvents)1240         public void onEvent(List<CarPropertyEvent> carPropertyEvents) throws RemoteException {
1241             CarPropertyManager carPropertyManager = mCarPropertyManager.get();
1242             if (carPropertyManager != null) {
1243                 carPropertyManager.handleEvents(carPropertyEvents);
1244             }
1245         }
1246     }
1247 
handleEvents(List<CarPropertyEvent> carPropertyEvents)1248     private void handleEvents(List<CarPropertyEvent> carPropertyEvents) {
1249         if (mHandler != null) {
1250             mHandler.sendEvents(carPropertyEvents);
1251         }
1252     }
1253 
1254     /**
1255      * Stop getting property updates for the given {@link CarPropertyEventCallback}. If there are
1256      * multiple registrations for this {@link CarPropertyEventCallback}, all listening will be
1257      * stopped.
1258      *
1259      * @throws SecurityException if missing the appropriate permission.
1260      */
1261     @AddedInOrBefore(majorVersion = 33)
unregisterCallback(@onNull CarPropertyEventCallback carPropertyEventCallback)1262     public void unregisterCallback(@NonNull CarPropertyEventCallback carPropertyEventCallback) {
1263         if (DBG) {
1264             Log.d(TAG, "unregisterCallback, callback: " + carPropertyEventCallback);
1265         }
1266         requireNonNull(carPropertyEventCallback);
1267         int[] propertyIds;
1268         synchronized (mLock) {
1269             propertyIds = new int[mPropertyIdToCarPropertyEventCallbackController.size()];
1270             for (int i = 0; i < mPropertyIdToCarPropertyEventCallbackController.size(); i++) {
1271                 propertyIds[i] = mPropertyIdToCarPropertyEventCallbackController.keyAt(i);
1272             }
1273         }
1274         for (int propertyId : propertyIds) {
1275             unregisterCallback(carPropertyEventCallback, propertyId);
1276         }
1277     }
1278 
1279     /**
1280      * Stop getting update for {@code propertyId} to the given {@link CarPropertyEventCallback}. If
1281      * the same {@link CarPropertyEventCallback} is used for other properties, those subscriptions
1282      * will not be affected.
1283      *
1284      * @throws SecurityException if missing the appropriate permission.
1285      */
1286     @AddedInOrBefore(majorVersion = 33)
1287     @SuppressWarnings("FormatString")
unregisterCallback(@onNull CarPropertyEventCallback carPropertyEventCallback, int propertyId)1288     public void unregisterCallback(@NonNull CarPropertyEventCallback carPropertyEventCallback,
1289             int propertyId) {
1290         if (DBG) {
1291             Log.d(TAG, String.format("unregisterCallback, callback: %s, property Id: %s",
1292                     carPropertyEventCallback, VehiclePropertyIds.toString(propertyId)));
1293         }
1294         requireNonNull(carPropertyEventCallback);
1295         if (!CarPropertyHelper.isSupported(propertyId)) {
1296             Log.e(TAG, "unregisterCallback: propertyId: "
1297                     + VehiclePropertyIds.toString(propertyId) + " is not supported");
1298             return;
1299         }
1300         synchronized (mLock) {
1301             CarPropertyEventCallbackController carPropertyEventCallbackController =
1302                     mPropertyIdToCarPropertyEventCallbackController.get(propertyId);
1303 
1304             if (carPropertyEventCallbackController == null) {
1305                 return;
1306             }
1307 
1308             boolean allCallbacksRemoved = carPropertyEventCallbackController.remove(
1309                     carPropertyEventCallback);
1310             if (allCallbacksRemoved) {
1311                 mPropertyIdToCarPropertyEventCallbackController.remove(propertyId);
1312             }
1313         }
1314     }
1315 
1316     /**
1317      * @return the list of properties supported by this car that the application may access
1318      */
1319     @NonNull
1320     @AddedInOrBefore(majorVersion = 33)
getPropertyList()1321     public List<CarPropertyConfig> getPropertyList() {
1322         if (DBG) {
1323             Log.d(TAG, "getPropertyList");
1324         }
1325         List<CarPropertyConfig> configs;
1326         try {
1327             configs = mService.getPropertyList().getConfigs();
1328         } catch (RemoteException e) {
1329             Log.e(TAG, "getPropertyList exception ", e);
1330             return handleRemoteExceptionFromCarService(e, new ArrayList<>());
1331         }
1332         if (DBG) {
1333             Log.d(TAG, "getPropertyList returns " + configs.size() + " configs");
1334             for (int i = 0; i < configs.size(); i++) {
1335                 Log.v(TAG, i + ": " + configs.get(i));
1336             }
1337         }
1338         return configs;
1339     }
1340 
1341     /**
1342      * Checks the given property IDs and returns a list of property configs supported by the car.
1343      *
1344      * If some of the properties in the given ID list are not supported, they will not be returned.
1345      *
1346      * @param propertyIds the list of property IDs
1347      * @return the list of property configs
1348      */
1349     @NonNull
1350     @AddedInOrBefore(majorVersion = 33)
getPropertyList(@onNull ArraySet<Integer> propertyIds)1351     public List<CarPropertyConfig> getPropertyList(@NonNull ArraySet<Integer> propertyIds) {
1352         if (DBG) {
1353             Log.d(TAG, "getPropertyList(" + CarPropertyHelper.propertyIdsToString(propertyIds)
1354                     + ")");
1355         }
1356         List<Integer> filteredPropertyIds = new ArrayList<>();
1357         for (int propertyId : propertyIds) {
1358             if (!CarPropertyHelper.isSupported(propertyId)) {
1359                 continue;
1360             }
1361             filteredPropertyIds.add(propertyId);
1362         }
1363         int[] filteredPropertyIdsArray = new int[filteredPropertyIds.size()];
1364         for (int i = 0; i < filteredPropertyIds.size(); i++) {
1365             filteredPropertyIdsArray[i] = filteredPropertyIds.get(i);
1366         }
1367         try {
1368             List<CarPropertyConfig> configs = mService.getPropertyConfigList(
1369                     filteredPropertyIdsArray).getConfigs();
1370             if (DBG) {
1371                 Log.d(TAG, "getPropertyList(" + CarPropertyHelper.propertyIdsToString(propertyIds)
1372                         + ") returns " + configs.size() + " configs");
1373                 for (int i = 0; i < configs.size(); i++) {
1374                     Log.v(TAG, i + ": " + configs.get(i));
1375                 }
1376             }
1377             return configs;
1378         } catch (RemoteException e) {
1379             Log.e(TAG, "getPropertyList exception ", e);
1380             return handleRemoteExceptionFromCarService(e, new ArrayList<>());
1381         }
1382     }
1383 
1384     /**
1385      * Get {@link CarPropertyConfig} by property ID.
1386      *
1387      * @param propertyId the property ID
1388      * @return the {@link CarPropertyConfig} for the selected property, {@code null} if the property
1389      * is not available
1390      */
1391     @Nullable
1392     @AddedInOrBefore(majorVersion = 33)
getCarPropertyConfig(int propertyId)1393     public CarPropertyConfig<?> getCarPropertyConfig(int propertyId) {
1394         if (DBG) {
1395             Log.d(TAG, "getCarPropertyConfig(" + VehiclePropertyIds.toString(propertyId) + ")");
1396         }
1397         if (!CarPropertyHelper.isSupported(propertyId)) {
1398             Log.w(TAG, "Property: " + VehiclePropertyIds.toString(propertyId)
1399                     + " is not supported");
1400             return null;
1401         }
1402         List<CarPropertyConfig> configs;
1403         try {
1404             configs = mService.getPropertyConfigList(new int[] {propertyId}).getConfigs();
1405         } catch (RemoteException e) {
1406             Log.e(TAG, "getPropertyList exception ", e);
1407             return handleRemoteExceptionFromCarService(e, null);
1408         }
1409         CarPropertyConfig<?> config = configs.size() == 0 ? null : configs.get(0);
1410         if (DBG) {
1411             Log.d(TAG, "getCarPropertyConfig(" + VehiclePropertyIds.toString(propertyId)
1412                     + ") returns " + config);
1413         }
1414         return config;
1415     }
1416 
1417     /**
1418      * Returns areaId contains the selected area for the property.
1419      *
1420      * @param propertyId the property ID
1421      * @param area the area enum such as Enums in {@link android.car.VehicleAreaSeat}
1422      * @throws IllegalArgumentException if the property is not available in the vehicle for
1423      * the selected area
1424      * @return the {@code AreaId} containing the selected area for the property
1425      */
1426     @AddedInOrBefore(majorVersion = 33)
getAreaId(int propertyId, int area)1427     public int getAreaId(int propertyId, int area) {
1428         String propertyIdStr = VehiclePropertyIds.toString(propertyId);
1429         if (DBG) {
1430             Log.d(TAG, "getAreaId(propertyId = " + propertyIdStr + ", area = " + area + ")");
1431         }
1432         CarPropertyConfig<?> propConfig = getCarPropertyConfig(propertyId);
1433         if (propConfig == null) {
1434             throw new IllegalArgumentException("The propertyId: " + propertyIdStr
1435                     + " is not available");
1436         }
1437         // For the global property, areaId is 0
1438         if (propConfig.isGlobalProperty()) {
1439             if (DBG) {
1440                 Log.d(TAG, "getAreaId returns the global area ID (0)");
1441             }
1442             return 0;
1443         }
1444         for (int areaId : propConfig.getAreaIds()) {
1445             if ((area & areaId) == area) {
1446                 if (DBG) {
1447                     Log.d(TAG, "getAreaId returns " + areaId);
1448                 }
1449                 return areaId;
1450             }
1451         }
1452 
1453         throw new IllegalArgumentException("The propertyId: " + propertyIdStr
1454                 + " is not available at the area: 0x" + toHexString(area));
1455     }
1456 
1457     /**
1458      * Return read permission string for given property ID.
1459      *
1460      * @param propId the property ID to query
1461      * @return the permission needed to read this property, {@code null} if the property ID is not
1462      * available
1463      *
1464      * @hide
1465      */
1466     @Nullable
1467     @AddedInOrBefore(majorVersion = 33)
getReadPermission(int propId)1468     public String getReadPermission(int propId) {
1469         try {
1470             String permission = mService.getReadPermission(propId);
1471             if (DBG) {
1472                 Log.d(TAG, "getReadPermission(propId =" + VehiclePropertyIds.toString(propId)
1473                         + ") returns " + permission);
1474             }
1475             return permission;
1476         } catch (RemoteException e) {
1477             return handleRemoteExceptionFromCarService(e, "");
1478         }
1479     }
1480 
1481     /**
1482      * Return write permission string for given property ID.
1483      *
1484      * @param propId the property ID to query
1485      * @return the permission needed to write this property, {@code null} if the property ID is not
1486      * available.
1487      *
1488      * @hide
1489      */
1490     @Nullable
1491     @AddedInOrBefore(majorVersion = 33)
getWritePermission(int propId)1492     public String getWritePermission(int propId) {
1493         try {
1494             String permission = mService.getWritePermission(propId);
1495             if (DBG) {
1496                 Log.d(TAG, "getWritePermission(propId = " + VehiclePropertyIds.toString(propId)
1497                         + ") returns " + permission);
1498             }
1499             return permission;
1500         } catch (RemoteException e) {
1501             return handleRemoteExceptionFromCarService(e, "");
1502         }
1503     }
1504 
1505 
1506     /**
1507      * Check whether a given property is available or disabled based on the car's current state.
1508      *
1509      * @param propertyId the property ID
1510      * @param areaId the area ID
1511      * @return {@code true} if {@link CarPropertyValue#STATUS_AVAILABLE}, {@code false} otherwise
1512      * (eg {@link CarPropertyValue#STATUS_UNAVAILABLE})
1513      */
1514     @AddedInOrBefore(majorVersion = 33)
isPropertyAvailable(int propertyId, int areaId)1515     public boolean isPropertyAvailable(int propertyId, int areaId) {
1516         if (DBG) {
1517             Log.d(TAG, "isPropertyAvailable(propertyId = "
1518                     + VehiclePropertyIds.toString(propertyId) + ", areaId = " + areaId + ")");
1519         }
1520         if (!CarPropertyHelper.isSupported(propertyId)) {
1521             if (DBG) {
1522                 Log.d(TAG, "Property: " + VehiclePropertyIds.toString(propertyId)
1523                         + " is not supported");
1524             }
1525             return false;
1526         }
1527 
1528         try {
1529             CarPropertyValue propValue = runSyncOperation(() -> {
1530                 if (DBG) {
1531                     Log.d(TAG, "calling getProperty to check property's availability");
1532                 }
1533                 return mService.getProperty(propertyId, areaId);
1534             });
1535             return (propValue != null
1536                     && propValue.getStatus() == CarPropertyValue.STATUS_AVAILABLE);
1537         } catch (RemoteException e) {
1538             return handleRemoteExceptionFromCarService(e, false);
1539         } catch (ServiceSpecificException e) {
1540             Log.e(TAG, "unable to get property, error: " + e);
1541             return false;
1542         }
1543     }
1544 
1545     /**
1546      * Returns value of a bool property
1547      *
1548      * <p>This method may take couple seconds to complete, so it needs to be called from a
1549      * non-main thread.
1550      *
1551      * <p>Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} equal
1552      * or later than {@link Build.VERSION_CODES#R} will receive the following exceptions when
1553      * request is failed.
1554      * <ul>
1555      *     <li>{@link CarInternalErrorException}
1556      *     <li>{@link PropertyAccessDeniedSecurityException}
1557      *     <li>{@link PropertyNotAvailableAndRetryException}
1558      *     <li>{@link PropertyNotAvailableException}
1559      *     <li>{@link IllegalArgumentException}
1560      * </ul>
1561      *
1562      * <p>Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion}
1563      * earlier than {@link Build.VERSION_CODES#R} will receive the following exceptions if the call
1564      * fails.
1565      * <ul>
1566      *     <li>{@link IllegalStateException} when there is an error detected in cars, or when
1567      *         cars denied the access of the property, or when the property is not available and
1568      *         might be unavailable for a while, or when unexpected error happens.
1569      *     <li>{@link IllegalArgumentException} when the [propertyId, areaId] is not supported.
1570      * </ul>
1571      *
1572      * <p>For pre-R client, the returned value is {@code false} if the property is temporarily not
1573      * available.
1574      *
1575      * @param propertyId the property ID to get
1576      * @param areaId the area ID of the property to get
1577      *
1578      * @throws CarInternalErrorException when there is an unexpected error detected in cars
1579      * @throws PropertyAccessDeniedSecurityException when cars denied the access of the
1580      * property
1581      * @throws PropertyNotAvailableAndRetryException when the property is temporarily
1582      * not available and likely that retrying will be successful
1583      * @throws PropertyNotAvailableException when the property is not available and might be
1584      * unavailable for a while.
1585      * @throws IllegalArgumentException when the [propertyId, areaId] is not supported.
1586      *
1587      * @return the value of a bool property, or {@code false} for pre-R client if the property is
1588      *         temporarily not available
1589      */
1590     @AddedInOrBefore(majorVersion = 33)
getBooleanProperty(int propertyId, int areaId)1591     public boolean getBooleanProperty(int propertyId, int areaId) {
1592         CarPropertyValue<Boolean> carProp = getProperty(Boolean.class, propertyId, areaId);
1593         return handleNullAndPropertyStatus(carProp, areaId, false);
1594     }
1595 
1596     /**
1597      * Returns value of a float property
1598      *
1599      * <p>This method may take couple seconds to complete, so it needs to be called from a
1600      * non-main thread.
1601      *
1602      * <p>This method has the same exception behavior as {@link #getBooleanProperty(int, int)}.
1603      *
1604      * @param propertyId the property ID to get
1605      * @param areaId the area ID of the property to get
1606      *
1607      * @throws CarInternalErrorException when there is an unexpected error detected in cars
1608      * @throws PropertyAccessDeniedSecurityException when cars denied the access of the
1609      * property
1610      * @throws PropertyNotAvailableAndRetryException when the property is temporarily
1611      * not available and likely that retrying will be successful
1612      * @throws PropertyNotAvailableException when the property is not available and might be
1613      * unavailable for a while.
1614      * @throws IllegalArgumentException when the [propertyId, areaId] is not supported.
1615      *
1616      * @return the value of a float property, or 0 if client is pre-R and the property is
1617      *         temporarily not available
1618      */
1619     @AddedInOrBefore(majorVersion = 33)
getFloatProperty(int propertyId, int areaId)1620     public float getFloatProperty(int propertyId, int areaId) {
1621         CarPropertyValue<Float> carProp = getProperty(Float.class, propertyId, areaId);
1622         return handleNullAndPropertyStatus(carProp, areaId, 0f);
1623     }
1624 
1625     /**
1626      * Returns value of an integer property
1627      *
1628      * <p>This method may take couple seconds to complete, so it needs to be called form a
1629      * non-main thread.
1630      *
1631      * <p>This method has the same exception behavior as {@link #getBooleanProperty(int, int)}.
1632      *
1633      * @param propertyId the property ID to get
1634      * @param areaId the area ID of the property to get
1635      *
1636      * @throws CarInternalErrorException when there is an unexpected error detected in cars
1637      * @throws PropertyAccessDeniedSecurityException} when cars denied the access of the
1638      * property
1639      * @throws PropertyNotAvailableAndRetryException} when the property is temporarily
1640      * not available and likely that retrying will be successful
1641      * @throws PropertyNotAvailableException when the property is not available and might be
1642      * unavailable for a while.
1643      * @throws IllegalArgumentException when the [propertyId, areaId] is not supported.
1644      *
1645      * @return the value of a integer property, or 0 if client is pre-R and the property is
1646      *         temporarily not available
1647      */
1648     @AddedInOrBefore(majorVersion = 33)
getIntProperty(int propertyId, int areaId)1649     public int getIntProperty(int propertyId, int areaId) {
1650         CarPropertyValue<Integer> carProp = getProperty(Integer.class, propertyId, areaId);
1651         return handleNullAndPropertyStatus(carProp, areaId, 0);
1652     }
1653 
1654     /**
1655      * Returns value of an integer array property
1656      *
1657      * <p>This method may take couple seconds to complete, so it needs to be called from a
1658      * non-main thread.
1659      *
1660      * <p>This method has the same exception behavior as {@link #getBooleanProperty(int, int)}.
1661      *
1662      * @param propertyId the property ID to get
1663      * @param areaId the area ID of the property to get
1664      *
1665      * @throws CarInternalErrorException when there is an unexpected error detected in cars
1666      * @throws PropertyAccessDeniedSecurityException when cars denied the access of the
1667      * property
1668      * @throws PropertyNotAvailableAndRetryException} when the property is temporarily
1669      * not available and likely that retrying will be successful
1670      * @throws PropertyNotAvailableException} when the property is not available and might be
1671      * unavailable for a while.
1672      * @throws IllegalArgumentException} when the [propertyId, areaId] is not supported.
1673      *
1674      * @return the value of a integer array property, or an empty integer array if client is pre-R
1675      *         and the property is temporarily not available
1676      */
1677     @NonNull
1678     @AddedInOrBefore(majorVersion = 33)
getIntArrayProperty(int propertyId, int areaId)1679     public int[] getIntArrayProperty(int propertyId, int areaId) {
1680         CarPropertyValue<Integer[]> carProp = getProperty(Integer[].class, propertyId, areaId);
1681         Integer[] res = handleNullAndPropertyStatus(carProp, areaId, new Integer[0]);
1682         return toIntArray(res);
1683     }
1684 
toIntArray(Integer[] input)1685     private static int[] toIntArray(Integer[] input) {
1686         int len = input.length;
1687         int[] arr = new int[len];
1688         for (int i = 0; i < len; i++) {
1689             arr[i] = input[i];
1690         }
1691         return arr;
1692     }
1693 
handleNullAndPropertyStatus(CarPropertyValue<T> propertyValue, int areaId, T defaultValue)1694     private <T> T handleNullAndPropertyStatus(CarPropertyValue<T> propertyValue, int areaId,
1695             T defaultValue) {
1696         if (propertyValue == null) {
1697             return defaultValue;
1698         }
1699 
1700         // Keeps the same behavior as android R.
1701         if (mAppTargetSdk < Build.VERSION_CODES.S) {
1702             return propertyValue.getStatus() == CarPropertyValue.STATUS_AVAILABLE
1703                     ? propertyValue.getValue() : defaultValue;
1704         }
1705 
1706         // throws new exceptions in android S.
1707         switch (propertyValue.getStatus()) {
1708             case CarPropertyValue.STATUS_ERROR:
1709                 throw new CarInternalErrorException(propertyValue.getPropertyId(), areaId);
1710             case CarPropertyValue.STATUS_UNAVAILABLE:
1711                 throw new PropertyNotAvailableException(propertyValue.getPropertyId(),
1712                         areaId, /*vendorErrorCode=*/0);
1713             default:
1714                 return propertyValue.getValue();
1715         }
1716     }
1717 
1718     @FunctionalInterface
1719     private interface RemoteCallable<V> {
call()1720         V call() throws RemoteException;
1721     }
1722 
1723     @Nullable
runSyncOperation(RemoteCallable<V> c)1724     private static <V> V runSyncOperation(RemoteCallable<V> c)
1725             throws RemoteException, ServiceSpecificException {
1726         int retryCount = 0;
1727         while (retryCount < SYNC_OP_RETRY_MAX_COUNT) {
1728             retryCount++;
1729             try {
1730                 return c.call();
1731             } catch (ServiceSpecificException e) {
1732                 if (e.errorCode != SYNC_OP_LIMIT_TRY_AGAIN) {
1733                     throw e;
1734                 }
1735                 // If car service don't have enough binder thread to handle this request. Sleep for
1736                 // 10ms and try again.
1737                 Log.d(TAG, "too many sync request, sleeping for " + SYNC_OP_RETRY_SLEEP_IN_MS
1738                         + " ms before retry");
1739                 SystemClock.sleep(SYNC_OP_RETRY_SLEEP_IN_MS);
1740                 continue;
1741             } catch (RemoteException e) {
1742                 throw e;
1743             }
1744         }
1745         throw new ServiceSpecificException(VehicleHalStatusCode.STATUS_INTERNAL_ERROR,
1746                 "failed to call car service sync operations after " + retryCount + " retries");
1747     }
1748 
1749     /**
1750      * Return {@link CarPropertyValue}
1751      *
1752      * <p>This method may take couple seconds to complete, so it needs to be called from a
1753      * non-main thread.
1754      *
1755      * <p>Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} equal
1756      * or later than {@link Build.VERSION_CODES#R} will receive the following exceptions when
1757      * request is failed.
1758      * <ul>
1759      *     <li>{@link CarInternalErrorException}
1760      *     <li>{@link PropertyAccessDeniedSecurityException}
1761      *     <li>{@link PropertyNotAvailableAndRetryException}
1762      *     <li>{@link PropertyNotAvailableException}
1763      *     <li>{@link IllegalArgumentException}
1764      * </ul>
1765      *
1766      * <p>For R or later version client, the returned value will never be null.
1767      *
1768      * <p>Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion}
1769      * earlier than {@link Build.VERSION_CODES#R} will receive the following exceptions when request
1770      * is failed.
1771      * <ul>
1772      *     <li>{@link IllegalStateException} when there is an error detected in cars, or when
1773      *         cars denied the access of the property, or when the property is not available and
1774      *         might be unavailable for a while, or when unexpected error happens.
1775      *     <li>{@link IllegalArgumentException} when the [propertyId, areaId] is not supported.
1776      * </ul>
1777      *
1778      * <p>For pre-R client, the returned value might be null if the property is temporarily not
1779      * available. The client should try again in this case.
1780      *
1781      * @param clazz the class object for the CarPropertyValue
1782      * @param propertyId the property ID to get
1783      * @param areaId the area ID of the property to get
1784      *
1785      * @throws CarInternalErrorException when there is an unexpected error detected in cars
1786      * @throws PropertyAccessDeniedSecurityException when cars denied the access of the
1787      * property
1788      * @throws PropertyNotAvailableAndRetryException when the property is temporarily
1789      * not available and likely that retrying will be successful
1790      * @throws PropertyNotAvailableException when the property is not available and might be
1791      * unavailable for a while.
1792      * @throws IllegalArgumentException when the [propertyId, areaId] is not supported.
1793      *
1794      * @return the value of a property
1795      */
1796     @SuppressWarnings("unchecked")
1797     @Nullable
1798     @AddedInOrBefore(majorVersion = 33)
getProperty(@onNull Class<E> clazz, int propertyId, int areaId)1799     public <E> CarPropertyValue<E> getProperty(@NonNull Class<E> clazz, int propertyId,
1800             int areaId) {
1801         CarPropertyValue<E> carPropertyValue = getProperty(propertyId, areaId);
1802         if (carPropertyValue == null) {
1803             return null;
1804         }
1805         Class<?> actualClass = carPropertyValue.getValue().getClass();
1806         if (actualClass != clazz) {
1807             throw new IllegalArgumentException(
1808                     "Invalid property type. " + "Expected: " + clazz + ", but was: "
1809                             + actualClass);
1810         }
1811         return carPropertyValue;
1812     }
1813 
1814     /**
1815      * Query {@link CarPropertyValue} with property id and areaId.
1816      *
1817      * <p>This method may take couple seconds to complete, so it needs to be called from a
1818      * non-main thread.
1819      *
1820      * <p>Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} equal
1821      * or later than {@link Build.VERSION_CODES#R} will receive the following exceptions when
1822      * request is failed.
1823      * <ul>
1824      *     <li>{@link CarInternalErrorException}
1825      *     <li>{@link PropertyAccessDeniedSecurityException}
1826      *     <li>{@link PropertyNotAvailableAndRetryException}
1827      *     <li>{@link PropertyNotAvailableException}
1828      *     <li>{@link IllegalArgumentException}
1829      * </ul>
1830      *
1831      * <p>For R or later version client, the returned value will never be null.
1832      *
1833      * <p>Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion}
1834      * earlier than {@link Build.VERSION_CODES#R} will receive the following exceptions when request
1835      * is failed.
1836      * <ul>
1837      *     <li>{@link IllegalStateException} when there is an error detected in cars, or when
1838      *         cars denied the access of the property, or when the property is not available and
1839      *         might be unavailable for a while, or when unexpected error happens.
1840      *     <li>{@link IllegalArgumentException} when the [propertyId, areaId] is not supported.
1841      * </ul>
1842      *
1843      * <p>For pre-R client, the returned value might be null if the property is temporarily not
1844      * available. The client should try again in this case.
1845      *
1846      * @param propertyId the property ID to get
1847      * @param areaId the area ID of the property to get
1848      * @param <E> the class type of the property
1849      *
1850      * @throws CarInternalErrorException when there is an unexpected error detected in cars.
1851      * @throws PropertyAccessDeniedSecurityException when cars denied the access of the
1852      * property.
1853      * @throws PropertyNotAvailableAndRetryException when the property is temporarily
1854      * not available and likely that retrying will be successful.
1855      * @throws PropertyNotAvailableException when the property is not available and might be
1856      * unavailable for a while.
1857      * @throws IllegalArgumentException when the [propertyId, areaId] is not supported.
1858      *
1859      * @return the value of a property
1860      */
1861     @Nullable
1862     @AddedInOrBefore(majorVersion = 33)
getProperty(int propertyId, int areaId)1863     public <E> CarPropertyValue<E> getProperty(int propertyId, int areaId) {
1864         if (DBG) {
1865             Log.d(TAG, "getProperty, propertyId: " + VehiclePropertyIds.toString(propertyId)
1866                     + ", areaId: 0x" + toHexString(areaId));
1867         }
1868 
1869         assertPropertyIdIsSupported(propertyId);
1870 
1871         Trace.beginSection("getProperty-" + propertyId + "/" + areaId);
1872         try {
1873             CarPropertyValue<E> carPropertyValue = (CarPropertyValue<E>) (runSyncOperation(() -> {
1874                 return mService.getProperty(propertyId, areaId);
1875             }));
1876             if (carPropertyValue == null) {
1877                 return null;
1878             }
1879             if (mAppTargetSdk >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
1880                 if (carPropertyValue.getStatus() == CarPropertyValue.STATUS_UNAVAILABLE) {
1881                     throw new ServiceSpecificException(VehicleHalStatusCode.STATUS_NOT_AVAILABLE,
1882                             "getProperty returned value with UNAVAILABLE status: "
1883                                     + carPropertyValue);
1884                 } else if (carPropertyValue.getStatus() != CarPropertyValue.STATUS_AVAILABLE) {
1885                     throw new ServiceSpecificException(VehicleHalStatusCode.STATUS_INTERNAL_ERROR,
1886                             "getProperty returned value with error or unknown status: "
1887                                     + carPropertyValue);
1888                 }
1889             }
1890             return carPropertyValue;
1891         } catch (RemoteException e) {
1892             return handleRemoteExceptionFromCarService(e, null);
1893         } catch (ServiceSpecificException e) {
1894             if (DBG) {
1895                 Log.d(TAG, "getProperty received service specific exception, code: " + e.errorCode);
1896             }
1897             if (mAppTargetSdk < Build.VERSION_CODES.R) {
1898                 if (e.errorCode == VehicleHalStatusCode.STATUS_TRY_AGAIN) {
1899                     return null;
1900                 } else {
1901                     throw new IllegalStateException("Failed to get propertyId: "
1902                             + VehiclePropertyIds.toString(propertyId) + " areaId: 0x"
1903                             + toHexString(areaId), e);
1904                 }
1905             }
1906             handleCarServiceSpecificException(e, propertyId, areaId);
1907 
1908             // Never reaches here.
1909             return null;
1910         } finally {
1911             Trace.endSection();
1912         }
1913     }
1914 
1915     /**
1916      * Set value of car property by areaId.
1917      *
1918      * <p>If multiple clients set a property for the same area ID simultaneously, which one takes
1919      * precedence is undefined. Typically, the last set operation (in the order that they are issued
1920      * to the car's ECU) overrides the previous set operations.
1921      *
1922      * <p>This method may take couple seconds to complete, so it needs to be called form a
1923      * non-main thread.
1924      *
1925      * <p>Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} equal
1926      * or later than {@link Build.VERSION_CODES#R} will receive the following exceptions when
1927      * request is failed.
1928      * <ul>
1929      *     <li>{@link CarInternalErrorException}
1930      *     <li>{@link PropertyAccessDeniedSecurityException}
1931      *     <li>{@link PropertyNotAvailableAndRetryException}
1932      *     <li>{@link PropertyNotAvailableException}
1933      *     <li>{@link IllegalArgumentException}
1934      * </ul>
1935      * <p>Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion}
1936      * earlier than {@link Build.VERSION_CODES#R} will receive the following exceptions when request
1937      * is failed.
1938      * <ul>
1939      *     <li>{@link RuntimeException} when the property is temporarily not available.
1940      *     <li>{@link IllegalStateException} when there is an error detected in cars, or when
1941      *         cars denied the access of the property, or when the property is not available and
1942      *         might be unavailable for a while, or when unexpected error happens.
1943      *     <li>{@link IllegalArgumentException} when the [propertyId, areaId] is not supported.
1944      * </ul>
1945      *
1946      * @param clazz the class object for the CarPropertyValue
1947      * @param propertyId the property ID to modify
1948      * @param areaId the area ID to apply the modification
1949      * @param val the value to set
1950      * @param <E> the class type of the given property, for example property that was
1951      * defined as {@code VEHICLE_VALUE_TYPE_INT32} in vehicle HAL could be accessed using
1952      * {@code Integer.class}.
1953      *
1954      * @throws CarInternalErrorException when there is an unexpected error detected in cars.
1955      * @throws PropertyAccessDeniedSecurityException when cars denied the access of the property.
1956      * @throws PropertyNotAvailableException when the property is not available and might be
1957      * unavailable for a while.
1958      * @throws PropertyNotAvailableAndRetryException when the property is temporarily not available
1959      * and likely that retrying will be successful.
1960      * @throws IllegalArgumentException when the [propertyId, areaId] is not supported.
1961      */
1962     @AddedInOrBefore(majorVersion = 33)
setProperty(@onNull Class<E> clazz, int propertyId, int areaId, @NonNull E val)1963     public <E> void setProperty(@NonNull Class<E> clazz, int propertyId, int areaId,
1964             @NonNull E val) {
1965         if (DBG) {
1966             Log.d(TAG, "setProperty, propertyId: " + VehiclePropertyIds.toString(propertyId)
1967                     + ", areaId: 0x" + toHexString(areaId) + ", class: " + clazz + ", val: " + val);
1968         }
1969 
1970         assertPropertyIdIsSupported(propertyId);
1971 
1972         Trace.beginSection("setProperty-" + propertyId + "/" + areaId);
1973         try {
1974             runSyncOperation(() -> {
1975                 mService.setProperty(new CarPropertyValue<>(propertyId, areaId, val),
1976                         mCarPropertyEventToService);
1977                 return null;
1978             });
1979         } catch (RemoteException e) {
1980             handleRemoteExceptionFromCarService(e);
1981             return;
1982         } catch (ServiceSpecificException e) {
1983             if (DBG) {
1984                 Log.d(TAG, "setProperty received service specific exception", e);
1985             }
1986             if (mAppTargetSdk < Build.VERSION_CODES.R) {
1987                 if (e.errorCode == VehicleHalStatusCode.STATUS_TRY_AGAIN) {
1988                     throw new RuntimeException("Failed to set propertyId: "
1989                             + VehiclePropertyIds.toString(propertyId) + " areaId: 0x"
1990                             + toHexString(areaId), e);
1991                 } else {
1992                     throw new IllegalStateException("Failed to set propertyId: "
1993                             + VehiclePropertyIds.toString(propertyId) + " areaId: 0x"
1994                             + toHexString(areaId), e);
1995                 }
1996             }
1997             handleCarServiceSpecificException(e, propertyId, areaId);
1998             return;
1999         } finally {
2000             Trace.endSection();
2001         }
2002     }
2003 
2004     /**
2005      * Modifies a property.  If the property modification doesn't occur, an error event shall be
2006      * generated and propagated back to the application.
2007      *
2008      * <p>This method may take couple seconds to complete, so it needs to be called from a
2009      * non-main thread.
2010      *
2011      * @param propertyId the property ID to modify
2012      * @param areaId the area ID to apply the modification
2013      * @param val the value to set
2014      */
2015     @AddedInOrBefore(majorVersion = 33)
setBooleanProperty(int propertyId, int areaId, boolean val)2016     public void setBooleanProperty(int propertyId, int areaId, boolean val) {
2017         setProperty(Boolean.class, propertyId, areaId, val);
2018     }
2019 
2020     /**
2021      * Set float value of property
2022      *
2023      * <p>This method may take couple seconds to complete, so it needs to be called from a
2024      * non-main thread.
2025      *
2026      * @param propertyId the property ID to modify
2027      * @param areaId the area ID to apply the modification
2028      * @param val the value to set
2029      */
2030     @AddedInOrBefore(majorVersion = 33)
setFloatProperty(int propertyId, int areaId, float val)2031     public void setFloatProperty(int propertyId, int areaId, float val) {
2032         setProperty(Float.class, propertyId, areaId, val);
2033     }
2034 
2035     /**
2036      * Set int value of property
2037      *
2038      * <p>This method may take couple seconds to complete, so it needs to be called from a
2039      * non-main thread.
2040      *
2041      * @param propertyId the property ID to modify
2042      * @param areaId the area ID to apply the modification
2043      * @param val the value to set
2044      */
2045     @AddedInOrBefore(majorVersion = 33)
setIntProperty(int propertyId, int areaId, int val)2046     public void setIntProperty(int propertyId, int areaId, int val) {
2047         setProperty(Integer.class, propertyId, areaId, val);
2048     }
2049 
2050     /**
2051      *  Handles {@code ServiceSpecificException} in {@code CarService} for R and later version.
2052      */
handleCarServiceSpecificException( ServiceSpecificException e, int propertyId, int areaId)2053     private void handleCarServiceSpecificException(
2054             ServiceSpecificException e, int propertyId, int areaId) {
2055         // We are not passing the error message down, so log it here.
2056         Log.w(TAG, "received ServiceSpecificException: " + e);
2057         int errorCode = CarPropertyHelper.getVhalSystemErrorCode(e.errorCode);
2058         int vendorErrorCode = CarPropertyHelper.getVhalVendorErrorCode(e.errorCode);
2059 
2060         switch (errorCode) {
2061             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE:
2062                 throw new PropertyNotAvailableException(propertyId, areaId, vendorErrorCode);
2063             case VehicleHalStatusCode.STATUS_TRY_AGAIN:
2064                 // Vendor error code is ignored for STATUS_TRY_AGAIN error
2065                 throw new PropertyNotAvailableAndRetryException(propertyId, areaId);
2066             case VehicleHalStatusCode.STATUS_ACCESS_DENIED:
2067                 // Vendor error code is ignored for STATUS_ACCESS_DENIED error
2068                 throw new PropertyAccessDeniedSecurityException(propertyId, areaId);
2069             case VehicleHalStatusCode.STATUS_INTERNAL_ERROR:
2070                 throw new CarInternalErrorException(propertyId, areaId, vendorErrorCode);
2071             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_DISABLED:
2072             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_SPEED_LOW:
2073             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_SPEED_HIGH:
2074             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_POOR_VISIBILITY:
2075             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_SAFETY:
2076                 throw new PropertyNotAvailableException(propertyId, areaId,
2077                         getPropertyNotAvailableErrorCodeFromStatusCode(errorCode), vendorErrorCode);
2078             default:
2079                 Log.e(TAG, "Invalid errorCode: " + errorCode + " in CarService");
2080                 throw new CarInternalErrorException(propertyId, areaId);
2081         }
2082     }
2083 
2084     /**
2085      * Convert {@link VehicleHalStatusCode} into public {@link PropertyNotAvailableErrorCode}
2086      * equivalents.
2087      *
2088      * @throws IllegalArgumentException if an invalid status code is passed in.
2089      * @hide
2090      */
getPropertyNotAvailableErrorCodeFromStatusCode(int statusCode)2091     private static int getPropertyNotAvailableErrorCodeFromStatusCode(int statusCode) {
2092         switch (statusCode) {
2093             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE:
2094                 return PropertyNotAvailableErrorCode.NOT_AVAILABLE;
2095             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_DISABLED:
2096                 return PropertyNotAvailableErrorCode.NOT_AVAILABLE_DISABLED;
2097             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_SPEED_LOW:
2098                 return PropertyNotAvailableErrorCode.NOT_AVAILABLE_SPEED_LOW;
2099             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_SPEED_HIGH:
2100                 return PropertyNotAvailableErrorCode.NOT_AVAILABLE_SPEED_HIGH;
2101             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_POOR_VISIBILITY:
2102                 return PropertyNotAvailableErrorCode.NOT_AVAILABLE_POOR_VISIBILITY;
2103             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_SAFETY:
2104                 return PropertyNotAvailableErrorCode.NOT_AVAILABLE_SAFETY;
2105             default:
2106                 throw new IllegalArgumentException("Invalid status code: " + statusCode);
2107         }
2108     }
2109 
clearRequestIdToAsyncRequestInfo( List<? extends AsyncPropertyRequest> asyncPropertyRequests)2110     private void clearRequestIdToAsyncRequestInfo(
2111             List<? extends AsyncPropertyRequest> asyncPropertyRequests) {
2112         if (DBG) {
2113             Log.d(TAG, "clear pending async requests: " + asyncPropertyRequests);
2114         }
2115         synchronized (mLock) {
2116             for (int i = 0; i < asyncPropertyRequests.size(); i++) {
2117                 mRequestIdToAsyncRequestInfo.remove(asyncPropertyRequests.get(i).getRequestId());
2118             }
2119         }
2120     }
2121 
2122     /**
2123      * Set an {@code onCancelListener} for the cancellation signal.
2124      *
2125      * <p>When the signal is cancelled, car service will remove the stored state for the specified
2126      * pending request IDs and ignore all the future results.
2127      */
setOnCancelListener(CancellationSignal cancellationSignal, List<Integer> requestIds)2128     private void setOnCancelListener(CancellationSignal cancellationSignal,
2129             List<Integer> requestIds) {
2130         cancellationSignal.setOnCancelListener(() -> {
2131             int[] requestIdsArray = new int[requestIds.size()];
2132             synchronized (mLock) {
2133                 for (int i = 0; i < requestIds.size(); i++) {
2134                     int requestId = requestIds.get(i);
2135                     requestIdsArray[i] = requestId;
2136                     mRequestIdToAsyncRequestInfo.remove(requestId);
2137                 }
2138             }
2139             try {
2140                 mService.cancelRequests(requestIdsArray);
2141             } catch (RemoteException e) {
2142                 handleRemoteExceptionFromCarService(e);
2143             }
2144         });
2145     }
2146 
2147     /** @hide */
2148     @Override
2149     @AddedInOrBefore(majorVersion = 33)
onCarDisconnected()2150     public void onCarDisconnected() {
2151         synchronized (mLock) {
2152             mPropertyIdToCarPropertyEventCallbackController.clear();
2153         }
2154     }
2155 
2156     /**
2157      * Generate unique get request ID and return to the client.
2158      *
2159      * @param propertyId the property ID
2160      * @param areaId the area ID
2161      * @return the GetPropertyRequest object
2162      */
2163     @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
2164             minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
2165     @NonNull
2166     @SuppressWarnings("FormatString")
generateGetPropertyRequest(int propertyId, int areaId)2167     public GetPropertyRequest generateGetPropertyRequest(int propertyId, int areaId) {
2168         int requestIdCounter = mRequestIdCounter.getAndIncrement();
2169         if (DBG) {
2170             Log.d(TAG, String.format("generateGetPropertyRequest, requestId: %d, propertyId: %s, "
2171                     + "areaId: %d", requestIdCounter, VehiclePropertyIds.toString(propertyId),
2172                     areaId));
2173         }
2174         return new GetPropertyRequest(requestIdCounter, propertyId, areaId);
2175     }
2176 
2177     /**
2178      * Generate unique set request ID and return to the client.
2179      *
2180      * @param <T> the type for the property value, must be one of Object, Boolean, Float, Integer,
2181      *      Long, Float[], Integer[], Long[], String, byte[], Object[]
2182      * @param propertyId the property ID
2183      * @param areaId the area ID
2184      * @param value the value to set
2185      * @return the {@link SetPropertyRequest} object
2186      */
2187     @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
2188             minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
2189     @NonNull
2190     @SuppressWarnings("FormatString")
generateSetPropertyRequest(int propertyId, int areaId, @NonNull T value)2191     public <T> SetPropertyRequest<T> generateSetPropertyRequest(int propertyId, int areaId,
2192             @NonNull T value) {
2193         requireNonNull(value);
2194         int requestIdCounter = mRequestIdCounter.getAndIncrement();
2195         if (DBG) {
2196             Log.d(TAG, String.format("generateSetPropertyRequest, requestId: %d, propertyId: %s, "
2197                     + "areaId: %d, value: %s", requestIdCounter,
2198                     VehiclePropertyIds.toString(propertyId), areaId, value));
2199         }
2200         return new SetPropertyRequest(requestIdCounter, propertyId, areaId, value);
2201     }
2202 
checkAsyncArguments(Object requests, Object callback, long timeoutInMs)2203     private void checkAsyncArguments(Object requests, Object callback, long timeoutInMs) {
2204         requireNonNull(requests);
2205         requireNonNull(callback);
2206         if (timeoutInMs <= 0) {
2207             throw new IllegalArgumentException("timeoutInMs must be a positive number");
2208         }
2209     }
2210 
2211     /**
2212      * Query a list of {@link CarPropertyValue} with property ID and area ID asynchronously.
2213      *
2214      * <p>This function would return immediately before the results are ready. For each request,
2215      * the corresponding result would either be delivered through one
2216      * {@code resultCallback.onSuccess} call if the request succeeded or through one
2217      * {@code errorCallback.onFailure} call if failed. It is guaranteed that the total times the
2218      * callback functions are called is equal to the number of requests if this function does not
2219      * throw an exception. It is guaranteed that none of the callback functions are called if an
2220      * exception is thrown. If the {@code callbackExecutor} is {@code null}, the callback will be
2221      * executed on the default event handler thread. If the callback is doing heavy work, it is
2222      * recommended that the {@code callbackExecutor} is provided.
2223      *
2224      * <p>If the operation is cancelled, it is guaranteed that no more callbacks will be called.
2225      *
2226      * <p>For one request, if the property's status is not available,
2227      * {@code errorCallback.onFailure} will be called once with {@link #STATUS_ERROR_NOT_AVAILABLE}.
2228      *
2229      * <p>For one request, if the property's status is error,
2230      * {@code errorCallback.onFailure} will be called once with {@link
2231      * #STATUS_ERROR_INTERNAL_ERROR}.
2232      *
2233      * @param getPropertyRequests a list of properties to get
2234      * @param timeoutInMs the timeout for the operation, in milliseconds
2235      * @param cancellationSignal a signal that could be used to cancel the on-going operation
2236      * @param callbackExecutor the executor to execute the callback with
2237      * @param getPropertyCallback the callback function to deliver the result
2238      * @throws SecurityException if missing permission to read one of the specific properties.
2239      * @throws IllegalArgumentException if one of the properties to read is not supported.
2240      */
2241     @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
2242             minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getPropertiesAsync( @onNull List<GetPropertyRequest> getPropertyRequests, long timeoutInMs, @Nullable CancellationSignal cancellationSignal, @Nullable Executor callbackExecutor, @NonNull GetPropertyCallback getPropertyCallback)2243     public void getPropertiesAsync(
2244             @NonNull List<GetPropertyRequest> getPropertyRequests,
2245             long timeoutInMs,
2246             @Nullable CancellationSignal cancellationSignal,
2247             @Nullable Executor callbackExecutor,
2248             @NonNull GetPropertyCallback getPropertyCallback) {
2249         if (DBG) {
2250             Log.d(TAG, "getPropertiesAsync, requests: " + getPropertyRequests + ", timeoutInMs: "
2251                     + timeoutInMs + ", callback: " + getPropertyCallback);
2252         }
2253 
2254         checkAsyncArguments(getPropertyRequests, getPropertyCallback, timeoutInMs);
2255         if (callbackExecutor == null) {
2256             callbackExecutor = new HandlerExecutor(getEventHandler());
2257         }
2258 
2259         List<AsyncPropertyServiceRequest> getPropertyServiceRequests = new ArrayList<>(
2260                 getPropertyRequests.size());
2261         for (int i = 0; i < getPropertyRequests.size(); i++) {
2262             GetPropertyRequest getPropertyRequest = getPropertyRequests.get(i);
2263             int propertyId = getPropertyRequest.getPropertyId();
2264             int areaId = getPropertyRequest.getAreaId();
2265             assertPropertyIdIsSupported(propertyId);
2266 
2267             getPropertyServiceRequests.add(AsyncPropertyServiceRequest.newGetAsyncRequest(
2268                     getPropertyRequest));
2269         }
2270 
2271         List<Integer> requestIds = storePendingRequestInfo(getPropertyRequests, callbackExecutor,
2272                 getPropertyCallback);
2273 
2274         try {
2275             if (DBG) {
2276                 Log.d(TAG, "calling CarPropertyService.getPropertiesAsync");
2277             }
2278             mService.getPropertiesAsync(new AsyncPropertyServiceRequestList(
2279                     getPropertyServiceRequests), mAsyncPropertyResultCallback, timeoutInMs);
2280             if (DBG) {
2281                 Log.d(TAG, "CarPropertyService.getPropertiesAsync succeed");
2282             }
2283         } catch (RemoteException e) {
2284             clearRequestIdToAsyncRequestInfo(getPropertyRequests);
2285             handleRemoteExceptionFromCarService(e);
2286         } catch (IllegalArgumentException | SecurityException e) {
2287             clearRequestIdToAsyncRequestInfo(getPropertyRequests);
2288             throw e;
2289         }
2290         if (cancellationSignal != null) {
2291             setOnCancelListener(cancellationSignal, requestIds);
2292         }
2293     }
2294 
2295     /**
2296      * Query a list of {@link CarPropertyValue} with property Id and area Id asynchronously.
2297      *
2298      * Same as {@link CarPropertyManager#getPropertiesAsync(List, long, CancellationSignal,
2299      * Executor, GetPropertyCallback)} with default timeout 10s.
2300      */
2301     @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
2302             minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
getPropertiesAsync( @onNull List<GetPropertyRequest> getPropertyRequests, @Nullable CancellationSignal cancellationSignal, @Nullable Executor callbackExecutor, @NonNull GetPropertyCallback getPropertyCallback)2303     public void getPropertiesAsync(
2304             @NonNull List<GetPropertyRequest> getPropertyRequests,
2305             @Nullable CancellationSignal cancellationSignal,
2306             @Nullable Executor callbackExecutor,
2307             @NonNull GetPropertyCallback getPropertyCallback) {
2308         getPropertiesAsync(getPropertyRequests, ASYNC_GET_DEFAULT_TIMEOUT_MS, cancellationSignal,
2309                 callbackExecutor, getPropertyCallback);
2310     }
2311 
2312     /**
2313      * Sets a list of car property values asynchronously.
2314      *
2315      * <p>This function would return immediately before the results are ready. For each request,
2316      * the corresponding result would either be delivered through one
2317      * {@code resultCallback.onSuccess} call if the request succeeded or through one
2318      * {@code errorCallback.onFailure} call if failed. It is guaranteed that the total times the
2319      * callback functions are called is equal to the number of requests if this function does not
2320      * throw an exception. It is guaranteed that none of the callback functions are called if an
2321      * exception is thrown. If the {@code callbackExecutor} is {@code null}, the callback will be
2322      * executed on the default event handler thread. If the callback is doing heavy work, it is
2323      * recommended that the {@code callbackExecutor} is provided.
2324      *
2325      * <p>If the operation is cancelled, it is guaranteed that no more callbacks will be called.
2326      *
2327      * <p>If multiple clients set a property for the same area ID simultaneously, which one takes
2328      * precedence is undefined. Typically, the last set operation (in the order that they are issued
2329      * to the car's ECU) overrides the previous set operations.
2330      *
2331      * <p>When the success callback will be called depends on whether {@code waitForPropertyUpdate}
2332      * for each request is set. If this is set to {@code true} (by default), the success callback
2333      * will be called when the set operation is successfully delivered to vehicle bus AND either
2334      * target value is the same as the current or when the property is updated to the target value.
2335      *
2336      * <p>When {@code waitForPropertyUpdate} is set to {@code false}, the success callback will be
2337      * called as long as the set operation is successfully delivered to vehicle bus.
2338      *
2339      * <p>Under most cases, client should wait for the property update to verify that the set
2340      * operation actually succeeded.
2341      *
2342      * <p>For cases when the property is write-only (no way to get property update event) or when
2343      * the property represents some action, instead of an actual state, e.g. key stroke where the
2344      * property's current value is not meaningful, caller must set {@code waitForPropertyUpdate}
2345      * to {@code false}.
2346      *
2347      * <p>For {@code HVAC_TEMPERATURE_VALUE_SUGGESTION}, this must be set to {@code false}
2348      * because the updated property value will not be the same as the value to be set.
2349      *
2350      * @param setPropertyRequests a list of properties to set
2351      * @param timeoutInMs the timeout for the operation, in milliseconds
2352      * @param cancellationSignal a signal that could be used to cancel the on-going operation
2353      * @param callbackExecutor the executor to execute the callback with
2354      * @param setPropertyCallback the callback function to deliver the result
2355      * @throws SecurityException if missing permission to write one of the specific properties.
2356      * @throws IllegalArgumentException if one of the properties to set is not supported.
2357      * @throws IllegalArgumentException if one of the properties is not readable and does not set
2358      *   {@code waitForPropertyUpdate} to {@code false}.
2359      * @throws IllegalArgumentException if one of the properties is
2360      *   {@code HVAC_TEMPERATURE_VALUE_SUGGESTION} and does not set {@code waitForPropertyUpdate}
2361      *   to {@code false}.
2362      */
2363     @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
2364             minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
setPropertiesAsync( @onNull List<SetPropertyRequest<?>> setPropertyRequests, long timeoutInMs, @Nullable CancellationSignal cancellationSignal, @Nullable Executor callbackExecutor, @NonNull SetPropertyCallback setPropertyCallback)2365     public void setPropertiesAsync(
2366             @NonNull List<SetPropertyRequest<?>> setPropertyRequests,
2367             long timeoutInMs,
2368             @Nullable CancellationSignal cancellationSignal,
2369             @Nullable Executor callbackExecutor,
2370             @NonNull SetPropertyCallback setPropertyCallback) {
2371         if (DBG) {
2372             Log.d(TAG, "setPropertiesAsync, requests: " + setPropertyRequests + ", timeoutInMs: "
2373                     + timeoutInMs + ", callback: " + setPropertyCallback);
2374         }
2375 
2376         checkAsyncArguments(setPropertyRequests, setPropertyCallback, timeoutInMs);
2377         if (callbackExecutor == null) {
2378             callbackExecutor = new HandlerExecutor(getEventHandler());
2379         }
2380 
2381         List<AsyncPropertyServiceRequest> setPropertyServiceRequests = new ArrayList<>(
2382                 setPropertyRequests.size());
2383         for (int i = 0; i < setPropertyRequests.size(); i++) {
2384             SetPropertyRequest setPropertyRequest = setPropertyRequests.get(i);
2385             int propertyId = setPropertyRequest.getPropertyId();
2386             int areaId = setPropertyRequest.getAreaId();
2387             requireNonNull(setPropertyRequest.getValue());
2388             assertPropertyIdIsSupported(propertyId);
2389 
2390             setPropertyServiceRequests.add(AsyncPropertyServiceRequest.newSetAsyncRequest(
2391                     setPropertyRequest));
2392         }
2393 
2394         List<Integer> requestIds = storePendingRequestInfo(setPropertyRequests, callbackExecutor,
2395                 setPropertyCallback);
2396 
2397         try {
2398             if (DBG) {
2399                 Log.d(TAG, "calling CarPropertyService.setPropertiesAsync");
2400             }
2401             mService.setPropertiesAsync(new AsyncPropertyServiceRequestList(
2402                     setPropertyServiceRequests), mAsyncPropertyResultCallback, timeoutInMs);
2403             if (DBG) {
2404                 Log.d(TAG, "CarPropertyService.setPropertiesAsync succeed");
2405             }
2406         } catch (RemoteException e) {
2407             clearRequestIdToAsyncRequestInfo(setPropertyRequests);
2408             handleRemoteExceptionFromCarService(e);
2409         } catch (IllegalArgumentException | SecurityException e) {
2410             clearRequestIdToAsyncRequestInfo(setPropertyRequests);
2411             throw e;
2412         }
2413         if (cancellationSignal != null) {
2414             setOnCancelListener(cancellationSignal, requestIds);
2415         }
2416     }
2417 
2418     /**
2419      * Sets a list of car property values asynchronously.
2420      *
2421      * Same as {@link CarPropertyManager#setPropertiesAsync(List, long, CancellationSignal,
2422      * Executor, SetPropertyCallback)} with default timeout 10s.
2423      */
2424     @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
2425             minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
setPropertiesAsync( @onNull List<SetPropertyRequest<?>> setPropertyRequests, @Nullable CancellationSignal cancellationSignal, @Nullable Executor callbackExecutor, @NonNull SetPropertyCallback setPropertyCallback)2426     public void setPropertiesAsync(
2427             @NonNull List<SetPropertyRequest<?>> setPropertyRequests,
2428             @Nullable CancellationSignal cancellationSignal,
2429             @Nullable Executor callbackExecutor,
2430             @NonNull SetPropertyCallback setPropertyCallback) {
2431         setPropertiesAsync(setPropertyRequests, ASYNC_GET_DEFAULT_TIMEOUT_MS, cancellationSignal,
2432                 callbackExecutor, setPropertyCallback);
2433     }
2434 
assertPropertyIdIsSupported(int propertyId)2435     private void assertPropertyIdIsSupported(int propertyId) {
2436         if (!CarPropertyHelper.isSupported(propertyId)) {
2437             throw new IllegalArgumentException("The property: "
2438                     + VehiclePropertyIds.toString(propertyId) + " is unsupported");
2439         }
2440     }
2441 
2442     private <RequestType extends AsyncPropertyRequest, CallbackType> List<Integer>
storePendingRequestInfo( List<RequestType> requests, Executor callbackExecutor, CallbackType callback)2443             storePendingRequestInfo(
2444                     List<RequestType> requests, Executor callbackExecutor, CallbackType callback) {
2445         if (DBG) {
2446             Log.d(TAG, "store pending async requests: " + requests);
2447         }
2448         List<Integer> requestIds = new ArrayList<>();
2449         SparseArray<AsyncPropertyRequestInfo<?, ?>> requestInfoToAdd = new SparseArray<>();
2450         synchronized (mLock) {
2451             for (int i = 0; i < requests.size(); i++) {
2452                 RequestType request = requests.get(i);
2453                 AsyncPropertyRequestInfo<RequestType, CallbackType> requestInfo =
2454                         new AsyncPropertyRequestInfo(request, callbackExecutor, callback);
2455                 int requestId = request.getRequestId();
2456                 requestIds.add(requestId);
2457                 if (mRequestIdToAsyncRequestInfo.contains(requestId)
2458                         || requestInfoToAdd.contains(requestId)) {
2459                     throw new IllegalArgumentException(
2460                             "Request ID: " + requestId + " already exists");
2461                 }
2462                 requestInfoToAdd.put(requestId, requestInfo);
2463             }
2464             for (int i = 0; i < requestInfoToAdd.size(); i++) {
2465                 mRequestIdToAsyncRequestInfo.put(requestInfoToAdd.keyAt(i),
2466                         requestInfoToAdd.valueAt(i));
2467             }
2468         }
2469         return requestIds;
2470     }
2471 }
2472