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.CarPropertyErrorCodes.STATUS_OK;
21 import static com.android.car.internal.property.CarPropertyErrorCodes.STATUS_TRY_AGAIN;
22 import static com.android.car.internal.property.CarPropertyHelper.SYNC_OP_LIMIT_TRY_AGAIN;
23 
24 import static java.lang.Integer.toHexString;
25 import static java.util.Objects.requireNonNull;
26 
27 import android.annotation.CallbackExecutor;
28 import android.annotation.FlaggedApi;
29 import android.annotation.FloatRange;
30 import android.annotation.IntDef;
31 import android.annotation.NonNull;
32 import android.annotation.Nullable;
33 import android.annotation.SuppressLint;
34 import android.annotation.SystemApi;
35 import android.car.Car;
36 import android.car.CarManagerBase;
37 import android.car.VehiclePropertyIds;
38 import android.car.feature.FeatureFlags;
39 import android.car.feature.FeatureFlagsImpl;
40 import android.car.feature.Flags;
41 import android.car.hardware.CarPropertyConfig;
42 import android.car.hardware.CarPropertyValue;
43 import android.os.Binder;
44 import android.os.Build;
45 import android.os.CancellationSignal;
46 import android.os.Handler;
47 import android.os.IBinder;
48 import android.os.RemoteException;
49 import android.os.ServiceSpecificException;
50 import android.os.SystemClock;
51 import android.util.ArrayMap;
52 import android.util.ArraySet;
53 import android.util.Log;
54 import android.util.Slog;
55 import android.util.SparseArray;
56 
57 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
58 import com.android.car.internal.ICarBase;
59 import com.android.car.internal.SingleMessageHandler;
60 import com.android.car.internal.dep.Trace;
61 import com.android.car.internal.os.HandlerExecutor;
62 import com.android.car.internal.property.AsyncPropertyServiceRequest;
63 import com.android.car.internal.property.AsyncPropertyServiceRequestList;
64 import com.android.car.internal.property.CarPropertyErrorCodes;
65 import com.android.car.internal.property.CarPropertyEventCallbackController;
66 import com.android.car.internal.property.CarPropertyHelper;
67 import com.android.car.internal.property.CarSubscription;
68 import com.android.car.internal.property.GetPropertyConfigListResult;
69 import com.android.car.internal.property.GetSetValueResult;
70 import com.android.car.internal.property.GetSetValueResultList;
71 import com.android.car.internal.property.IAsyncPropertyResultCallback;
72 import com.android.car.internal.property.InputSanitizationUtils;
73 import com.android.car.internal.property.SubscriptionManager;
74 import com.android.car.internal.util.IntArray;
75 import com.android.car.internal.util.PairSparseArray;
76 import com.android.internal.annotations.GuardedBy;
77 import com.android.internal.annotations.VisibleForTesting;
78 
79 import java.lang.annotation.Retention;
80 import java.lang.annotation.RetentionPolicy;
81 import java.lang.ref.WeakReference;
82 import java.util.ArrayList;
83 import java.util.List;
84 import java.util.Map;
85 import java.util.Set;
86 import java.util.concurrent.Executor;
87 import java.util.concurrent.atomic.AtomicInteger;
88 
89 /**
90  * Provides an application interface for interacting with the Vehicle specific properties.
91  * For details about the individual properties, see the descriptions in {@link VehiclePropertyIds}
92  */
93 public class CarPropertyManager extends CarManagerBase {
94     private static final String TAG = "CarPropertyManager";
95     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
96     private static final int MSG_GENERIC_EVENT = 0;
97     private static final int SYNC_OP_RETRY_SLEEP_IN_MS = 10;
98     private static final int SYNC_OP_RETRY_MAX_COUNT = 10;
99     // The default update rate used when subscribePropertyEvents does not contain updateRateHz as
100     // an argument.
101     private static final float DEFAULT_UPDATE_RATE_HZ = 1f;
102 
103     /**
104      * The default timeout in MS for {@link CarPropertyManager#getPropertiesAsync}.
105      */
106     public static final long ASYNC_GET_DEFAULT_TIMEOUT_MS = 10_000;
107 
108     private final SingleMessageHandler<CarPropertyEvent> mHandler;
109     private final ICarProperty mService;
110     private final int mAppTargetSdk;
111     private final Executor mExecutor;
112     private final AtomicInteger mRequestIdCounter = new AtomicInteger(0);
113     @GuardedBy("mLock")
114     private final SparseArray<AsyncPropertyRequestInfo<?, ?>> mRequestIdToAsyncRequestInfo =
115             new SparseArray<>();
116     private final AsyncPropertyResultCallback mAsyncPropertyResultCallback =
117             new AsyncPropertyResultCallback();
118 
119     private final CarPropertyEventListenerToService mCarPropertyEventToService =
120             new CarPropertyEventListenerToService(this);
121 
122     // This lock is shared with all CarPropertyEventCallbackController instances to prevent
123     // potential deadlock.
124     private final Object mLock = new Object();
125     @GuardedBy("mLock")
126     private final Map<CarPropertyEventCallback, CarPropertyEventCallbackController>
127             mCpeCallbackToCpeCallbackController = new ArrayMap<>();
128     @GuardedBy("mLock")
129     private final SparseArray<ArraySet<CarPropertyEventCallbackController>>
130             mPropIdToCpeCallbackControllerList = new SparseArray<>();
131 
132     private final GetPropertyResultCallback mGetPropertyResultCallback =
133             new GetPropertyResultCallback();
134 
135     private final SetPropertyResultCallback mSetPropertyResultCallback =
136             new SetPropertyResultCallback();
137     @GuardedBy("mLock")
138     private final SubscriptionManager<CarPropertyEventCallback> mSubscriptionManager =
139             new SubscriptionManager<>();
140 
141     private FeatureFlags mFeatureFlags = new FeatureFlagsImpl();
142 
143     /**
144      * Sets fake feature flag for unit testing.
145      *
146      * @hide
147      */
148     @VisibleForTesting
setFeatureFlags(FeatureFlags fakeFeatureFlags)149     public void setFeatureFlags(FeatureFlags fakeFeatureFlags) {
150         mFeatureFlags = fakeFeatureFlags;
151     }
152 
153     /**
154      * Application registers {@link CarPropertyEventCallback} object to receive updates and changes
155      * to subscribed Vehicle specific properties.
156      */
157     public interface CarPropertyEventCallback {
158         /**
159          * Called when a property is updated.
160          *
161          * <p>For an on-change property or a continuous property with Variable Update Rate enabled
162          * (by default), this is called when a property's value or status changes.
163          *
164          * <p>For a continuous property with VUR disabled, this is called periodically based on
165          * the update rate.
166          *
167          * <p>This will also be called once to deliver the initial value (or a value with
168          * unavailable or error status) for every new subscription.
169          *
170          * @param value the new value of the property
171          */
onChangeEvent(CarPropertyValue value)172         void onChangeEvent(CarPropertyValue value);
173 
174         /**
175          * Called when an error happens for a recent {@link CarPropertyManager#setProperty}.
176          *
177          * @param propertyId the property ID which has detected an error
178          * @param areaId the area ID which has detected an error
179          *
180          * <p>Client is recommended to override
181          * {@link CarPropertyEventCallback#onErrorEvent(int, int, int)} and override this as no-op.
182          *
183          * <p>For legacy clients, {@link CarPropertyEventCallback#onErrorEvent(int, int, int)}
184          * should use the default implementation, which will internally call this.
185          *
186          * @see CarPropertyEventCallback#onErrorEvent(int, int, int)
187          */
onErrorEvent(int propertyId, int areaId)188         void onErrorEvent(int propertyId, int areaId);
189 
190         /**
191          * Called when an error happens for a recent {@link CarPropertyManager#setProperty}.
192          *
193          * <p>Note that {@link CarPropertyManager#setPropertiesAsync} will not trigger this. In the
194          * case of failure, {@link CarPropertyManager.SetPropertyCallback#onFailure} will be called.
195          *
196          * <p>Clients which changed the property value in the areaId most recently will receive
197          * this callback. If multiple clients set a property for the same area ID simultaneously,
198          * which one takes precedence is undefined. Typically, the last set operation
199          * (in the order that they are issued to car's ECU) overrides the previous set operations.
200          * The delivered error reflects the error happened in the last set operation.
201          *
202          * <p>If clients override this, implementation does not have to call
203          * {@link CarPropertyEventCallback#onErrorEvent(int, int)} inside this function.
204          * {@link CarPropertyEventCallback#onErrorEvent(int, int)} should be overridden as no-op.
205          *
206          * @param propertyId the property ID which is detected an error
207          * @param areaId the area ID which is detected an error
208          * @param errorCode the error code is raised in the car
209          */
onErrorEvent(int propertyId, int areaId, @CarSetPropertyErrorCode int errorCode)210         default void onErrorEvent(int propertyId, int areaId,
211                 @CarSetPropertyErrorCode int errorCode) {
212             if (DBG) {
213                 Slog.d(TAG, "onErrorEvent propertyId: " + VehiclePropertyIds.toString(propertyId)
214                         + " areaId: 0x" + toHexString(areaId) + " ErrorCode: " + errorCode);
215             }
216             onErrorEvent(propertyId, areaId);
217         }
218     }
219 
220     /**
221      * A callback {@link CarPropertyManager#getPropertiesAsync} when succeeded or failed.
222      */
223     public interface GetPropertyCallback {
224         /**
225          * Method called when {@link GetPropertyRequest} successfully gets a result.
226          */
onSuccess(@onNull GetPropertyResult<?> getPropertyResult)227         void onSuccess(@NonNull GetPropertyResult<?> getPropertyResult);
228 
229         /**
230          * Method called when {@link GetPropertyRequest} returns an error.
231          */
onFailure(@onNull PropertyAsyncError propertyAsyncError)232         void onFailure(@NonNull PropertyAsyncError propertyAsyncError);
233     }
234 
235     /**
236      * A callback {@link CarPropertyManager#setPropertiesAsync} when succeeded or failed.
237      */
238     public interface SetPropertyCallback {
239         /**
240          * Method called when the {@link SetPropertyRequest} successfully set the value.
241          *
242          * <p>This means: the set value request is successfully sent to vehicle
243          *
244          * <p>and
245          *
246          * <p>either the current property value is already the target value, or we have received a
247          * property update event indicating the value is updated to the target value.
248          *
249          * <p>If multiple clients set a property for the same area ID simultaneously with different
250          * values, the order is undefined. One possible case is that both requests are sent to the
251          * vehicle bus, which causes two property update events. As a result, the success callback
252          * would be called for both clients, but in an undefined order. This means that even if
253          * the success callback is called, it doesn't necessarily mean getting the property would
254          * return the same value you just set. Another client might have changed the value after you
255          * set it.
256          *
257          * <p>If only one requests is successfully processed by the vehicle bus, overwriting the
258          * other request, then only one success callback would be called for one client. The other
259          * client would get the failure callback with
260          * {@link CarPropertyManager#STATUS_ERROR_TIMEOUT} error code.
261          *
262          * <p>If multiple clients set a property for the same area ID simultaneously with the same
263          * value. The success callback for both clients would be called in an undefined order.
264          */
onSuccess(@onNull SetPropertyResult setPropertyResult)265         void onSuccess(@NonNull SetPropertyResult setPropertyResult);
266 
267         /**
268          * Method called when {@link SetPropertyRequest} returns an error.
269          */
onFailure(@onNull PropertyAsyncError propertyAsyncError)270         void onFailure(@NonNull PropertyAsyncError propertyAsyncError);
271     }
272 
273     /**
274      * An async get/set property request.
275      */
276     public interface AsyncPropertyRequest {
277         /**
278          * Returns the unique ID for this request.
279          *
280          * <p>Each request must have a unique request ID so the responses can be differentiated.
281          */
getRequestId()282         int getRequestId();
283 
284         /**
285          * Returns the ID for the property of this request.
286          *
287          * <p>The ID must be one of the {@link VehiclePropertyIds} or vendor property IDs.
288          */
getPropertyId()289         int getPropertyId();
290 
291         /**
292          * Returns the area ID for the property of this request.
293          */
getAreaId()294         int getAreaId();
295     }
296 
297     /**
298      * A request for {@link CarPropertyManager#getPropertiesAsync(List, long, CancellationSignal,
299      * Executor, GetPropertyCallback)}.
300      */
301     public static final class GetPropertyRequest implements AsyncPropertyRequest {
302         private final int mRequestId;
303         private final int mPropertyId;
304         private final int mAreaId;
305 
306         /**
307          * @see AsyncPropertyRequest#getRequestId
308          */
309         @Override
getRequestId()310         public int getRequestId() {
311             return mRequestId;
312         }
313 
314         /**
315          * @see AsyncPropertyRequest#getPropertyId
316          */
317         @Override
getPropertyId()318         public int getPropertyId() {
319             return mPropertyId;
320         }
321 
322         /**
323          * @see AsyncPropertyRequest#getAreaId
324          */
325         @Override
getAreaId()326         public int getAreaId() {
327             return mAreaId;
328         }
329 
330         /**
331          * Internal use only. Users should use {@link #generateGetPropertyRequest(int, int)}
332          * instead.
333          */
GetPropertyRequest(int requestId, int propertyId, int areaId)334         private GetPropertyRequest(int requestId, int propertyId, int areaId) {
335             mRequestId = requestId;
336             mPropertyId = propertyId;
337             mAreaId = areaId;
338         }
339 
340         /**
341          * Prints out debug message.
342          */
343         @Override
344         @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
toString()345         public String toString() {
346             return new StringBuilder()
347                     .append("GetPropertyRequest{request ID: ")
348                     .append(mRequestId)
349                     .append(", property ID: ")
350                     .append(VehiclePropertyIds.toString(mPropertyId))
351                     .append(", area ID: ")
352                     .append(mAreaId)
353                     .append("}").toString();
354         }
355     }
356 
357     /**
358      * A request for {@link CarPropertyManager#setPropertiesAsync(List, long, CancellationSignal,
359      * Executor, SetPropertyCallback)}.
360      *
361      * @param <T> the type for the property value, must be one of Object, Boolean, Float, Integer,
362      *      Long, Float[], Integer[], Long[], String, byte[], Object[]
363      */
364     public static final class SetPropertyRequest<T> implements AsyncPropertyRequest {
365         private final int mRequestId;
366         private final int mPropertyId;
367         private final int mAreaId;
368         private float mUpdateRateHz = 0.f;
369         // By default, the async set operation will wait for the property to be updated to the
370         // target value before calling the success callback (or when the target value is the
371         // same as the current value).
372         private boolean mWaitForPropertyUpdate = true;
373 
374         /**
375          * The value to set.
376          */
377         private final T mValue;
378 
379         /**
380          * Sets the update rate in Hz for listening for property updates for continuous property.
381          *
382          * <p>If {@code waitForPropertyUpdate} is set to {@code true} (by default) and if the
383          * property is set to a different value than its current value, the success callback will be
384          * called when a property update event for the new value arrived. This option controls how
385          * frequent the property update event should be reported for continuous property. This is
386          * similar to {@code updateRateHz} in {@link CarPropertyManager#registerCallback}.
387          *
388          * <p>This is ignored for non-continuous properties.
389          *
390          * <p>This is ignored if {@code waitForPropertyUpdate} is set to {@code false}.
391          */
setUpdateRateHz(float updateRateHz)392         public void setUpdateRateHz(float updateRateHz) {
393             mUpdateRateHz = updateRateHz;
394         }
395 
396         /**
397          * Sets whether to wait for the property update event before calling success callback.
398          *
399          * <p>This arguments controls under what conditions the operation is considered succeeded
400          * and the success callback will be called.
401          *
402          * <p>If this is set to {@code true} (by default), the success callback will be called when
403          * both of the following coniditions are met:
404          *
405          * <ul>
406          * <li>the set operation is successfully delivered to vehicle bus.
407          * <li>the {@code mPropertyId}+{@code mAreaId}'s value already equal to {@code mValue} or
408          * is successfully updated to the {@code mValue} through the set operation.
409          * </ul>
410          *
411          * <p>Even if the target value is the same as the current value, we will still send the set
412          * operation to the vehicle bus. If caller wants to reduce unnecessary overhead, caller must
413          * check existing values before issuing the requests.
414          *
415          * <p>If the first condition fails, the error callback will be called. If the second
416          * condition fails, which means we don't see the property updated to the target value within
417          * a specified timeout, the error callback will be called with {@link
418          * #STATUS_ERROR_TIMEOUT}.
419          *
420          * <p>If this is set to {@code false}, the success callback will be called after the
421          * set operation is successfully delivered to vehicle bus.
422          *
423          * <p>Under most cases, client should wait for the property update to verify that the set
424          * operation actually succeeded.
425          *
426          * <p>For cases when the property is write-only (no way to get property update event) or
427          * when the property represents some action, instead of an actual state, e.g. key stroke
428          * where the property's current value is not meaningful, caller should set
429          * {@code waitForPropertyUpdate} to {@code false}.
430          *
431          * <p>For {@code HVAC_TEMPERATURE_VALUE_SUGGESTION}, this must be set to {@code false}
432          * because the updated property value will not be the same as the value to be set.
433          *
434          * <p>Note that even if this is set to {@code true}, it is only guaranteed that the property
435          * value is the target value after the success callback is called if no other clients are
436          * changing the property at the same time. It is always possible that another client changes
437          * the property value after the property is updated to the target value, but before the
438          * client success callback runs. We only guarantee that at some point during the period
439          * after the client issues the request and before the success callback is called, the
440          * property value was set to the target value.
441          */
setWaitForPropertyUpdate(boolean waitForPropertyUpdate)442         public void setWaitForPropertyUpdate(boolean waitForPropertyUpdate) {
443             mWaitForPropertyUpdate = waitForPropertyUpdate;
444         }
445 
446         /**
447          * @see AsyncPropertyRequest#getRequestId
448          */
449         @Override
getRequestId()450         public int getRequestId() {
451             return mRequestId;
452         }
453 
454         /**
455          * @see AsyncPropertyRequest#getPropertyId
456          */
457         @Override
getPropertyId()458         public int getPropertyId() {
459             return mPropertyId;
460         }
461 
462         /**
463          * @see AsyncPropertyRequest#getAreaId
464          */
465         @Override
getAreaId()466         public int getAreaId() {
467             return mAreaId;
468         }
469 
470         /**
471          * Gets the property value to set.
472          */
getValue()473         public T getValue() {
474             return mValue;
475         }
476 
477         /**
478          * Gets the update rate for listening for property updates.
479          */
getUpdateRateHz()480         public float getUpdateRateHz() {
481             return mUpdateRateHz;
482         }
483 
484         /**
485          * Gets whether to wait for property update event before calling success callback.
486          */
isWaitForPropertyUpdate()487         public boolean isWaitForPropertyUpdate() {
488             return mWaitForPropertyUpdate;
489         }
490 
491         /**
492          * Internal use only. Users should use {@link #generateSetPropertyRequest(int, int, T)}
493          * instead.
494          */
SetPropertyRequest(int requestId, int propertyId, int areaId, T value)495         private SetPropertyRequest(int requestId, int propertyId, int areaId, T value) {
496             mRequestId = requestId;
497             mPropertyId = propertyId;
498             mAreaId = areaId;
499             mValue = value;
500         }
501 
502         /**
503          * Prints out debug message.
504          */
505         @Override
506         @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
toString()507         public String toString() {
508             return new StringBuilder()
509                     .append("SetPropertyRequest{request ID: ")
510                     .append(mRequestId)
511                     .append(", property ID: ")
512                     .append(VehiclePropertyIds.toString(mPropertyId))
513                     .append(", area ID: ")
514                     .append(mAreaId)
515                     .append(", value: ")
516                     .append(mValue)
517                     .append(", waitForPropertyUpdate: ")
518                     .append(mWaitForPropertyUpdate)
519                     .append(", mUpdateRateHz: ")
520                     .append(mUpdateRateHz)
521                     .append("}").toString();
522         }
523     }
524 
525     /**
526      * An error result for {@link GetPropertyCallback} or {@link SetPropertyCallback}.
527      */
528     public static final class PropertyAsyncError {
529         private final int mRequestId;
530         private final int mPropertyId;
531         private final int mAreaId;
532         private final CarPropertyErrorCodes mCarPropertyErrorCodes;
533 
getRequestId()534         public int getRequestId() {
535             return mRequestId;
536         }
537 
getPropertyId()538         public int getPropertyId() {
539             return mPropertyId;
540         }
541 
getAreaId()542         public int getAreaId() {
543             return mAreaId;
544         }
545 
getErrorCode()546         public @CarPropertyAsyncErrorCode int getErrorCode() {
547             return getCarPropertyAsyncErrorCodeFromCarPropertyManagerErrorCode(
548                     mCarPropertyErrorCodes.getCarPropertyManagerErrorCode());
549         }
550 
551         /**
552          * Gets the vendor error codes to allow for more detailed error codes.
553          *
554          * @return the vendor error code if it is set, otherwise 0. A vendor error code will have a
555          * range from 0x0000 to 0xffff.
556          *
557          * @hide
558          */
559         @SystemApi
getVendorErrorCode()560         public int getVendorErrorCode() {
561             return mCarPropertyErrorCodes.getVendorErrorCode();
562         }
563 
564         /**
565          * Gets the detailed system error code.
566          *
567          * These must be a value defined in
568          * {@link DetailedErrorCode}. The values in {@link DetailedErrorCode}
569          * may be extended in the future to include additional error codes.
570          *
571          * @return the detailed error code if it is set, otherwise set to 0.
572          */
573         @FlaggedApi(Flags.FLAG_CAR_PROPERTY_DETAILED_ERROR_CODES)
getDetailedErrorCode()574         public int getDetailedErrorCode() {
575             return getDetailedErrorCodeFromSystemErrorCode(
576                     mCarPropertyErrorCodes.getSystemErrorCode());
577         }
578 
579         /**
580          * Creates a new error result for async property request.
581          *
582          * @param requestId the request ID
583          * @param propertyId the property ID in the request
584          * @param areaId the area ID for the property in the request
585          * @param carPropertyErrorCodes the codes indicating the error
586          */
PropertyAsyncError(int requestId, int propertyId, int areaId, CarPropertyErrorCodes carPropertyErrorCodes)587         PropertyAsyncError(int requestId, int propertyId, int areaId,
588                 CarPropertyErrorCodes carPropertyErrorCodes) {
589             mRequestId = requestId;
590             mPropertyId = propertyId;
591             mAreaId = areaId;
592             mCarPropertyErrorCodes = carPropertyErrorCodes;
593         }
594 
595         /**
596          * Prints out debug message.
597          */
598         @Override
599         @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
toString()600         public String toString() {
601             return new StringBuilder()
602                     .append("PropertyAsyncError{request ID: ")
603                     .append(mRequestId)
604                     .append(", property: ")
605                     .append(VehiclePropertyIds.toString(mPropertyId))
606                     .append(", areaId: ")
607                     .append(mAreaId)
608                     .append(", error codes: ")
609                     .append(mCarPropertyErrorCodes)
610                     .append("}").toString();
611         }
612     }
613 
614     /**
615      * A successful result for {@link GetPropertyCallback}.
616      *
617      * @param <T> the type for the property value, must be one of Object, Boolean, Float, Integer,
618      *      Long, Float[], Integer[], Long[], String, byte[], Object[]
619      */
620     public static final class GetPropertyResult<T> {
621         private final int mRequestId;
622         private final int mPropertyId;
623         private final int mAreaId;
624         private final long mTimestampNanos;
625         private final T mValue;
626 
627         /**
628          * Returns the unique ID for the {@link GetPropertyRequest} this result is for.
629          */
getRequestId()630         public int getRequestId() {
631             return mRequestId;
632         }
633 
634         /**
635          * Returns the property ID for this result.
636          */
getPropertyId()637         public int getPropertyId() {
638             return mPropertyId;
639         }
640 
641         /**
642          * Returns the area ID for this result.
643          */
getAreaId()644         public int getAreaId() {
645             return mAreaId;
646         }
647 
648         /**
649          * Returns the property's value.
650          */
651         @NonNull
getValue()652         public T getValue() {
653             return mValue;
654         }
655 
656         /**
657          * Returns the timestamp in nanoseconds at which the value for the vehicle property
658          * happened. For a given vehicle property, each new timestamp should be monotonically
659          * increasing using the same time base as {@link SystemClock#elapsedRealtimeNanos()}.
660          *
661          * <p>NOTE: Timestamp should be synchronized with other signals from the platform (e.g.
662          * {@link android.location.Location} and {@link android.hardware.SensorEvent} instances).
663          * Ideally, timestamp synchronization error should be below 1 millisecond.
664          */
getTimestampNanos()665         public long getTimestampNanos() {
666             return mTimestampNanos;
667         }
668 
669         /**
670          * Creates a new value result for async GetProperty request.
671          *
672          * @param requestId the request ID
673          * @param propertyId the property ID in the request
674          * @param areaId the area ID for the property in the request
675          * @param timestampNanos the timestamp in nanoseconds when this property is updated
676          * @param value the property's value
677          */
GetPropertyResult(int requestId, int propertyId, int areaId, long timestampNanos, @NonNull T value)678         GetPropertyResult(int requestId, int propertyId, int areaId, long timestampNanos,
679                  @NonNull T value) {
680             mRequestId = requestId;
681             mPropertyId = propertyId;
682             mAreaId = areaId;
683             mTimestampNanos = timestampNanos;
684             mValue = value;
685         }
686 
687         /**
688          * Prints out debug message.
689          */
690         @Override
691         @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
toString()692         public String toString() {
693             return new StringBuilder()
694                     .append("GetPropertyResult{type: ")
695                     .append(mValue.getClass())
696                     .append(", request ID: ")
697                     .append(mRequestId)
698                     .append(", property: ")
699                     .append(VehiclePropertyIds.toString(mPropertyId))
700                     .append(", areaId: ")
701                     .append(mAreaId)
702                     .append(", value: ")
703                     .append(mValue)
704                     .append(", timestamp: ")
705                     .append(mTimestampNanos).append("ns")
706                     .append("}").toString();
707         }
708     }
709 
710     /**
711      * A successful result for {@link SetPropertyCallback}.
712      */
713     public static final class SetPropertyResult {
714         private final int mRequestId;
715         private final int mPropertyId;
716         private final int mAreaId;
717         private final long mUpdateTimestampNanos;
718 
719         /**
720          * Gets the ID for the request this result is for.
721          */
getRequestId()722         public int getRequestId() {
723             return mRequestId;
724         }
725 
726         /**
727          * Gets the property ID this result is for.
728          */
getPropertyId()729         public int getPropertyId() {
730             return mPropertyId;
731         }
732 
733         /**
734          * Gets the area ID this result is for.
735          */
getAreaId()736         public int getAreaId() {
737             return mAreaId;
738         }
739 
740         /**
741          * Gets the timestamp in nanoseconds at which the property was updated to the desired value.
742          *
743          * <p>The timestamp will use the same time base as
744          * {@link SystemClock#elapsedRealtimeNanos()}.
745          *
746          * <p>NOTE: If {@code waitForPropertyUpdate} is set to {@code false} for the request, then
747          * this value will be the time when the async set request is successfully sent to the
748          * vehicle bus, not when the property is updated since we have no way of knowing that.
749          *
750          * <p>NOTE: Timestamp should be synchronized with other signals from the platform (e.g.
751          * {@link android.location.Location} and {@link android.hardware.SensorEvent} instances).
752          * Ideally, timestamp synchronization error should be below 1 millisecond.
753          */
getUpdateTimestampNanos()754         public long getUpdateTimestampNanos() {
755             return mUpdateTimestampNanos;
756         }
757 
SetPropertyResult(int requestId, int propertyId, int areaId, long updateTimestampNanos)758         SetPropertyResult(int requestId, int propertyId, int areaId, long updateTimestampNanos) {
759             mRequestId = requestId;
760             mPropertyId = propertyId;
761             mAreaId = areaId;
762             mUpdateTimestampNanos = updateTimestampNanos;
763         }
764 
765         /**
766          * Prints out debug message.
767          */
768         @Override
769         @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
toString()770         public String toString() {
771             return new StringBuilder()
772                     .append("SetPropertyResult{request ID: ")
773                     .append(mRequestId)
774                     .append(", property: ")
775                     .append(VehiclePropertyIds.toString(mPropertyId))
776                     .append(", areaId: ")
777                     .append(mAreaId)
778                     .append(", updated timestamp: ")
779                     .append(mUpdateTimestampNanos).append("ns")
780                     .append("}").toString();
781         }
782     }
783 
784     /**
785      * An abstract interface for converting async get/set result and calling client callbacks.
786      */
787     private interface PropertyResultCallback<CallbackType, ResultType> {
build(int requestId, int propertyId, int areaId, long timestampNanos, @Nullable Object value)788         ResultType build(int requestId, int propertyId, int areaId, long timestampNanos,
789                 @Nullable Object value);
onSuccess(CallbackType callback, ResultType result)790         void onSuccess(CallbackType callback, ResultType result);
onFailure(CallbackType callback, PropertyAsyncError error)791         void onFailure(CallbackType callback, PropertyAsyncError error);
792     }
793 
794     /**
795      * Class to hide implementation detail for get/set callbacks.
796      */
797     private static final class GetPropertyResultCallback implements
798             PropertyResultCallback<GetPropertyCallback, GetPropertyResult> {
build(int requestId, int propertyId, int areaId, long timestampNanos, @Nullable Object value)799         public GetPropertyResult build(int requestId, int propertyId, int areaId,
800                 long timestampNanos, @Nullable Object value) {
801             return new GetPropertyResult(requestId, propertyId, areaId, timestampNanos, value);
802         }
803 
onSuccess(GetPropertyCallback callback, GetPropertyResult result)804         public void onSuccess(GetPropertyCallback callback, GetPropertyResult result) {
805             if (DBG) {
806                 Slog.d(TAG, "delivering success get property result: " + result);
807             }
808             callback.onSuccess(result);
809         }
810 
onFailure(GetPropertyCallback callback, PropertyAsyncError error)811         public void onFailure(GetPropertyCallback callback, PropertyAsyncError error) {
812             if (DBG) {
813                 Slog.d(TAG, "delivering error get property result: " + error);
814             }
815             callback.onFailure(error);
816         }
817     }
818 
819     /**
820      * Class to hide implementation detail for get/set callbacks.
821      */
822     private static final class SetPropertyResultCallback implements
823             PropertyResultCallback<SetPropertyCallback, SetPropertyResult> {
build(int requestId, int propertyId, int areaId, long timestampNanos, @Nullable Object value)824         public  SetPropertyResult build(int requestId, int propertyId, int areaId,
825                 long timestampNanos, @Nullable Object value) {
826             return new SetPropertyResult(requestId, propertyId, areaId, timestampNanos);
827         }
828 
onSuccess(SetPropertyCallback callback, SetPropertyResult result)829         public void onSuccess(SetPropertyCallback callback, SetPropertyResult result) {
830             if (DBG) {
831                 Slog.d(TAG, "delivering success set property result: " + result);
832             }
833             callback.onSuccess(result);
834         }
835 
onFailure(SetPropertyCallback callback, PropertyAsyncError error)836         public void onFailure(SetPropertyCallback callback, PropertyAsyncError error) {
837             if (DBG) {
838                 Slog.d(TAG, "delivering error set property result: " + error);
839             }
840             callback.onFailure(error);
841         }
842     }
843 
844     /**
845      * A class for delivering {@link GetPropertyCallback} or {@link SetPropertyCallback} client
846      * callback when {@code IAsyncPropertyResultCallback} returns results.
847      */
848     private class AsyncPropertyResultCallback extends IAsyncPropertyResultCallback.Stub {
849 
850         @Override
asBinder()851         public IBinder asBinder() {
852             return this;
853         }
854 
855         @Override
onGetValueResults(GetSetValueResultList getValueResults)856         public void onGetValueResults(GetSetValueResultList getValueResults) {
857             this.<GetPropertyRequest, GetPropertyCallback, GetPropertyResult>onResults(
858                     getValueResults.getList(), mGetPropertyResultCallback);
859         }
860 
861         @Override
onSetValueResults(GetSetValueResultList setValueResults)862         public void onSetValueResults(GetSetValueResultList setValueResults) {
863             this.<SetPropertyRequest<?>, SetPropertyCallback, SetPropertyResult>onResults(
864                     setValueResults.getList(), mSetPropertyResultCallback);
865         }
866 
867         @SuppressLint("WrongConstant")
onResults( List<GetSetValueResult> results, PropertyResultCallback<CallbackType, ResultType> propertyResultCallback)868         private <RequestType extends AsyncPropertyRequest, CallbackType, ResultType> void onResults(
869                 List<GetSetValueResult> results,
870                 PropertyResultCallback<CallbackType, ResultType> propertyResultCallback) {
871             for (int i = 0; i < results.size(); i++) {
872                 GetSetValueResult result = results.get(i);
873                 int requestId = result.getRequestId();
874                 AsyncPropertyRequestInfo<RequestType, CallbackType> requestInfo;
875                 synchronized (mLock) {
876                     requestInfo =
877                             (AsyncPropertyRequestInfo<RequestType, CallbackType>)
878                             mRequestIdToAsyncRequestInfo.get(requestId);
879                     mRequestIdToAsyncRequestInfo.remove(requestId);
880                 }
881                 if (requestInfo == null) {
882                     Slog.w(TAG, "onResults: Request ID: " + requestId
883                             + " might have been completed, cancelled or an exception might have "
884                             + "been thrown");
885                     continue;
886                 }
887                 Executor callbackExecutor = requestInfo.getCallbackExecutor();
888                 CallbackType clientCallback = requestInfo.getCallback();
889                 @CarPropertyAsyncErrorCode int errorCode =
890                         result.getCarPropertyErrorCodes().getCarPropertyManagerErrorCode();
891                 int propertyId = requestInfo.getRequest().getPropertyId();
892                 String propertyName = VehiclePropertyIds.toString(propertyId);
893                 int areaId = requestInfo.getRequest().getAreaId();
894                 if (errorCode == STATUS_OK) {
895                     CarPropertyValue<?> carPropertyValue = result.getCarPropertyValue();
896                     long timestampNanos;
897                     if (carPropertyValue != null) {
898                         // This is a get result.
899                         int valuePropertyId = carPropertyValue.getPropertyId();
900                         if (propertyId  != valuePropertyId) {
901                             Slog.e(TAG, "onResults: Request ID: " + requestId + " received get "
902                                     + "property value result, but has mismatch property ID, "
903                                     + " expect: " + propertyName + ", got: "
904                                     + VehiclePropertyIds.toString(valuePropertyId));
905                         }
906                         int valueAreaId = carPropertyValue.getAreaId();
907                         if (areaId  != valueAreaId) {
908                             Slog.e(TAG, "onResults: Property: " + propertyName + " Request ID: "
909                                     + requestId + " received get property value result, but has "
910                                     + "mismatch area ID, expect: " + areaId + ", got: "
911                                     + valueAreaId);
912                         }
913                         timestampNanos = carPropertyValue.getTimestamp();
914                     } else {
915                         // This is a set result.
916                         timestampNanos = result.getUpdateTimestampNanos();
917                     }
918 
919                     ResultType clientResult = propertyResultCallback.build(
920                             requestId, propertyId, areaId, timestampNanos,
921                             carPropertyValue == null ? null : carPropertyValue.getValue());
922                     Binder.clearCallingIdentity();
923                     callbackExecutor.execute(() -> propertyResultCallback.onSuccess(
924                             clientCallback, clientResult));
925                 } else {
926                     Binder.clearCallingIdentity();
927                     callbackExecutor.execute(() -> propertyResultCallback.onFailure(clientCallback,
928                             new PropertyAsyncError(requestId, propertyId, areaId,
929                                     result.getCarPropertyErrorCodes())));
930                 }
931             }
932         }
933     }
934 
935     /**
936      * A class to store async get/set property request info.
937      */
938     private static final class AsyncPropertyRequestInfo<RequestType, CallbackType> {
939         private final RequestType mRequest;
940         private final Executor mCallbackExecutor;
941         private final CallbackType mCallback;
942 
getRequest()943         public RequestType getRequest() {
944             return mRequest;
945         }
946 
getCallbackExecutor()947         public Executor getCallbackExecutor() {
948             return mCallbackExecutor;
949         }
950 
getCallback()951         public CallbackType getCallback() {
952             return mCallback;
953         }
954 
AsyncPropertyRequestInfo(RequestType request, Executor callbackExecutor, CallbackType callback)955         private AsyncPropertyRequestInfo(RequestType request, Executor callbackExecutor,
956                 CallbackType callback) {
957             mRequest = request;
958             mCallbackExecutor = callbackExecutor;
959             mCallback = callback;
960         }
961     }
962 
963     /** Read ONCHANGE sensors. */
964     public static final float SENSOR_RATE_ONCHANGE = 0f;
965     /** Read sensors at the rate of  1 hertz */
966     public static final float SENSOR_RATE_NORMAL = 1f;
967     /** Read sensors at the rate of 5 hertz */
968     public static final float SENSOR_RATE_UI = 5f;
969     /** Read sensors at the rate of 10 hertz */
970     public static final float SENSOR_RATE_FAST = 10f;
971     /** Read sensors at the rate of 100 hertz */
972     public static final float SENSOR_RATE_FASTEST = 100f;
973 
974 
975 
976     /**
977      * Status to indicate that set operation failed. Try it again.
978      */
979     public static final int CAR_SET_PROPERTY_ERROR_CODE_TRY_AGAIN = 1;
980 
981     /**
982      * Status to indicate that set operation failed because of an invalid argument.
983      */
984     public static final int CAR_SET_PROPERTY_ERROR_CODE_INVALID_ARG = 2;
985 
986     /**
987      * Status to indicate that set operation failed because the property is not available.
988      */
989     public static final int CAR_SET_PROPERTY_ERROR_CODE_PROPERTY_NOT_AVAILABLE = 3;
990 
991     /**
992      * Status to indicate that set operation failed because car denied access to the property.
993      */
994     public static final int CAR_SET_PROPERTY_ERROR_CODE_ACCESS_DENIED = 4;
995 
996     /**
997      * Status to indicate that set operation failed because of a general error in cars.
998      */
999     public static final int CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN = 5;
1000 
1001     /** @hide */
1002     @IntDef(prefix = {"CAR_SET_PROPERTY_ERROR_CODE_"}, value = {
1003             CAR_SET_PROPERTY_ERROR_CODE_TRY_AGAIN,
1004             CAR_SET_PROPERTY_ERROR_CODE_INVALID_ARG,
1005             CAR_SET_PROPERTY_ERROR_CODE_PROPERTY_NOT_AVAILABLE,
1006             CAR_SET_PROPERTY_ERROR_CODE_ACCESS_DENIED,
1007             CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN,
1008     })
1009     @Retention(RetentionPolicy.SOURCE)
1010     public @interface CarSetPropertyErrorCode {}
1011 
1012     /**
1013      * Error indicating that there is an error detected in cars.
1014      */
1015     public static final int STATUS_ERROR_INTERNAL_ERROR = 1;
1016     /**
1017      * Error indicating that the property is temporarily not available.
1018      */
1019     public static final int STATUS_ERROR_NOT_AVAILABLE = 2;
1020     /**
1021      * Error indicating the operation has timed-out.
1022      */
1023     public static final int STATUS_ERROR_TIMEOUT = 3;
1024 
1025     /** @hide */
1026     @IntDef(prefix = {"STATUS_"}, value = {
1027             STATUS_OK,
1028             STATUS_ERROR_INTERNAL_ERROR,
1029             STATUS_ERROR_NOT_AVAILABLE,
1030             STATUS_ERROR_TIMEOUT
1031     })
1032     @Retention(RetentionPolicy.SOURCE)
1033     public @interface CarPropertyAsyncErrorCode {}
1034 
1035     /**
1036      * Get an instance of the CarPropertyManager.
1037      *
1038      * Should not be obtained directly by clients, use {@link Car#getCarManager(String)} instead.
1039      *
1040      * @param car the Car instance
1041      * @param service the ICarProperty instance
1042      * @hide
1043      */
CarPropertyManager(ICarBase car, @NonNull ICarProperty service)1044     public CarPropertyManager(ICarBase car, @NonNull ICarProperty service) {
1045         super(car);
1046         mService = service;
1047         mAppTargetSdk = getContext().getApplicationInfo().targetSdkVersion;
1048 
1049         Handler eventHandler = getEventHandler();
1050         if (eventHandler == null) {
1051             mHandler = null;
1052             mExecutor = null;
1053             return;
1054         }
1055         mExecutor = new HandlerExecutor(getEventHandler());
1056         mHandler = new SingleMessageHandler<>(eventHandler.getLooper(), MSG_GENERIC_EVENT) {
1057             @Override
1058             protected void handleEvent(CarPropertyEvent carPropertyEvent) {
1059                 int propertyId = carPropertyEvent.getCarPropertyValue().getPropertyId();
1060                 List<CarPropertyEventCallbackController> cpeCallbacks = new ArrayList<>();
1061                 synchronized (mLock) {
1062                     ArraySet<CarPropertyEventCallbackController> cpeCallbackControllerSet =
1063                             mPropIdToCpeCallbackControllerList.get(propertyId);
1064                     if (cpeCallbackControllerSet == null) {
1065                         Slog.w(TAG, "handleEvent: could not find any callbacks for propertyId="
1066                                 + VehiclePropertyIds.toString(propertyId));
1067                         return;
1068                     }
1069                     for (int i = 0; i < cpeCallbackControllerSet.size(); i++) {
1070                         cpeCallbacks.add(cpeCallbackControllerSet.valueAt(i));
1071                     }
1072                 }
1073 
1074                 for (int i = 0; i < cpeCallbacks.size(); i++) {
1075                     cpeCallbacks.get(i).onEvent(carPropertyEvent);
1076                 }
1077             }
1078         };
1079     }
1080 
1081     /**
1082      * @deprecated Use
1083      * {@link CarPropertyManager#subscribePropertyEvents(int, float, CarPropertyEventCallback)}
1084      * instead. Note that {@code subscribePropertyEvents} by default has variable update rate on
1085      * for continuous properties, but {@code registerCallback} by default has variable update rate
1086      * off. If you want to keep the current behavior of receiving property events for duplicate
1087      * values (which hurts performance), please specify the variable update rate option via
1088      * {@link Subscription.Builder#setVariableUpdateRateEnabled}.
1089      *
1090      * Registers {@link CarPropertyEventCallback} to get property updates.
1091      * Multiple callbacks can be registered for a single property or the same callback can be used
1092      * for different properties. If the same callback is registered again for the same property,
1093      * it will be updated to new {@code updateRateHz}.
1094      *
1095      * <p>Rate could be one of the following:
1096      * <ul>
1097      *   <li>{@link CarPropertyManager#SENSOR_RATE_ONCHANGE}</li>
1098      *   <li>{@link CarPropertyManager#SENSOR_RATE_NORMAL}</li>
1099      *   <li>{@link CarPropertyManager#SENSOR_RATE_UI}</li>
1100      *   <li>{@link CarPropertyManager#SENSOR_RATE_FAST}</li>
1101      *   <li>{@link CarPropertyManager#SENSOR_RATE_FASTEST}</li>
1102      * </ul>
1103      *
1104      * <p>
1105      * <b>Note:</b>Rate has no effect if the property has one of the following change modes:
1106      * <ul>
1107      *   <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_STATIC}</li>
1108      *   <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}</li>
1109      * </ul>
1110      *
1111      * <p>
1112      * <b>Note:</b> When this function is called, the callback will receive the current
1113      * values for all the areaIds for the property through property change events if they are
1114      * currently okay for reading. If they are not available for reading or in error state,
1115      * property change events with a unavailable or error status will be generated.
1116      *
1117      * <p>For properties that might be unavailable for reading because their power state
1118      * is off, property change events containing the property's initial value will be generated
1119      * once their power state is on.
1120      *
1121      * <p>If {@code updateRateHz} is higher than {@link CarPropertyConfig#getMaxSampleRate()}, it
1122      * will be registered with max sample {@code updateRateHz}.
1123      *
1124      * <p>If {@code updateRateHz} is lower than {@link CarPropertyConfig#getMinSampleRate()}, it
1125      * will be registered with min sample {@code updateRateHz}.
1126      *
1127      * <p>
1128      * <b>Note:</b>Caller must check the value of {@link CarPropertyValue#getStatus()} for property
1129      * change events and only use {@link CarPropertyValue#getValue()} when
1130      * {@link CarPropertyValue#getStatus()} is {@link CarPropertyValue#STATUS_AVAILABLE}. If not,
1131      * the {@link CarPropertyValue#getValue()} is meaningless.
1132      *
1133      * <p>
1134      * <b>Note:</b>A property change event may/may not happen when the property's status
1135      * changes. Caller should not depend on the change event to check property's status. For
1136      * properties that might be unavailable because they depend on certain power state, caller
1137      * should subscribe to the power state property (e.g.
1138      * {@link VehiclePropertyIds#HVAC_POWER_ON} for hvac power dependent properties) to decide this
1139      * property's availability.
1140      *
1141      * <p>
1142      * If the registration failed, this will return {@code false}. Caller must check the return
1143      * value to make sure the registration succeeded.
1144      *
1145      * <p>
1146      * If the property is not supported, this will return {@code false}.
1147      *
1148      * <p>
1149      * If the property is supported and the caller does not have read or write permission to it,
1150      * this will return {@code false}.
1151      *
1152      * <p>
1153      * If the caller has write permission but does not have read permission, this will throw
1154      * {@code SecurityException}.
1155      *
1156      * <p>Note that the callback will be executed on the event handler provided to the
1157      * {@link android.car.Car} or the main thread if none was provided.
1158      *
1159      * <p>
1160      * If one {@link CarPropertyEventCallback} is already registered using
1161      * {@link CarPropertyManager#subscribePropertyEvents}, caller must make sure the executor was
1162      * null (using the default executor) when calling subscribePropertyEvents.
1163      *
1164      * @param carPropertyEventCallback the CarPropertyEventCallback to be registered
1165      * @param propertyId               the property ID to subscribe
1166      * @param updateRateHz             how fast the property events are delivered in Hz
1167      * @return {@code true} if the listener is successfully registered.
1168      * @throws SecurityException if the property is supported and the caller has write permission,
1169      *                           but does not have read permission.
1170      */
1171     @Deprecated
1172     @SuppressWarnings("FormatString")
registerCallback(@onNull CarPropertyEventCallback carPropertyEventCallback, int propertyId, @FloatRange(from = 0.0, to = 100.0) float updateRateHz)1173     public boolean registerCallback(@NonNull CarPropertyEventCallback carPropertyEventCallback,
1174             int propertyId, @FloatRange(from = 0.0, to = 100.0) float updateRateHz) {
1175         if (DBG) {
1176             Slog.d(TAG, String.format("registerCallback, callback: %s propertyId: %s, "
1177                             + "updateRateHz: %f", carPropertyEventCallback,
1178                     VehiclePropertyIds.toString(propertyId), updateRateHz));
1179         }
1180 
1181         boolean hasWritePermissionOnly = false;
1182         try {
1183             hasWritePermissionOnly = mService.isSupportedAndHasWritePermissionOnly(propertyId);
1184         } catch (RemoteException e) {
1185             handleRemoteExceptionFromCarService(e);
1186             return false;
1187         }
1188 
1189         if (hasWritePermissionOnly) {
1190             throw new SecurityException(
1191                     "Only has write permission, missing read permission for property: "
1192                     + CarPropertyHelper.propertyIdsToString(List.of(propertyId)));
1193         }
1194 
1195         // We require updateRateHz to be within 0 and 100, however, in the previous implementation,
1196         // we did not actually check this range. In order to prevent the existing behavior, and
1197         // to prevent Subscription.Builder.setUpdateRateHz to throw exception, we fit the
1198         // input within the expected range.
1199         if (updateRateHz > 100.0f) {
1200             updateRateHz = 100.0f;
1201         }
1202         if (updateRateHz < 0.0f) {
1203             updateRateHz = 0.0f;
1204         }
1205         CarSubscription subscribeOption = new CarSubscription();
1206         subscribeOption.propertyId = propertyId;
1207         subscribeOption.updateRateHz = updateRateHz;
1208         // Make sure areaIds is not null.
1209         subscribeOption.areaIds = new int[0];
1210         try {
1211             return subscribePropertyEventsInternal(List.of(subscribeOption),
1212                     /* callbackExecutor= */ null, carPropertyEventCallback);
1213         } catch (IllegalArgumentException | SecurityException e) {
1214             Slog.w(TAG, "register: PropertyId=" + propertyId + ", exception=", e);
1215             return false;
1216         }
1217     }
1218 
1219     /**
1220      * Subscribes to property events for all areaIds for the property.
1221      *
1222      * <p>For continuous property, variable update rate is enabled. The update rate is 1Hz or
1223      * the max supported rate (if lower than 1hz).
1224      *
1225      * <p>Note that the callback will be executed on the event handler provided to the
1226      * {@link android.car.Car} or the main thread if none was provided.
1227      *
1228      * @param propertyId The ID for the property to subscribe to.
1229      * @param carPropertyEventCallback The callback to deliver property update/error events.
1230      * @return {@code true} if the listener is successfully registered
1231      * @throws SecurityException if the caller does not have read permission to one of the supported
1232      *                           properties.
1233      * @throws IllegalArgumentException if there are over-lapping areaIds or the executor is
1234      *                                  registered to another callback or one of the properties are
1235      *                                  not supported.
1236      *
1237      * @see #subscribePropertyEvents(int, int, float, boolean, CarPropertyEventCallback) for more
1238      * options.
1239      */
1240     @FlaggedApi(Flags.FLAG_VARIABLE_UPDATE_RATE)
subscribePropertyEvents(int propertyId, @NonNull CarPropertyEventCallback carPropertyEventCallback)1241     public boolean subscribePropertyEvents(int propertyId,
1242             @NonNull CarPropertyEventCallback carPropertyEventCallback) {
1243         return subscribePropertyEvents(List.of(
1244                 new Subscription.Builder(propertyId).setUpdateRateHz(DEFAULT_UPDATE_RATE_HZ)
1245                 .build()), /* callbackExecutor= */ null, carPropertyEventCallback);
1246     }
1247 
1248     /**
1249      * Subscribes to property events for all areaIds for the property.
1250      *
1251      * <p>For continuous property, variable update rate is enabled.
1252      *
1253      * <p>Note that the callback will be executed on the event handler provided to the
1254      * {@link android.car.Car} or the main thread if none was provided.
1255      *
1256      * @param propertyId The ID for the property to subscribe to.
1257      * @param updateRateHz Only meaningful for continuous property. The update rate in Hz. A common
1258      *      value is 1Hz. See {@link Subscription.Builder#setUpdateRateHz} for detail.
1259      * @param carPropertyEventCallback The callback to deliver property update/error events.
1260      * @return {@code true} if the listener is successfully registered
1261      * @throws SecurityException if the caller does not have read permission to one of the supported
1262      *                           properties.
1263      * @throws IllegalArgumentException if there are over-lapping areaIds or the executor is
1264      *                                  registered to another callback or one of the properties are
1265      *                                  not supported.
1266      *
1267      * @see #subscribePropertyEvents(int, int, float, boolean, CarPropertyEventCallback) for more
1268      * options.
1269      */
1270     @FlaggedApi(Flags.FLAG_VARIABLE_UPDATE_RATE)
subscribePropertyEvents(int propertyId, @FloatRange(from = 0.0, to = 100.0) float updateRateHz, @NonNull CarPropertyEventCallback carPropertyEventCallback)1271     public boolean subscribePropertyEvents(int propertyId,
1272             @FloatRange(from = 0.0, to = 100.0) float updateRateHz,
1273             @NonNull CarPropertyEventCallback carPropertyEventCallback) {
1274         return subscribePropertyEvents(List.of(
1275                 new Subscription.Builder(propertyId).setUpdateRateHz(updateRateHz).build()),
1276                 /* callbackExecutor= */ null, carPropertyEventCallback);
1277     }
1278 
1279 
1280     /**
1281      * Subscribes to property events for the specific area ID for the property.
1282      *
1283      * <p>For continuous property, variable update rate is enabled. The update rate is 1Hz or
1284      * the max supported rate (if lower than 1hz).
1285      *
1286      * <p>Note that the callback will be executed on the event handler provided to the
1287      * {@link android.car.Car} or the main thread if none was provided.
1288      *
1289      * @param propertyId The ID for the property to subscribe to.
1290      * @param areaId The ID for the area to subscribe to.
1291      * @param carPropertyEventCallback The callback to deliver property update/error events.
1292      * @return {@code true} if the listener is successfully registered
1293      * @throws SecurityException if the caller does not have read permission to one of the supported
1294      *                           properties.
1295      * @throws IllegalArgumentException if there are over-lapping areaIds or the executor is
1296      *                                  registered to another callback or one of the properties are
1297      *                                  not supported.
1298      *
1299      * @see #subscribePropertyEvents(int, int, float, boolean, CarPropertyEventCallback) for more
1300      * options.
1301      */
1302     @FlaggedApi(Flags.FLAG_VARIABLE_UPDATE_RATE)
subscribePropertyEvents(int propertyId, int areaId, @NonNull CarPropertyEventCallback carPropertyEventCallback)1303     public boolean subscribePropertyEvents(int propertyId, int areaId,
1304             @NonNull CarPropertyEventCallback carPropertyEventCallback) {
1305         return subscribePropertyEvents(List.of(
1306                 new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)
1307                         .build()),
1308                 /* callbackExecutor= */ null, carPropertyEventCallback);
1309     }
1310 
1311     /**
1312      * Subscribes to property events for the specific area ID for the property.
1313      *
1314      * <p>For continuous property, variable update rate is enabled.
1315      *
1316      * <p>A property event is used to indicate a property's value/status changes (a.k.a
1317      * property update event) or used to indicate a previous {@link #setProperty} operation failed
1318      * (a.k.a property error event).
1319      *
1320      * <p>It is allowed to register multiple {@code carPropertyEventCallback} for a single
1321      * [PropertyId, areaId]. All the registered callbacks will be invoked.
1322      *
1323      * <p>It is only allowed to have one {@code updateRateHz} for a single
1324      * [propertyId, areaId, carPropertyEventCallback] combination. A new {@code updateRateHz} for
1325      * such combination will update the {@code updateRateHz}.
1326      *
1327      * <p>It is only allowed to have one {@code setVariableUpdateRateEnabled} setting for a single
1328      * [propertyId, areaId, carPropertyEventCallback] combination. A new setting will overwrite
1329      * the current setting for the combination.
1330      *
1331      * <p>The {@code carPropertyEventCallback} is executed on a single default event handler thread.
1332      *
1333      * <p>
1334      * <b>Note:</b> When this function is called, the callback will receive the current
1335      * values of the subscribed [propertyId, areaId]s through property change events if they are
1336      * currently okay for reading. If they are not available for reading or in error state,
1337      * property change events with a unavailable or error status will be generated.
1338      *
1339      * <p>Note that the callback will be executed on the event handler provided to the
1340      * {@link android.car.Car} or the main thread if none was provided.
1341      *
1342      * @param propertyId The ID for the property to subscribe to.
1343      * @param areaId The ID for the area to subscribe to.
1344      * @param updateRateHz Only meaningful for continuous property. The update rate in Hz. A common
1345      *      value is 1Hz. See {@link Subscription.Builder#setUpdateRateHz} for detail.
1346      * @param carPropertyEventCallback The callback to deliver property update/error events.
1347      * @return {@code true} if the listener is successfully registered
1348      * @throws SecurityException if the caller does not have read permission to one of the supported
1349      *                           properties.
1350      * @throws IllegalArgumentException if there are over-lapping areaIds or the executor is
1351      *                                  registered to another callback or one of the properties are
1352      *                                  not supported.
1353      *
1354      * @see #subscribePropertyEvents(List, Executor, CarPropertyEventCallback) for
1355      * more detailed explanation on property subscription and batched subscription usage.
1356      */
1357     @FlaggedApi(Flags.FLAG_VARIABLE_UPDATE_RATE)
subscribePropertyEvents(int propertyId, int areaId, @FloatRange(from = 0.0, to = 100.0) float updateRateHz, @NonNull CarPropertyEventCallback carPropertyEventCallback)1358     public boolean subscribePropertyEvents(int propertyId, int areaId,
1359             @FloatRange(from = 0.0, to = 100.0) float updateRateHz,
1360             @NonNull CarPropertyEventCallback carPropertyEventCallback) {
1361         Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)
1362                 .setUpdateRateHz(updateRateHz).setVariableUpdateRateEnabled(false).build();
1363         return subscribePropertyEvents(List.of(subscription), /* callbackExecutor= */ null,
1364                 carPropertyEventCallback);
1365     }
1366 
1367     /**
1368      * Subscribes to multiple [propertyId, areaId]s for property events.
1369      *
1370      * <p>
1371      * If caller don't need use different subscription options among different areaIds for
1372      * one property (e.g. 1 Hz update rate for front-left and 5 Hz update rate for front-right), it
1373      * is recommended to use one {@link Subscription} per property ID.
1374      *
1375      * <p>It is allowed to register multiple {@code carPropertyEventCallback} for a single
1376      * [PropertyId, areaId]. All the registered callbacks will be invoked.
1377      *
1378      * <p>It is only allowed to have one {@code updateRateHz} for a single
1379      * [propertyId, areaId, carPropertyEventCallback] combination. A new {@code updateRateHz} for
1380      * such combination will update the {@code updateRateHz}.
1381      *
1382      * <p>It is only allowed to have one {@code setVariableUpdateRateEnabled} setting for a single
1383      * [propertyId, areaId, carPropertyEventCallback] combination. A new setting will overwrite
1384      * the current setting for the combination.
1385      *
1386      * <p>
1387      * It is allowed to have the same PropertyId in different {@link Subscription}s
1388      * provided in one call. However, they must have non-overlapping AreaIds. A.k.a., one
1389      * [PropertyId, AreaId] must only be associated with one {@link Subscription} within one call.
1390      * Otherwise, {@link IllegalArgumentException} will be thrown.
1391      *
1392      * <p>
1393      * If the
1394      * {@code callbackExecutor} is {@code null}, the callback will be executed on the default event
1395      * handler thread. If no AreaIds are specified, then it will subscribe to all AreaIds for that
1396      * PropertyId.
1397      *
1398      * <p>
1399      * Only one executor can be registered to a callback. The callback must be unregistered before
1400      * trying to register another executor for the same callback. (A callback cannot have
1401      * multiple executors)
1402      *
1403      * <p>Only one executor can be registered to a callback. The callback must be unregistered
1404      * before trying to register another executor for the same callback. (E.G. A callback cannot
1405      * have multiple executors)
1406      *
1407      * <p>
1408      * <b>Note:</b>Rate has no effect if the property has one of the following change modes:
1409      * <ul>
1410      *   <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_STATIC}</li>
1411      *   <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}</li>
1412      * </ul>
1413      *
1414      * <p>
1415      * If the property has the change mode:
1416      * {@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS}, {@code updateRateHz} in
1417      * {@code Subscription} specifies how frequent the property value has to be polled. If
1418      * {@code setVariableUpdateRateEnabled} is not called with {@code false} and variable update
1419      * rate is supported based on
1420      * {@link android.car.hardware.property.AreaIdConfig#isVariableUpdateRateSupported},
1421      * then the client will receive property update event only when the property's value changes
1422      * (a.k.a behaves the same as {@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}).
1423      * If {@code setVariableUpdateRateEnabled} is called with {@code false} or variable update rate
1424      * is not supported, then the client will receive all the property update events based on the
1425      * update rate even if the events contain the same property value.
1426      *
1427      * <p>See {@link Subscription.Builder#setVariableUpdateRateEnabled} for more detail.
1428      *
1429      * <p>
1430      * <b>Note:</b> When this function is called, the callback will receive the current
1431      * values of the subscribed [propertyId, areaId]s through property change events if they are
1432      * currently okay for reading. If they are not available for reading or in error state,
1433      * property change events with a unavailable or error status will be generated.
1434      *
1435      * <p>For properties that might be unavailable for reading because their power state is off,
1436      * PropertyId change events containing the PropertyId's initial value will be
1437      * generated once their power state is on.
1438      *
1439      * <p>If the update rate specified in the {@code subscriptions} for a given PropertyId is
1440      * higher than the PropertyId's maximum sample rate, the subscription will be registered at the
1441      * PropertyId's maximum sample rate specified by {@link CarPropertyConfig#getMaxSampleRate()}.
1442 
1443      * <p>If the update rate specified in the {@code subscriptions} for a given PropertyId is
1444      * lower than the PropertyId's minimum sample rate, the subscription will be registered at the
1445      * PropertyId's minimum sample rate specified by {@link CarPropertyConfig#getMinSampleRate()}.
1446      *
1447      * <p>
1448      * <b>Note:</b>Caller must check the value of {@link CarPropertyValue#getStatus()} for
1449      * PropertyId change events and only use {@link CarPropertyValue#getValue()} when
1450      * {@link CarPropertyValue#getStatus()} is {@link CarPropertyValue#STATUS_AVAILABLE}. If not,
1451      * the {@link CarPropertyValue#getValue()} is meaningless.
1452      *
1453      * <p>
1454      * <b>Note:</b>A PropertyId change event may/may not happen when the PropertyId's status
1455      * changes. Caller should not depend on the change event to check PropertyId's status. For
1456      * properties that might be unavailable because they depend on certain power state, caller
1457      * should subscribe to the power state PropertyId (e.g. {@link VehiclePropertyIds#HVAC_POWER_ON}
1458      * for hvac power dependent properties) to decide this PropertyId's availability.
1459      *
1460      * <p>
1461      * If one {@link CarPropertyEventCallback} is already registered using
1462      * {@link CarPropertyManager#registerCallback}, caller must make sure the executor is
1463      * null (using the default executor) for subscribePropertyEvents.
1464      *
1465      * @param subscriptions A list of subscriptions to add, which specifies PropertyId, AreaId, and
1466      *                      updateRateHz. Caller should typically use one Subscription for one
1467      *                      property ID.
1468      * @param callbackExecutor The executor in which the callback is done on. If this is null, the
1469      *                         callback will be executed on the event handler provided to the
1470      *                         {@link android.car.Car} or the main thread if none was provided.
1471      * @param carPropertyEventCallback The callback to deliver property update/error events.
1472      * @return {@code true} if the listener is successfully registered
1473      * @throws SecurityException if the caller does not have read permission to one of the supported
1474      *                           properties.
1475      * @throws IllegalArgumentException if there are over-lapping areaIds or the executor is
1476      *                                  registered to another callback or one of the properties are
1477      *                                  not supported.
1478      */
1479     @FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS)
subscribePropertyEvents(@onNull List<Subscription> subscriptions, @Nullable @CallbackExecutor Executor callbackExecutor, @NonNull CarPropertyEventCallback carPropertyEventCallback)1480     public boolean subscribePropertyEvents(@NonNull List<Subscription> subscriptions,
1481             @Nullable @CallbackExecutor Executor callbackExecutor,
1482             @NonNull CarPropertyEventCallback carPropertyEventCallback) {
1483         requireNonNull(subscriptions);
1484         List<CarSubscription> subscribeOptions = convertToCarSubscribeOptions(subscriptions);
1485         return subscribePropertyEventsInternal(subscribeOptions, callbackExecutor,
1486                 carPropertyEventCallback);
1487     }
1488 
1489     /**
1490      * Converts the {@link Subscription} from client to internal {@link CarSubscription}.
1491      *
1492      * This is only called by APIs with FLAG_BATCHED_SUBSCRIPTIONS.
1493      */
convertToCarSubscribeOptions(List<Subscription> subscriptions)1494     private List<CarSubscription> convertToCarSubscribeOptions(List<Subscription> subscriptions) {
1495         List<CarSubscription> carSubscribeOptions = new ArrayList<>();
1496         for (int i = 0; i < subscriptions.size(); i++) {
1497             Subscription clientOption = subscriptions.get(i);
1498             CarSubscription internalOption = new CarSubscription();
1499             internalOption.propertyId = clientOption.getPropertyId();
1500             internalOption.areaIds = clientOption.getAreaIds();
1501             internalOption.updateRateHz = clientOption.getUpdateRateHz();
1502             internalOption.enableVariableUpdateRate = clientOption.isVariableUpdateRateEnabled();
1503             internalOption.resolution = clientOption.getResolution();
1504             carSubscribeOptions.add(internalOption);
1505         }
1506         return carSubscribeOptions;
1507     }
1508 
subscribePropertyEventsInternal(List<CarSubscription> subscribeOptions, @Nullable @CallbackExecutor Executor callbackExecutor, CarPropertyEventCallback carPropertyEventCallback)1509     private boolean subscribePropertyEventsInternal(List<CarSubscription> subscribeOptions,
1510             @Nullable @CallbackExecutor Executor callbackExecutor,
1511             CarPropertyEventCallback carPropertyEventCallback) {
1512         requireNonNull(carPropertyEventCallback);
1513         validateAreaDisjointness(subscribeOptions);
1514         if (DBG) {
1515             Slog.d(TAG, String.format("subscribePropertyEvents, callback: %s subscribeOptions: %s",
1516                              carPropertyEventCallback, subscribeOptions));
1517         }
1518         int[] noReadPermPropertyIds;
1519         try {
1520             noReadPermPropertyIds = getSupportedNoReadPermPropIds(subscribeOptions);
1521         } catch (RemoteException e) {
1522             handleRemoteExceptionFromCarService(e);
1523             return false;
1524         }
1525         if (noReadPermPropertyIds.length != 0) {
1526             // propertyIdsToString does not work primitive array.
1527             List<Integer> noReadPermPropertyIdsCopy = new ArrayList<>();
1528             for (int i = 0; i < noReadPermPropertyIds.length; i++) {
1529                 noReadPermPropertyIdsCopy.add(noReadPermPropertyIds[i]);
1530             }
1531             throw new SecurityException("Do not have read permissions for properties: "
1532                     + CarPropertyHelper.propertyIdsToString(noReadPermPropertyIdsCopy));
1533         }
1534 
1535         if (callbackExecutor == null) {
1536             callbackExecutor = mExecutor;
1537         }
1538 
1539         List<CarSubscription> sanitizedSubscribeOptions;
1540         try {
1541             sanitizedSubscribeOptions = sanitizeSubscribeOptions(subscribeOptions);
1542         } catch (IllegalStateException e) {
1543             Slog.e(TAG, "failed to sanitize update rate", e);
1544             return false;
1545         }
1546 
1547         synchronized (mLock) {
1548             CarPropertyEventCallbackController cpeCallbackController =
1549                     mCpeCallbackToCpeCallbackController.get(carPropertyEventCallback);
1550             if (cpeCallbackController != null
1551                     && cpeCallbackController.getExecutor() != callbackExecutor) {
1552                 throw new IllegalArgumentException("A different executor is already associated with"
1553                         + " this callback, please use the same executor.");
1554             }
1555 
1556             mSubscriptionManager.stageNewOptions(carPropertyEventCallback,
1557                     sanitizedSubscribeOptions);
1558 
1559             if (!applySubscriptionChangesLocked()) {
1560                 Slog.e(TAG, "Subscription failed: failed to apply subscription changes");
1561                 return false;
1562             }
1563 
1564             if (cpeCallbackController == null) {
1565                 cpeCallbackController =
1566                         new CarPropertyEventCallbackController(carPropertyEventCallback,
1567                                 callbackExecutor);
1568                 mCpeCallbackToCpeCallbackController.put(carPropertyEventCallback,
1569                         cpeCallbackController);
1570             }
1571 
1572             // Must use sanitizedSubscribeOptions instead of subscribeOptions here since we need to
1573             // use sanitized update rate.
1574             for (int i = 0; i < sanitizedSubscribeOptions.size(); i++) {
1575                 CarSubscription option = sanitizedSubscribeOptions.get(i);
1576                 int propertyId = option.propertyId;
1577                 float sanitizedUpdateRateHz = option.updateRateHz;
1578                 int[] areaIds = option.areaIds;
1579                 // After {@code sanitizeSubscribeOptions}, update rate must be 0
1580                 // for on-change property and non-0 for continuous property.
1581                 // There is an edge case where minSampleRate is 0 and client uses 0 as sample rate
1582                 // for continuous property. In this case, it is really impossible to do VUR so treat
1583                 // it as an on-change property is fine.
1584                 if (sanitizedUpdateRateHz == 0) {
1585                     cpeCallbackController.addOnChangeProperty(propertyId, areaIds);
1586                 } else {
1587                     cpeCallbackController.addContinuousProperty(propertyId, areaIds,
1588                             sanitizedUpdateRateHz, option.enableVariableUpdateRate,
1589                             option.resolution);
1590                 }
1591 
1592                 ArraySet<CarPropertyEventCallbackController> cpeCallbackControllerSet =
1593                         mPropIdToCpeCallbackControllerList.get(propertyId);
1594                 if (cpeCallbackControllerSet == null) {
1595                     cpeCallbackControllerSet = new ArraySet<>();
1596                     mPropIdToCpeCallbackControllerList.put(propertyId, cpeCallbackControllerSet);
1597                 }
1598                 cpeCallbackControllerSet.add(cpeCallbackController);
1599             }
1600         }
1601         return true;
1602     }
1603 
1604     /**
1605      * Checks if any subscription have overlapping [propertyId, areaId] pairs.
1606      *
1607      * @param subscribeOptions The list of subscribe options to check.
1608      */
validateAreaDisjointness(List<CarSubscription> subscribeOptions)1609     private void validateAreaDisjointness(List<CarSubscription> subscribeOptions) {
1610         PairSparseArray<Object> propertyToAreaId = new PairSparseArray<>();
1611         Object placeHolder = new Object();
1612         for (int i = 0; i < subscribeOptions.size(); i++) {
1613             CarSubscription option = subscribeOptions.get(i);
1614             int propertyId = option.propertyId;
1615             int[] areaIds = option.areaIds;
1616             for (int areaId : areaIds) {
1617                 if (propertyToAreaId.contains(propertyId, areaId)) {
1618                     throw new IllegalArgumentException("Subscribe options contain overlapping "
1619                             + "propertyId: " + VehiclePropertyIds.toString(propertyId) + " areaId: "
1620                             + areaId);
1621                 }
1622                 propertyToAreaId.append(propertyId, areaId, placeHolder);
1623             }
1624         }
1625     }
1626 
1627     private static class CarPropertyEventListenerToService extends ICarPropertyEventListener.Stub {
1628         private final WeakReference<CarPropertyManager> mCarPropertyManager;
1629 
CarPropertyEventListenerToService(CarPropertyManager carPropertyManager)1630         CarPropertyEventListenerToService(CarPropertyManager carPropertyManager) {
1631             mCarPropertyManager = new WeakReference<>(carPropertyManager);
1632         }
1633 
1634         @Override
onEvent(List<CarPropertyEvent> carPropertyEvents)1635         public void onEvent(List<CarPropertyEvent> carPropertyEvents) throws RemoteException {
1636             CarPropertyManager carPropertyManager = mCarPropertyManager.get();
1637             if (carPropertyManager != null) {
1638                 carPropertyManager.handleEvents(carPropertyEvents);
1639             }
1640         }
1641     }
1642 
handleEvents(List<CarPropertyEvent> carPropertyEvents)1643     private void handleEvents(List<CarPropertyEvent> carPropertyEvents) {
1644         if (mHandler != null) {
1645             mHandler.sendEvents(carPropertyEvents);
1646         }
1647     }
1648 
1649     /**
1650      * Update the property ID and area IDs subscription in {@link #mService}.
1651      *
1652      * @return {@code true} if the property has been successfully registered with the service.
1653      * @throws SecurityException if missing the appropriate property access permission.
1654      */
1655     @GuardedBy("mLock")
applySubscriptionChangesLocked()1656     private boolean applySubscriptionChangesLocked() {
1657         List<CarSubscription> updatedCarSubscriptions = new ArrayList<>();
1658         List<Integer> propertiesToUnsubscribe = new ArrayList<>();
1659 
1660         mSubscriptionManager.diffBetweenCurrentAndStage(updatedCarSubscriptions,
1661                 propertiesToUnsubscribe);
1662 
1663         if (propertiesToUnsubscribe.isEmpty() && updatedCarSubscriptions.isEmpty()) {
1664             Slog.d(TAG, "There is nothing to subscribe or unsubscribe to CarPropertyService");
1665             mSubscriptionManager.commit();
1666             return true;
1667         }
1668 
1669         if (DBG) {
1670             Slog.d(TAG, "updatedCarSubscriptions to subscribe is: "
1671                     + updatedCarSubscriptions + " and the list of properties to unsubscribe is: "
1672                     + CarPropertyHelper.propertyIdsToString(propertiesToUnsubscribe));
1673         }
1674 
1675         try {
1676             if (!updatedCarSubscriptions.isEmpty()) {
1677                 if (!registerLocked(updatedCarSubscriptions)) {
1678                     mSubscriptionManager.dropCommit();
1679                     return false;
1680                 }
1681             }
1682 
1683             if (!propertiesToUnsubscribe.isEmpty()) {
1684                 for (int i = 0; i < propertiesToUnsubscribe.size(); i++) {
1685                     if (!unregisterLocked(propertiesToUnsubscribe.get(i))) {
1686                         Slog.w(TAG, "Failed to unsubscribe to: " + VehiclePropertyIds.toString(
1687                                 propertiesToUnsubscribe.get(i)));
1688                         mSubscriptionManager.dropCommit();
1689                         return false;
1690                     }
1691                 }
1692             }
1693         } catch (SecurityException e) {
1694             mSubscriptionManager.dropCommit();
1695             throw e;
1696         }
1697 
1698         mSubscriptionManager.commit();
1699         return true;
1700     }
1701 
1702     /**
1703      * Called when {@code propertyId} registration needs to be updated.
1704      *
1705      * @return {@code true} if registration was successful, otherwise {@code false}.
1706      * @throws SecurityException if missing the appropriate property access permission.
1707      */
1708     @GuardedBy("mLock")
registerLocked(List<CarSubscription> options)1709     private boolean registerLocked(List<CarSubscription> options) {
1710         try {
1711             mService.registerListener(options, mCarPropertyEventToService);
1712         } catch (RemoteException e) {
1713             handleRemoteExceptionFromCarService(e);
1714             return false;
1715         } catch (SecurityException e) {
1716             throw e;
1717         } catch (Exception e) {
1718             Slog.w(TAG, "registerLocked with options: " + options
1719                     + ", unexpected exception=", e);
1720             return false;
1721         }
1722         return true;
1723     }
1724 
1725     /**
1726      * Called when {@code propertyId} needs to be unregistered.
1727      *
1728      * @return {@code true} if unregistering was successful, otherwise {@code false}.
1729      * @throws SecurityException if missing the appropriate property access permission.
1730      */
1731     @GuardedBy("mLock")
unregisterLocked(int propertyId)1732     private boolean unregisterLocked(int propertyId) {
1733         try {
1734             mService.unregisterListener(propertyId, mCarPropertyEventToService);
1735         } catch (RemoteException e) {
1736             handleRemoteExceptionFromCarService(e);
1737             return false;
1738         } catch (SecurityException e) {
1739             throw e;
1740         } catch (Exception e) {
1741             Slog.w(TAG, "unregisterLocked with property: "
1742                     + VehiclePropertyIds.toString(propertyId)
1743                     + ", unexpected exception=", e);
1744             return false;
1745         }
1746         return true;
1747     }
1748 
1749     /**
1750      * Stop getting property updates for the given {@link CarPropertyEventCallback}. If there are
1751      * multiple registrations for this {@link CarPropertyEventCallback}, all listening will be
1752      * stopped.
1753      *
1754      * @param carPropertyEventCallback A previously subscribed callback to unsubscribe.
1755      * @throws SecurityException if the caller does not have read permission to the properties
1756      *                           registered for this callback.
1757      */
1758     @FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS)
unsubscribePropertyEvents( @onNull CarPropertyEventCallback carPropertyEventCallback)1759     public void unsubscribePropertyEvents(
1760             @NonNull CarPropertyEventCallback carPropertyEventCallback) {
1761         requireNonNull(carPropertyEventCallback);
1762         if (DBG) {
1763             Slog.d(TAG, "unsubscribePropertyEvents, callback: " + carPropertyEventCallback);
1764         }
1765         int[] propertyIds;
1766         synchronized (mLock) {
1767             CarPropertyEventCallbackController cpeCallbackController =
1768                     mCpeCallbackToCpeCallbackController.get(carPropertyEventCallback);
1769             if (cpeCallbackController == null) {
1770                 Slog.w(TAG, "unsubscribePropertyEvents: callback was not previously registered.");
1771                 return;
1772             }
1773             propertyIds = cpeCallbackController.getSubscribedProperties();
1774         }
1775         ArrayList<Integer> propertyIdsList = new ArrayList<>(propertyIds.length);
1776         for (int i = 0; i < propertyIds.length; i++) {
1777             propertyIdsList.add(propertyIds[i]);
1778         }
1779         unsubscribePropertyEventsInternal(propertyIdsList, carPropertyEventCallback);
1780     }
1781 
1782     /**
1783      * @deprecated Use
1784      * {@link CarPropertyManager#unsubscribePropertyEvents(CarPropertyEventCallback)} instead.
1785      *
1786      * Stop getting property updates for the given {@link CarPropertyEventCallback}. If there are
1787      * multiple registrations for this {@link CarPropertyEventCallback}, all listening will be
1788      * stopped.
1789      *
1790      * @param carPropertyEventCallback A previously subscribed callback to unsubscribe.
1791      * @throws SecurityException if the caller does not have read permission to the properties
1792      *                           registered for this callback.
1793      */
1794     @Deprecated
unregisterCallback(@onNull CarPropertyEventCallback carPropertyEventCallback)1795     public void unregisterCallback(@NonNull CarPropertyEventCallback carPropertyEventCallback) {
1796         if (DBG) {
1797             Slog.d(TAG, "unregisterCallback, callback: " + carPropertyEventCallback);
1798         }
1799         requireNonNull(carPropertyEventCallback);
1800         int[] propertyIds;
1801         synchronized (mLock) {
1802             CarPropertyEventCallbackController cpeCallbackController =
1803                     mCpeCallbackToCpeCallbackController.get(carPropertyEventCallback);
1804             if (cpeCallbackController == null) {
1805                 Slog.w(TAG, "unregisterCallback: callback was not previously registered.");
1806                 return;
1807             }
1808             propertyIds = cpeCallbackController.getSubscribedProperties();
1809         }
1810         ArrayList<Integer> propertyIdsList = new ArrayList<>(propertyIds.length);
1811         for (int i = 0; i < propertyIds.length; i++) {
1812             propertyIdsList.add(propertyIds[i]);
1813         }
1814         unsubscribePropertyEventsInternal(propertyIdsList, carPropertyEventCallback);
1815     }
1816 
1817     /**
1818      * Stop getting update for {@code propertyId} to the given {@link CarPropertyEventCallback}. If
1819      * the same {@link CarPropertyEventCallback} is used for other properties, those subscriptions
1820      * will not be affected.
1821      *
1822      * @param propertyId The property ID to unsubscribe.
1823      * @param carPropertyEventCallback A previously subscribed callback to unsubscribe.
1824      * @throws SecurityException if the caller does not have read permission to the property.
1825      */
1826     @FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS)
unsubscribePropertyEvents(int propertyId, @NonNull CarPropertyEventCallback carPropertyEventCallback)1827     public void unsubscribePropertyEvents(int propertyId,
1828             @NonNull CarPropertyEventCallback carPropertyEventCallback) {
1829         requireNonNull(carPropertyEventCallback);
1830         unsubscribePropertyEventsInternal(List.of(propertyId), carPropertyEventCallback);
1831     }
1832 
1833     /**
1834      * @deprecated Use
1835      * {@link CarPropertyManager#unsubscribePropertyEvents(int, CarPropertyEventCallback)} instead.
1836      *
1837      * Stop getting update for {@code propertyId} to the given {@link CarPropertyEventCallback}. If
1838      * the same {@link CarPropertyEventCallback} is used for other properties, those subscriptions
1839      * will not be affected.
1840      *
1841      * @param carPropertyEventCallback A previously subscribed callback to unsubscribe.
1842      * @param propertyId The property ID to unsubscribe.
1843      * @throws SecurityException if the caller does not have read permission to the property.
1844      */
1845     @Deprecated
1846     @SuppressWarnings("FormatString")
unregisterCallback(@onNull CarPropertyEventCallback carPropertyEventCallback, int propertyId)1847     public void unregisterCallback(@NonNull CarPropertyEventCallback carPropertyEventCallback,
1848             int propertyId) {
1849         if (DBG) {
1850             Slog.d(TAG, String.format("unregisterCallback, callback: %s, property Id: %s",
1851                     carPropertyEventCallback, VehiclePropertyIds.toString(propertyId)));
1852         }
1853         requireNonNull(carPropertyEventCallback);
1854         unsubscribePropertyEventsInternal(List.of(propertyId), carPropertyEventCallback);
1855     }
1856 
unsubscribePropertyEventsInternal( List<Integer> propertyIds, CarPropertyEventCallback carPropertyEventCallback)1857     private void unsubscribePropertyEventsInternal(
1858             List<Integer> propertyIds, CarPropertyEventCallback carPropertyEventCallback) {
1859         synchronized (mLock) {
1860             CarPropertyEventCallbackController cpeCallbackController =
1861                     mCpeCallbackToCpeCallbackController.get(carPropertyEventCallback);
1862             if (cpeCallbackController == null) {
1863                 return;
1864             }
1865             // Filter out User HAL property IDs so that getPropertyConfigsFromService will not
1866             // throw IllegalArgumentException.
1867             List<Integer> filteredPropertyIds = filterOutUserHalProperty(propertyIds);
1868             CarPropertyConfigs configs = getPropertyConfigsFromService(filteredPropertyIds);
1869 
1870             if (configs == null) {
1871                 Slog.e(TAG, "failed to get property config list from car service, do nothing");
1872                 return;
1873             }
1874             for (int i = 0; i < filteredPropertyIds.size(); i++) {
1875                 int propertyId = filteredPropertyIds.get(i);
1876                 if (DBG) {
1877                     Slog.d(TAG, String.format(
1878                             "unsubscribePropertyEvents, callback: %s, property Id: %s",
1879                             carPropertyEventCallback, VehiclePropertyIds.toString(propertyId)));
1880                 }
1881 
1882                 if (configs.isNotSupported(propertyId)) {
1883                     Slog.e(TAG, "unsubscribePropertyEvents: not supported property: "
1884                             + VehiclePropertyIds.toString(propertyId));
1885                     continue;
1886                 }
1887                 if (configs.missingPermission(propertyId)) {
1888                     Slog.e(TAG, "unsubscribePropertyEvents: missing read/write permission for "
1889                             + "property: " + VehiclePropertyIds.toString(propertyId));
1890                     continue;
1891                 }
1892                 ArraySet<CarPropertyEventCallbackController> cpeCallbackControllerSet =
1893                         mPropIdToCpeCallbackControllerList.get(propertyId);
1894 
1895                 if (cpeCallbackControllerSet == null) {
1896                     Slog.e(TAG,
1897                             "unsubscribePropertyEvents: callback was not previously registered.");
1898                     continue;
1899                 } else if (!cpeCallbackControllerSet.contains(cpeCallbackController)) {
1900                     Slog.e(TAG,
1901                             "unsubscribePropertyEvents: callback was not previously registered for"
1902                                     + " propertyId=" + VehiclePropertyIds.toString(propertyId));
1903                     continue;
1904                 }
1905 
1906                 mSubscriptionManager.stageUnregister(carPropertyEventCallback,
1907                         new ArraySet<>(Set.of(propertyId)));
1908 
1909                 if (!applySubscriptionChangesLocked()) {
1910                     continue;
1911                 }
1912 
1913                 boolean allPropertiesRemoved = cpeCallbackController.remove(propertyId);
1914                 if (allPropertiesRemoved) {
1915                     mCpeCallbackToCpeCallbackController.remove(carPropertyEventCallback);
1916                 }
1917 
1918                 cpeCallbackControllerSet.remove(cpeCallbackController);
1919                 if (cpeCallbackControllerSet.isEmpty()) {
1920                     mPropIdToCpeCallbackControllerList.remove(propertyId);
1921                 }
1922             }
1923         }
1924     }
1925 
1926     /**
1927      * @return the list of properties supported by this car that the application may access
1928      */
1929     @NonNull
getPropertyList()1930     public List<CarPropertyConfig> getPropertyList() {
1931         if (DBG) {
1932             Slog.d(TAG, "getPropertyList");
1933         }
1934         List<CarPropertyConfig> configs;
1935         try {
1936             configs = mService.getPropertyList().getConfigs();
1937         } catch (RemoteException e) {
1938             Slog.e(TAG, "getPropertyList exception ", e);
1939             return handleRemoteExceptionFromCarService(e, new ArrayList<>());
1940         }
1941         if (DBG) {
1942             Slog.d(TAG, "getPropertyList returns " + configs.size() + " configs");
1943             for (int i = 0; i < configs.size(); i++) {
1944                 Slog.v(TAG, i + ": " + configs.get(i));
1945             }
1946         }
1947         return configs;
1948     }
1949 
1950     /**
1951      * Checks the given property IDs and returns a list of property configs supported by the car.
1952      *
1953      * If some of the properties in the given ID list are not supported, they will not be returned.
1954      *
1955      * @param propertyIds the list of property IDs
1956      * @return the list of property configs
1957      */
1958     @NonNull
getPropertyList(@onNull ArraySet<Integer> propertyIds)1959     public List<CarPropertyConfig> getPropertyList(@NonNull ArraySet<Integer> propertyIds) {
1960         if (DBG) {
1961             Slog.d(TAG, "getPropertyList(" + CarPropertyHelper.propertyIdsToString(propertyIds)
1962                     + ")");
1963         }
1964         CarPropertyConfigs configs = getPropertyConfigsFromService(propertyIds);
1965         if (configs == null) {
1966             return new ArrayList<>();
1967         }
1968         if (configs.getMissingPermissionPropIds().length != 0) {
1969             Slog.w(TAG, "Missing required permissions to access properties: "
1970                     + CarPropertyHelper.propertyIdsToString(configs.getMissingPermissionPropIds()));
1971         }
1972         if (configs.getUnsupportedPropIds().length != 0) {
1973             Slog.w(TAG, "The following properties are not supported: "
1974                     + CarPropertyHelper.propertyIdsToString(configs.getUnsupportedPropIds()));
1975         }
1976         List<CarPropertyConfig> configList = configs.getConfigs();
1977         if (DBG) {
1978             Slog.d(TAG, "getPropertyList(" + CarPropertyHelper.propertyIdsToString(propertyIds)
1979                     + ") returns " + configList.size() + " configs");
1980             for (int i = 0; i < configList.size(); i++) {
1981                 Slog.v(TAG, i + ": " + configList.get(i));
1982             }
1983         }
1984         return configList;
1985     }
1986 
1987     /**
1988      * Get {@link CarPropertyConfig} by property ID.
1989      *
1990      * @param propertyId the property ID
1991      * @return the {@link CarPropertyConfig} for the selected property, {@code null} if missing
1992      * the required permission to read/write the property or the property is not supported.
1993      */
1994     @Nullable
getCarPropertyConfig(int propertyId)1995     public CarPropertyConfig<?> getCarPropertyConfig(int propertyId) {
1996         if (DBG) {
1997             Slog.d(TAG, "getCarPropertyConfig(" + VehiclePropertyIds.toString(propertyId) + ")");
1998         }
1999         assertNotUserHalProperty(propertyId);
2000         if (!CarPropertyHelper.isSupported(propertyId)) {
2001             Slog.w(TAG, "Property: " + VehiclePropertyIds.toString(propertyId)
2002                     + " is not supported");
2003             return null;
2004         }
2005 
2006         CarPropertyConfigs configs = getPropertyConfigsFromService(
2007                 new ArraySet(Set.of(propertyId)));
2008         if (configs == null) {
2009             return null;
2010         }
2011 
2012         if (configs.missingPermission(propertyId)) {
2013             Slog.w(TAG, "Missing required permissions to access property: "
2014                     + VehiclePropertyIds.toString(propertyId));
2015             return null;
2016         }
2017         if (configs.isNotSupported(propertyId)) {
2018             Slog.w(TAG, "The property is not supported: "
2019                     + VehiclePropertyIds.toString(propertyId));
2020             return null;
2021         }
2022 
2023         CarPropertyConfig<?> config = configs.getConfigs().get(0);
2024         if (DBG) {
2025             Slog.d(TAG, "getCarPropertyConfig(" + VehiclePropertyIds.toString(propertyId)
2026                     + ") returns " + config);
2027         }
2028         return config;
2029     }
2030 
2031     /**
2032      * Returns areaId contains the selected area for the property.
2033      *
2034      * @param propertyId the property ID
2035      * @param area the area enum such as Enums in {@link android.car.VehicleAreaSeat}
2036      * @throws IllegalArgumentException if the property is not supported in the vehicle for
2037      * the selected area or the caller does not have read or write permission to the property.
2038      * @return the {@code AreaId} containing the selected area for the property
2039      */
getAreaId(int propertyId, int area)2040     public int getAreaId(int propertyId, int area) {
2041         assertNotUserHalProperty(propertyId);
2042         String propertyIdStr = VehiclePropertyIds.toString(propertyId);
2043         if (DBG) {
2044             Slog.d(TAG, "getAreaId(propertyId = " + propertyIdStr + ", area = " + area + ")");
2045         }
2046         CarPropertyConfigs configs = getPropertyConfigsFromService(
2047                 new ArraySet<>(Set.of(propertyId)));
2048         if (configs == null) {
2049             throw new IllegalArgumentException("Failed to getPropertyConfigList from car service");
2050         }
2051         if (configs.missingPermission(propertyId)) {
2052             throw new IllegalArgumentException("Missing required permissions to access property: "
2053                     + propertyIdStr);
2054         }
2055         if (configs.isNotSupported(propertyId)) {
2056             throw new IllegalArgumentException("The property is not supported: " + propertyIdStr);
2057         }
2058         CarPropertyConfig<?> propConfig = configs.getConfigs().get(0);
2059         // For the global property, areaId is 0
2060         if (propConfig.isGlobalProperty()) {
2061             if (DBG) {
2062                 Slog.d(TAG, "getAreaId returns the global area ID (0)");
2063             }
2064             return 0;
2065         }
2066         for (int areaId : propConfig.getAreaIds()) {
2067             if ((area & areaId) == area) {
2068                 if (DBG) {
2069                     Slog.d(TAG, "getAreaId returns " + areaId);
2070                 }
2071                 return areaId;
2072             }
2073         }
2074 
2075         throw new IllegalArgumentException("The propertyId: " + propertyIdStr
2076                 + " is not available at the area: 0x" + toHexString(area));
2077     }
2078 
2079     /**
2080      * Return read permission string for given property ID. The format of the return value of this
2081      * function has changed over time and thus should not be relied on.
2082      *
2083      * @param propId the property ID to query
2084      * @return the permission needed to read this property, {@code null} if the property ID is not
2085      * available
2086      *
2087      * @hide
2088      */
2089     @Nullable
getReadPermission(int propId)2090     public String getReadPermission(int propId) {
2091         assertNotUserHalProperty(propId);
2092         try {
2093             String permission = mService.getReadPermission(propId);
2094             if (DBG) {
2095                 Slog.d(TAG, "getReadPermission(propId =" + VehiclePropertyIds.toString(propId)
2096                         + ") returns " + permission);
2097             }
2098             return permission;
2099         } catch (RemoteException e) {
2100             return handleRemoteExceptionFromCarService(e, "");
2101         }
2102     }
2103 
2104     /**
2105      * Return write permission string for given property ID. The format of the return value of this
2106      * function has changed over time and thus should not be relied on.
2107      *
2108      * @param propId the property ID to query
2109      * @return the permission needed to write this property, {@code null} if the property ID is not
2110      * available.
2111      *
2112      * @hide
2113      */
2114     @Nullable
getWritePermission(int propId)2115     public String getWritePermission(int propId) {
2116         assertNotUserHalProperty(propId);
2117         try {
2118             String permission = mService.getWritePermission(propId);
2119             if (DBG) {
2120                 Slog.d(TAG, "getWritePermission(propId = " + VehiclePropertyIds.toString(propId)
2121                         + ") returns " + permission);
2122             }
2123             return permission;
2124         } catch (RemoteException e) {
2125             return handleRemoteExceptionFromCarService(e, "");
2126         }
2127     }
2128 
2129 
2130     /**
2131      * Check whether a given property is available or disabled based on the car's current state.
2132      *
2133      * @param propertyId the property ID
2134      * @param areaId the area ID
2135      * @return {@code true} if {@link CarPropertyValue#STATUS_AVAILABLE}, {@code false} otherwise
2136      * (eg {@link CarPropertyValue#STATUS_UNAVAILABLE})
2137      */
isPropertyAvailable(int propertyId, int areaId)2138     public boolean isPropertyAvailable(int propertyId, int areaId) {
2139         if (DBG) {
2140             Slog.d(TAG, "isPropertyAvailable(propertyId = "
2141                     + VehiclePropertyIds.toString(propertyId) + ", areaId = " + areaId + ")");
2142         }
2143         assertNotUserHalProperty(propertyId);
2144         if (!CarPropertyHelper.isSupported(propertyId)) {
2145             if (DBG) {
2146                 Slog.d(TAG, "Property: " + VehiclePropertyIds.toString(propertyId)
2147                         + " is not supported");
2148             }
2149             return false;
2150         }
2151 
2152         try {
2153             CarPropertyValue propValue = runSyncOperation(() -> {
2154                 if (DBG) {
2155                     Slog.d(TAG, "calling getProperty to check property's availability");
2156                 }
2157                 return mService.getProperty(propertyId, areaId);
2158             });
2159             return (propValue != null
2160                     && propValue.getStatus() == CarPropertyValue.STATUS_AVAILABLE);
2161         } catch (RemoteException e) {
2162             return handleRemoteExceptionFromCarService(e, false);
2163         } catch (ServiceSpecificException e) {
2164             Slog.e(TAG, "unable to get property, error: " + e);
2165             return false;
2166         }
2167     }
2168 
2169     /**
2170      * Returns value of a bool property
2171      *
2172      * <p>This method may take couple seconds to complete, so it needs to be called from a
2173      * non-main thread.
2174      *
2175      * <p>Note: Client MUST NOT use one of the following as propertyId, otherwise the behavior is
2176      * undefined:
2177      *
2178      * <ul>
2179      * <li>{@code INITIAL_USER_INFO}
2180      * <li>{@code SWITCH_USER}
2181      * <li>{@code CREATE_USER}
2182      * <li>{@code REMOVE_USER}
2183      * <li>{@code USER_IDENTIFICATION_ASSOCIATION}
2184      * </ul>
2185      *
2186      * <p>Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} equal
2187      * or later than {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} will receive the following
2188      * exceptions when request failed.
2189      * <ul>
2190      *     <li>{@link CarInternalErrorException} when there is an unexpected error detected in cars
2191      *     <li>{@link PropertyAccessDeniedSecurityException} when cars denied the access of the
2192      *     property
2193      *     <li>{@link PropertyNotAvailableAndRetryException} when the property is temporarily
2194      *     not available and likely that retrying will be successful
2195      *     <li>{@link PropertyNotAvailableException} when the property is not available and might be
2196      *     unavailable for a while.
2197      *     <li>{@link IllegalArgumentException} when the [propertyId, areaId] is not supported or
2198      *     when the property is of wrong type.
2199      * </ul>
2200      *
2201      * <p>Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} equal
2202      * or later than {@link Build.VERSION_CODES#R}, before
2203      * {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} will
2204      * receive the following exceptions or {@code false} when request failed.
2205      * <ul>
2206      *     <li>{@link CarInternalErrorException} when there is an unexpected error detected in cars
2207      *     <li>{@link PropertyAccessDeniedSecurityException} when cars denied the access of the
2208      *     property
2209      *     <li>{@link PropertyNotAvailableAndRetryException} when the property is temporarily
2210      *     not available and likely that retrying will be successful
2211      *     <li>{@link PropertyNotAvailableException} when the property is not available and might be
2212      *     unavailable for a while.
2213      *     <li>{@link IllegalArgumentException} when the property is of wrong type.
2214      *     <li>{@code false} when the [propertyId, areaId] is not supported
2215      * </ul>
2216      *
2217      * <p>Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion}
2218      * earlier than {@link Build.VERSION_CODES#R} will receive the following exceptions or
2219      * {@code false} when request failed.
2220      * <ul>
2221      *     <li>{@link IllegalStateException} when there is an error detected in cars, or when
2222      *         cars denied the access of the property, or when the property is not available and
2223      *         might be unavailable for a while, or when unexpected error happens.
2224      *     <li>{@link IllegalArgumentException} when the property is of wrong type.
2225      *     <li>{@code false} when the [propertyId, areaId] is not supported or when the property is
2226      *     temporarily not available.
2227      * </ul>
2228      *
2229      * <p>For pre-R client, the returned value might be {@code false} if the property is temporarily
2230      * not available. The client should try again in this case.
2231      *
2232      * <p>For pre-U client, when the [propertyId, areaId] is not supported, this will return
2233      * {@code false}.
2234      *
2235      * <p>For U and later client, when the [propertyId, areaId] is not supported, this is
2236      * guaranteed to throw {@code IllegalArgumentException}.
2237      *
2238      * @param propertyId the property ID to get
2239      * @param areaId the area ID of the property to get
2240      *
2241      * @throws CarInternalErrorException when there is an unexpected error detected in cars
2242      * @throws PropertyAccessDeniedSecurityException when cars denied the access of the
2243      * property
2244      * @throws PropertyNotAvailableAndRetryException when [propertyId, areaId] is temporarily
2245      * not available and likely that retrying will be successful
2246      * @throws PropertyNotAvailableException when [propertyId, areaId] is not available and might be
2247      * unavailable for a while.
2248      * @throws IllegalArgumentException when the [propertyId, areaId] is not supported for U and
2249      * later client, or when the property is of wrong type.
2250      *
2251      * @return the value of a bool property or {@code false}.
2252      */
getBooleanProperty(int propertyId, int areaId)2253     public boolean getBooleanProperty(int propertyId, int areaId) {
2254         CarPropertyValue<Boolean> carProp = getProperty(Boolean.class, propertyId, areaId);
2255         return handleNullAndPropertyStatus(carProp, areaId, false);
2256     }
2257 
2258     /**
2259      * Returns value of a float property
2260      *
2261      * <p>This method may take couple seconds to complete, so it needs to be called from a
2262      * non-main thread.
2263      *
2264      * <p>This method has the same exception behavior as {@link #getBooleanProperty(int, int)}.
2265      *
2266      * @param propertyId the property ID to get
2267      * @param areaId the area ID of the property to get
2268      *
2269      * @throws CarInternalErrorException when there is an unexpected error detected in cars
2270      * @throws PropertyAccessDeniedSecurityException when cars denied the access of the
2271      * property
2272      * @throws PropertyNotAvailableAndRetryException when [propertyId, areaId] is temporarily
2273      * not available and likely that retrying will be successful
2274      * @throws PropertyNotAvailableException when [propertyId, areaId] is not available and might be
2275      * unavailable for a while.
2276      * @throws IllegalArgumentException when the [propertyId, areaId] is not supported for U and
2277      * later client, or when the property is of wrong type.
2278      *
2279      * @return the value of a float property or {@code 0}.
2280      */
getFloatProperty(int propertyId, int areaId)2281     public float getFloatProperty(int propertyId, int areaId) {
2282         CarPropertyValue<Float> carProp = getProperty(Float.class, propertyId, areaId);
2283         return handleNullAndPropertyStatus(carProp, areaId, 0f);
2284     }
2285 
2286     /**
2287      * Returns value of an integer property
2288      *
2289      * <p>This method may take couple seconds to complete, so it needs to be called form a
2290      * non-main thread.
2291      *
2292      * <p>This method has the same exception behavior as {@link #getBooleanProperty(int, int)}.
2293      *
2294      * @param propertyId the property ID to get
2295      * @param areaId the area ID of the property to get
2296      *
2297      * @throws CarInternalErrorException when there is an unexpected error detected in cars
2298      * @throws PropertyAccessDeniedSecurityException when cars denied the access of the
2299      * property
2300      * @throws PropertyNotAvailableAndRetryException when [propertyId, areaId] is temporarily
2301      * not available and likely that retrying will be successful
2302      * @throws PropertyNotAvailableException when [propertyId, areaId] is not available and might be
2303      * unavailable for a while.
2304      * @throws IllegalArgumentException when the [propertyId, areaId] is not supported for U and
2305      * later client, or when the property is of wrong type.
2306      *
2307      * @return the value of aa integer property or {@code 0}.
2308      */
getIntProperty(int propertyId, int areaId)2309     public int getIntProperty(int propertyId, int areaId) {
2310         CarPropertyValue<Integer> carProp = getProperty(Integer.class, propertyId, areaId);
2311         return handleNullAndPropertyStatus(carProp, areaId, 0);
2312     }
2313 
2314     /**
2315      * Returns value of an integer array property
2316      *
2317      * <p>This method may take couple seconds to complete, so it needs to be called from a
2318      * non-main thread.
2319      *
2320      * <p>This method has the same exception behavior as {@link #getBooleanProperty(int, int)}.
2321      *
2322      * @param propertyId the property ID to get
2323      * @param areaId the area ID of the property to get
2324      *
2325      * @throws CarInternalErrorException when there is an unexpected error detected in cars
2326      * @throws PropertyAccessDeniedSecurityException when cars denied the access of the
2327      * property
2328      * @throws PropertyNotAvailableAndRetryException when [propertyId, areaId] is temporarily
2329      * not available and likely that retrying will be successful
2330      * @throws PropertyNotAvailableException when [propertyId, areaId] is not available and might be
2331      * unavailable for a while.
2332      * @throws IllegalArgumentException when the [propertyId, areaId] is not supported for U and
2333      * later client, or when the property is of wrong type.
2334      *
2335      * @return the value of an integer array property or an empty array.
2336      */
2337     @NonNull
getIntArrayProperty(int propertyId, int areaId)2338     public int[] getIntArrayProperty(int propertyId, int areaId) {
2339         CarPropertyValue<Integer[]> carProp = getProperty(Integer[].class, propertyId, areaId);
2340         Integer[] res = handleNullAndPropertyStatus(carProp, areaId, new Integer[0]);
2341         return toIntArray(res);
2342     }
2343 
toIntArray(Integer[] input)2344     private static int[] toIntArray(Integer[] input) {
2345         int len = input.length;
2346         int[] arr = new int[len];
2347         for (int i = 0; i < len; i++) {
2348             arr[i] = input[i];
2349         }
2350         return arr;
2351     }
2352 
handleNullAndPropertyStatus(CarPropertyValue<T> propertyValue, int areaId, T defaultValue)2353     private <T> T handleNullAndPropertyStatus(CarPropertyValue<T> propertyValue, int areaId,
2354             T defaultValue) {
2355         if (propertyValue == null) {
2356             return defaultValue;
2357         }
2358 
2359         // Keeps the same behavior as android R.
2360         if (mAppTargetSdk < Build.VERSION_CODES.S) {
2361             return propertyValue.getStatus() == CarPropertyValue.STATUS_AVAILABLE
2362                     ? propertyValue.getValue() : defaultValue;
2363         }
2364 
2365         // throws new exceptions in android S.
2366         switch (propertyValue.getStatus()) {
2367             case CarPropertyValue.STATUS_ERROR:
2368                 throw new CarInternalErrorException(propertyValue.getPropertyId(), areaId);
2369             case CarPropertyValue.STATUS_UNAVAILABLE:
2370                 throw new PropertyNotAvailableException(propertyValue.getPropertyId(),
2371                         areaId, /*vendorErrorCode=*/0);
2372             default:
2373                 return propertyValue.getValue();
2374         }
2375     }
2376 
2377     @FunctionalInterface
2378     private interface RemoteCallable<V> {
call()2379         V call() throws RemoteException;
2380     }
2381 
2382     @Nullable
runSyncOperation(RemoteCallable<V> c)2383     private static <V> V runSyncOperation(RemoteCallable<V> c)
2384             throws RemoteException, ServiceSpecificException {
2385         int retryCount = 0;
2386         while (retryCount < SYNC_OP_RETRY_MAX_COUNT) {
2387             retryCount++;
2388             try {
2389                 return c.call();
2390             } catch (ServiceSpecificException e) {
2391                 if (e.errorCode != SYNC_OP_LIMIT_TRY_AGAIN) {
2392                     throw e;
2393                 }
2394                 // If car service don't have enough binder thread to handle this request. Sleep for
2395                 // 10ms and try again.
2396                 Slog.d(TAG, "too many sync request, sleeping for " + SYNC_OP_RETRY_SLEEP_IN_MS
2397                         + " ms before retry");
2398                 SystemClock.sleep(SYNC_OP_RETRY_SLEEP_IN_MS);
2399             } catch (RemoteException e) {
2400                 throw e;
2401             }
2402         }
2403         throw new ServiceSpecificException(VehicleHalStatusCode.STATUS_INTERNAL_ERROR,
2404                 "failed to call car service sync operations after " + retryCount + " retries");
2405     }
2406 
2407     /**
2408      * Return {@link CarPropertyValue}
2409      *
2410      * <p>This method may take couple seconds to complete, so it needs to be called from a
2411      * non-main thread.
2412      *
2413      * <p>Note: Client MUST NOT use one of the following as propertyId, otherwise the behavior is
2414      * undefined (might throw exception or might return null):
2415      *
2416      * <ul>
2417      * <li>{@code INITIAL_USER_INFO}
2418      * <li>{@code SWITCH_USER}
2419      * <li>{@code CREATE_USER}
2420      * <li>{@code REMOVE_USER}
2421      * <li>{@code USER_IDENTIFICATION_ASSOCIATION}
2422      * </ul>
2423      *
2424      * <p>Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} equal
2425      * or later than {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} will receive the following
2426      * exceptions when request failed.
2427      * <ul>
2428      *     <li>{@link CarInternalErrorException} when there is an unexpected error detected in cars
2429      *     <li>{@link PropertyAccessDeniedSecurityException} when cars denied the access of the
2430      *     property
2431      *     <li>{@link PropertyNotAvailableAndRetryException} when the property is temporarily
2432      *     not available and likely that retrying will be successful
2433      *     <li>{@link PropertyNotAvailableException} when the property is not available and might be
2434      *     unavailable for a while.
2435      *     <li>{@link IllegalArgumentException} when the [propertyId, areaId] is not supported or
2436      *     when the specified class does not match the property type.
2437      * </ul>
2438      *
2439      * <p>Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} equal
2440      * or later than {@link Build.VERSION_CODES#R}, before
2441      * {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} will
2442      * receive the following exceptions or {@code null} when request failed.
2443      * <ul>
2444      *     <li>{@link CarInternalErrorException} when there is an unexpected error detected in cars
2445      *     <li>{@link PropertyAccessDeniedSecurityException} when cars denied the access of the
2446      *     property
2447      *     <li>{@link PropertyNotAvailableAndRetryException} when the property is temporarily
2448      *     not available and likely that retrying will be successful
2449      *     <li>{@link PropertyNotAvailableException} when the property is not available and might be
2450      *     unavailable for a while.
2451      *     <li>{@link IllegalArgumentException} when the specified class does not match the property
2452      *     type.
2453      *     <li>{@code null} when the [propertyId, areaId] is not supported
2454      * </ul>
2455      *
2456      * <p>Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion}
2457      * earlier than {@link Build.VERSION_CODES#R} will receive the following exceptions or
2458      * {@code null} when request failed.
2459      * <ul>
2460      *     <li>{@link IllegalStateException} when there is an error detected in cars, or when
2461      *         cars denied the access of the property, or when the property is not available and
2462      *         might be unavailable for a while, or when unexpected error happens.
2463      *     <li>{@link IllegalArgumentException} when the specified class does not match the
2464      *         property type.
2465      *     <li>{@code null} when the [propertyId, areaId] is not supported or when the property is
2466      *     temporarily not available.
2467      * </ul>
2468      *
2469      * <p>For pre-R client, the returned value might be null if the property is temporarily not
2470      * available. The client should try again in this case.
2471      *
2472      * <p>For pre-U client, when the [propertyId, areaId] is not supported, this will return
2473      * {@code null}.
2474      *
2475      * <p>For pre-U client, the returned {@link CarPropertyValue} might contain unavailable or
2476      * error status. Client must use {@link CarPropertyValue#getStatus} to check. If the returned
2477      * status is not {@link CarPropertyValue#STATUS_AVAILABLE}, then the value returned via
2478      * {@link CarPropertyValue#getValue} is undefined.
2479      *
2480      * <p>For U and later client, when the [propertyId, areaId] is not supported, this is
2481      * guaranteed to throw {@code IllegalArgumentException}. This method will never return
2482      * {@code null}.
2483      *
2484      * <p>For U and later client, if the property's status is
2485      * {@link CarPropertyValue#STATUS_UNAVAILABLE}, then {@link PropertyNotAvailableException} will
2486      * be thrown. If the property's status is {@link CarPropertyValue#STATUS_ERROR}, then
2487      * {@link CarInternalErrorException} will be thrown. If no exception is thrown, the returned
2488      * {@link CarPropertyValue#getStatus} is guaranteed to be
2489      * {@link CarPropertyValue#STATUS_AVAILABLE} so client do not need to check.
2490      *
2491      * @param clazz the class object for the CarPropertyValue
2492      * @param propertyId the property ID to get
2493      * @param areaId the area ID of the property to get
2494      *
2495      * @throws CarInternalErrorException when there is an unexpected error detected in cars
2496      * @throws PropertyAccessDeniedSecurityException when cars denied the access of the
2497      * property
2498      * @throws PropertyNotAvailableAndRetryException when [propertyId, areaId] is temporarily
2499      * not available and likely that retrying will be successful
2500      * @throws PropertyNotAvailableException when [propertyId, areaId] is not available and might be
2501      * unavailable for a while.
2502      * @throws IllegalArgumentException when the [propertyId, areaId] is not supported for U and
2503      * later client, or when the specified class does not match the property type.
2504      *
2505      * @return the value of a property or {@code null}.
2506      */
2507     @SuppressWarnings("unchecked")
2508     @Nullable
getProperty(@onNull Class<E> clazz, int propertyId, int areaId)2509     public <E> CarPropertyValue<E> getProperty(@NonNull Class<E> clazz, int propertyId,
2510             int areaId) {
2511         CarPropertyValue<E> carPropertyValue = getProperty(propertyId, areaId);
2512         if (carPropertyValue == null) {
2513             return null;
2514         }
2515         Class<?> actualClass = carPropertyValue.getValue().getClass();
2516         if (actualClass != clazz) {
2517             throw new IllegalArgumentException(
2518                     "Invalid property type. " + "Expected: " + clazz + ", but was: "
2519                             + actualClass);
2520         }
2521         return carPropertyValue;
2522     }
2523 
2524     /**
2525      * Query {@link CarPropertyValue} with property id and areaId.
2526      *
2527      * <p>This method may take couple seconds to complete, so it needs to be called from a
2528      * non-main thread.
2529      *
2530      * <p>Note: Client MUST NOT use one of the following as propertyId, otherwise the behavior is
2531      * undefined (might throw exception or might return null):
2532      *
2533      * <ul>
2534      * <li>{@code INITIAL_USER_INFO}
2535      * <li>{@code SWITCH_USER}
2536      * <li>{@code CREATE_USER}
2537      * <li>{@code REMOVE_USER}
2538      * <li>{@code USER_IDENTIFICATION_ASSOCIATION}
2539      * </ul>
2540      *
2541      * <p>Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} equal
2542      * or later than {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} will receive the following
2543      * exceptions when request failed.
2544      * <ul>
2545      *     <li>{@link CarInternalErrorException} when there is an unexpected error detected in cars
2546      *     <li>{@link PropertyAccessDeniedSecurityException} when cars denied the access of the
2547      *     property
2548      *     <li>{@link PropertyNotAvailableAndRetryException} when the property is temporarily
2549      *     not available and likely that retrying will be successful
2550      *     <li>{@link PropertyNotAvailableException} when the property is not available and might be
2551      *     unavailable for a while.
2552      *     <li>{@link IllegalArgumentException} when the [propertyId, areaId] is not supported.
2553      * </ul>
2554      *
2555      * <p>Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} equal
2556      * or later than {@link Build.VERSION_CODES#R}, before
2557      * {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} will
2558      * receive the following exceptions or {@code null} when request failed.
2559      * <ul>
2560      *     <li>{@link CarInternalErrorException} when there is an unexpected error detected in cars
2561      *     <li>{@link PropertyAccessDeniedSecurityException} when cars denied the access of the
2562      *     property
2563      *     <li>{@link PropertyNotAvailableAndRetryException} when the property is temporarily
2564      *     not available and likely that retrying will be successful
2565      *     <li>{@link PropertyNotAvailableException} when the property is not available and might be
2566      *     unavailable for a while.
2567      *     <li>{@code null} when the [propertyId, areaId] is not supported
2568      * </ul>
2569      *
2570      * <p>Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion}
2571      * earlier than {@link Build.VERSION_CODES#R} will receive the following exceptions or
2572      * {@code null} when request failed.
2573      * <ul>
2574      *     <li>{@link IllegalStateException} when there is an error detected in cars, or when
2575      *         cars denied the access of the property, or when the property is not available and
2576      *         might be unavailable for a while, or when unexpected error happens.
2577      *     <li>{@code null} when the [propertyId, areaId] is not supported or when the property is
2578      *     temporarily not available.
2579      * </ul>
2580      *
2581      * <p>For pre-R client, the returned value might be null if the property is temporarily not
2582      * available. The client should try again in this case.
2583      *
2584      * <p>For pre-U client, when the [propertyId, areaId] is not supported, this will return
2585      * {@code null}.
2586      *
2587      * <p>For pre-U client, the returned {@link CarPropertyValue} might contain unavailable or
2588      * error status. Client must use {@link CarPropertyValue#getStatus} to check. If the returned
2589      * status is not {@link CarPropertyValue#STATUS_AVAILABLE}, then the value returned via
2590      * {@link CarPropertyValue#getValue} is undefined.
2591      *
2592      * <p>For U and later client, when the [propertyId, areaId] is not supported, this is
2593      * guaranteed to throw {@code IllegalArgumentException}. This method will never return
2594      * {@code null}.
2595      *
2596      * <p>For U and later client, if the property's status is
2597      * {@link CarPropertyValue#STATUS_UNAVAILABLE}, then {@link PropertyNotAvailableException} will
2598      * be thrown. If the property's status is {@link CarPropertyValue#STATUS_ERROR}, then
2599      * {@link CarInternalErrorException} will be thrown. If no exception is thrown, the returned
2600      * {@link CarPropertyValue#getStatus} is guaranteed to be
2601      * {@link CarPropertyValue#STATUS_AVAILABLE} so client do not need to check.
2602      *
2603      * @param propertyId the property ID to get
2604      * @param areaId the area ID of the property to get
2605      * @param <E> the class type of the property
2606      *
2607      * @throws CarInternalErrorException when there is an unexpected error detected in cars
2608      * @throws PropertyAccessDeniedSecurityException when cars denied the access of the
2609      * property
2610      * @throws PropertyNotAvailableAndRetryException when [propertyId, areaId] is temporarily
2611      * not available and likely that retrying will be successful
2612      * @throws PropertyNotAvailableException when [propertyId, areaId] is not available and might be
2613      * unavailable for a while.
2614      * @throws IllegalArgumentException when the [propertyId, areaId] is not supported for U and
2615      * later client.
2616      *
2617      * @return the value of a property
2618      */
2619     @Nullable
getProperty(int propertyId, int areaId)2620     public <E> CarPropertyValue<E> getProperty(int propertyId, int areaId) {
2621         if (DBG) {
2622             Slog.d(TAG, "getProperty, propertyId: " + VehiclePropertyIds.toString(propertyId)
2623                     + ", areaId: 0x" + toHexString(areaId));
2624         }
2625 
2626         assertNotUserHalProperty(propertyId);
2627 
2628         try {
2629             assertPropertyIdIsSupported(propertyId);
2630         } catch (IllegalArgumentException e) {
2631             if (mAppTargetSdk >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
2632                 throw e;
2633             } else {
2634                 // Return null for pre-U unsupported [propertyId, areaId].
2635                 return null;
2636             }
2637         }
2638 
2639         Trace.beginSection("getProperty-" + propertyId + "/" + areaId);
2640         try {
2641             CarPropertyValue<E> carPropertyValue = (CarPropertyValue<E>) (runSyncOperation(() -> {
2642                 return mService.getProperty(propertyId, areaId);
2643             }));
2644             if (carPropertyValue == null) {
2645                 return null;
2646             }
2647             if (mAppTargetSdk >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
2648                 if (carPropertyValue.getStatus() == CarPropertyValue.STATUS_UNAVAILABLE) {
2649                     throw new ServiceSpecificException(VehicleHalStatusCode.STATUS_NOT_AVAILABLE,
2650                             "getProperty returned value with UNAVAILABLE status: "
2651                                     + carPropertyValue);
2652                 } else if (carPropertyValue.getStatus() != CarPropertyValue.STATUS_AVAILABLE) {
2653                     throw new ServiceSpecificException(VehicleHalStatusCode.STATUS_INTERNAL_ERROR,
2654                             "getProperty returned value with error or unknown status: "
2655                                     + carPropertyValue);
2656                 }
2657             }
2658             return carPropertyValue;
2659         } catch (IllegalArgumentException e) {
2660             if (mAppTargetSdk >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
2661                 throw e;
2662             } else {
2663                 // Return null for pre-U unsupported [propertyId, areaId].
2664                 return null;
2665             }
2666         } catch (RemoteException e) {
2667             return handleRemoteExceptionFromCarService(e, null);
2668         } catch (ServiceSpecificException e) {
2669             if (DBG) {
2670                 Slog.d(TAG, "getProperty received service specific exception, code: "
2671                         + e.errorCode);
2672             }
2673             if (mAppTargetSdk < Build.VERSION_CODES.R) {
2674                 if (e.errorCode == VehicleHalStatusCode.STATUS_TRY_AGAIN) {
2675                     return null;
2676                 } else {
2677                     throw new IllegalStateException("Failed to get propertyId: "
2678                             + VehiclePropertyIds.toString(propertyId) + " areaId: 0x"
2679                             + toHexString(areaId), e);
2680                 }
2681             }
2682             handleCarServiceSpecificException(e, propertyId, areaId);
2683 
2684             // Never reaches here.
2685             return null;
2686         } finally {
2687             Trace.endSection();
2688         }
2689     }
2690 
2691     /**
2692      * Set value of car property by areaId.
2693      *
2694      * <p>If multiple clients set a property for the same area ID simultaneously, which one takes
2695      * precedence is undefined. Typically, the last set operation (in the order that they are issued
2696      * to the car's ECU) overrides the previous set operations.
2697      *
2698      * <p>This method may take couple seconds to complete, so it needs to be called form a
2699      * non-main thread.
2700      *
2701      * <p>Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} equal
2702      * or later than {@link Build.VERSION_CODES#R} will receive the following exceptions when
2703      * request failed.
2704      * <ul>
2705      *     <li>{@link CarInternalErrorException}
2706      *     <li>{@link PropertyAccessDeniedSecurityException}
2707      *     <li>{@link PropertyNotAvailableAndRetryException}
2708      *     <li>{@link PropertyNotAvailableException}
2709      *     <li>{@link IllegalArgumentException}
2710      * </ul>
2711      * <p>Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion}
2712      * earlier than {@link Build.VERSION_CODES#R} will receive the following exceptions when request
2713      * failed.
2714      * <ul>
2715      *     <li>{@link RuntimeException} when the property is temporarily not available.
2716      *     <li>{@link IllegalStateException} when there is an error detected in cars, or when
2717      *         cars denied the access of the property, or when the property is not available and
2718      *         might be unavailable for a while, or when unexpected error happens.
2719      *     <li>{@link IllegalArgumentException} when the [propertyId, areaId] is not supported.
2720      * </ul>
2721      *
2722      * <p>Returning from this method does not necessary mean the set operation succeeded. In order
2723      * to determine whether the operation succeeded/failed, Client should use
2724      * {@link CarPropertyManager#registerCallback} to register for property updates for this
2725      * [propertyId, areaId] before the set operation. The operation succeeded when
2726      * {@link CarPropertyEventCallback#onChangeEvent} is called with the value to be set. The
2727      * operation failed when {@link CarPropertyEventCallback#onErrorEvent} is called for this
2728      * [propertyId, areaId]. Note that the registration must happen before the set operation
2729      * otherwise the callback might be invoked after the set operation, but before the registration.
2730      *
2731      *
2732      * <p>Note that if the value to set is the same as the current value, the set request will
2733      * still be sent to vehicle hardware, however, a new property change event will not be
2734      * generated for the set operation. If client want to prevent the set request to be sent,
2735      * client must use {@link getProperty} to check the current value before calling this.
2736      *
2737      * @param clazz the class object for the CarPropertyValue
2738      * @param propertyId the property ID to modify
2739      * @param areaId the area ID to apply the modification
2740      * @param val the value to set
2741      * @param <E> the class type of the given property, for example property that was
2742      * defined as {@code VEHICLE_VALUE_TYPE_INT32} in vehicle HAL could be accessed using
2743      * {@code Integer.class}.
2744      *
2745      * @throws CarInternalErrorException when there is an unexpected error detected in cars.
2746      * @throws PropertyAccessDeniedSecurityException when cars denied the access of the property.
2747      * @throws PropertyNotAvailableException when [propertyId, areaId] is not available and might be
2748      * unavailable for a while.
2749      * @throws PropertyNotAvailableAndRetryException when [propertyId, areaId] is temporarily not
2750      * available and likely that retrying will be successful.
2751      * @throws IllegalArgumentException when the [propertyId, areaId] is not supported.
2752      */
setProperty(@onNull Class<E> clazz, int propertyId, int areaId, @NonNull E val)2753     public <E> void setProperty(@NonNull Class<E> clazz, int propertyId, int areaId,
2754             @NonNull E val) {
2755         if (DBG) {
2756             Slog.d(TAG, "setProperty, propertyId: " + VehiclePropertyIds.toString(propertyId)
2757                     + ", areaId: 0x" + toHexString(areaId) + ", class: " + clazz + ", val: " + val);
2758         }
2759 
2760         assertNotUserHalProperty(propertyId);
2761 
2762         assertPropertyIdIsSupported(propertyId);
2763 
2764         Trace.beginSection("setProperty-" + propertyId + "/" + areaId);
2765         try {
2766             runSyncOperation(() -> {
2767                 mService.setProperty(new CarPropertyValue<>(propertyId, areaId, val),
2768                         mCarPropertyEventToService);
2769                 return null;
2770             });
2771         } catch (RemoteException e) {
2772             handleRemoteExceptionFromCarService(e);
2773         } catch (ServiceSpecificException e) {
2774             if (DBG) {
2775                 Slog.d(TAG, "setProperty received service specific exception", e);
2776             }
2777             if (mAppTargetSdk < Build.VERSION_CODES.R) {
2778                 if (e.errorCode == VehicleHalStatusCode.STATUS_TRY_AGAIN) {
2779                     throw new RuntimeException("Failed to set propertyId: "
2780                             + VehiclePropertyIds.toString(propertyId) + " areaId: 0x"
2781                             + toHexString(areaId), e);
2782                 } else {
2783                     throw new IllegalStateException("Failed to set propertyId: "
2784                             + VehiclePropertyIds.toString(propertyId) + " areaId: 0x"
2785                             + toHexString(areaId), e);
2786                 }
2787             }
2788             handleCarServiceSpecificException(e, propertyId, areaId);
2789         } finally {
2790             Trace.endSection();
2791         }
2792     }
2793 
2794     /**
2795      * Modifies a property.  If the property modification doesn't occur, an error event shall be
2796      * generated and propagated back to the application.
2797      *
2798      * <p>This method may take couple seconds to complete, so it needs to be called from a
2799      * non-main thread.
2800      *
2801      * @param propertyId the property ID to modify
2802      * @param areaId the area ID to apply the modification
2803      * @param val the value to set
2804      */
setBooleanProperty(int propertyId, int areaId, boolean val)2805     public void setBooleanProperty(int propertyId, int areaId, boolean val) {
2806         setProperty(Boolean.class, propertyId, areaId, val);
2807     }
2808 
2809     /**
2810      * Set float value of property
2811      *
2812      * <p>This method may take couple seconds to complete, so it needs to be called from a
2813      * non-main thread.
2814      *
2815      * @param propertyId the property ID to modify
2816      * @param areaId the area ID to apply the modification
2817      * @param val the value to set
2818      */
setFloatProperty(int propertyId, int areaId, float val)2819     public void setFloatProperty(int propertyId, int areaId, float val) {
2820         setProperty(Float.class, propertyId, areaId, val);
2821     }
2822 
2823     /**
2824      * Set int value of property
2825      *
2826      * <p>This method may take couple seconds to complete, so it needs to be called from a
2827      * non-main thread.
2828      *
2829      * @param propertyId the property ID to modify
2830      * @param areaId the area ID to apply the modification
2831      * @param val the value to set
2832      */
setIntProperty(int propertyId, int areaId, int val)2833     public void setIntProperty(int propertyId, int areaId, int val) {
2834         setProperty(Integer.class, propertyId, areaId, val);
2835     }
2836 
2837     /**
2838      *  Handles {@code ServiceSpecificException} in {@code CarService} for R and later version.
2839      */
handleCarServiceSpecificException( ServiceSpecificException e, int propertyId, int areaId)2840     private void handleCarServiceSpecificException(
2841             ServiceSpecificException e, int propertyId, int areaId) {
2842         // We are not passing the error message down, so log it here.
2843         Slog.w(TAG, "received ServiceSpecificException: " + e);
2844         int errorCode = CarPropertyErrorCodes.getVhalSystemErrorCode(e.errorCode);
2845         int vendorErrorCode = CarPropertyErrorCodes.getVhalVendorErrorCode(e.errorCode);
2846 
2847         switch (errorCode) {
2848             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE:
2849                 throw new PropertyNotAvailableException(propertyId, areaId, vendorErrorCode);
2850             case VehicleHalStatusCode.STATUS_TRY_AGAIN:
2851                 // Vendor error code is ignored for STATUS_TRY_AGAIN error
2852                 throw new PropertyNotAvailableAndRetryException(propertyId, areaId);
2853             case VehicleHalStatusCode.STATUS_ACCESS_DENIED:
2854                 // Vendor error code is ignored for STATUS_ACCESS_DENIED error
2855                 throw new PropertyAccessDeniedSecurityException(propertyId, areaId);
2856             case VehicleHalStatusCode.STATUS_INTERNAL_ERROR:
2857                 throw new CarInternalErrorException(propertyId, areaId, vendorErrorCode);
2858             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_DISABLED:
2859             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_SPEED_LOW:
2860             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_SPEED_HIGH:
2861             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_POOR_VISIBILITY:
2862             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_SAFETY:
2863                 throw new PropertyNotAvailableException(propertyId, areaId,
2864                         getPropertyNotAvailableErrorCodeFromStatusCode(errorCode), vendorErrorCode);
2865             default:
2866                 Slog.e(TAG, "Invalid errorCode: " + errorCode + " in CarService");
2867                 throw new CarInternalErrorException(propertyId, areaId);
2868         }
2869     }
2870 
2871     /**
2872      * Convert {@link VehicleHalStatusCode} into public {@link PropertyNotAvailableErrorCode}
2873      * equivalents.
2874      *
2875      * @throws IllegalArgumentException if an invalid status code is passed in.
2876      * @hide
2877      */
getPropertyNotAvailableErrorCodeFromStatusCode(int statusCode)2878     private static int getPropertyNotAvailableErrorCodeFromStatusCode(int statusCode) {
2879         switch (statusCode) {
2880             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE:
2881                 return PropertyNotAvailableErrorCode.NOT_AVAILABLE;
2882             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_DISABLED:
2883                 return PropertyNotAvailableErrorCode.NOT_AVAILABLE_DISABLED;
2884             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_SPEED_LOW:
2885                 return PropertyNotAvailableErrorCode.NOT_AVAILABLE_SPEED_LOW;
2886             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_SPEED_HIGH:
2887                 return PropertyNotAvailableErrorCode.NOT_AVAILABLE_SPEED_HIGH;
2888             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_POOR_VISIBILITY:
2889                 return PropertyNotAvailableErrorCode.NOT_AVAILABLE_POOR_VISIBILITY;
2890             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_SAFETY:
2891                 return PropertyNotAvailableErrorCode.NOT_AVAILABLE_SAFETY;
2892             default:
2893                 throw new IllegalArgumentException("Invalid status code: " + statusCode);
2894         }
2895     }
2896 
2897     /**
2898      * Convert {@link VehicleHalStatusCode} system error code into its public
2899      * {@link DetailedErrorCode} equivalent.
2900      *
2901      * @return the detailed error code if available, otherwise set to 0.
2902      * @throws IllegalArgumentException if an invalid error code is passed in.
2903      */
getDetailedErrorCodeFromSystemErrorCode(int systemErrorCode)2904     private static int getDetailedErrorCodeFromSystemErrorCode(int systemErrorCode) {
2905         if (Flags.carPropertyDetailedErrorCodes()) {
2906             switch (systemErrorCode) {
2907                 case VehicleHalStatusCode.STATUS_OK: // Fallthrough
2908                 case VehicleHalStatusCode.STATUS_TRY_AGAIN: // Fallthrough
2909                 case VehicleHalStatusCode.STATUS_INVALID_ARG: // Fallthrough
2910                 case VehicleHalStatusCode.STATUS_NOT_AVAILABLE: // Fallthrough
2911                 case VehicleHalStatusCode.STATUS_ACCESS_DENIED: // Fallthrough
2912                 case VehicleHalStatusCode.STATUS_INTERNAL_ERROR: // Fallthrough
2913                     return DetailedErrorCode.NO_DETAILED_ERROR_CODE;
2914                 case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_DISABLED:
2915                     return DetailedErrorCode.NOT_AVAILABLE_DISABLED;
2916                 case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_SPEED_LOW:
2917                     return DetailedErrorCode.NOT_AVAILABLE_SPEED_LOW;
2918                 case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_SPEED_HIGH:
2919                     return DetailedErrorCode.NOT_AVAILABLE_SPEED_HIGH;
2920                 case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_POOR_VISIBILITY:
2921                     return DetailedErrorCode.NOT_AVAILABLE_POOR_VISIBILITY;
2922                 case VehicleHalStatusCode.STATUS_NOT_AVAILABLE_SAFETY:
2923                     return DetailedErrorCode.NOT_AVAILABLE_SAFETY;
2924                 default:
2925                     throw new IllegalArgumentException("Invalid error code: " + systemErrorCode);
2926             }
2927         }
2928 
2929         return 0;
2930     }
2931 
2932     /**
2933      * Convert {@link CarPropMgrErrorCode} error code in {@link CarPropertyErrorCodes} into the
2934      * {@link CarPropertyAsyncErrorCode} equivalent.
2935      *
2936      * @return the async error code
2937      * @throws IllegalArgumentException if an invalid error code is passed in.
2938      */
getCarPropertyAsyncErrorCodeFromCarPropertyManagerErrorCode(int errorCode)2939     private static int getCarPropertyAsyncErrorCodeFromCarPropertyManagerErrorCode(int errorCode) {
2940         switch (errorCode) {
2941             case STATUS_OK: // Fallthrough
2942             case STATUS_ERROR_INTERNAL_ERROR: // Fallthrough
2943             case STATUS_ERROR_NOT_AVAILABLE: // Fallthrough
2944             case STATUS_ERROR_TIMEOUT: // Fallthrough
2945                 return errorCode;
2946             case STATUS_TRY_AGAIN: // Fallthrough
2947             default:
2948                 throw new IllegalArgumentException("Invalid error code: " + errorCode);
2949         }
2950     }
2951 
clearRequestIdToAsyncRequestInfo( List<? extends AsyncPropertyRequest> asyncPropertyRequests)2952     private void clearRequestIdToAsyncRequestInfo(
2953             List<? extends AsyncPropertyRequest> asyncPropertyRequests) {
2954         if (DBG) {
2955             Slog.d(TAG, "clear pending async requests: " + asyncPropertyRequests);
2956         }
2957         synchronized (mLock) {
2958             for (int i = 0; i < asyncPropertyRequests.size(); i++) {
2959                 mRequestIdToAsyncRequestInfo.remove(asyncPropertyRequests.get(i).getRequestId());
2960             }
2961         }
2962     }
2963 
2964     /**
2965      * Set an {@code onCancelListener} for the cancellation signal.
2966      *
2967      * <p>When the signal is cancelled, car service will remove the stored state for the specified
2968      * pending request IDs and ignore all the future results.
2969      */
setOnCancelListener(CancellationSignal cancellationSignal, List<Integer> requestIds)2970     private void setOnCancelListener(CancellationSignal cancellationSignal,
2971             List<Integer> requestIds) {
2972         cancellationSignal.setOnCancelListener(() -> {
2973             int[] requestIdsArray = new int[requestIds.size()];
2974             synchronized (mLock) {
2975                 for (int i = 0; i < requestIds.size(); i++) {
2976                     int requestId = requestIds.get(i);
2977                     requestIdsArray[i] = requestId;
2978                     mRequestIdToAsyncRequestInfo.remove(requestId);
2979                 }
2980             }
2981             try {
2982                 mService.cancelRequests(requestIdsArray);
2983             } catch (RemoteException e) {
2984                 handleRemoteExceptionFromCarService(e);
2985             }
2986         });
2987     }
2988 
2989     /** @hide */
2990     @Override
onCarDisconnected()2991     public void onCarDisconnected() {
2992         synchronized (mLock) {
2993             mCpeCallbackToCpeCallbackController.clear();
2994             mPropIdToCpeCallbackControllerList.clear();
2995             mSubscriptionManager.clear();
2996         }
2997     }
2998 
2999     /**
3000      * Generate unique get request ID and return to the client.
3001      *
3002      * @param propertyId the property ID
3003      * @param areaId the area ID
3004      * @return the GetPropertyRequest object
3005      */
3006     @NonNull
3007     @SuppressWarnings("FormatString")
generateGetPropertyRequest(int propertyId, int areaId)3008     public GetPropertyRequest generateGetPropertyRequest(int propertyId, int areaId) {
3009         int requestIdCounter = mRequestIdCounter.getAndIncrement();
3010         if (DBG) {
3011             Slog.d(TAG, String.format("generateGetPropertyRequest, requestId: %d, propertyId: %s, "
3012                     + "areaId: %d", requestIdCounter, VehiclePropertyIds.toString(propertyId),
3013                     areaId));
3014         }
3015         return new GetPropertyRequest(requestIdCounter, propertyId, areaId);
3016     }
3017 
3018     /**
3019      * Generate unique set request ID and return to the client.
3020      *
3021      * @param <T> the type for the property value, must be one of Object, Boolean, Float, Integer,
3022      *      Long, Float[], Integer[], Long[], String, byte[], Object[]
3023      * @param propertyId the property ID
3024      * @param areaId the area ID
3025      * @param value the value to set
3026      * @return the {@link SetPropertyRequest} object
3027      */
3028     @NonNull
3029     @SuppressWarnings("FormatString")
generateSetPropertyRequest(int propertyId, int areaId, @NonNull T value)3030     public <T> SetPropertyRequest<T> generateSetPropertyRequest(int propertyId, int areaId,
3031             @NonNull T value) {
3032         requireNonNull(value);
3033         int requestIdCounter = mRequestIdCounter.getAndIncrement();
3034         if (DBG) {
3035             Slog.d(TAG, String.format("generateSetPropertyRequest, requestId: %d, propertyId: %s, "
3036                     + "areaId: %d, value: %s", requestIdCounter,
3037                     VehiclePropertyIds.toString(propertyId), areaId, value));
3038         }
3039         return new SetPropertyRequest(requestIdCounter, propertyId, areaId, value);
3040     }
3041 
checkAsyncArguments(Object requests, Object callback, long timeoutInMs)3042     private void checkAsyncArguments(Object requests, Object callback, long timeoutInMs) {
3043         requireNonNull(requests);
3044         requireNonNull(callback);
3045         if (timeoutInMs <= 0) {
3046             throw new IllegalArgumentException("timeoutInMs must be a positive number");
3047         }
3048     }
3049 
3050     /**
3051      * Query a list of {@link CarPropertyValue} with property ID and area ID asynchronously.
3052      *
3053      * <p>This function would return immediately before the results are ready. For each request,
3054      * the corresponding result would either be delivered through one
3055      * {@code resultCallback.onSuccess} call if the request succeeded or through one
3056      * {@code errorCallback.onFailure} call if failed. It is guaranteed that the total times the
3057      * callback functions are called is equal to the number of requests if this function does not
3058      * throw an exception. It is guaranteed that none of the callback functions are called if an
3059      * exception is thrown. If the {@code callbackExecutor} is {@code null}, the callback will be
3060      * executed on the default event handler thread. If the callback is doing heavy work, it is
3061      * recommended that the {@code callbackExecutor} is provided.
3062      *
3063      * <p>If the operation is cancelled, it is guaranteed that no more callbacks will be called.
3064      *
3065      * <p>For one request, if the property's status is not available,
3066      * {@code errorCallback.onFailure} will be called once with {@link #STATUS_ERROR_NOT_AVAILABLE}.
3067      *
3068      * <p>For one request, if the property's status is error,
3069      * {@code errorCallback.onFailure} will be called once with {@link
3070      * #STATUS_ERROR_INTERNAL_ERROR}.
3071      *
3072      * @param getPropertyRequests a list of properties to get
3073      * @param timeoutInMs the timeout for the operation, in milliseconds
3074      * @param cancellationSignal a signal that could be used to cancel the on-going operation
3075      * @param callbackExecutor the executor to execute the callback with
3076      * @param getPropertyCallback the callback function to deliver the result
3077      * @throws SecurityException if missing permission to read one of the specific properties.
3078      * @throws IllegalArgumentException if one of the properties to read is not supported.
3079      */
getPropertiesAsync( @onNull List<GetPropertyRequest> getPropertyRequests, long timeoutInMs, @Nullable CancellationSignal cancellationSignal, @Nullable @CallbackExecutor Executor callbackExecutor, @NonNull GetPropertyCallback getPropertyCallback)3080     public void getPropertiesAsync(
3081             @NonNull List<GetPropertyRequest> getPropertyRequests,
3082             long timeoutInMs,
3083             @Nullable CancellationSignal cancellationSignal,
3084             @Nullable @CallbackExecutor Executor callbackExecutor,
3085             @NonNull GetPropertyCallback getPropertyCallback) {
3086         if (DBG) {
3087             Slog.d(TAG, "getPropertiesAsync, requests: " + getPropertyRequests + ", timeoutInMs: "
3088                     + timeoutInMs + ", callback: " + getPropertyCallback);
3089         }
3090 
3091         checkAsyncArguments(getPropertyRequests, getPropertyCallback, timeoutInMs);
3092         if (callbackExecutor == null) {
3093             callbackExecutor = new HandlerExecutor(getEventHandler());
3094         }
3095 
3096         List<AsyncPropertyServiceRequest> getPropertyServiceRequests = new ArrayList<>(
3097                 getPropertyRequests.size());
3098         for (int i = 0; i < getPropertyRequests.size(); i++) {
3099             GetPropertyRequest getPropertyRequest = getPropertyRequests.get(i);
3100             int propertyId = getPropertyRequest.getPropertyId();
3101             assertPropertyIdIsSupported(propertyId);
3102 
3103             getPropertyServiceRequests.add(AsyncPropertyServiceRequest.newGetAsyncRequest(
3104                     getPropertyRequest));
3105         }
3106 
3107         List<Integer> requestIds = storePendingRequestInfo(getPropertyRequests, callbackExecutor,
3108                 getPropertyCallback);
3109 
3110         try {
3111             if (DBG) {
3112                 Slog.d(TAG, "calling CarPropertyService.getPropertiesAsync");
3113             }
3114             mService.getPropertiesAsync(new AsyncPropertyServiceRequestList(
3115                     getPropertyServiceRequests), mAsyncPropertyResultCallback, timeoutInMs);
3116             if (DBG) {
3117                 Slog.d(TAG, "CarPropertyService.getPropertiesAsync succeed");
3118             }
3119         } catch (RemoteException e) {
3120             clearRequestIdToAsyncRequestInfo(getPropertyRequests);
3121             handleRemoteExceptionFromCarService(e);
3122         } catch (IllegalArgumentException | SecurityException e) {
3123             clearRequestIdToAsyncRequestInfo(getPropertyRequests);
3124             throw e;
3125         }
3126         if (cancellationSignal != null) {
3127             setOnCancelListener(cancellationSignal, requestIds);
3128         }
3129     }
3130 
3131     /**
3132      * Query a list of {@link CarPropertyValue} with property Id and area Id asynchronously.
3133      *
3134      * Same as {@link CarPropertyManager#getPropertiesAsync(List, long, CancellationSignal,
3135      * Executor, GetPropertyCallback)} with default timeout 10s.
3136      */
getPropertiesAsync( @onNull List<GetPropertyRequest> getPropertyRequests, @Nullable CancellationSignal cancellationSignal, @Nullable @CallbackExecutor Executor callbackExecutor, @NonNull GetPropertyCallback getPropertyCallback)3137     public void getPropertiesAsync(
3138             @NonNull List<GetPropertyRequest> getPropertyRequests,
3139             @Nullable CancellationSignal cancellationSignal,
3140             @Nullable @CallbackExecutor Executor callbackExecutor,
3141             @NonNull GetPropertyCallback getPropertyCallback) {
3142         getPropertiesAsync(getPropertyRequests, ASYNC_GET_DEFAULT_TIMEOUT_MS, cancellationSignal,
3143                 callbackExecutor, getPropertyCallback);
3144     }
3145 
3146     /**
3147      * Sets a list of car property values asynchronously.
3148      *
3149      * <p>This function would return immediately before the results are ready. For each request,
3150      * the corresponding result would either be delivered through one
3151      * {@code resultCallback.onSuccess} call if the request succeeded or through one
3152      * {@code errorCallback.onFailure} call if failed. It is guaranteed that the total times the
3153      * callback functions are called is equal to the number of requests if this function does not
3154      * throw an exception. It is guaranteed that none of the callback functions are called if an
3155      * exception is thrown. If the {@code callbackExecutor} is {@code null}, the callback will be
3156      * executed on the default event handler thread. If the callback is doing heavy work, it is
3157      * recommended that the {@code callbackExecutor} is provided.
3158      *
3159      * <p>If the operation is cancelled, it is guaranteed that no more callbacks will be called.
3160      *
3161      * <p>If multiple clients set a property for the same area ID simultaneously, which one takes
3162      * precedence is undefined. Typically, the last set operation (in the order that they are issued
3163      * to the car's ECU) overrides the previous set operations.
3164      *
3165      * <p>When the success callback will be called depends on whether {@code waitForPropertyUpdate}
3166      * for each request is set. If this is set to {@code true} (by default), the success callback
3167      * will be called when the set operation is successfully delivered to vehicle bus AND either
3168      * target value is the same as the current or when the property is updated to the target value.
3169      *
3170      * <p>When {@code waitForPropertyUpdate} is set to {@code false}, the success callback will be
3171      * called as long as the set operation is successfully delivered to vehicle bus.
3172      *
3173      * <p>Under most cases, client should wait for the property update to verify that the set
3174      * operation actually succeeded.
3175      *
3176      * <p>For cases when the property is write-only (no way to get property update event) or when
3177      * the property represents some action, instead of an actual state, e.g. key stroke where the
3178      * property's current value is not meaningful, caller must set {@code waitForPropertyUpdate}
3179      * to {@code false}.
3180      *
3181      * <p>For {@code HVAC_TEMPERATURE_VALUE_SUGGESTION}, this must be set to {@code false}
3182      * because the updated property value will not be the same as the value to be set.
3183      *
3184      * @param setPropertyRequests a list of properties to set
3185      * @param timeoutInMs the timeout for the operation, in milliseconds
3186      * @param cancellationSignal a signal that could be used to cancel the on-going operation
3187      * @param callbackExecutor the executor to execute the callback with
3188      * @param setPropertyCallback the callback function to deliver the result
3189      * @throws SecurityException if missing permission to write one of the specific properties.
3190      * @throws IllegalArgumentException if one of the properties to set is not supported.
3191      * @throws IllegalArgumentException if one of the properties is not readable and does not set
3192      *   {@code waitForPropertyUpdate} to {@code false}.
3193      * @throws IllegalArgumentException if one of the properties is
3194      *   {@code HVAC_TEMPERATURE_VALUE_SUGGESTION} and does not set {@code waitForPropertyUpdate}
3195      *   to {@code false}.
3196      */
setPropertiesAsync( @onNull List<SetPropertyRequest<?>> setPropertyRequests, long timeoutInMs, @Nullable CancellationSignal cancellationSignal, @Nullable @CallbackExecutor Executor callbackExecutor, @NonNull SetPropertyCallback setPropertyCallback)3197     public void setPropertiesAsync(
3198             @NonNull List<SetPropertyRequest<?>> setPropertyRequests,
3199             long timeoutInMs,
3200             @Nullable CancellationSignal cancellationSignal,
3201             @Nullable @CallbackExecutor Executor callbackExecutor,
3202             @NonNull SetPropertyCallback setPropertyCallback) {
3203         if (DBG) {
3204             Slog.d(TAG, "setPropertiesAsync, requests: " + setPropertyRequests + ", timeoutInMs: "
3205                     + timeoutInMs + ", callback: " + setPropertyCallback);
3206         }
3207 
3208         checkAsyncArguments(setPropertyRequests, setPropertyCallback, timeoutInMs);
3209         if (callbackExecutor == null) {
3210             callbackExecutor = new HandlerExecutor(getEventHandler());
3211         }
3212 
3213         List<AsyncPropertyServiceRequest> setPropertyServiceRequests = new ArrayList<>(
3214                 setPropertyRequests.size());
3215         for (int i = 0; i < setPropertyRequests.size(); i++) {
3216             SetPropertyRequest setPropertyRequest = setPropertyRequests.get(i);
3217             int propertyId = setPropertyRequest.getPropertyId();
3218             requireNonNull(setPropertyRequest.getValue());
3219             assertPropertyIdIsSupported(propertyId);
3220 
3221             setPropertyServiceRequests.add(AsyncPropertyServiceRequest.newSetAsyncRequest(
3222                     setPropertyRequest));
3223         }
3224 
3225         List<Integer> requestIds = storePendingRequestInfo(setPropertyRequests, callbackExecutor,
3226                 setPropertyCallback);
3227 
3228         try {
3229             if (DBG) {
3230                 Slog.d(TAG, "calling CarPropertyService.setPropertiesAsync");
3231             }
3232             mService.setPropertiesAsync(new AsyncPropertyServiceRequestList(
3233                     setPropertyServiceRequests), mAsyncPropertyResultCallback, timeoutInMs);
3234             if (DBG) {
3235                 Slog.d(TAG, "CarPropertyService.setPropertiesAsync succeed");
3236             }
3237         } catch (RemoteException e) {
3238             clearRequestIdToAsyncRequestInfo(setPropertyRequests);
3239             handleRemoteExceptionFromCarService(e);
3240         } catch (IllegalArgumentException | SecurityException e) {
3241             clearRequestIdToAsyncRequestInfo(setPropertyRequests);
3242             throw e;
3243         }
3244         if (cancellationSignal != null) {
3245             setOnCancelListener(cancellationSignal, requestIds);
3246         }
3247     }
3248 
3249     /**
3250      * Sets a list of car property values asynchronously.
3251      *
3252      * Same as {@link CarPropertyManager#setPropertiesAsync(List, long, CancellationSignal,
3253      * Executor, SetPropertyCallback)} with default timeout 10s.
3254      */
setPropertiesAsync( @onNull List<SetPropertyRequest<?>> setPropertyRequests, @Nullable CancellationSignal cancellationSignal, @Nullable @CallbackExecutor Executor callbackExecutor, @NonNull SetPropertyCallback setPropertyCallback)3255     public void setPropertiesAsync(
3256             @NonNull List<SetPropertyRequest<?>> setPropertyRequests,
3257             @Nullable CancellationSignal cancellationSignal,
3258             @Nullable @CallbackExecutor Executor callbackExecutor,
3259             @NonNull SetPropertyCallback setPropertyCallback) {
3260         setPropertiesAsync(setPropertyRequests, ASYNC_GET_DEFAULT_TIMEOUT_MS, cancellationSignal,
3261                 callbackExecutor, setPropertyCallback);
3262     }
3263 
assertPropertyIdIsSupported(int propertyId)3264     private void assertPropertyIdIsSupported(int propertyId) {
3265         if (!CarPropertyHelper.isSupported(propertyId)) {
3266             throw new IllegalArgumentException("The property: "
3267                     + VehiclePropertyIds.toString(propertyId) + " is unsupported");
3268         }
3269     }
3270 
3271     private <RequestType extends AsyncPropertyRequest, CallbackType> List<Integer>
storePendingRequestInfo( List<RequestType> requests, Executor callbackExecutor, CallbackType callback)3272             storePendingRequestInfo(
3273                     List<RequestType> requests, Executor callbackExecutor, CallbackType callback) {
3274         if (DBG) {
3275             Slog.d(TAG, "store pending async requests: " + requests);
3276         }
3277         List<Integer> requestIds = new ArrayList<>();
3278         SparseArray<AsyncPropertyRequestInfo<?, ?>> requestInfoToAdd = new SparseArray<>();
3279         synchronized (mLock) {
3280             for (int i = 0; i < requests.size(); i++) {
3281                 RequestType request = requests.get(i);
3282                 AsyncPropertyRequestInfo<RequestType, CallbackType> requestInfo =
3283                         new AsyncPropertyRequestInfo(request, callbackExecutor, callback);
3284                 int requestId = request.getRequestId();
3285                 requestIds.add(requestId);
3286                 if (mRequestIdToAsyncRequestInfo.contains(requestId)
3287                         || requestInfoToAdd.contains(requestId)) {
3288                     throw new IllegalArgumentException(
3289                             "Request ID: " + requestId + " already exists");
3290                 }
3291                 requestInfoToAdd.put(requestId, requestInfo);
3292             }
3293             for (int i = 0; i < requestInfoToAdd.size(); i++) {
3294                 mRequestIdToAsyncRequestInfo.put(requestInfoToAdd.keyAt(i),
3295                         requestInfoToAdd.valueAt(i));
3296             }
3297         }
3298         return requestIds;
3299     }
3300 
sanitizeSubscribeOptions(List<CarSubscription> subscribeOptions)3301     private List<CarSubscription> sanitizeSubscribeOptions(List<CarSubscription> subscribeOptions)
3302             throws IllegalArgumentException, IllegalStateException {
3303         ArraySet<Integer> propertyIds = new ArraySet<>();
3304         for (int i = 0; i < subscribeOptions.size(); i++) {
3305             propertyIds.add(subscribeOptions.get(i).propertyId);
3306         }
3307         CarPropertyConfigs configs = getPropertyConfigsFromService(propertyIds);
3308         if (configs == null) {
3309             throw new IllegalStateException("Failed to get property config list from car service");
3310         }
3311 
3312         List<CarSubscription> output = new ArrayList<>();
3313         for (int i = 0; i < subscribeOptions.size(); i++) {
3314             CarSubscription subscribeOption = subscribeOptions.get(i);
3315             int propertyId = subscribeOption.propertyId;
3316 
3317             if (configs.isNotSupported(propertyId)) {
3318                 String errorMessage = "propertyId is not in carPropertyConfig list: "
3319                         + VehiclePropertyIds.toString(propertyId);
3320                 Slog.e(TAG, "sanitizeUpdateRate: " + errorMessage);
3321                 throw new IllegalArgumentException(errorMessage);
3322             }
3323             if (configs.missingPermission(propertyId)) {
3324                 // This should not happen since we already checked whether the caller has read
3325                 // permission via getSupportedNoReadPermPropIds. If the caller does not have
3326                 // read or write permission, {@code SecurityException} should be thrown before this.
3327                 String errorMessage = "missing required read/write permission for: "
3328                         + VehiclePropertyIds.toString(propertyId);
3329                 Slog.wtf(TAG, "sanitizeUpdateRate: " + errorMessage);
3330                 throw new SecurityException(errorMessage);
3331             }
3332 
3333             CarPropertyConfig<?> carPropertyConfig = configs.getConfig(propertyId);
3334             CarSubscription carSubscription = new CarSubscription();
3335             carSubscription.propertyId = propertyId;
3336             carSubscription.areaIds = subscribeOption.areaIds;
3337             if (carSubscription.areaIds.length == 0) {
3338                 // Subscribe to all areaIds if not specified.
3339                 carSubscription.areaIds = carPropertyConfig.getAreaIds();
3340             }
3341             carSubscription.enableVariableUpdateRate =
3342                     subscribeOption.enableVariableUpdateRate;
3343             carSubscription.updateRateHz = InputSanitizationUtils.sanitizeUpdateRateHz(
3344                     carPropertyConfig, subscribeOption.updateRateHz);
3345             float resolution = mFeatureFlags.subscriptionWithResolution()
3346                     ? subscribeOption.resolution : 0.0f;
3347             carSubscription.resolution = InputSanitizationUtils.sanitizeResolution(mFeatureFlags,
3348                     carPropertyConfig, resolution);
3349             output.addAll(InputSanitizationUtils.sanitizeEnableVariableUpdateRate(
3350                     mFeatureFlags, carPropertyConfig, carSubscription));
3351         }
3352         return output;
3353     }
3354 
3355     /**
3356      * Checks if the given property ID is one of the user HAL property.
3357      *
3358      * <p>Properties related to user management should only be manipulated by
3359      * {@code UserHalService} and should not be used by directly by the client.
3360      *
3361      * <p>This check is no longer necessary for clients after U, but this logic exists before U so
3362      * we still need this to keep backward compatibility for clients before U.
3363      *
3364      * @param propId property to be checked
3365      *
3366      * @throws IllegalArgumentException if the property is not supported.
3367      */
assertNotUserHalProperty(int propId)3368     private void assertNotUserHalProperty(int propId) {
3369         if (mAppTargetSdk >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
3370             // After Android U, we treat this the same as other unsupported property IDs and this
3371             // special logic is no longer required.
3372             return;
3373         }
3374         switch (propId) {
3375             case VehiclePropertyIds.INITIAL_USER_INFO:
3376             case VehiclePropertyIds.SWITCH_USER:
3377             case VehiclePropertyIds.CREATE_USER:
3378             case VehiclePropertyIds.REMOVE_USER:
3379             case VehiclePropertyIds.USER_IDENTIFICATION_ASSOCIATION:
3380                 throw new IllegalArgumentException("Unsupported property: "
3381                         + VehiclePropertyIds.toString(propId) + " (" + propId + ")");
3382         }
3383     }
3384 
filterOutUserHalProperty(List<Integer> propertyIds)3385     private List<Integer> filterOutUserHalProperty(List<Integer> propertyIds) {
3386         if (mAppTargetSdk >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
3387             // After Android U, we treat this the same as other unsupported property IDs and this
3388             // special logic is no longer required.
3389             return propertyIds;
3390         }
3391         List<Integer> filteredPropertyIds = new ArrayList<>();
3392         for (int i = 0; i < propertyIds.size(); i++) {
3393             switch (propertyIds.get(i)) {
3394                 case VehiclePropertyIds.INITIAL_USER_INFO:
3395                 case VehiclePropertyIds.SWITCH_USER:
3396                 case VehiclePropertyIds.CREATE_USER:
3397                 case VehiclePropertyIds.REMOVE_USER:
3398                 case VehiclePropertyIds.USER_IDENTIFICATION_ASSOCIATION:
3399                     continue;
3400             }
3401             filteredPropertyIds.add(propertyIds.get(i));
3402         }
3403         return filteredPropertyIds;
3404     }
3405 
getSupportedNoReadPermPropIds(List<CarSubscription> subscribeOptions)3406     private int[] getSupportedNoReadPermPropIds(List<CarSubscription> subscribeOptions)
3407             throws RemoteException {
3408         ArraySet<Integer> propertyIds = new ArraySet<>();
3409         for (int i = 0; i < subscribeOptions.size(); i++) {
3410             propertyIds.add(subscribeOptions.get(i).propertyId);
3411         }
3412         int[] propertyIdsArray = new int[propertyIds.size()];
3413         for (int i = 0; i < propertyIds.size(); i++) {
3414             propertyIdsArray[i] = propertyIds.valueAt(i);
3415         }
3416 
3417         return mService.getSupportedNoReadPermPropIds(propertyIdsArray);
3418     }
3419 
3420     // Wraps the result returned from {@code ICarProperty.getPropertyConfigList}.
3421     private static final class CarPropertyConfigs {
3422         private final SparseArray<CarPropertyConfig<?>> mCarPropertyConfigById =
3423                 new SparseArray<>();
3424         private final ArraySet<Integer> mMissingPermissionPropIds = new ArraySet<>();
3425         private final ArraySet<Integer> mUnsupportedPropIds = new ArraySet<>();
3426         private final GetPropertyConfigListResult mResult;
3427 
3428         // The unsupportedPropIds are the property Ids we filtered out before we send out
3429         // the request to car service to get the configs.
CarPropertyConfigs(GetPropertyConfigListResult result, IntArray unsupportedPropIds)3430         CarPropertyConfigs(GetPropertyConfigListResult result, IntArray unsupportedPropIds) {
3431             mResult = result;
3432             List<CarPropertyConfig> configs = result.carPropertyConfigList.getConfigs();
3433             for (int i = 0; i < configs.size(); i++) {
3434                 mCarPropertyConfigById.put(configs.get(i).getPropertyId(), configs.get(i));
3435             }
3436             for (int i = 0; i < result.missingPermissionPropIds.length; i++) {
3437                 mMissingPermissionPropIds.add(result.missingPermissionPropIds[i]);
3438             }
3439             for (int i = 0; i < result.unsupportedPropIds.length; i++) {
3440                 mUnsupportedPropIds.add(result.unsupportedPropIds[i]);
3441             }
3442             for (int i = 0; i < unsupportedPropIds.size(); i++) {
3443                 mUnsupportedPropIds.add(unsupportedPropIds.get(i));
3444             }
3445         }
3446 
3447         // For the propertyIds provided to {@code getPropertyConfigsFromService}, this must not
3448         // return null if both {@code isNotSupported} and {@code missingPermission} is false.
3449         @Nullable
getConfig(int propertyId)3450         CarPropertyConfig<?> getConfig(int propertyId) {
3451             return mCarPropertyConfigById.get(propertyId);
3452         }
3453 
3454         // Returns whether the property is not supported.
isNotSupported(int propertyId)3455         boolean isNotSupported(int propertyId) {
3456             return mUnsupportedPropIds.contains(propertyId);
3457         }
3458 
3459         // Returns whether the caller does not have read and does not have write access to this
3460         // property, hence the caller cannot get the property's config.
missingPermission(int propertyId)3461         boolean missingPermission(int propertyId) {
3462             return mMissingPermissionPropIds.contains(propertyId);
3463         }
3464 
getConfigs()3465         List<CarPropertyConfig> getConfigs() {
3466             return mResult.carPropertyConfigList.getConfigs();
3467         }
3468 
getMissingPermissionPropIds()3469         int[] getMissingPermissionPropIds() {
3470             return mResult.missingPermissionPropIds;
3471         }
3472 
getUnsupportedPropIds()3473         int[] getUnsupportedPropIds() {
3474             return mResult.unsupportedPropIds;
3475         }
3476     }
3477 
3478     @Nullable
getPropertyConfigsFromService(Iterable<Integer> propertyIds)3479     private CarPropertyConfigs getPropertyConfigsFromService(Iterable<Integer> propertyIds) {
3480         IntArray filteredPropertyIds = new IntArray();
3481         IntArray unsupportedPropertyIds = new IntArray();
3482         for (int propertyId : propertyIds) {
3483             assertNotUserHalProperty(propertyId);
3484             if (!CarPropertyHelper.isSupported(propertyId)) {
3485                 unsupportedPropertyIds.add(propertyId);
3486                 continue;
3487             }
3488             filteredPropertyIds.add(propertyId);
3489         }
3490         GetPropertyConfigListResult result;
3491         try {
3492             result = mService.getPropertyConfigList(filteredPropertyIds.toArray());
3493         } catch (RemoteException e) {
3494             Slog.e(TAG, "CarPropertyService.getPropertyConfigList exception ", e);
3495             return handleRemoteExceptionFromCarService(e, null);
3496         }
3497         return new CarPropertyConfigs(result, unsupportedPropertyIds);
3498     }
3499 
3500 }
3501