• 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 com.android.car;
18 
19 import static android.car.hardware.CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS;
20 
21 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
22 import static com.android.car.internal.common.CommonConstants.EMPTY_INT_ARRAY;
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.propertyIdsToString;
26 import static com.android.car.internal.util.DebugUtils.toAreaIdString;
27 
28 import static java.util.Objects.requireNonNull;
29 
30 import android.annotation.NonNull;
31 import android.annotation.Nullable;
32 import android.car.Car;
33 import android.car.VehiclePropertyIds;
34 import android.car.builtin.os.TraceHelper;
35 import android.car.builtin.util.Slogf;
36 import android.car.feature.FeatureFlags;
37 import android.car.feature.FeatureFlagsImpl;
38 import android.car.hardware.CarHvacFanDirection;
39 import android.car.hardware.CarPropertyConfig;
40 import android.car.hardware.CarPropertyValue;
41 import android.car.hardware.property.AreaIdConfig;
42 import android.car.hardware.property.CarPropertyEvent;
43 import android.car.hardware.property.CruiseControlType;
44 import android.car.hardware.property.ErrorState;
45 import android.car.hardware.property.EvStoppingMode;
46 import android.car.hardware.property.ICarProperty;
47 import android.car.hardware.property.ICarPropertyEventListener;
48 import android.car.hardware.property.WindshieldWipersSwitch;
49 import android.content.Context;
50 import android.os.Handler;
51 import android.os.HandlerThread;
52 import android.os.IBinder;
53 import android.os.RemoteException;
54 import android.os.ServiceSpecificException;
55 import android.os.SystemClock;
56 import android.os.Trace;
57 import android.util.ArrayMap;
58 import android.util.ArraySet;
59 import android.util.Log;
60 import android.util.SparseArray;
61 import android.util.proto.ProtoOutputStream;
62 
63 import com.android.car.hal.PropertyHalService;
64 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
65 import com.android.car.internal.property.AsyncPropertyServiceRequest;
66 import com.android.car.internal.property.AsyncPropertyServiceRequestList;
67 import com.android.car.internal.property.CarPropertyConfigList;
68 import com.android.car.internal.property.CarPropertyErrorCodes;
69 import com.android.car.internal.property.CarPropertyHelper;
70 import com.android.car.internal.property.CarSubscription;
71 import com.android.car.internal.property.GetPropertyConfigListResult;
72 import com.android.car.internal.property.IAsyncPropertyResultCallback;
73 import com.android.car.internal.property.ISupportedValuesChangeCallback;
74 import com.android.car.internal.property.InputSanitizationUtils;
75 import com.android.car.internal.property.MinMaxSupportedPropertyValue;
76 import com.android.car.internal.property.PropIdAreaId;
77 import com.android.car.internal.property.RawPropertyValue;
78 import com.android.car.internal.property.SubscriptionManager;
79 import com.android.car.internal.util.ArrayUtils;
80 import com.android.car.internal.util.IndentingPrintWriter;
81 import com.android.car.internal.util.IntArray;
82 import com.android.car.internal.util.Lists;
83 import com.android.car.logging.HistogramFactoryInterface;
84 import com.android.car.logging.SystemHistogramFactory;
85 import com.android.car.property.CarPropertyServiceClient;
86 import com.android.internal.annotations.GuardedBy;
87 import com.android.internal.annotations.VisibleForTesting;
88 import com.android.internal.util.Preconditions;
89 import com.android.modules.expresslog.Histogram;
90 
91 import java.util.ArrayList;
92 import java.util.Arrays;
93 import java.util.HashSet;
94 import java.util.List;
95 import java.util.Map;
96 import java.util.Objects;
97 import java.util.Set;
98 import java.util.concurrent.Callable;
99 import java.util.concurrent.CountDownLatch;
100 import java.util.concurrent.TimeUnit;
101 
102 /**
103  * This class implements the binder interface for ICarProperty.aidl to make it easier to create
104  * multiple managers that deal with Vehicle Properties. The property Ids in this class are IDs in
105  * manager level.
106  */
107 public class CarPropertyService extends ICarProperty.Stub
108         implements CarServiceBase, PropertyHalService.PropertyHalListener {
109     // The tolerance we allow for float property value comparison. This is set to a relatively
110     // large value so that we avoid false negative, but may cause false positive, which is okay
111     // since VHAL is supposed to check again.
112     private static final float EPSILON = 0.001f;
113     private static final String TAG = CarLog.tagFor(CarPropertyService.class);
114     private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
115     // Maximum count of sync get/set property operation allowed at once. The reason we limit this
116     // is because each sync get/set property operation takes up one binder thread. If they take
117     // all the binder thread, we do not have thread left for the result callback from VHAL. This
118     // will cause all the pending sync operation to timeout because result cannot be delivered.
119     private static final int SYNC_GET_SET_PROPERTY_OP_LIMIT = 16;
120     private static final long TRACE_TAG = TraceHelper.TRACE_TAG_CAR_SERVICE;
121     // A list of properties that must not set waitForPropertyUpdate to {@code true} for set async.
122     private static final Set<Integer> NOT_ALLOWED_WAIT_FOR_UPDATE_PROPERTIES =
123             new HashSet<>(Arrays.asList(
124                 VehiclePropertyIds.HVAC_TEMPERATURE_VALUE_SUGGESTION
125             ));
126 
127     private static final Set<Integer> ERROR_STATES =
128             new HashSet<Integer>(Arrays.asList(
129                     ErrorState.OTHER_ERROR_STATE,
130                     ErrorState.NOT_AVAILABLE_DISABLED,
131                     ErrorState.NOT_AVAILABLE_SPEED_LOW,
132                     ErrorState.NOT_AVAILABLE_SPEED_HIGH,
133                     ErrorState.NOT_AVAILABLE_SAFETY
134             ));
135     private static final Set<Integer> CAR_HVAC_FAN_DIRECTION_UNWRITABLE_STATES =
136             new HashSet<Integer>(Arrays.asList(
137                     CarHvacFanDirection.UNKNOWN
138             ));
139     private static final Set<Integer> CRUISE_CONTROL_TYPE_UNWRITABLE_STATES =
140             new HashSet<Integer>(Arrays.asList(
141                     CruiseControlType.OTHER
142             ));
143     static {
144         CRUISE_CONTROL_TYPE_UNWRITABLE_STATES.addAll(ERROR_STATES);
145     }
146     private static final Set<Integer> EV_STOPPING_MODE_UNWRITABLE_STATES =
147             new HashSet<Integer>(Arrays.asList(
148                     EvStoppingMode.STATE_OTHER
149             ));
150     private static final Set<Integer> WINDSHIELD_WIPERS_SWITCH_UNWRITABLE_STATES =
151             new HashSet<Integer>(Arrays.asList(
152                     WindshieldWipersSwitch.OTHER
153             ));
154 
155     private static final SparseArray<Set<Integer>> PROPERTY_ID_TO_UNWRITABLE_STATES =
156             new SparseArray<>();
157     static {
PROPERTY_ID_TO_UNWRITABLE_STATES.put( VehiclePropertyIds.CRUISE_CONTROL_TYPE, CRUISE_CONTROL_TYPE_UNWRITABLE_STATES)158         PROPERTY_ID_TO_UNWRITABLE_STATES.put(
159                 VehiclePropertyIds.CRUISE_CONTROL_TYPE,
160                 CRUISE_CONTROL_TYPE_UNWRITABLE_STATES);
PROPERTY_ID_TO_UNWRITABLE_STATES.put( VehiclePropertyIds.EV_STOPPING_MODE, EV_STOPPING_MODE_UNWRITABLE_STATES)161         PROPERTY_ID_TO_UNWRITABLE_STATES.put(
162                 VehiclePropertyIds.EV_STOPPING_MODE,
163                 EV_STOPPING_MODE_UNWRITABLE_STATES);
PROPERTY_ID_TO_UNWRITABLE_STATES.put( VehiclePropertyIds.HVAC_FAN_DIRECTION, CAR_HVAC_FAN_DIRECTION_UNWRITABLE_STATES)164         PROPERTY_ID_TO_UNWRITABLE_STATES.put(
165                 VehiclePropertyIds.HVAC_FAN_DIRECTION,
166                 CAR_HVAC_FAN_DIRECTION_UNWRITABLE_STATES);
PROPERTY_ID_TO_UNWRITABLE_STATES.put( VehiclePropertyIds.WINDSHIELD_WIPERS_SWITCH, WINDSHIELD_WIPERS_SWITCH_UNWRITABLE_STATES)167         PROPERTY_ID_TO_UNWRITABLE_STATES.put(
168                 VehiclePropertyIds.WINDSHIELD_WIPERS_SWITCH,
169                 WINDSHIELD_WIPERS_SWITCH_UNWRITABLE_STATES);
170     }
171 
172     private final FeatureFlags mFeatureFlags;
173     private final HistogramFactoryInterface mHistogramFactory;
174     private final MinMaxSupportedPropertyValueHelper mMinMaxSupportedPropertyValueHelper;
175 
176     private Histogram mConcurrentSyncOperationHistogram;
177     private Histogram mGetPropertySyncLatencyHistogram;
178     private Histogram mSetPropertySyncLatencyHistogram;
179     private Histogram mSubscriptionUpdateRateHistogram;
180     private Histogram mGetAsyncLatencyHistogram;
181     private Histogram mSetAsyncLatencyHistogram;
182 
183     private final Context mContext;
184     private final PropertyHalService mPropertyHalService;
185     private final Object mLock = new Object();
186     @GuardedBy("mLock")
187     private final Map<IBinder, CarPropertyServiceClient> mClientMap = new ArrayMap<>();
188     @GuardedBy("mLock")
189     private final SubscriptionManager<CarPropertyServiceClient> mSubscriptionManager =
190             new SubscriptionManager<>();
191     @GuardedBy("mLock")
192     private final SparseArray<SparseArray<CarPropertyServiceClient>> mSetOpClientByAreaIdByPropId =
193             new SparseArray<>();
194     private final HandlerThread mHandlerThread =
195             CarServiceUtils.getHandlerThread(getClass().getSimpleName());
196     private final Handler mHandler = new Handler(mHandlerThread.getLooper());
197     // Use SparseArray instead of map to save memory.
198     @GuardedBy("mLock")
199     private SparseArray<CarPropertyConfig<?>> mPropertyIdToCarPropertyConfig = new SparseArray<>();
200     @GuardedBy("mLock")
201     private int mSyncGetSetPropertyOpCount;
202 
203     /**
204      * An interface to get RawPropertyValue min/max value from MinMaxSupportedPropertyValue.
205      *
206      * This interface is designed so that unit test could fake the implementation.
207      */
208     public interface MinMaxSupportedPropertyValueHelper {
209         /** Gets the min raw property value. */
getMinValue( MinMaxSupportedPropertyValue minMaxSupportedPropertyValue)210         @Nullable RawPropertyValue getMinValue(
211                 MinMaxSupportedPropertyValue minMaxSupportedPropertyValue);
212          /** Gets the max raw property value. */
getMaxValue( MinMaxSupportedPropertyValue minMaxSupportedPropertyValue)213         @Nullable RawPropertyValue getMaxValue(
214                 MinMaxSupportedPropertyValue minMaxSupportedPropertyValue);
215     }
216 
217     private static final class SystemMinMaxSupportedPropertyValueHelper implements
218             MinMaxSupportedPropertyValueHelper {
getMinValue( MinMaxSupportedPropertyValue minMaxSupportedPropertyValue)219         public @Nullable RawPropertyValue getMinValue(
220                 MinMaxSupportedPropertyValue minMaxSupportedPropertyValue) {
221             if (minMaxSupportedPropertyValue.minValue == null) {
222                 return null;
223             }
224             return minMaxSupportedPropertyValue.minValue.getParcelable(RawPropertyValue.class);
225         }
226 
getMaxValue( MinMaxSupportedPropertyValue minMaxSupportedPropertyValue)227         public @Nullable RawPropertyValue getMaxValue(
228                 MinMaxSupportedPropertyValue minMaxSupportedPropertyValue) {
229             if (minMaxSupportedPropertyValue.maxValue == null) {
230                 return null;
231             }
232             return minMaxSupportedPropertyValue.maxValue.getParcelable(RawPropertyValue.class);
233         }
234     }
235 
236     /**
237      * The builder for {@link com.android.car.CarPropertyService}.
238      */
239     public static final class Builder {
240         private Context mContext;
241         private PropertyHalService mPropertyHalService;
242         private @Nullable FeatureFlags mFeatureFlags;
243         private @Nullable HistogramFactoryInterface mHistogramFactory;
244         private boolean mBuilt;
245         private @Nullable MinMaxSupportedPropertyValueHelper mMinMaxSupportedPropertyValueHelper;
246 
247         /** Sets the context. */
setContext(Context context)248         public Builder setContext(Context context) {
249             mContext = context;
250             return this;
251         }
252 
253         /** Sets the {@link PropertyHalService}. */
setPropertyHalService(PropertyHalService propertyHalService)254         public Builder setPropertyHalService(PropertyHalService propertyHalService) {
255             mPropertyHalService = propertyHalService;
256             return this;
257         }
258 
259         /**
260          * Builds the {@link com.android.car.CarPropertyService}.
261          */
build()262         public CarPropertyService build() {
263             if (mBuilt) {
264                 throw new IllegalStateException("Only allowed to be built once");
265             }
266             mBuilt = true;
267             return new CarPropertyService(this);
268         }
269 
270         /** Sets fake feature flag for unit testing. */
271         @VisibleForTesting
setFeatureFlags(FeatureFlags fakeFeatureFlags)272         Builder setFeatureFlags(FeatureFlags fakeFeatureFlags) {
273             mFeatureFlags = fakeFeatureFlags;
274             return this;
275         }
276 
277         /** Sets fake histogram builder for unit testing. */
278         @VisibleForTesting
setHistogramFactory(HistogramFactoryInterface histogramFactory)279         Builder setHistogramFactory(HistogramFactoryInterface histogramFactory) {
280             mHistogramFactory = histogramFactory;
281             return this;
282         }
283 
284         /** Sets fake MinMaxSupportedPropertyValueHelper for unit testing. */
285         @VisibleForTesting
setMinMaxSupportedPropertyValueHelper(MinMaxSupportedPropertyValueHelper helper)286         Builder setMinMaxSupportedPropertyValueHelper(MinMaxSupportedPropertyValueHelper helper) {
287             mMinMaxSupportedPropertyValueHelper = helper;
288             return this;
289         }
290     }
291 
CarPropertyService(Builder builder)292     private CarPropertyService(Builder builder) {
293         if (DBG) {
294             Slogf.d(TAG, "CarPropertyService started!");
295         }
296         mPropertyHalService = Objects.requireNonNull(builder.mPropertyHalService);
297         mContext = Objects.requireNonNull(builder.mContext);
298         mFeatureFlags = Objects.requireNonNullElseGet(builder.mFeatureFlags,
299                 () -> new FeatureFlagsImpl());
300         mHistogramFactory = Objects.requireNonNullElseGet(builder.mHistogramFactory,
301                 () -> new SystemHistogramFactory());
302         mMinMaxSupportedPropertyValueHelper = Objects.requireNonNullElseGet(
303                 builder.mMinMaxSupportedPropertyValueHelper,
304                 () -> new SystemMinMaxSupportedPropertyValueHelper());
305         initializeHistogram();
306     }
307 
308     @VisibleForTesting
finishHandlerTasks(int timeoutInMs)309     void finishHandlerTasks(int timeoutInMs) throws InterruptedException {
310         CountDownLatch cdLatch = new CountDownLatch(1);
311         mHandler.post(() -> {
312             cdLatch.countDown();
313         });
314         cdLatch.await(timeoutInMs, TimeUnit.MILLISECONDS);
315     }
316 
317     @Override
init()318     public void init() {
319         synchronized (mLock) {
320             // Cache the configs list to avoid subsequent binder calls
321             mPropertyIdToCarPropertyConfig = mPropertyHalService.getPropertyList();
322             if (DBG) {
323                 Slogf.d(TAG, "cache CarPropertyConfigs " + mPropertyIdToCarPropertyConfig.size());
324             }
325         }
326         mPropertyHalService.setPropertyHalListener(this);
327     }
328 
329     @Override
release()330     public void release() {
331         synchronized (mLock) {
332             mClientMap.clear();
333             mSubscriptionManager.clear();
334             mPropertyHalService.setPropertyHalListener(null);
335             mSetOpClientByAreaIdByPropId.clear();
336         }
337     }
338 
339     @Override
340     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)341     public void dump(IndentingPrintWriter writer) {
342         writer.println("*CarPropertyService*");
343         writer.increaseIndent();
344         synchronized (mLock) {
345             writer.println("There are " + mClientMap.size() + " clients that have registered"
346                     + " listeners in CarPropertyService.");
347             writer.println("Current sync operation count: " + mSyncGetSetPropertyOpCount);
348             writer.println("Properties registered: ");
349             writer.increaseIndent();
350             mSubscriptionManager.dump(writer);
351             writer.decreaseIndent();
352             writer.println("Properties that have a listener registered for setProperty:");
353             writer.increaseIndent();
354             for (int i = 0; i < mSetOpClientByAreaIdByPropId.size(); i++) {
355                 int propId = mSetOpClientByAreaIdByPropId.keyAt(i);
356                 SparseArray areaIdToClient = mSetOpClientByAreaIdByPropId.valueAt(i);
357                 for (int j = 0; j < areaIdToClient.size(); j++) {
358                     int areaId = areaIdToClient.keyAt(j);
359                     writer.println("Client: " + areaIdToClient.valueAt(j).hashCode() + " propId: "
360                             + VehiclePropertyIds.toString(propId)  + " areaId: "
361                             + toAreaIdString(propId, areaId));
362                 }
363             }
364             writer.decreaseIndent();
365         }
366         writer.decreaseIndent();
367     }
368 
369     @Override
370     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProto(ProtoOutputStream proto)371     public void dumpProto(ProtoOutputStream proto) {}
372 
373     /**
374      * Subscribes to the property update events for the property ID.
375      *
376      * Used internally in car service.
377      */
registerListener(int propertyId, float updateRateHz, boolean enableVariableUpdateRate, float resolution, ICarPropertyEventListener carPropertyEventListener)378     public void registerListener(int propertyId, float updateRateHz,
379             boolean enableVariableUpdateRate, float resolution,
380             ICarPropertyEventListener carPropertyEventListener) {
381         CarSubscription option = new CarSubscription();
382         int[] areaIds = EMPTY_INT_ARRAY;
383         CarPropertyConfig<?> carPropertyConfig = getCarPropertyConfig(propertyId);
384         // carPropertyConfig nullity check will be done in registerListener
385         if (carPropertyConfig != null) {
386             areaIds = carPropertyConfig.getAreaIds();
387         }
388         option.propertyId = propertyId;
389         option.updateRateHz = updateRateHz;
390         option.areaIds = areaIds;
391         option.enableVariableUpdateRate = enableVariableUpdateRate;
392         option.resolution = resolution;
393         registerListener(List.of(option), carPropertyEventListener);
394     }
395 
396     /**
397      * Subscribes to the property update events for the property ID at a resolution of 0.
398      *
399      * Used internally in car service.
400      */
registerListener(int propertyId, float updateRateHz, boolean enableVariableUpdateRate, ICarPropertyEventListener carPropertyEventListener)401     public void registerListener(int propertyId, float updateRateHz,
402             boolean enableVariableUpdateRate,
403             ICarPropertyEventListener carPropertyEventListener) {
404         registerListener(propertyId, updateRateHz, enableVariableUpdateRate, /* resolution */ 0.0f,
405                 carPropertyEventListener);
406     }
407 
408     /**
409      * Subscribes to the property update events for the property ID with VUR enabled for continuous
410      * property and a resolution of 0.
411      *
412      * Used internally in car service.
413      */
registerListener(int propertyId, float updateRateHz, ICarPropertyEventListener carPropertyEventListener)414     public void registerListener(int propertyId, float updateRateHz,
415             ICarPropertyEventListener carPropertyEventListener)
416             throws IllegalArgumentException, ServiceSpecificException {
417         CarPropertyConfig<?> carPropertyConfig = getCarPropertyConfig(propertyId);
418         boolean enableVariableUpdateRate = false;
419         // carPropertyConfig nullity check will be done in registerListener
420         if (carPropertyConfig != null
421                 && carPropertyConfig.getChangeMode() == VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) {
422             enableVariableUpdateRate = true;
423         }
424         registerListener(propertyId, updateRateHz, enableVariableUpdateRate, /* resolution */ 0.0f,
425                 carPropertyEventListener);
426     }
427 
428     /**
429      * Validates the subscribe options and sanitize the update rate inside it.
430      *
431      * The update rate will be fit within the {@code minSampleRate} and {@code maxSampleRate}.
432      *
433      * @throws IllegalArgumentException if one of the options is not valid.
434      */
validateAndSanitizeSubscriptions( List<CarSubscription> carSubscriptions)435     private List<CarSubscription> validateAndSanitizeSubscriptions(
436                 List<CarSubscription> carSubscriptions)
437             throws IllegalArgumentException {
438         List<CarSubscription> sanitizedSubscriptions = new ArrayList<>();
439         for (int i = 0; i < carSubscriptions.size(); i++) {
440             CarSubscription subscription = carSubscriptions.get(i);
441             CarPropertyConfig<?> carPropertyConfig = validateRegisterParameterAndGetConfig(
442                     subscription.propertyId, subscription.areaIds);
443             subscription.updateRateHz = InputSanitizationUtils.sanitizeUpdateRateHz(
444                     carPropertyConfig, subscription.updateRateHz);
445             subscription.resolution = InputSanitizationUtils.sanitizeResolution(
446                     mFeatureFlags, carPropertyConfig, subscription.resolution);
447             sanitizedSubscriptions.addAll(InputSanitizationUtils.sanitizeEnableVariableUpdateRate(
448                     mFeatureFlags, carPropertyConfig, subscription));
449         }
450         return sanitizedSubscriptions;
451     }
452 
453     /**
454      * Gets the {@code CarPropertyServiceClient} for the binder, create a new one if not exists.
455      *
456      * @param carPropertyEventListener The client callback.
457      * @return the client for the binder, or null if the client is already dead.
458      */
459     @GuardedBy("mLock")
getOrCreateClientForBinderLocked( ICarPropertyEventListener carPropertyEventListener)460     private @Nullable CarPropertyServiceClient getOrCreateClientForBinderLocked(
461             ICarPropertyEventListener carPropertyEventListener) {
462         IBinder listenerBinder = carPropertyEventListener.asBinder();
463         CarPropertyServiceClient client = mClientMap.get(listenerBinder);
464         if (client != null) {
465             return client;
466         }
467         client = new CarPropertyServiceClient(carPropertyEventListener,
468                 this::unregisterListenerBinderForProps);
469         if (client.isDead()) {
470             Slogf.w(TAG, "the ICarPropertyEventListener is already dead");
471             return null;
472         }
473         mClientMap.put(listenerBinder, client);
474         return client;
475     }
476 
477     @Override
registerListener(List<CarSubscription> carSubscriptions, ICarPropertyEventListener carPropertyEventListener)478     public void registerListener(List<CarSubscription> carSubscriptions,
479             ICarPropertyEventListener carPropertyEventListener)
480             throws IllegalArgumentException, ServiceSpecificException {
481         requireNonNull(carSubscriptions);
482         requireNonNull(carPropertyEventListener);
483 
484         List<CarSubscription> sanitizedOptions =
485                 validateAndSanitizeSubscriptions(carSubscriptions);
486 
487         CarPropertyServiceClient finalClient;
488         synchronized (mLock) {
489             // We create the client first so that we will not subscribe if the binder is already
490             // dead.
491             CarPropertyServiceClient client = getOrCreateClientForBinderLocked(
492                     carPropertyEventListener);
493             if (client == null) {
494                 // The client is already dead.
495                 return;
496             }
497 
498             for (int i = 0; i < sanitizedOptions.size(); i++) {
499                 CarSubscription option = sanitizedOptions.get(i);
500                 mSubscriptionUpdateRateHistogram.logSample(option.updateRateHz);
501                 if (DBG) {
502                     Slogf.d(TAG, "registerListener after update rate sanitization, options: "
503                             + sanitizedOptions.get(i));
504                 }
505             }
506 
507             // Store the new subscritpion state in the staging area. This does not affect the
508             // current state.
509             mSubscriptionManager.stageNewOptions(client, sanitizedOptions);
510 
511             // Try to apply the staged changes.
512             try {
513                 applyStagedChangesLocked();
514             } catch (Exception e) {
515                 mSubscriptionManager.dropCommit();
516                 throw e;
517             }
518 
519             // After subscribeProperty succeeded, adds the client to the
520             // [propertyId -> subscribed clients list] map. Adds the property to the client's
521             // [areaId -> update rate] map.
522             mSubscriptionManager.commit();
523             for (int i = 0; i < sanitizedOptions.size(); i++) {
524                 CarSubscription option = sanitizedOptions.get(i);
525                 // After {@code validateAndSanitizeSubscriptions}, update rate must be 0 for
526                 // on-change property and non-0 for continuous property.
527                 if (option.updateRateHz != 0) {
528                     client.addContinuousProperty(
529                             option.propertyId, option.areaIds, option.updateRateHz,
530                             option.enableVariableUpdateRate, option.resolution);
531                 } else {
532                     client.addOnChangeProperty(option.propertyId, option.areaIds);
533                 }
534             }
535             finalClient = client;
536         }
537 
538         var propIdAreaIds = getPropIdAreaIdsFromCarSubscriptions(sanitizedOptions);
539         mHandler.post(() ->
540                 getAndDispatchPropertyInitValue(propIdAreaIds, finalClient));
541     }
542 
543     /**
544      * Register property listener for car service's internal usage.
545      *
546      * This function catches all exceptions and return {@code true} if succeed.
547      */
registerListenerSafe(int propertyId, float updateRateHz, boolean enableVariableUpdateRate, ICarPropertyEventListener iCarPropertyEventListener)548     public boolean registerListenerSafe(int propertyId, float updateRateHz,
549             boolean enableVariableUpdateRate,
550             ICarPropertyEventListener iCarPropertyEventListener) {
551         try {
552             registerListener(propertyId, updateRateHz, enableVariableUpdateRate,
553                     iCarPropertyEventListener);
554             return true;
555         } catch (Exception e) {
556             Slogf.e(TAG, e, "registerListenerSafe() failed for property ID: %s updateRateHz: %f",
557                     VehiclePropertyIds.toString(propertyId), updateRateHz);
558             return false;
559         }
560     }
561 
562     /**
563      * Register property listener for car service's internal usage with VUR enabled for continuous
564      * property.
565      *
566      * This function catches all exceptions and return {@code true} if succeed.
567      */
registerListenerSafe(int propertyId, float updateRateHz, ICarPropertyEventListener iCarPropertyEventListener)568     public boolean registerListenerSafe(int propertyId, float updateRateHz,
569             ICarPropertyEventListener iCarPropertyEventListener) {
570         try {
571             registerListener(propertyId, updateRateHz, iCarPropertyEventListener);
572             return true;
573         } catch (Exception e) {
574             Slogf.e(TAG, e, "registerListenerSafe() failed for property ID: %s updateRateHz: %f",
575                     VehiclePropertyIds.toString(propertyId), updateRateHz);
576             return false;
577         }
578     }
579 
580     @GuardedBy("mLock")
applyStagedChangesLocked()581     void applyStagedChangesLocked() throws ServiceSpecificException {
582         List<CarSubscription> filteredSubscriptions = new ArrayList<>();
583         List<Integer> propertyIdsToUnsubscribe = new ArrayList<>();
584         mSubscriptionManager.diffBetweenCurrentAndStage(/* out */ filteredSubscriptions,
585                 /* out */ propertyIdsToUnsubscribe);
586 
587         if (DBG) {
588             Slogf.d(TAG, "Subscriptions after filtering out options that are already"
589                     + " subscribed at the same or a higher rate: " + filteredSubscriptions);
590         }
591 
592         if (!filteredSubscriptions.isEmpty()) {
593             try {
594                 mPropertyHalService.subscribeProperty(filteredSubscriptions);
595             } catch (ServiceSpecificException e) {
596                 Slogf.e(TAG, "PropertyHalService.subscribeProperty failed", e);
597                 throw e;
598             }
599         }
600 
601         for (int i = 0; i < propertyIdsToUnsubscribe.size(); i++) {
602             Slogf.d(TAG, "Property: %s is no longer subscribed",
603                     propertyIdsToUnsubscribe.get(i));
604             try {
605                 mPropertyHalService.unsubscribeProperty(propertyIdsToUnsubscribe.get(i));
606             } catch (ServiceSpecificException e) {
607                 Slogf.e(TAG, "failed to call PropertyHalService.unsubscribeProperty", e);
608                 throw e;
609             }
610         }
611     }
612 
613     @Override
getAndDispatchInitialValue(List<PropIdAreaId> propIdAreaIds, ICarPropertyEventListener carPropertyEventListener)614     public void getAndDispatchInitialValue(List<PropIdAreaId> propIdAreaIds,
615             ICarPropertyEventListener carPropertyEventListener) {
616         requireNonNull(propIdAreaIds);
617         requireNonNull(carPropertyEventListener);
618         CarPropertyServiceClient client;
619         synchronized (mLock) {
620             // We create the client first so that we will not subscribe if the binder is already
621             // dead.
622             client = getOrCreateClientForBinderLocked(carPropertyEventListener);
623             if (client == null) {
624                 // The client is already dead.
625                 return;
626             }
627         }
628         mHandler.post(() ->
629                 getAndDispatchPropertyInitValue(new ArraySet<PropIdAreaId>(propIdAreaIds),
630                         client));
631     }
632 
getAndDispatchPropertyInitValue(Set<PropIdAreaId> propIdAreaIds, CarPropertyServiceClient client)633     private void getAndDispatchPropertyInitValue(Set<PropIdAreaId> propIdAreaIds,
634             CarPropertyServiceClient client) {
635         List<CarPropertyEvent> events = new ArrayList<>();
636         for (var propIdAreaId : propIdAreaIds) {
637             int propertyId = propIdAreaId.propId;
638             int areaId = propIdAreaId.areaId;
639             CarPropertyValue carPropertyValue = null;
640             try {
641                 carPropertyValue = getProperty(propertyId, areaId);
642             } catch (ServiceSpecificException e) {
643                 Slogf.w(TAG, "Get initial carPropertyValue for registerCallback failed -"
644                                 + " property ID: %s, area ID %s, exception: %s",
645                         VehiclePropertyIds.toString(propertyId), toAreaIdString(propertyId, areaId),
646                         e);
647                 int errorCode = CarPropertyErrorCodes.getVhalSystemErrorCode(e.errorCode);
648                 long timestampNanos = SystemClock.elapsedRealtimeNanos();
649                 CarPropertyConfig<?> carPropertyConfig = getCarPropertyConfig(propertyId);
650                 Object defaultValue = CarPropertyHelper.getDefaultValue(
651                         carPropertyConfig.getPropertyType());
652                 if (CarPropertyErrorCodes.isNotAvailableVehicleHalStatusCode(errorCode)) {
653                     carPropertyValue = new CarPropertyValue<>(propertyId, areaId,
654                             CarPropertyValue.STATUS_UNAVAILABLE, timestampNanos, defaultValue);
655                 } else {
656                     carPropertyValue = new CarPropertyValue<>(propertyId, areaId,
657                             CarPropertyValue.STATUS_ERROR, timestampNanos, defaultValue);
658                 }
659             } catch (Exception e) {
660                 // Do nothing.
661                 Slogf.e(TAG, "Get initial carPropertyValue for registerCallback failed -"
662                                 + " property ID: %s, area ID %s, exception: %s",
663                         VehiclePropertyIds.toString(propertyId), toAreaIdString(propertyId, areaId),
664                         e);
665             }
666             if (carPropertyValue != null) {
667                 CarPropertyEvent event = new CarPropertyEvent(
668                         CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, carPropertyValue);
669                 events.add(event);
670             }
671         }
672 
673         if (events.isEmpty()) {
674             return;
675         }
676         try {
677             if (mFeatureFlags.alwaysSendInitialValueEvent()) {
678                 // Do not filter the initial value events. We always want the initial value events
679                 // to reach the clients.
680                 client.onFilteredEvents(events);
681             } else {
682                 client.onEvent(events);
683             }
684         } catch (RemoteException ex) {
685             // If we cannot send a record, it's likely the connection snapped. Let the binder
686             // death handle the situation.
687             Slogf.e(TAG, "onEvent calling failed", ex);
688         }
689     }
690 
691     @Override
unregisterListener(int propertyId, ICarPropertyEventListener iCarPropertyEventListener)692     public void unregisterListener(int propertyId,
693             ICarPropertyEventListener iCarPropertyEventListener)
694             throws IllegalArgumentException, ServiceSpecificException {
695         requireNonNull(iCarPropertyEventListener);
696 
697         // We do not have to call validateRegisterParameterAndGetConfig since if the property was
698         // previously subscribed, then the client already had the read permssion. If not, then we
699         // would do nothing.
700         // We also need to consider the case where the client has write-only permission and uses
701         // setProperty before, we must remove the listener associated with property set error.
702         assertConfigNotNullAndGetConfig(propertyId);
703 
704         if (DBG) {
705             Slogf.d(TAG,
706                     "unregisterListener property ID=" + VehiclePropertyIds.toString(propertyId));
707         }
708 
709         IBinder listenerBinder = iCarPropertyEventListener.asBinder();
710         unregisterListenerBinderForProps(List.of(propertyId), listenerBinder);
711     }
712 
713     /**
714      * Unregister property listener for car service's internal usage.
715      */
unregisterListenerSafe(int propertyId, ICarPropertyEventListener iCarPropertyEventListener)716     public boolean unregisterListenerSafe(int propertyId,
717             ICarPropertyEventListener iCarPropertyEventListener) {
718         try {
719             unregisterListener(propertyId, iCarPropertyEventListener);
720             return true;
721         } catch (Exception e) {
722             Slogf.e(TAG, e, "unregisterListenerSafe() failed for property ID: %s",
723                     VehiclePropertyIds.toString(propertyId));
724             return false;
725         }
726     }
727 
unregisterListenerBinderForProps(List<Integer> propertyIds, IBinder listenerBinder)728     private void unregisterListenerBinderForProps(List<Integer> propertyIds, IBinder listenerBinder)
729             throws ServiceSpecificException {
730         synchronized (mLock) {
731             CarPropertyServiceClient client = mClientMap.get(listenerBinder);
732             if (client == null) {
733                 Slogf.e(TAG, "unregisterListener: Listener was not previously "
734                         + "registered for any property");
735                 return;
736             }
737 
738             ArraySet<Integer> validPropertyIds = new ArraySet<>();
739             for (int i = 0; i < propertyIds.size(); i++) {
740                 int propertyId = propertyIds.get(i);
741                 if (mPropertyIdToCarPropertyConfig.get(propertyId) == null) {
742                     // Do not attempt to unregister an invalid propertyId
743                     Slogf.e(TAG, "unregisterListener: propertyId is not in config list: %s",
744                             VehiclePropertyIds.toString(propertyId));
745                     continue;
746                 }
747                 validPropertyIds.add(propertyId);
748             }
749 
750             if (validPropertyIds.isEmpty()) {
751                 Slogf.e(TAG, "All properties are invalid: " + propertyIdsToString(propertyIds));
752                 return;
753             }
754 
755             // Clear the onPropertySetError callback associated with this property.
756             clearSetOperationRecorderLocked(validPropertyIds, client);
757 
758             mSubscriptionManager.stageUnregister(client, validPropertyIds);
759 
760             try {
761                 applyStagedChangesLocked();
762             } catch (Exception e) {
763                 mSubscriptionManager.dropCommit();
764                 throw e;
765             }
766 
767             mSubscriptionManager.commit();
768             boolean allPropertiesRemoved = client.remove(validPropertyIds);
769             if (allPropertiesRemoved) {
770                 mClientMap.remove(listenerBinder);
771             }
772         }
773     }
774 
775     /**
776      * Return the list of properties' configs that the caller may access.
777      */
778     @NonNull
779     @Override
getPropertyList()780     public CarPropertyConfigList getPropertyList() {
781         int[] allPropIds;
782         // Avoid permission checking under lock.
783         synchronized (mLock) {
784             allPropIds = new int[mPropertyIdToCarPropertyConfig.size()];
785             for (int i = 0; i < mPropertyIdToCarPropertyConfig.size(); i++) {
786                 allPropIds[i] = mPropertyIdToCarPropertyConfig.keyAt(i);
787             }
788         }
789         return getPropertyConfigList(allPropIds).carPropertyConfigList;
790     }
791 
792     /**
793      * @param propIds Array of property Ids
794      * @return the list of properties' configs that the caller may access.
795      */
796     @NonNull
797     @Override
getPropertyConfigList(int[] propIds)798     public GetPropertyConfigListResult getPropertyConfigList(int[] propIds) {
799         GetPropertyConfigListResult result = new GetPropertyConfigListResult();
800         List<CarPropertyConfig> availableProp = new ArrayList<>();
801         IntArray missingPermissionPropIds = new IntArray(availableProp.size());
802         IntArray unsupportedPropIds = new IntArray(availableProp.size());
803 
804         synchronized (mLock) {
805             for (int propId : propIds) {
806                 if (!mPropertyIdToCarPropertyConfig.contains(propId)) {
807                     unsupportedPropIds.add(propId);
808                     continue;
809                 }
810 
811                 if (!mPropertyHalService.isReadable(mContext, propId)
812                         && !mPropertyHalService.isWritable(mContext, propId)) {
813                     missingPermissionPropIds.add(propId);
814                     continue;
815                 }
816 
817                 availableProp.add(mPropertyIdToCarPropertyConfig.get(propId));
818             }
819         }
820         if (DBG) {
821             Slogf.d(TAG, "getPropertyList returns " + availableProp.size() + " configs");
822         }
823         result.carPropertyConfigList = new CarPropertyConfigList(availableProp);
824         result.missingPermissionPropIds = missingPermissionPropIds.toArray();
825         result.unsupportedPropIds = unsupportedPropIds.toArray();
826         return result;
827     }
828 
829     @Nullable
runSyncOperationCheckLimit(Callable<V> c)830     private <V> V runSyncOperationCheckLimit(Callable<V> c) {
831         synchronized (mLock) {
832             if (mSyncGetSetPropertyOpCount >= SYNC_GET_SET_PROPERTY_OP_LIMIT) {
833                 mConcurrentSyncOperationHistogram.logSample(mSyncGetSetPropertyOpCount);
834                 throw new ServiceSpecificException(SYNC_OP_LIMIT_TRY_AGAIN);
835             }
836             mSyncGetSetPropertyOpCount += 1;
837             mConcurrentSyncOperationHistogram.logSample(mSyncGetSetPropertyOpCount);
838             if (DBG) {
839                 Slogf.d(TAG, "mSyncGetSetPropertyOpCount: %d", mSyncGetSetPropertyOpCount);
840             }
841         }
842         try {
843             Trace.traceBegin(TRACE_TAG, "call sync operation");
844             return c.call();
845         } catch (RuntimeException e) {
846             throw e;
847         } catch (Exception e) {
848             Slogf.e(TAG, e, "catching unexpected exception for getProperty/setProperty");
849             return null;
850         } finally {
851             Trace.traceEnd(TRACE_TAG);
852             synchronized (mLock) {
853                 mSyncGetSetPropertyOpCount -= 1;
854                 if (DBG) {
855                     Slogf.d(TAG, "mSyncGetSetPropertyOpCount: %d", mSyncGetSetPropertyOpCount);
856                 }
857             }
858         }
859     }
860 
861     @Override
getProperty(int propertyId, int areaId)862     public CarPropertyValue getProperty(int propertyId, int areaId)
863             throws IllegalArgumentException, ServiceSpecificException {
864         validateGetParameters(propertyId, areaId);
865         Trace.traceBegin(TRACE_TAG, "CarPropertyValue#getProperty");
866         long currentTimeMs = System.currentTimeMillis();
867         try {
868             return runSyncOperationCheckLimit(() -> {
869                 return mPropertyHalService.getProperty(propertyId, areaId);
870             });
871         } finally {
872             if (DBG) {
873                 Slogf.d(TAG, "Latency of getPropertySync is: %f", (float) (System
874                         .currentTimeMillis() - currentTimeMs));
875             }
876             mGetPropertySyncLatencyHistogram.logSample((float) (System.currentTimeMillis()
877                     - currentTimeMs));
878             Trace.traceEnd(TRACE_TAG);
879         }
880     }
881 
882     /**
883      * Get property value for car service's internal usage.
884      *
885      * @return null if property is not implemented or there is an exception in the vehicle.
886      */
887     @Nullable
getPropertySafe(int propertyId, int areaId)888     public CarPropertyValue getPropertySafe(int propertyId, int areaId) {
889         try {
890             return getProperty(propertyId, areaId);
891         } catch (Exception e) {
892             Slogf.w(TAG, e, "getPropertySafe() failed for property ID: %s area ID: %s",
893                     VehiclePropertyIds.toString(propertyId), toAreaIdString(propertyId, areaId));
894             return null;
895         }
896     }
897 
898     /**
899      * Return read permission string for given property ID. The format of the return value of this
900      * function has changed over time and thus should not be relied on.
901      *
902      * @param propId the property ID to query
903      * @return the permission needed to read this property, {@code null} if the property ID is not
904      * available
905      */
906     @Nullable
907     @Override
getReadPermission(int propId)908     public String getReadPermission(int propId) {
909         return mPropertyHalService.getReadPermission(propId);
910     }
911 
912     /**
913      * Return write permission string for given property ID. The format of the return value of this
914      * function has changed over time and thus should not be relied on.
915      *
916      * @param propId the property ID to query
917      * @return the permission needed to write this property, {@code null} if the property ID is not
918      * available
919      */
920     @Nullable
921     @Override
getWritePermission(int propId)922     public String getWritePermission(int propId) {
923         return mPropertyHalService.getWritePermission(propId);
924     }
925 
926     @Override
setProperty(CarPropertyValue carPropertyValue, ICarPropertyEventListener iCarPropertyEventListener)927     public void setProperty(CarPropertyValue carPropertyValue,
928             ICarPropertyEventListener iCarPropertyEventListener)
929             throws IllegalArgumentException, ServiceSpecificException {
930         requireNonNull(iCarPropertyEventListener);
931         validateSetParameters(carPropertyValue);
932         long currentTimeMs = System.currentTimeMillis();
933 
934         runSyncOperationCheckLimit(() -> {
935             mPropertyHalService.setProperty(carPropertyValue);
936             return null;
937         });
938 
939         IBinder listenerBinder = iCarPropertyEventListener.asBinder();
940         synchronized (mLock) {
941             CarPropertyServiceClient client = mClientMap.get(listenerBinder);
942             if (client == null) {
943                 client = new CarPropertyServiceClient(iCarPropertyEventListener,
944                         this::unregisterListenerBinderForProps);
945             }
946             if (client.isDead()) {
947                 Slogf.w(TAG, "the ICarPropertyEventListener is already dead");
948                 return;
949             }
950             // Note that here we are not calling addContinuousProperty or addOnChangeProperty
951             // for this client because we will not enable filtering in this client, so no need to
952             // record these filtering information.
953             mClientMap.put(listenerBinder, client);
954             updateSetOperationRecorderLocked(carPropertyValue.getPropertyId(),
955                     carPropertyValue.getAreaId(), client);
956             if (DBG) {
957                 Slogf.d(TAG, "Latency of setPropertySync is: %f", (float) (System
958                         .currentTimeMillis() - currentTimeMs));
959             }
960             mSetPropertySyncLatencyHistogram.logSample((float) (System.currentTimeMillis()
961                     - currentTimeMs));
962         }
963     }
964 
965     // Updates recorder for set operation.
966     @GuardedBy("mLock")
updateSetOperationRecorderLocked(int propertyId, int areaId, CarPropertyServiceClient client)967     private void updateSetOperationRecorderLocked(int propertyId, int areaId,
968             CarPropertyServiceClient client) {
969         if (mSetOpClientByAreaIdByPropId.get(propertyId) != null) {
970             mSetOpClientByAreaIdByPropId.get(propertyId).put(areaId, client);
971         } else {
972             SparseArray<CarPropertyServiceClient> areaIdToClient = new SparseArray<>();
973             areaIdToClient.put(areaId, client);
974             mSetOpClientByAreaIdByPropId.put(propertyId, areaIdToClient);
975         }
976     }
977 
978     // Clears map when client unregister for property.
979     @GuardedBy("mLock")
clearSetOperationRecorderLocked(ArraySet<Integer> propertyIds, CarPropertyServiceClient client)980     private void clearSetOperationRecorderLocked(ArraySet<Integer> propertyIds,
981             CarPropertyServiceClient client) {
982         for (int i = 0; i < propertyIds.size(); i++) {
983             int propertyId = propertyIds.valueAt(i);
984             SparseArray<CarPropertyServiceClient> areaIdToClient = mSetOpClientByAreaIdByPropId.get(
985                     propertyId);
986             if (areaIdToClient == null) {
987                 continue;
988             }
989             List<Integer> areaIdsToRemove = new ArrayList<>();
990             for (int j = 0; j < areaIdToClient.size(); j++) {
991                 if (client.equals(areaIdToClient.valueAt(j))) {
992                     areaIdsToRemove.add(areaIdToClient.keyAt(j));
993                 }
994             }
995             for (int j = 0; j < areaIdsToRemove.size(); j++) {
996                 if (DBG) {
997                     Slogf.d(TAG, "clear set operation client for property: %s, area ID: %s",
998                             VehiclePropertyIds.toString(propertyId),
999                             toAreaIdString(propertyId, areaIdsToRemove.get(j)));
1000                 }
1001                 areaIdToClient.remove(areaIdsToRemove.get(j));
1002             }
1003             if (areaIdToClient.size() == 0) {
1004                 mSetOpClientByAreaIdByPropId.remove(propertyId);
1005             }
1006         }
1007     }
1008 
1009     // Implement PropertyHalListener interface
1010     @Override
onPropertyChange(List<CarPropertyEvent> events)1011     public void onPropertyChange(List<CarPropertyEvent> events) {
1012         Map<CarPropertyServiceClient, List<CarPropertyEvent>> eventsToDispatch = new ArrayMap<>();
1013         synchronized (mLock) {
1014             for (int i = 0; i < events.size(); i++) {
1015                 CarPropertyEvent event = events.get(i);
1016                 int propId = event.getCarPropertyValue().getPropertyId();
1017                 int areaId = event.getCarPropertyValue().getAreaId();
1018                 Set<CarPropertyServiceClient> clients = mSubscriptionManager.getClients(
1019                         propId, areaId);
1020                 if (clients == null) {
1021                     Slogf.e(TAG,
1022                             "onPropertyChange: no listener registered for propId=%s, areaId=%s",
1023                             VehiclePropertyIds.toString(propId), toAreaIdString(propId, areaId));
1024                     continue;
1025                 }
1026 
1027                 for (CarPropertyServiceClient client : clients) {
1028                     List<CarPropertyEvent> eventsForClient = eventsToDispatch.get(client);
1029                     if (eventsForClient == null) {
1030                         eventsToDispatch.put(client, new ArrayList<CarPropertyEvent>());
1031                     }
1032                     eventsToDispatch.get(client).add(event);
1033                 }
1034             }
1035         }
1036 
1037         // Parse the dispatch list to send events. We must call the callback outside the
1038         // scoped lock since the callback might call some function in CarPropertyService
1039         // which might cause deadlock.
1040         // In rare cases, if this specific client is unregistered after the lock but before
1041         // the callback, we would call callback on an unregistered client which should be ok because
1042         // 'onEvent' is an async oneway callback that might be delivered after unregistration
1043         // anyway.
1044         for (CarPropertyServiceClient client : eventsToDispatch.keySet()) {
1045             try {
1046                 client.onEvent(eventsToDispatch.get(client));
1047             } catch (RemoteException ex) {
1048                 // If we cannot send a record, it's likely the connection snapped. Let binder
1049                 // death handle the situation.
1050                 Slogf.e(TAG, "onEvent calling failed: " + ex);
1051             }
1052         }
1053     }
1054 
1055     @Override
onPropertySetError(int property, int areaId, int errorCode)1056     public void onPropertySetError(int property, int areaId, int errorCode) {
1057         CarPropertyServiceClient lastOperatedClient = null;
1058         synchronized (mLock) {
1059             if (mSetOpClientByAreaIdByPropId.get(property) != null
1060                     && mSetOpClientByAreaIdByPropId.get(property).get(areaId) != null) {
1061                 lastOperatedClient = mSetOpClientByAreaIdByPropId.get(property).get(areaId);
1062             } else {
1063                 Slogf.e(TAG, "Can not find the client changed property ID: "
1064                         + VehiclePropertyIds.toString(property) + " in areaId: " + toAreaIdString(
1065                         property, areaId));
1066             }
1067 
1068         }
1069         if (lastOperatedClient != null) {
1070             try {
1071                 List<CarPropertyEvent> eventList = new ArrayList<>();
1072                 eventList.add(
1073                         CarPropertyEvent.createErrorEventWithErrorCode(property, areaId,
1074                                 errorCode));
1075                 // We want all the error events to be delivered to this client with no filtering.
1076                 lastOperatedClient.onFilteredEvents(eventList);
1077             } catch (RemoteException ex) {
1078                 Slogf.e(TAG, "onFilteredEvents calling failed: " + ex);
1079             }
1080         }
1081     }
1082 
validateGetSetAsyncParameters(AsyncPropertyServiceRequestList requests, IAsyncPropertyResultCallback asyncPropertyResultCallback, long timeoutInMs)1083     private static void validateGetSetAsyncParameters(AsyncPropertyServiceRequestList requests,
1084             IAsyncPropertyResultCallback asyncPropertyResultCallback,
1085             long timeoutInMs) throws IllegalArgumentException {
1086         requireNonNull(requests);
1087         requireNonNull(asyncPropertyResultCallback);
1088         Preconditions.checkArgument(timeoutInMs > 0, "timeoutInMs must be a positive number");
1089     }
1090 
1091     /**
1092      * Gets CarPropertyValues asynchronously.
1093      */
1094     @Override
getPropertiesAsync( AsyncPropertyServiceRequestList getPropertyServiceRequestsParcelable, IAsyncPropertyResultCallback asyncPropertyResultCallback, long timeoutInMs)1095     public void getPropertiesAsync(
1096             AsyncPropertyServiceRequestList getPropertyServiceRequestsParcelable,
1097             IAsyncPropertyResultCallback asyncPropertyResultCallback, long timeoutInMs) {
1098         validateGetSetAsyncParameters(getPropertyServiceRequestsParcelable,
1099                 asyncPropertyResultCallback, timeoutInMs);
1100         long currentTime = System.currentTimeMillis();
1101         List<AsyncPropertyServiceRequest> getPropertyServiceRequests =
1102                 getPropertyServiceRequestsParcelable.getList();
1103         for (int i = 0; i < getPropertyServiceRequests.size(); i++) {
1104             validateGetParameters(getPropertyServiceRequests.get(i).getPropertyId(),
1105                     getPropertyServiceRequests.get(i).getAreaId());
1106         }
1107         mPropertyHalService.getCarPropertyValuesAsync(getPropertyServiceRequests,
1108                 asyncPropertyResultCallback, timeoutInMs, currentTime);
1109         if (DBG) {
1110             Slogf.d(TAG, "Latency of getPropertyAsync is: %f", (float) (System
1111                     .currentTimeMillis() - currentTime));
1112         }
1113         mGetAsyncLatencyHistogram.logSample((float) (System.currentTimeMillis() - currentTime));
1114     }
1115 
1116     /**
1117      * Sets CarPropertyValues asynchronously.
1118      */
1119     @SuppressWarnings("FormatString")
1120     @Override
setPropertiesAsync(AsyncPropertyServiceRequestList setPropertyServiceRequests, IAsyncPropertyResultCallback asyncPropertyResultCallback, long timeoutInMs)1121     public void setPropertiesAsync(AsyncPropertyServiceRequestList setPropertyServiceRequests,
1122             IAsyncPropertyResultCallback asyncPropertyResultCallback,
1123             long timeoutInMs) {
1124         validateGetSetAsyncParameters(setPropertyServiceRequests, asyncPropertyResultCallback,
1125                 timeoutInMs);
1126         long currentTime = System.currentTimeMillis();
1127         List<AsyncPropertyServiceRequest> setPropertyServiceRequestList =
1128                 setPropertyServiceRequests.getList();
1129         for (int i = 0; i < setPropertyServiceRequestList.size(); i++) {
1130             AsyncPropertyServiceRequest request = setPropertyServiceRequestList.get(i);
1131             CarPropertyValue carPropertyValueToSet = request.getCarPropertyValue();
1132             int propertyId = request.getPropertyId();
1133             int valuePropertyId = carPropertyValueToSet.getPropertyId();
1134             int areaId = request.getAreaId();
1135             int valueAreaId = carPropertyValueToSet.getAreaId();
1136             String propertyName = VehiclePropertyIds.toString(propertyId);
1137             if (valuePropertyId != propertyId) {
1138                 throw new IllegalArgumentException(String.format(
1139                         "Property ID in request and CarPropertyValue mismatch: %s vs %s",
1140                         VehiclePropertyIds.toString(valuePropertyId), propertyName));
1141             }
1142             if (valueAreaId != areaId) {
1143                 throw new IllegalArgumentException(String.format(
1144                         "For property: %s, area ID in request and CarPropertyValue mismatch: %s vs"
1145                                 + " %s", propertyName, toAreaIdString(propertyId, valueAreaId),
1146                         toAreaIdString(propertyId, areaId)));
1147             }
1148             validateSetParameters(carPropertyValueToSet);
1149             if (request.isWaitForPropertyUpdate()) {
1150                 if (NOT_ALLOWED_WAIT_FOR_UPDATE_PROPERTIES.contains(propertyId)) {
1151                     throw new IllegalArgumentException("Property: "
1152                             + propertyName + " must set waitForPropertyUpdate to false");
1153                 }
1154                 validateGetParameters(propertyId, areaId);
1155             }
1156         }
1157         mPropertyHalService.setCarPropertyValuesAsync(setPropertyServiceRequestList,
1158                 asyncPropertyResultCallback, timeoutInMs, currentTime);
1159         if (DBG) {
1160             Slogf.d(TAG, "Latency of setPropertyAsync is: %f", (float) (System
1161                     .currentTimeMillis() - currentTime));
1162         }
1163         mSetAsyncLatencyHistogram.logSample((float) (System.currentTimeMillis() - currentTime));
1164     }
1165 
1166     @Override
getSupportedNoReadPermPropIds(int[] propertyIds)1167     public int[] getSupportedNoReadPermPropIds(int[] propertyIds) {
1168         List<Integer> noReadPermPropertyIds = new ArrayList<>();
1169         for (int propertyId : propertyIds) {
1170             if (getCarPropertyConfig(propertyId) == null) {
1171                 // Not supported
1172                 continue;
1173             }
1174             if (!mPropertyHalService.isReadable(mContext, propertyId)) {
1175                 noReadPermPropertyIds.add(propertyId);
1176             }
1177         }
1178         return ArrayUtils.convertToIntArray(noReadPermPropertyIds);
1179     }
1180 
1181     @Override
isSupportedAndHasWritePermissionOnly(int propertyId)1182     public boolean isSupportedAndHasWritePermissionOnly(int propertyId) {
1183         return getCarPropertyConfig(propertyId) != null
1184                 && mPropertyHalService.isWritable(mContext, propertyId)
1185                 && !mPropertyHalService.isReadable(mContext, propertyId);
1186     }
1187 
1188     /**
1189      * Cancel on-going async requests.
1190      *
1191      * @param serviceRequestIds A list of async get/set property request IDs.
1192      */
1193     @Override
cancelRequests(int[] serviceRequestIds)1194     public void cancelRequests(int[] serviceRequestIds) {
1195         mPropertyHalService.cancelRequests(serviceRequestIds);
1196     }
1197 
1198     /**
1199      * Gets the currently min/max supported value.
1200      *
1201      * @return The currently supported min/max value.
1202      * @throws IllegalArgumentException if [propertyId, areaId] is not supported.
1203      * @throws SecurityException if the caller does not have read and does not have write access
1204      *      for the property.
1205      * @throws ServiceSpecificException If VHAL returns error.
1206      */
1207     @Override
getMinMaxSupportedValue(int propertyId, int areaId)1208     public MinMaxSupportedPropertyValue getMinMaxSupportedValue(int propertyId, int areaId) {
1209         var areaIdConfig = verifyGetSupportedValueRequestAndGetAreaIdConfig(propertyId, areaId);
1210         return mPropertyHalService.getMinMaxSupportedValue(propertyId, areaId, areaIdConfig);
1211     }
1212 
1213     /**
1214      * Gets the currently supported values list.
1215      *
1216      * <p>The returned supported value list is in sorted ascending order if the property is of
1217      * type int32, int64 or float.
1218      *
1219      * @return The currently supported values list.
1220      * @throws IllegalArgumentException if [propertyId, areaId] is not supported.
1221      * @throws SecurityException if the caller does not have read and does not have write access
1222      *      for the property.
1223      * @throws ServiceSpecificException If VHAL returns error.
1224      */
1225     @Override
getSupportedValuesList(int propertyId, int areaId)1226     public @Nullable List<RawPropertyValue> getSupportedValuesList(int propertyId, int areaId) {
1227         var areaIdConfig = verifyGetSupportedValueRequestAndGetAreaIdConfig(propertyId, areaId);
1228         return mPropertyHalService.getSupportedValuesList(propertyId, areaId, areaIdConfig);
1229     }
1230 
1231     /**
1232      * Registers the callback to be called when the min/max supported value or supported values
1233      * list change.
1234      *
1235      * @throws IllegalArgumentException if one of the [propertyId, areaId]s are not supported.
1236      * @throws SecurityException if the caller does not have read and does not have write access
1237      *      for any of the requested property.
1238      * @throws ServiceSpecificException If VHAL returns error.
1239      */
1240     @Override
registerSupportedValuesChangeCallback(List<PropIdAreaId> propIdAreaIds, ISupportedValuesChangeCallback callback)1241     public void registerSupportedValuesChangeCallback(List<PropIdAreaId> propIdAreaIds,
1242             ISupportedValuesChangeCallback callback) {
1243         for (int i = 0; i < propIdAreaIds.size(); i++) {
1244             var propIdAreaId = propIdAreaIds.get(i);
1245             // Verify [propId, areaId] is supported and the caller has read or write permission.
1246             // This may throw IllegalArgumentException or SecurityException.
1247             verifyGetSupportedValueRequestAndGetAreaIdConfig(propIdAreaId.propId,
1248                     propIdAreaId.areaId);
1249         }
1250         mPropertyHalService.registerSupportedValuesChangeCallback(propIdAreaIds, callback);
1251     }
1252 
1253     /**
1254      * Unregisters the callback previously registered with registerSupportedValuesChangeCallback.
1255      *
1256      * Do nothing if the [propertyId, areaId]s were not previously registered.
1257      */
1258     @Override
unregisterSupportedValuesChangeCallback(List<PropIdAreaId> propIdAreaIds, ISupportedValuesChangeCallback callback)1259     public void unregisterSupportedValuesChangeCallback(List<PropIdAreaId> propIdAreaIds,
1260             ISupportedValuesChangeCallback callback) {
1261         mPropertyHalService.unregisterSupportedValuesChangeCallback(propIdAreaIds, callback);
1262     }
1263 
1264     /**
1265      * @throws IllegalArgumentException If the propertyId or areaId is not supported.
1266      * @throws SecurityException If caller does not have read and does not have write permission.
1267      */
verifyGetSupportedValueRequestAndGetAreaIdConfig( int propertyId, int areaId)1268     private AreaIdConfig<?> verifyGetSupportedValueRequestAndGetAreaIdConfig(
1269             int propertyId, int areaId) {
1270         var config = getCarPropertyConfig(propertyId);
1271         var propertyIdStr = VehiclePropertyIds.toString(propertyId);
1272         if (config == null) {
1273             throw new IllegalArgumentException("The property: " + propertyIdStr
1274                     + " is not supported");
1275         }
1276         // This will throw IllegalArgumentException if areaId is not supported.
1277         AreaIdConfig<?> areaIdConfig = config.getAreaIdConfig(areaId);
1278 
1279         if (!mPropertyHalService.isReadable(mContext, propertyId)
1280                 && !mPropertyHalService.isWritable(mContext, propertyId)) {
1281             throw new SecurityException("Caller missing read or write permission to access"
1282                     + " property: " + propertyIdStr);
1283         }
1284         return areaIdConfig;
1285     }
1286 
1287     @Override
registerRecordingListener(ICarPropertyEventListener callback)1288     public CarPropertyConfigList registerRecordingListener(ICarPropertyEventListener callback) {
1289         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_RECORD_VEHICLE_PROPERTIES);
1290         CarServiceUtils.assertBuildIsDebuggable();
1291         List<CarPropertyConfig> carPropertyConfigList = mPropertyHalService
1292                 .registerRecordingListener(callback);
1293         return new CarPropertyConfigList(carPropertyConfigList);
1294     }
1295 
1296     @Override
isRecordingVehicleProperties()1297     public boolean isRecordingVehicleProperties() {
1298         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_RECORD_VEHICLE_PROPERTIES);
1299         CarServiceUtils.assertBuildIsDebuggable();
1300         return mPropertyHalService.isRecordingVehicleProperties();
1301     }
1302 
1303     @Override
stopRecordingVehicleProperties(ICarPropertyEventListener callback)1304     public void stopRecordingVehicleProperties(ICarPropertyEventListener callback) {
1305         CarServiceUtils.assertBuildIsDebuggable();
1306         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_RECORD_VEHICLE_PROPERTIES);
1307         if (callback == null) {
1308             Slogf.w(TAG, "Callback is null, unable to unregister null callback");
1309             return;
1310         }
1311         mPropertyHalService.stopRecordingVehicleProperties(callback);
1312     }
1313 
1314     @Override
enableInjectionMode(int[] propertyIdsFromRealHardware)1315     public long enableInjectionMode(int[] propertyIdsFromRealHardware) {
1316         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_INJECT_VEHICLE_PROPERTIES);
1317         CarServiceUtils.assertBuildIsDebuggable();
1318         return mPropertyHalService.enableInjectionMode(Lists.asImmutableList(
1319                 propertyIdsFromRealHardware));
1320     }
1321 
1322     @Override
disableInjectionMode()1323     public void disableInjectionMode() {
1324         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_INJECT_VEHICLE_PROPERTIES);
1325         CarServiceUtils.assertBuildIsDebuggable();
1326         mPropertyHalService.disableInjectionMode();
1327     }
1328 
1329     @Override
isVehiclePropertyInjectionModeEnabled()1330     public boolean isVehiclePropertyInjectionModeEnabled() {
1331         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_INJECT_VEHICLE_PROPERTIES);
1332         CarServiceUtils.assertBuildIsDebuggable();
1333         return mPropertyHalService.isVehiclePropertyInjectionModeEnabled();
1334     }
1335 
1336     @Override
1337     @Nullable
getLastInjectedVehicleProperty(int propertyId)1338     public CarPropertyValue getLastInjectedVehicleProperty(int propertyId) {
1339         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_INJECT_VEHICLE_PROPERTIES);
1340         CarServiceUtils.assertBuildIsDebuggable();
1341         return mPropertyHalService.getLastInjectedVehicleProperty(propertyId);
1342     }
1343 
1344     @Override
injectVehicleProperties(List<CarPropertyValue> carPropertyValues)1345     public void injectVehicleProperties(List<CarPropertyValue> carPropertyValues) {
1346         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_INJECT_VEHICLE_PROPERTIES);
1347         CarServiceUtils.assertBuildIsDebuggable();
1348         mPropertyHalService.injectVehicleProperties(carPropertyValues);
1349     }
1350 
assertPropertyIsReadable(CarPropertyConfig<?> carPropertyConfig, int areaId)1351     private void assertPropertyIsReadable(CarPropertyConfig<?> carPropertyConfig,
1352             int areaId) {
1353         int accessLevel = mFeatureFlags.areaIdConfigAccess()
1354                 ? carPropertyConfig.getAreaIdConfig(areaId).getAccess()
1355                 : carPropertyConfig.getAccess();
1356         Preconditions.checkArgument(
1357                 accessLevel == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ
1358                         || accessLevel == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
1359                 "Property: %s is not readable at areaId: %d",
1360                 VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()), areaId);
1361     }
1362 
assertAreaIdIsSupported(CarPropertyConfig<?> carPropertyConfig, int areaId)1363     private static void assertAreaIdIsSupported(CarPropertyConfig<?> carPropertyConfig,
1364             int areaId) {
1365         Preconditions.checkArgument(ArrayUtils.contains(carPropertyConfig.getAreaIds(), areaId),
1366                 "area ID: " + toAreaIdString(carPropertyConfig.getPropertyId(), areaId)
1367                         + " not supported for property ID: "
1368                         + VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()));
1369     }
1370 
initializeHistogram()1371     private void initializeHistogram() {
1372         mConcurrentSyncOperationHistogram = mHistogramFactory.newUniformHistogram(
1373                 "automotive_os.value_concurrent_sync_operations",
1374                 /* binCount= */ 17, /* minValue= */ 0, /* exclusiveMaxValue= */ 17);
1375         mGetPropertySyncLatencyHistogram = mHistogramFactory.newScaledRangeHistogram(
1376                 "automotive_os.value_sync_get_property_latency",
1377                 /* binCount= */ 20, /* minValue= */ 0,
1378                 /* firstBinWidth= */ 2, /* scaleFactor= */ 1.5f);
1379         mSetPropertySyncLatencyHistogram = mHistogramFactory.newScaledRangeHistogram(
1380                 "automotive_os.value_sync_set_property_latency",
1381                 /* binCount= */ 20, /* minValue= */ 0,
1382                 /* firstBinWidth= */ 2, /* scaleFactor= */ 1.5f);
1383         mSubscriptionUpdateRateHistogram = mHistogramFactory.newUniformHistogram(
1384                 "automotive_os.value_subscription_update_rate",
1385                 /* binCount= */ 101, /* minValue= */ 0, /* exclusiveMaxValue= */ 101);
1386         mGetAsyncLatencyHistogram = mHistogramFactory.newUniformHistogram(
1387                 "automotive_os.value_get_async_latency",
1388                 /* binCount= */ 20, /* minValue= */ 0, /* exclusiveMaxValue= */ 1000);
1389         mSetAsyncLatencyHistogram = mHistogramFactory.newUniformHistogram(
1390                 "automotive_os.value_set_async_latency",
1391                 /* binCount= */ 20, /* minValue= */ 0, /* exclusiveMaxValue= */ 1000);
1392     }
1393 
1394     @Nullable
getCarPropertyConfig(int propertyId)1395     private CarPropertyConfig<?> getCarPropertyConfig(int propertyId) {
1396         CarPropertyConfig<?> carPropertyConfig;
1397         synchronized (mLock) {
1398             carPropertyConfig = mPropertyIdToCarPropertyConfig.get(propertyId);
1399         }
1400         return carPropertyConfig;
1401     }
1402 
assertReadPermissionGranted(int propertyId)1403     private void assertReadPermissionGranted(int propertyId) {
1404         if (!mPropertyHalService.isReadable(mContext, propertyId)) {
1405             throw new SecurityException(
1406                     "Platform does not have permission to read value for property ID: "
1407                             + VehiclePropertyIds.toString(propertyId));
1408         }
1409     }
1410 
assertConfigNotNullAndGetConfig(int propertyId)1411     private CarPropertyConfig assertConfigNotNullAndGetConfig(int propertyId) {
1412         CarPropertyConfig<?> carPropertyConfig = getCarPropertyConfig(propertyId);
1413         Preconditions.checkArgument(carPropertyConfig != null,
1414                 "property ID is not in carPropertyConfig list, and so it is not supported: %s",
1415                 VehiclePropertyIds.toString(propertyId));
1416         return carPropertyConfig;
1417     }
1418 
assertIfReadableAtAreaIds(CarPropertyConfig<?> carPropertyConfig, int[] areaIds)1419     private void assertIfReadableAtAreaIds(CarPropertyConfig<?> carPropertyConfig, int[] areaIds) {
1420         for (int i = 0; i < areaIds.length; i++) {
1421             assertAreaIdIsSupported(carPropertyConfig, areaIds[i]);
1422             assertPropertyIsReadable(carPropertyConfig, areaIds[i]);
1423         }
1424         assertReadPermissionGranted(carPropertyConfig.getPropertyId());
1425     }
1426 
validateRegisterParameterAndGetConfig(int propertyId, int[] areaIds)1427     private CarPropertyConfig validateRegisterParameterAndGetConfig(int propertyId,
1428             int[] areaIds) {
1429         CarPropertyConfig<?> carPropertyConfig = assertConfigNotNullAndGetConfig(propertyId);
1430         Preconditions.checkArgument(areaIds != null, "AreaIds must not be null");
1431         Preconditions.checkArgument(areaIds.length != 0, "AreaIds must not be empty");
1432         assertIfReadableAtAreaIds(carPropertyConfig, areaIds);
1433         return carPropertyConfig;
1434     }
1435 
validateGetParameters(int propertyId, int areaId)1436     private void validateGetParameters(int propertyId, int areaId) {
1437         CarPropertyConfig<?> carPropertyConfig = assertConfigNotNullAndGetConfig(propertyId);
1438         assertAreaIdIsSupported(carPropertyConfig, areaId);
1439         assertPropertyIsReadable(carPropertyConfig, areaId);
1440         assertReadPermissionGranted(propertyId);
1441     }
1442 
validateSetParameters(CarPropertyValue<?> carPropertyValue)1443     private void validateSetParameters(CarPropertyValue<?> carPropertyValue) {
1444         requireNonNull(carPropertyValue);
1445         int propertyId = carPropertyValue.getPropertyId();
1446         int areaId = carPropertyValue.getAreaId();
1447         Object valueToSet = carPropertyValue.getValue();
1448         CarPropertyConfig<?> carPropertyConfig = assertConfigNotNullAndGetConfig(propertyId);
1449         assertAreaIdIsSupported(carPropertyConfig, areaId);
1450 
1451         // Assert property is writable.
1452         int accessLevel = mFeatureFlags.areaIdConfigAccess()
1453                 ? carPropertyConfig.getAreaIdConfig(areaId).getAccess()
1454                 : carPropertyConfig.getAccess();
1455         Preconditions.checkArgument(
1456                 accessLevel == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE
1457                         || accessLevel == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
1458                 "Property: %s is not writable at areaId: %d",
1459                 VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()), areaId);
1460 
1461         // Assert write permission is granted.
1462         if (!mPropertyHalService.isWritable(mContext, propertyId)) {
1463             throw new SecurityException(
1464                     "Platform does not have permission to write value for property ID: "
1465                             + VehiclePropertyIds.toString(propertyId));
1466         }
1467 
1468         // Assert set value is valid for property.
1469         Preconditions.checkArgument(valueToSet != null,
1470                 "setProperty: CarPropertyValue's must not be null - property ID: %s area ID: %s",
1471                 VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()),
1472                 toAreaIdString(carPropertyConfig.getPropertyId(), areaId));
1473         Preconditions.checkArgument(
1474                 valueToSet.getClass().equals(carPropertyConfig.getPropertyType()),
1475                 "setProperty: CarPropertyValue's value's type does not match property's type. - "
1476                         + "property ID: %s area ID: %s",
1477                 VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()),
1478                 toAreaIdString(carPropertyConfig.getPropertyId(), areaId));
1479 
1480         validateValueRange(carPropertyValue, carPropertyConfig);
1481 
1482         if (PROPERTY_ID_TO_UNWRITABLE_STATES.contains(carPropertyConfig.getPropertyId())) {
1483             Preconditions.checkArgument(!(PROPERTY_ID_TO_UNWRITABLE_STATES
1484                             .get(carPropertyConfig.getPropertyId()).contains(valueToSet)),
1485                     "setProperty: value to set: %s must not be an unwritable state value. - "
1486                             + "property ID: %s area ID: %s unwritable states: %s",
1487                     valueToSet,
1488                     VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()),
1489                     toAreaIdString(carPropertyConfig.getPropertyId(), areaId),
1490                     PROPERTY_ID_TO_UNWRITABLE_STATES.get(carPropertyConfig.getPropertyId()));
1491         }
1492     }
1493 
validateValueRange(CarPropertyValue<?> carPropertyValue, CarPropertyConfig<?> carPropertyConfig)1494     private void validateValueRange(CarPropertyValue<?> carPropertyValue,
1495             CarPropertyConfig<?> carPropertyConfig) {
1496         int propertyId = carPropertyValue.getPropertyId();
1497         int areaId = carPropertyValue.getAreaId();
1498         AreaIdConfig<?> areaIdConfig = carPropertyConfig.getAreaIdConfig(areaId);
1499         Object valueToSet = carPropertyValue.getValue();
1500 
1501         if (!mFeatureFlags.carPropertySupportedValue()) {
1502             validateValueRangeBasedOnConfig(carPropertyConfig, areaIdConfig, valueToSet);
1503             return;
1504         }
1505 
1506         if (areaIdConfig.hasMinSupportedValue() || areaIdConfig.hasMaxSupportedValue()) {
1507             MinMaxSupportedPropertyValue minMaxSupportedPropertyValue =
1508                     getMinMaxSupportedValue(propertyId, areaId);
1509             RawPropertyValue minRawPropertyValue = mMinMaxSupportedPropertyValueHelper
1510                     .getMinValue(minMaxSupportedPropertyValue);
1511             RawPropertyValue maxRawPropertyValue = mMinMaxSupportedPropertyValueHelper
1512                     .getMaxValue(minMaxSupportedPropertyValue);
1513             if (areaIdConfig.hasMinSupportedValue() && minRawPropertyValue != null) {
1514                 Object minValue = minRawPropertyValue.getTypedValue();
1515                 Preconditions.checkArgument(isGreaterThanOrEqualTo(
1516                         carPropertyConfig.getPropertyType(), valueToSet, minValue),
1517                         "setProperty: value to set must be greater than or equal to the area ID min"
1518                                 + " value. - " + "property ID: %s, area ID: %s, valueToSet: %s, "
1519                                 + "min value: %s",
1520                         VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()),
1521                         toAreaIdString(carPropertyConfig.getPropertyId(), areaId),
1522                         valueToSet, minValue);
1523             }
1524             if (areaIdConfig.hasMaxSupportedValue() && maxRawPropertyValue != null) {
1525                 Object maxValue = maxRawPropertyValue.getTypedValue();
1526                 Preconditions.checkArgument(isLessThanOrEqualTo(
1527                         carPropertyConfig.getPropertyType(), valueToSet, maxValue),
1528                         "setProperty: value to set must be less than or equal to the area ID max "
1529                                 + "value. - " + "property ID: %s area ID: %s, valueToSet: %s, "
1530                                 + "max value: %s",
1531                         VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()),
1532                         toAreaIdString(carPropertyConfig.getPropertyId(), areaId),
1533                         valueToSet, maxValue);
1534             }
1535         }
1536 
1537         if (areaIdConfig.hasSupportedValuesList()) {
1538             List<RawPropertyValue> supportedValues = getSupportedValuesList(propertyId, areaId);
1539             if (supportedValues != null) {
1540                 boolean found = false;
1541                 for (int i = 0; i < supportedValues.size(); i++) {
1542                     if (isEqualTo(carPropertyConfig.getPropertyType(), valueToSet,
1543                             supportedValues.get(i).getTypedValue())) {
1544                         found = true;
1545                         break;
1546                     }
1547                 }
1548                 Preconditions.checkArgument(found,
1549                         "setProperty: value to set must exist in set of supported values. - "
1550                                 + "value. - " + "property ID: %s area ID: %s, valueToSet: %s, "
1551                                 + "supported values: %s",
1552                         VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()),
1553                         toAreaIdString(carPropertyConfig.getPropertyId(), areaId),
1554                         valueToSet, Arrays.toString(supportedValues.toArray()));
1555             }
1556         }
1557     }
1558 
validateValueRangeBasedOnConfig(CarPropertyConfig<?> carPropertyConfig, AreaIdConfig<?> areaIdConfig, Object valueToSet)1559     private void validateValueRangeBasedOnConfig(CarPropertyConfig<?> carPropertyConfig,
1560             AreaIdConfig<?> areaIdConfig, Object valueToSet) {
1561         int areaId = areaIdConfig.getAreaId();
1562         if (areaIdConfig.getMinValue() != null) {
1563             Preconditions.checkArgument(isGreaterThanOrEqualTo(carPropertyConfig.getPropertyType(),
1564                     valueToSet, areaIdConfig.getMinValue()),
1565                     "setProperty: value to set must be greater than or equal to the area ID min "
1566                             + "value. - " + "property ID: %s area ID: %s min value: %s",
1567                     VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()),
1568                     toAreaIdString(carPropertyConfig.getPropertyId(), areaId),
1569                     areaIdConfig.getMinValue());
1570 
1571         }
1572 
1573         if (areaIdConfig.getMaxValue() != null) {
1574             Preconditions.checkArgument(isLessThanOrEqualTo(carPropertyConfig.getPropertyType(),
1575                     valueToSet, areaIdConfig.getMaxValue()),
1576                     "setProperty: value to set must be less than or equal to the area ID max "
1577                             + "value. - " + "property ID: %s area ID: %s min value: %s",
1578                     VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()),
1579                     toAreaIdString(carPropertyConfig.getPropertyId(), areaId),
1580                     areaIdConfig.getMaxValue());
1581 
1582         }
1583 
1584         if (!areaIdConfig.getSupportedEnumValues().isEmpty()) {
1585             Preconditions.checkArgument(areaIdConfig.getSupportedEnumValues().contains(valueToSet),
1586                     "setProperty: value to set must exist in set of supported enum values. - "
1587                             + "property ID: %s area ID: %s supported enum values: %s",
1588                     VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()),
1589                     toAreaIdString(carPropertyConfig.getPropertyId(), areaId),
1590                     areaIdConfig.getSupportedEnumValues());
1591         }
1592     }
1593 
isGreaterThanOrEqualTo(Class<?> clazz, Object left, Object right)1594     private static boolean isGreaterThanOrEqualTo(Class<?> clazz, Object left, Object right) {
1595         if (clazz.equals(Integer.class)) {
1596             return (Integer) left >= (Integer) right;
1597         } else if (clazz.equals(Long.class)) {
1598             return (Long) left >= (Long) right;
1599         } else if (clazz.equals(Float.class)) {
1600             return (Float) left >= (Float) right - EPSILON;
1601         }
1602         // We don't check for other type of properties.
1603         return true;
1604     }
1605 
isLessThanOrEqualTo(Class<?> clazz, Object left, Object right)1606     private static boolean isLessThanOrEqualTo(Class<?> clazz, Object left, Object right) {
1607         if (clazz.equals(Integer.class)) {
1608             return (Integer) left <= (Integer) right;
1609         } else if (clazz.equals(Long.class)) {
1610             return (Long) left <= (Long) right;
1611         } else if (clazz.equals(Float.class)) {
1612             return (Float) left <= (Float) right + EPSILON;
1613         }
1614         // We don't check for other type of properties.
1615         return true;
1616     }
1617 
isEqualTo(Class<?> clazz, Object left, Object right)1618     private static boolean isEqualTo(Class<?> clazz, Object left, Object right) {
1619         if (clazz.equals(Integer.class)) {
1620             return ((Integer) left).equals((Integer) right);
1621         } else if (clazz.equals(Long.class)) {
1622             return ((Long) left).equals((Long) right);
1623         } else if (clazz.equals(Float.class)) {
1624             return Math.abs((Float) left - (Float) right) < EPSILON;
1625         }
1626         // We don't check for other type of properties.
1627         return true;
1628     }
1629 }
1630