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