• 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 com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
20 import static com.android.car.internal.property.CarPropertyHelper.SYNC_OP_LIMIT_TRY_AGAIN;
21 
22 import static java.lang.Integer.toHexString;
23 import static java.util.Objects.requireNonNull;
24 
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.car.Car;
28 import android.car.VehiclePropertyIds;
29 import android.car.builtin.os.TraceHelper;
30 import android.car.builtin.util.Slogf;
31 import android.car.hardware.CarHvacFanDirection;
32 import android.car.hardware.CarPropertyConfig;
33 import android.car.hardware.CarPropertyValue;
34 import android.car.hardware.property.AreaIdConfig;
35 import android.car.hardware.property.CarPropertyEvent;
36 import android.car.hardware.property.CruiseControlType;
37 import android.car.hardware.property.ErrorState;
38 import android.car.hardware.property.EvStoppingMode;
39 import android.car.hardware.property.ICarProperty;
40 import android.car.hardware.property.ICarPropertyEventListener;
41 import android.car.hardware.property.WindshieldWipersSwitch;
42 import android.content.Context;
43 import android.os.Handler;
44 import android.os.HandlerThread;
45 import android.os.IBinder;
46 import android.os.RemoteException;
47 import android.os.ServiceSpecificException;
48 import android.os.SystemClock;
49 import android.os.Trace;
50 import android.util.ArrayMap;
51 import android.util.Pair;
52 import android.util.SparseArray;
53 
54 import com.android.car.hal.PropertyHalService;
55 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
56 import com.android.car.internal.property.AsyncPropertyServiceRequest;
57 import com.android.car.internal.property.AsyncPropertyServiceRequestList;
58 import com.android.car.internal.property.CarPropertyConfigList;
59 import com.android.car.internal.property.CarPropertyHelper;
60 import com.android.car.internal.property.IAsyncPropertyResultCallback;
61 import com.android.car.internal.property.InputSanitizationUtils;
62 import com.android.car.internal.util.ArrayUtils;
63 import com.android.car.internal.util.IndentingPrintWriter;
64 import com.android.internal.annotations.GuardedBy;
65 import com.android.internal.util.Preconditions;
66 
67 import java.util.ArrayList;
68 import java.util.Arrays;
69 import java.util.HashSet;
70 import java.util.List;
71 import java.util.Map;
72 import java.util.Set;
73 import java.util.concurrent.Callable;
74 
75 /**
76  * This class implements the binder interface for ICarProperty.aidl to make it easier to create
77  * multiple managers that deal with Vehicle Properties. The property Ids in this class are IDs in
78  * manager level.
79  */
80 public class CarPropertyService extends ICarProperty.Stub
81         implements CarServiceBase, PropertyHalService.PropertyHalListener {
82     private static final boolean DBG = false;
83     private static final String TAG = CarLog.tagFor(CarPropertyService.class);
84     // Maximum count of sync get/set property operation allowed at once. The reason we limit this
85     // is because each sync get/set property operation takes up one binder thread. If they take
86     // all the binder thread, we do not have thread left for the result callback from VHAL. This
87     // will cause all the pending sync operation to timeout because result cannot be delivered.
88     private static final int SYNC_GET_SET_PROPERTY_OP_LIMIT = 16;
89     private static final long TRACE_TAG = TraceHelper.TRACE_TAG_CAR_SERVICE;
90     // A list of properties that must not set waitForPropertyUpdate to {@code true} for set async.
91     private static final Set<Integer> NOT_ALLOWED_WAIT_FOR_UPDATE_PROPERTIES =
92             new HashSet<>(Arrays.asList(
93                 VehiclePropertyIds.HVAC_TEMPERATURE_VALUE_SUGGESTION
94             ));
95 
96     private static final Set<Integer> ERROR_STATES =
97             new HashSet<Integer>(Arrays.asList(
98                     ErrorState.OTHER_ERROR_STATE,
99                     ErrorState.NOT_AVAILABLE_DISABLED,
100                     ErrorState.NOT_AVAILABLE_SPEED_LOW,
101                     ErrorState.NOT_AVAILABLE_SPEED_HIGH,
102                     ErrorState.NOT_AVAILABLE_SAFETY
103             ));
104     private static final Set<Integer> CAR_HVAC_FAN_DIRECTION_UNWRITABLE_STATES =
105             new HashSet<Integer>(Arrays.asList(
106                     CarHvacFanDirection.UNKNOWN
107             ));
108     private static final Set<Integer> CRUISE_CONTROL_TYPE_UNWRITABLE_STATES =
109             new HashSet<Integer>(Arrays.asList(
110                     CruiseControlType.OTHER
111             ));
112     static {
113         CRUISE_CONTROL_TYPE_UNWRITABLE_STATES.addAll(ERROR_STATES);
114     }
115     private static final Set<Integer> EV_STOPPING_MODE_UNWRITABLE_STATES =
116             new HashSet<Integer>(Arrays.asList(
117                     EvStoppingMode.STATE_OTHER
118             ));
119     private static final Set<Integer> WINDSHIELD_WIPERS_SWITCH_UNWRITABLE_STATES =
120             new HashSet<Integer>(Arrays.asList(
121                     WindshieldWipersSwitch.OTHER
122             ));
123 
124     private static final SparseArray<Set<Integer>> PROPERTY_ID_TO_UNWRITABLE_STATES =
125             new SparseArray<>();
126     static {
PROPERTY_ID_TO_UNWRITABLE_STATES.put( VehiclePropertyIds.CRUISE_CONTROL_TYPE, CRUISE_CONTROL_TYPE_UNWRITABLE_STATES)127         PROPERTY_ID_TO_UNWRITABLE_STATES.put(
128                 VehiclePropertyIds.CRUISE_CONTROL_TYPE,
129                 CRUISE_CONTROL_TYPE_UNWRITABLE_STATES);
PROPERTY_ID_TO_UNWRITABLE_STATES.put( VehiclePropertyIds.EV_STOPPING_MODE, EV_STOPPING_MODE_UNWRITABLE_STATES)130         PROPERTY_ID_TO_UNWRITABLE_STATES.put(
131                 VehiclePropertyIds.EV_STOPPING_MODE,
132                 EV_STOPPING_MODE_UNWRITABLE_STATES);
PROPERTY_ID_TO_UNWRITABLE_STATES.put( VehiclePropertyIds.HVAC_FAN_DIRECTION, CAR_HVAC_FAN_DIRECTION_UNWRITABLE_STATES)133         PROPERTY_ID_TO_UNWRITABLE_STATES.put(
134                 VehiclePropertyIds.HVAC_FAN_DIRECTION,
135                 CAR_HVAC_FAN_DIRECTION_UNWRITABLE_STATES);
PROPERTY_ID_TO_UNWRITABLE_STATES.put( VehiclePropertyIds.WINDSHIELD_WIPERS_SWITCH, WINDSHIELD_WIPERS_SWITCH_UNWRITABLE_STATES)136         PROPERTY_ID_TO_UNWRITABLE_STATES.put(
137                 VehiclePropertyIds.WINDSHIELD_WIPERS_SWITCH,
138                 WINDSHIELD_WIPERS_SWITCH_UNWRITABLE_STATES);
139     }
140 
141     private final Context mContext;
142     private final PropertyHalService mPropertyHalService;
143     private final Object mLock = new Object();
144     @GuardedBy("mLock")
145     private final Map<IBinder, Client> mClientMap = new ArrayMap<>();
146     @GuardedBy("mLock")
147     private final SparseArray<List<Client>> mPropIdClientMap = new SparseArray<>();
148     @GuardedBy("mLock")
149     private final SparseArray<SparseArray<Client>> mSetOperationClientMap = new SparseArray<>();
150     private final HandlerThread mHandlerThread =
151             CarServiceUtils.getHandlerThread(getClass().getSimpleName());
152     private final Handler mHandler = new Handler(mHandlerThread.getLooper());
153     // Use SparseArray instead of map to save memory.
154     @GuardedBy("mLock")
155     private SparseArray<CarPropertyConfig<?>> mPropertyIdToCarPropertyConfig = new SparseArray<>();
156     @GuardedBy("mLock")
157     private SparseArray<Pair<String, String>> mPropToPermission = new SparseArray<>();
158     @GuardedBy("mLock")
159     private int mSyncGetSetPropertyOpCount;
160 
CarPropertyService(Context context, PropertyHalService propertyHalService)161     public CarPropertyService(Context context, PropertyHalService propertyHalService) {
162         if (DBG) {
163             Slogf.d(TAG, "CarPropertyService started!");
164         }
165         mPropertyHalService = propertyHalService;
166         mContext = context;
167     }
168 
169     // Helper class to keep track of listeners to this service.
170     private final class Client implements IBinder.DeathRecipient {
171         private final ICarPropertyEventListener mListener;
172         private final IBinder mListenerBinder;
173         private final Object mLock = new Object();
174         // propId->rate map.
175         @GuardedBy("mLock")
176         private final SparseArray<Float> mRateMap = new SparseArray<>();
177         @GuardedBy("mLock")
178         private boolean mIsDead = false;
179 
Client(ICarPropertyEventListener listener)180         Client(ICarPropertyEventListener listener) {
181             mListener = listener;
182             mListenerBinder = listener.asBinder();
183 
184             try {
185                 mListenerBinder.linkToDeath(this, 0);
186             } catch (RemoteException e) {
187                 mIsDead = true;
188             }
189         }
190 
191         /**
192          * Returns whether this client is already dead.
193          *
194          * Caller should not assume this client is alive after getting True response because the
195          * binder might die after this function checks the status. Caller should only use this
196          * function to fail early.
197          */
isDead()198         boolean isDead() {
199             synchronized (mLock) {
200                 return mIsDead;
201             }
202         }
203 
addProperty(int propId, float rate)204         void addProperty(int propId, float rate) {
205             synchronized (mLock) {
206                 if (mIsDead) {
207                     return;
208                 }
209                 mRateMap.put(propId, rate);
210             }
211         }
212 
getRate(int propId)213         float getRate(int propId) {
214             synchronized (mLock) {
215                 // Return 0 if no key found, since that is the slowest rate.
216                 return mRateMap.get(propId, 0.0f);
217             }
218         }
219 
removeProperty(int propId)220         int removeProperty(int propId) {
221             synchronized (mLock) {
222                 mRateMap.remove(propId);
223                 if (mRateMap.size() == 0) {
224                     mListenerBinder.unlinkToDeath(this, 0);
225                 }
226                 return mRateMap.size();
227             }
228         }
229 
230         /**
231          * Handler to be called when client died.
232          *
233          * Remove the listener from HAL service and unregister if this is the last client.
234          */
235         @Override
binderDied()236         public void binderDied() {
237             List<Integer> propIds = new ArrayList<>();
238             synchronized (mLock) {
239                 mIsDead = true;
240 
241                 if (DBG) {
242                     Slogf.d(TAG, "binderDied %s", mListenerBinder);
243                 }
244 
245                 // Because we set mIsDead to true here, we are sure mRateMap would not have new
246                 // elements. The propIds here is going to cover all the prop Ids that we need to
247                 // unregister.
248                 for (int i = 0; i < mRateMap.size(); i++) {
249                     propIds.add(mRateMap.keyAt(i));
250                 }
251             }
252 
253             CarPropertyService.this.unregisterListenerBinderForProps(propIds, mListenerBinder);
254         }
255 
256         /**
257          * Calls onEvent function on the listener if the binder is alive.
258          *
259          * There is still chance when onEvent might fail because binderDied is not called before
260          * this function.
261          */
onEvent(List<CarPropertyEvent> events)262         void onEvent(List<CarPropertyEvent> events) throws RemoteException {
263             synchronized (mLock) {
264                 if (mIsDead) {
265                     return;
266                 }
267             }
268             mListener.onEvent(events);
269         }
270     }
271 
272     @Override
init()273     public void init() {
274         synchronized (mLock) {
275             // Cache the configs list and permissions to avoid subsequent binder calls
276             mPropertyIdToCarPropertyConfig = mPropertyHalService.getPropertyList();
277             mPropToPermission = mPropertyHalService.getPermissionsForAllProperties();
278             if (DBG) {
279                 Slogf.d(TAG, "cache CarPropertyConfigs " + mPropertyIdToCarPropertyConfig.size());
280             }
281         }
282         mPropertyHalService.setPropertyHalListener(this);
283     }
284 
285     @Override
release()286     public void release() {
287         synchronized (mLock) {
288             mClientMap.clear();
289             mPropIdClientMap.clear();
290             mPropertyHalService.setPropertyHalListener(null);
291             mSetOperationClientMap.clear();
292         }
293     }
294 
295     @Override
296     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)297     public void dump(IndentingPrintWriter writer) {
298         writer.println("*CarPropertyService*");
299         writer.increaseIndent();
300         synchronized (mLock) {
301             writer.println(String.format("There are %d clients using CarPropertyService.",
302                     mClientMap.size()));
303             writer.println("Current sync operation count: " + mSyncGetSetPropertyOpCount);
304             writer.println("Properties registered: ");
305             writer.increaseIndent();
306             for (int i = 0; i < mPropIdClientMap.size(); i++) {
307                 int propId = mPropIdClientMap.keyAt(i);
308                 writer.println("propId: 0x" + toHexString(propId)
309                         + " is registered by " + mPropIdClientMap.valueAt(i).size()
310                         + " client(s).");
311             }
312             writer.decreaseIndent();
313             writer.println("Properties changed by CarPropertyService: ");
314             writer.increaseIndent();
315             for (int i = 0; i < mSetOperationClientMap.size(); i++) {
316                 int propId = mSetOperationClientMap.keyAt(i);
317                 SparseArray areaIdToClient = mSetOperationClientMap.valueAt(i);
318                 for (int j = 0; j < areaIdToClient.size(); j++) {
319                     int areaId = areaIdToClient.keyAt(j);
320                     writer.println(String.format("propId: 0x%s areaId: 0x%s by client: %s",
321                             toHexString(propId), toHexString(areaId), areaIdToClient.valueAt(j)));
322                 }
323             }
324             writer.decreaseIndent();
325         }
326         writer.decreaseIndent();
327     }
328 
329     @Override
registerListener(int propertyId, float updateRateHz, ICarPropertyEventListener iCarPropertyEventListener)330     public void registerListener(int propertyId, float updateRateHz,
331             ICarPropertyEventListener iCarPropertyEventListener) throws IllegalArgumentException {
332         requireNonNull(iCarPropertyEventListener);
333         validateRegisterParameter(propertyId);
334 
335         CarPropertyConfig<?> carPropertyConfig = getCarPropertyConfig(propertyId);
336 
337         float sanitizedUpdateRateHz = InputSanitizationUtils.sanitizeUpdateRateHz(carPropertyConfig,
338                 updateRateHz);
339 
340         if (DBG) {
341             Slogf.d(TAG, "registerListener: property ID=" + VehiclePropertyIds.toString(propertyId)
342                     + " updateRateHz=" + sanitizedUpdateRateHz);
343         }
344 
345         Client finalClient;
346         synchronized (mLock) {
347             // Get or create the client for this iCarPropertyEventListener
348             IBinder listenerBinder = iCarPropertyEventListener.asBinder();
349             Client client = mClientMap.get(listenerBinder);
350             if (client == null) {
351                 client = new Client(iCarPropertyEventListener);
352                 if (client.isDead()) {
353                     Slogf.w(TAG, "the ICarPropertyEventListener is already dead");
354                     return;
355                 }
356                 mClientMap.put(listenerBinder, client);
357             }
358             client.addProperty(propertyId, sanitizedUpdateRateHz);
359             // Insert the client into the propertyId --> clients map
360             List<Client> clients = mPropIdClientMap.get(propertyId);
361             if (clients == null) {
362                 clients = new ArrayList<>();
363                 mPropIdClientMap.put(propertyId, clients);
364             }
365             if (!clients.contains(client)) {
366                 clients.add(client);
367             }
368             // Set the new updateRateHz
369             if (sanitizedUpdateRateHz > mPropertyHalService.getSubscribedUpdateRateHz(propertyId)) {
370                 mPropertyHalService.subscribeProperty(propertyId, sanitizedUpdateRateHz);
371             }
372             finalClient = client;
373         }
374 
375         // propertyConfig and client are NonNull.
376         mHandler.post(() ->
377                 getAndDispatchPropertyInitValue(carPropertyConfig, finalClient));
378     }
379 
380     /**
381      * Register property listener for car service's internal usage.
382      */
registerListenerSafe(int propertyId, float updateRateHz, ICarPropertyEventListener iCarPropertyEventListener)383     public boolean registerListenerSafe(int propertyId, float updateRateHz,
384             ICarPropertyEventListener iCarPropertyEventListener) {
385         try {
386             registerListener(propertyId, updateRateHz, iCarPropertyEventListener);
387             return true;
388         } catch (Exception e) {
389             Slogf.e(TAG, e, "registerListenerSafe() failed for property ID: %s updateRateHz: %f",
390                     VehiclePropertyIds.toString(propertyId), updateRateHz);
391             return false;
392         }
393     }
394 
getAndDispatchPropertyInitValue(CarPropertyConfig carPropertyConfig, Client client)395     private void getAndDispatchPropertyInitValue(CarPropertyConfig carPropertyConfig,
396             Client client) {
397         List<CarPropertyEvent> events = new ArrayList<>();
398         int propertyId = carPropertyConfig.getPropertyId();
399         for (int areaId : carPropertyConfig.getAreaIds()) {
400             CarPropertyValue carPropertyValue = null;
401             try {
402                 carPropertyValue = getProperty(propertyId, areaId);
403             } catch (ServiceSpecificException e) {
404                 Slogf.w("Get initial carPropertyValue for registerCallback failed - property ID: "
405                                 + "%s, area ID $s, exception: %s",
406                         VehiclePropertyIds.toString(propertyId), Integer.toHexString(areaId), e);
407                 int errorCode = CarPropertyHelper.getVhalSystemErrorCode(e.errorCode);
408                 long timestampNanos = SystemClock.elapsedRealtimeNanos();
409                 Object defaultValue = CarPropertyHelper.getDefaultValue(
410                         carPropertyConfig.getPropertyType());
411                 if (CarPropertyHelper.isNotAvailableVehicleHalStatusCode(errorCode)) {
412                     carPropertyValue = new CarPropertyValue<>(propertyId, areaId,
413                             CarPropertyValue.STATUS_UNAVAILABLE, timestampNanos, defaultValue);
414                 } else {
415                     carPropertyValue = new CarPropertyValue<>(propertyId, areaId,
416                             CarPropertyValue.STATUS_ERROR, timestampNanos, defaultValue);
417                 }
418             } catch (Exception e) {
419                 // Do nothing.
420                 Slogf.e("Get initial carPropertyValue for registerCallback failed - property ID: "
421                                 + "%s, area ID $s, exception: %s",
422                         VehiclePropertyIds.toString(propertyId), Integer.toHexString(areaId), e);
423             }
424             if (carPropertyValue != null) {
425                 CarPropertyEvent event = new CarPropertyEvent(
426                         CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, carPropertyValue);
427                 events.add(event);
428             }
429         }
430         if (events.isEmpty()) {
431             return;
432         }
433         try {
434             client.onEvent(events);
435         } catch (RemoteException ex) {
436             // If we cannot send a record, it's likely the connection snapped. Let the binder
437             // death handle the situation.
438             Slogf.e(TAG, "onEvent calling failed", ex);
439         }
440     }
441 
442     @Override
unregisterListener(int propertyId, ICarPropertyEventListener iCarPropertyEventListener)443     public void unregisterListener(int propertyId,
444             ICarPropertyEventListener iCarPropertyEventListener) {
445         requireNonNull(iCarPropertyEventListener);
446         validateRegisterParameter(propertyId);
447 
448         if (DBG) {
449             Slogf.d(TAG,
450                     "unregisterListener property ID=" + VehiclePropertyIds.toString(propertyId));
451         }
452 
453         IBinder listenerBinder = iCarPropertyEventListener.asBinder();
454         unregisterListenerBinderForProps(List.of(propertyId), listenerBinder);
455     }
456 
457     /**
458      * Unregister property listener for car service's internal usage.
459      */
unregisterListenerSafe(int propertyId, ICarPropertyEventListener iCarPropertyEventListener)460     public boolean unregisterListenerSafe(int propertyId,
461             ICarPropertyEventListener iCarPropertyEventListener) {
462         try {
463             unregisterListener(propertyId, iCarPropertyEventListener);
464             return true;
465         } catch (Exception e) {
466             Slogf.e(TAG, e, "unregisterListenerSafe() failed for property ID: %s",
467                     VehiclePropertyIds.toString(propertyId));
468             return false;
469         }
470     }
471 
472 
473     @GuardedBy("mLock")
unregisterListenerBinderLocked(int propId, IBinder listenerBinder)474     private void unregisterListenerBinderLocked(int propId, IBinder listenerBinder) {
475         float updateMaxRate = 0f;
476         Client client = mClientMap.get(listenerBinder);
477         List<Client> propertyClients = mPropIdClientMap.get(propId);
478         if (mPropertyIdToCarPropertyConfig.get(propId) == null) {
479             // Do not attempt to unregister an invalid propId
480             Slogf.e(TAG, "unregisterListener: propId is not in config list:0x%s",
481                     toHexString(propId));
482             return;
483         }
484         if ((client == null) || (propertyClients == null)) {
485             Slogf.e(TAG, "unregisterListenerBinderLocked: Listener was not previously "
486                     + "registered.");
487             return;
488         }
489         if (propertyClients.remove(client)) {
490             int propLeft = client.removeProperty(propId);
491             if (propLeft == 0) {
492                 mClientMap.remove(listenerBinder);
493             }
494             clearSetOperationRecorderLocked(propId, client);
495 
496         } else {
497             Slogf.e(TAG, "unregisterListenerBinderLocked: Listener was not registered for "
498                     + "propId=0x" + toHexString(propId));
499             return;
500         }
501 
502         if (propertyClients.isEmpty()) {
503             // Last listener for this property unsubscribed.  Clean up
504             mPropIdClientMap.remove(propId);
505             mSetOperationClientMap.remove(propId);
506             mPropertyHalService.unsubscribeProperty(propId);
507             return;
508         }
509         // Other listeners are still subscribed.  Calculate the new rate
510         for (int i = 0; i < propertyClients.size(); i++) {
511             Client c = propertyClients.get(i);
512             float rate = c.getRate(propId);
513             updateMaxRate = Math.max(rate, updateMaxRate);
514         }
515         if (Float.compare(updateMaxRate,
516                 mPropertyHalService.getSubscribedUpdateRateHz(propId)) != 0) {
517             try {
518                 // Only reset the sample rate if needed
519                 mPropertyHalService.subscribeProperty(propId, updateMaxRate);
520             } catch (IllegalArgumentException e) {
521                 Slogf.e(TAG, "failed to subscribe to propId=0x" + toHexString(propId)
522                         + ", error: " + e);
523             }
524         }
525     }
526 
unregisterListenerBinderForProps(List<Integer> propIds, IBinder listenerBinder)527     private void unregisterListenerBinderForProps(List<Integer> propIds, IBinder listenerBinder) {
528         synchronized (mLock) {
529             for (int i = 0; i < propIds.size(); i++) {
530                 int propId = propIds.get(i);
531                 unregisterListenerBinderLocked(propId, listenerBinder);
532             }
533         }
534     }
535 
536     /**
537      * Return the list of properties' configs that the caller may access.
538      */
539     @NonNull
540     @Override
getPropertyList()541     public CarPropertyConfigList getPropertyList() {
542         int[] allPropId;
543         // Avoid permission checking under lock.
544         synchronized (mLock) {
545             allPropId = new int[mPropertyIdToCarPropertyConfig.size()];
546             for (int i = 0; i < mPropertyIdToCarPropertyConfig.size(); i++) {
547                 allPropId[i] = mPropertyIdToCarPropertyConfig.keyAt(i);
548             }
549         }
550         return getPropertyConfigList(allPropId);
551     }
552 
553     /**
554      * @param propIds Array of property Ids
555      * @return the list of properties' configs that the caller may access.
556      */
557     @NonNull
558     @Override
getPropertyConfigList(int[] propIds)559     public CarPropertyConfigList getPropertyConfigList(int[] propIds) {
560         // Cache the granted permissions
561         Set<String> grantedPermission = new HashSet<>();
562         List<CarPropertyConfig> availableProp = new ArrayList<>();
563         if (propIds == null) {
564             return new CarPropertyConfigList(availableProp);
565         }
566         for (int propId : propIds) {
567             String readPermission = getReadPermission(propId);
568             String writePermission = getWritePermission(propId);
569             if (readPermission == null && writePermission == null) {
570                 continue;
571             }
572 
573             // Check if context already granted permission first
574             if (checkAndUpdateGrantedPermissionSet(mContext, grantedPermission, readPermission)
575                     || checkAndUpdateGrantedWritePermissionSet(mContext, grantedPermission,
576                             writePermission, propId)
577                     || checkAndUpdateGrantedTemperatureDisplayUnitsPermissionSet(mContext,
578                             grantedPermission, propId)) {
579                 synchronized (mLock) {
580                     availableProp.add(mPropertyIdToCarPropertyConfig.get(propId));
581                 }
582             }
583         }
584         if (DBG) {
585             Slogf.d(TAG, "getPropertyList returns " + availableProp.size() + " configs");
586         }
587         return new CarPropertyConfigList(availableProp);
588     }
589 
checkAndUpdateGrantedWritePermissionSet(Context context, Set<String> grantedPermissions, @Nullable String permission, int propertyId)590     private boolean checkAndUpdateGrantedWritePermissionSet(Context context,
591             Set<String> grantedPermissions, @Nullable String permission, int propertyId) {
592         if (!checkAndUpdateGrantedPermissionSet(context, grantedPermissions, permission)) {
593             return false;
594         }
595         if (mPropertyHalService.isDisplayUnitsProperty(propertyId) && permission != null
596                 && permission.equals(Car.PERMISSION_CONTROL_DISPLAY_UNITS)) {
597             return checkAndUpdateGrantedPermissionSet(context, grantedPermissions,
598                     Car.PERMISSION_VENDOR_EXTENSION);
599         }
600         return true;
601     }
602 
checkAndUpdateGrantedTemperatureDisplayUnitsPermissionSet( Context context, Set<String> grantedPermissions, int propertyId)603     private static boolean checkAndUpdateGrantedTemperatureDisplayUnitsPermissionSet(
604             Context context, Set<String> grantedPermissions, int propertyId) {
605         return propertyId == VehiclePropertyIds.HVAC_TEMPERATURE_DISPLAY_UNITS
606                 && checkAndUpdateGrantedPermissionSet(context, grantedPermissions,
607                 Car.PERMISSION_READ_DISPLAY_UNITS);
608     }
609 
checkAndUpdateGrantedPermissionSet(Context context, Set<String> grantedPermissions, @Nullable String permission)610     private static boolean checkAndUpdateGrantedPermissionSet(Context context,
611             Set<String> grantedPermissions, @Nullable String permission) {
612         if (permission != null && (grantedPermissions.contains(permission)
613                 || CarServiceUtils.hasPermission(context, permission))) {
614             grantedPermissions.add(permission);
615             return true;
616         }
617         return false;
618     }
619 
620     @Nullable
runSyncOperationCheckLimit(Callable<V> c)621     private <V> V runSyncOperationCheckLimit(Callable<V> c) {
622         synchronized (mLock) {
623             if (mSyncGetSetPropertyOpCount >= SYNC_GET_SET_PROPERTY_OP_LIMIT) {
624                 throw new ServiceSpecificException(SYNC_OP_LIMIT_TRY_AGAIN);
625             }
626             mSyncGetSetPropertyOpCount += 1;
627             if (DBG) {
628                 Slogf.d(TAG, "mSyncGetSetPropertyOpCount: %d", mSyncGetSetPropertyOpCount);
629             }
630         }
631         try {
632             Trace.traceBegin(TRACE_TAG, "call sync operation");
633             return c.call();
634         } catch (RuntimeException e) {
635             throw e;
636         } catch (Exception e) {
637             Slogf.e(TAG, e, "catching unexpected exception for getProperty/setProperty");
638             return null;
639         } finally {
640             Trace.traceEnd(TRACE_TAG);
641             synchronized (mLock) {
642                 mSyncGetSetPropertyOpCount -= 1;
643                 if (DBG) {
644                     Slogf.d(TAG, "mSyncGetSetPropertyOpCount: %d", mSyncGetSetPropertyOpCount);
645                 }
646             }
647         }
648     }
649 
650     @Override
getProperty(int propertyId, int areaId)651     public CarPropertyValue getProperty(int propertyId, int areaId)
652             throws IllegalArgumentException, ServiceSpecificException {
653         validateGetParameters(propertyId, areaId);
654         Trace.traceBegin(TRACE_TAG, "CarPropertyValue#getProperty");
655         try {
656             return runSyncOperationCheckLimit(() -> {
657                 return mPropertyHalService.getProperty(propertyId, areaId);
658             });
659         } finally {
660             Trace.traceEnd(TRACE_TAG);
661         }
662     }
663 
664     /**
665      * Get property value for car service's internal usage.
666      *
667      * @return null if property is not implemented or there is an exception in the vehicle.
668      */
669     @Nullable
getPropertySafe(int propertyId, int areaId)670     public CarPropertyValue getPropertySafe(int propertyId, int areaId) {
671         try {
672             return getProperty(propertyId, areaId);
673         } catch (Exception e) {
674             Slogf.e(TAG, e, "getPropertySafe() failed for property id: %s area id: 0x%s",
675                     VehiclePropertyIds.toString(propertyId), toHexString(areaId));
676             return null;
677         }
678     }
679 
680     @Nullable
681     @Override
getReadPermission(int propId)682     public String getReadPermission(int propId) {
683         Pair<String, String> permissions;
684         synchronized (mLock) {
685             permissions = mPropToPermission.get(propId);
686         }
687         if (permissions == null) {
688             // Property ID does not exist
689             Slogf.e(TAG, "getReadPermission: propId is not in config list:0x"
690                     + toHexString(propId));
691             return null;
692         }
693         return permissions.first;
694     }
695 
696     @Nullable
697     @Override
getWritePermission(int propId)698     public String getWritePermission(int propId) {
699         Pair<String, String> permissions;
700         synchronized (mLock) {
701             permissions = mPropToPermission.get(propId);
702         }
703         if (permissions == null) {
704             // Property ID does not exist
705             Slogf.e(TAG, "getWritePermission: propId is not in config list:0x"
706                     + toHexString(propId));
707             return null;
708         }
709         return permissions.second;
710     }
711 
712     @Override
setProperty(CarPropertyValue carPropertyValue, ICarPropertyEventListener iCarPropertyEventListener)713     public void setProperty(CarPropertyValue carPropertyValue,
714             ICarPropertyEventListener iCarPropertyEventListener)
715             throws IllegalArgumentException, ServiceSpecificException {
716         requireNonNull(iCarPropertyEventListener);
717         validateSetParameters(carPropertyValue);
718 
719         runSyncOperationCheckLimit(() -> {
720             mPropertyHalService.setProperty(carPropertyValue);
721             return null;
722         });
723 
724         IBinder listenerBinder = iCarPropertyEventListener.asBinder();
725         synchronized (mLock) {
726             Client client = mClientMap.get(listenerBinder);
727             if (client == null) {
728                 client = new Client(iCarPropertyEventListener);
729             }
730             if (client.isDead()) {
731                 Slogf.w(TAG, "the ICarPropertyEventListener is already dead");
732                 return;
733             }
734             mClientMap.put(listenerBinder, client);
735             updateSetOperationRecorderLocked(carPropertyValue.getPropertyId(),
736                     carPropertyValue.getAreaId(), client);
737         }
738     }
739 
740     // Updates recorder for set operation.
741     @GuardedBy("mLock")
updateSetOperationRecorderLocked(int propId, int areaId, Client client)742     private void updateSetOperationRecorderLocked(int propId, int areaId, Client client) {
743         if (mSetOperationClientMap.get(propId) != null) {
744             mSetOperationClientMap.get(propId).put(areaId, client);
745         } else {
746             SparseArray<Client> areaIdToClient = new SparseArray<>();
747             areaIdToClient.put(areaId, client);
748             mSetOperationClientMap.put(propId, areaIdToClient);
749         }
750     }
751 
752     // Clears map when client unregister for property.
753     @GuardedBy("mLock")
clearSetOperationRecorderLocked(int propId, Client client)754     private void clearSetOperationRecorderLocked(int propId, Client client) {
755         SparseArray<Client> areaIdToClient = mSetOperationClientMap.get(propId);
756         if (areaIdToClient != null) {
757             List<Integer> indexNeedToRemove = new ArrayList<>();
758             for (int index = 0; index < areaIdToClient.size(); index++) {
759                 if (client.equals(areaIdToClient.valueAt(index))) {
760                     indexNeedToRemove.add(index);
761                 }
762             }
763 
764             for (int index : indexNeedToRemove) {
765                 if (DBG) {
766                     Slogf.d("ErrorEvent", " Clear propId:0x" + toHexString(propId)
767                             + " areaId: 0x" + toHexString(areaIdToClient.keyAt(index)));
768                 }
769                 areaIdToClient.removeAt(index);
770             }
771         }
772     }
773 
774     // Implement PropertyHalListener interface
775     @Override
onPropertyChange(List<CarPropertyEvent> events)776     public void onPropertyChange(List<CarPropertyEvent> events) {
777         Map<Client, List<CarPropertyEvent>> eventsToDispatch = new ArrayMap<>();
778         synchronized (mLock) {
779             for (int i = 0; i < events.size(); i++) {
780                 CarPropertyEvent event = events.get(i);
781                 int propId = event.getCarPropertyValue().getPropertyId();
782                 List<Client> clients = mPropIdClientMap.get(propId);
783                 if (clients == null) {
784                     Slogf.e(TAG, "onPropertyChange: no listener registered for propId=0x%s",
785                             toHexString(propId));
786                     continue;
787                 }
788 
789                 for (int j = 0; j < clients.size(); j++) {
790                     Client c = clients.get(j);
791                     List<CarPropertyEvent> p = eventsToDispatch.get(c);
792                     if (p == null) {
793                         // Initialize the linked list for the listener
794                         p = new ArrayList<CarPropertyEvent>();
795                         eventsToDispatch.put(c, p);
796                     }
797                     p.add(event);
798                 }
799             }
800         }
801 
802         // Parse the dispatch list to send events. We must call the callback outside the
803         // scoped lock since the callback might call some function in CarPropertyService
804         // which might cause deadlock.
805         // In rare cases, if this specific client is unregistered after the lock but before
806         // the callback, we would call callback on an unregistered client which should be ok because
807         // 'onEvent' is an async oneway callback that might be delivered after unregistration
808         // anyway.
809         for (Client client : eventsToDispatch.keySet()) {
810             try {
811                 client.onEvent(eventsToDispatch.get(client));
812             } catch (RemoteException ex) {
813                 // If we cannot send a record, it's likely the connection snapped. Let binder
814                 // death handle the situation.
815                 Slogf.e(TAG, "onEvent calling failed: " + ex);
816             }
817         }
818     }
819 
820     @Override
onPropertySetError(int property, int areaId, int errorCode)821     public void onPropertySetError(int property, int areaId, int errorCode) {
822         Client lastOperatedClient = null;
823         synchronized (mLock) {
824             if (mSetOperationClientMap.get(property) != null
825                     && mSetOperationClientMap.get(property).get(areaId) != null) {
826                 lastOperatedClient = mSetOperationClientMap.get(property).get(areaId);
827             } else {
828                 Slogf.e(TAG, "Can not find the client changed propertyId: 0x"
829                         + toHexString(property) + " in areaId: 0x" + toHexString(areaId));
830             }
831 
832         }
833         if (lastOperatedClient != null) {
834             dispatchToLastClient(property, areaId, errorCode, lastOperatedClient);
835         }
836     }
837 
dispatchToLastClient(int property, int areaId, int errorCode, Client lastOperatedClient)838     private void dispatchToLastClient(int property, int areaId, int errorCode,
839             Client lastOperatedClient) {
840         try {
841             List<CarPropertyEvent> eventList = new ArrayList<>();
842             eventList.add(
843                     CarPropertyEvent.createErrorEventWithErrorCode(property, areaId,
844                             errorCode));
845             lastOperatedClient.onEvent(eventList);
846         } catch (RemoteException ex) {
847             Slogf.e(TAG, "onEvent calling failed: " + ex);
848         }
849     }
850 
validateGetSetAsyncParameters(AsyncPropertyServiceRequestList requests, IAsyncPropertyResultCallback asyncPropertyResultCallback, long timeoutInMs)851     private static void validateGetSetAsyncParameters(AsyncPropertyServiceRequestList requests,
852             IAsyncPropertyResultCallback asyncPropertyResultCallback,
853             long timeoutInMs) throws IllegalArgumentException {
854         requireNonNull(requests);
855         requireNonNull(asyncPropertyResultCallback);
856         Preconditions.checkArgument(timeoutInMs > 0, "timeoutInMs must be a positive number");
857     }
858 
859     /**
860      * Gets CarPropertyValues asynchronously.
861      */
getPropertiesAsync( AsyncPropertyServiceRequestList getPropertyServiceRequestsParcelable, IAsyncPropertyResultCallback asyncPropertyResultCallback, long timeoutInMs)862     public void getPropertiesAsync(
863             AsyncPropertyServiceRequestList getPropertyServiceRequestsParcelable,
864             IAsyncPropertyResultCallback asyncPropertyResultCallback, long timeoutInMs) {
865         validateGetSetAsyncParameters(getPropertyServiceRequestsParcelable,
866                 asyncPropertyResultCallback, timeoutInMs);
867         List<AsyncPropertyServiceRequest> getPropertyServiceRequests =
868                 getPropertyServiceRequestsParcelable.getList();
869         for (int i = 0; i < getPropertyServiceRequests.size(); i++) {
870             validateGetParameters(getPropertyServiceRequests.get(i).getPropertyId(),
871                     getPropertyServiceRequests.get(i).getAreaId());
872         }
873         mPropertyHalService.getCarPropertyValuesAsync(getPropertyServiceRequests,
874                 asyncPropertyResultCallback, timeoutInMs);
875     }
876 
877     /**
878      * Sets CarPropertyValues asynchronously.
879      */
880     @SuppressWarnings("FormatString")
setPropertiesAsync(AsyncPropertyServiceRequestList setPropertyServiceRequests, IAsyncPropertyResultCallback asyncPropertyResultCallback, long timeoutInMs)881     public void setPropertiesAsync(AsyncPropertyServiceRequestList setPropertyServiceRequests,
882             IAsyncPropertyResultCallback asyncPropertyResultCallback,
883             long timeoutInMs) {
884         validateGetSetAsyncParameters(setPropertyServiceRequests, asyncPropertyResultCallback,
885                 timeoutInMs);
886         List<AsyncPropertyServiceRequest> setPropertyServiceRequestList =
887                 setPropertyServiceRequests.getList();
888         for (int i = 0; i < setPropertyServiceRequestList.size(); i++) {
889             AsyncPropertyServiceRequest request = setPropertyServiceRequestList.get(i);
890             CarPropertyValue carPropertyValueToSet = request.getCarPropertyValue();
891             int propertyId = request.getPropertyId();
892             int valuePropertyId = carPropertyValueToSet.getPropertyId();
893             int areaId = request.getAreaId();
894             int valueAreaId = carPropertyValueToSet.getAreaId();
895             String propertyName = VehiclePropertyIds.toString(propertyId);
896             if (valuePropertyId != propertyId) {
897                 throw new IllegalArgumentException(String.format(
898                         "Property ID in request and CarPropertyValue mismatch: %s vs %s",
899                         VehiclePropertyIds.toString(valuePropertyId), propertyName).toString());
900             }
901             if (valueAreaId != areaId) {
902                 throw new IllegalArgumentException(String.format(
903                         "For property: %s, area ID in request and CarPropertyValue mismatch: %d vs"
904                         + " %d", propertyName, valueAreaId, areaId).toString());
905             }
906             validateSetParameters(carPropertyValueToSet);
907             if (request.isWaitForPropertyUpdate()) {
908                 if (NOT_ALLOWED_WAIT_FOR_UPDATE_PROPERTIES.contains(propertyId)) {
909                     throw new IllegalArgumentException("Property: "
910                             + propertyName + " must set waitForPropertyUpdate to false");
911                 }
912                 validateGetParameters(propertyId, areaId);
913             }
914         }
915         mPropertyHalService.setCarPropertyValuesAsync(setPropertyServiceRequestList,
916                 asyncPropertyResultCallback, timeoutInMs);
917     }
918 
919     /**
920      * Cancel on-going async requests.
921      *
922      * @param serviceRequestIds A list of async get/set property request IDs.
923      */
cancelRequests(int[] serviceRequestIds)924     public void cancelRequests(int[] serviceRequestIds) {
925         mPropertyHalService.cancelRequests(serviceRequestIds);
926     }
927 
assertPropertyIsReadable(CarPropertyConfig<?> carPropertyConfig)928     private static void assertPropertyIsReadable(CarPropertyConfig<?> carPropertyConfig) {
929         Preconditions.checkArgument(
930                 carPropertyConfig.getAccess() == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ
931                         || carPropertyConfig.getAccess()
932                         == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
933                 "Property is not readable: %s",
934                 VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()));
935     }
936 
assertConfigIsNotNull(int propertyId, CarPropertyConfig<?> carPropertyConfig)937     private static void assertConfigIsNotNull(int propertyId,
938             CarPropertyConfig<?> carPropertyConfig) {
939         Preconditions.checkArgument(carPropertyConfig != null,
940                 "property ID is not in carPropertyConfig list, and so it is not supported: %s",
941                 VehiclePropertyIds.toString(propertyId));
942     }
943 
assertAreaIdIsSupported(int areaId, CarPropertyConfig<?> carPropertyConfig)944     private static void assertAreaIdIsSupported(int areaId,
945             CarPropertyConfig<?> carPropertyConfig) {
946         Preconditions.checkArgument(ArrayUtils.contains(carPropertyConfig.getAreaIds(), areaId),
947                 "area ID: 0x" + toHexString(areaId) + " not supported for property ID: "
948                         + VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()));
949     }
950 
951     @Nullable
getCarPropertyConfig(int propertyId)952     private CarPropertyConfig<?> getCarPropertyConfig(int propertyId) {
953         CarPropertyConfig<?> carPropertyConfig;
954         synchronized (mLock) {
955             carPropertyConfig = mPropertyIdToCarPropertyConfig.get(propertyId);
956         }
957         return carPropertyConfig;
958     }
959 
assertReadPermissionGranted(int propertyId)960     private void assertReadPermissionGranted(int propertyId) {
961         String readPermission = mPropertyHalService.getReadPermission(propertyId);
962         if (readPermission == null) {
963             throw new SecurityException(
964                     "Platform does not have permission to read value for property ID: "
965                             + VehiclePropertyIds.toString(propertyId));
966         }
967         if (propertyId == VehiclePropertyIds.HVAC_TEMPERATURE_DISPLAY_UNITS) {
968             CarServiceUtils.assertAnyPermission(mContext, readPermission,
969                     Car.PERMISSION_READ_DISPLAY_UNITS);
970             return;
971         }
972         if (propertyId == VehiclePropertyIds.EV_CHARGE_SWITCH) {
973             CarServiceUtils.assertAnyPermission(mContext, readPermission,
974                     Car.PERMISSION_CONTROL_CAR_ENERGY);
975             return;
976         }
977         CarServiceUtils.assertPermission(mContext, readPermission);
978     }
979 
validateRegisterParameter(int propertyId)980     private void validateRegisterParameter(int propertyId) {
981         CarPropertyConfig<?> carPropertyConfig = getCarPropertyConfig(propertyId);
982         assertConfigIsNotNull(propertyId, carPropertyConfig);
983         assertPropertyIsReadable(carPropertyConfig);
984         assertReadPermissionGranted(propertyId);
985     }
986 
validateGetParameters(int propertyId, int areaId)987     private void validateGetParameters(int propertyId, int areaId) {
988         CarPropertyConfig<?> carPropertyConfig = getCarPropertyConfig(propertyId);
989         assertConfigIsNotNull(propertyId, carPropertyConfig);
990         assertPropertyIsReadable(carPropertyConfig);
991         assertReadPermissionGranted(propertyId);
992         assertAreaIdIsSupported(areaId, carPropertyConfig);
993     }
994 
validateSetParameters(CarPropertyValue<?> carPropertyValue)995     private void validateSetParameters(CarPropertyValue<?> carPropertyValue) {
996         requireNonNull(carPropertyValue);
997         int propertyId = carPropertyValue.getPropertyId();
998         int areaId = carPropertyValue.getAreaId();
999         Object valueToSet = carPropertyValue.getValue();
1000         CarPropertyConfig<?> carPropertyConfig = getCarPropertyConfig(propertyId);
1001         assertConfigIsNotNull(propertyId, carPropertyConfig);
1002 
1003         // Assert property is writable.
1004         Preconditions.checkArgument(
1005                 carPropertyConfig.getAccess() == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE
1006                         || carPropertyConfig.getAccess()
1007                         == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
1008                 "Property is not writable: %s",
1009                 VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()));
1010 
1011         // Assert write permission is granted.
1012         String writePermission = mPropertyHalService.getWritePermission(propertyId);
1013         if (writePermission == null) {
1014             throw new SecurityException(
1015                     "Platform does not have permission to write value for property ID: "
1016                             + VehiclePropertyIds.toString(propertyId));
1017         }
1018         CarServiceUtils.assertPermission(mContext, writePermission);
1019         // need an extra permission for writing display units properties.
1020         if (mPropertyHalService.isDisplayUnitsProperty(propertyId)) {
1021             CarServiceUtils.assertPermission(mContext, Car.PERMISSION_VENDOR_EXTENSION);
1022         }
1023 
1024         assertAreaIdIsSupported(areaId, carPropertyConfig);
1025 
1026         // Assert set value is valid for property.
1027         Preconditions.checkArgument(valueToSet != null,
1028                 "setProperty: CarPropertyValue's must not be null - property ID: %s area ID: %s",
1029                 VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()),
1030                 toHexString(areaId));
1031         Preconditions.checkArgument(
1032                 valueToSet.getClass().equals(carPropertyConfig.getPropertyType()),
1033                 "setProperty: CarPropertyValue's value's type does not match property's type. - "
1034                         + "property ID: %s area ID: %s",
1035                 VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()),
1036                 toHexString(areaId));
1037 
1038         AreaIdConfig<?> areaIdConfig = carPropertyConfig.getAreaIdConfig(areaId);
1039         if (areaIdConfig.getMinValue() != null) {
1040             boolean isGreaterThanOrEqualToMinValue = false;
1041             if (carPropertyConfig.getPropertyType().equals(Integer.class)) {
1042                 isGreaterThanOrEqualToMinValue =
1043                         (Integer) valueToSet >= (Integer) areaIdConfig.getMinValue();
1044             } else if (carPropertyConfig.getPropertyType().equals(Long.class)) {
1045                 isGreaterThanOrEqualToMinValue =
1046                         (Long) valueToSet >= (Long) areaIdConfig.getMinValue();
1047             } else if (carPropertyConfig.getPropertyType().equals(Float.class)) {
1048                 isGreaterThanOrEqualToMinValue =
1049                         (Float) valueToSet >= (Float) areaIdConfig.getMinValue();
1050             }
1051             Preconditions.checkArgument(isGreaterThanOrEqualToMinValue,
1052                     "setProperty: value to set must be greater than or equal to the area ID min "
1053                             + "value. - " + "property ID: %s area ID: 0x%s min value: %s",
1054                     VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()),
1055                     toHexString(areaId), areaIdConfig.getMinValue());
1056 
1057         }
1058 
1059         if (areaIdConfig.getMaxValue() != null) {
1060             boolean isLessThanOrEqualToMaxValue = false;
1061             if (carPropertyConfig.getPropertyType().equals(Integer.class)) {
1062                 isLessThanOrEqualToMaxValue =
1063                         (Integer) valueToSet <= (Integer) areaIdConfig.getMaxValue();
1064             } else if (carPropertyConfig.getPropertyType().equals(Long.class)) {
1065                 isLessThanOrEqualToMaxValue =
1066                         (Long) valueToSet <= (Long) areaIdConfig.getMaxValue();
1067             } else if (carPropertyConfig.getPropertyType().equals(Float.class)) {
1068                 isLessThanOrEqualToMaxValue =
1069                         (Float) valueToSet <= (Float) areaIdConfig.getMaxValue();
1070             }
1071             Preconditions.checkArgument(isLessThanOrEqualToMaxValue,
1072                     "setProperty: value to set must be less than or equal to the area ID max "
1073                             + "value. - " + "property ID: %s area ID: 0x%s min value: %s",
1074                     VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()),
1075                     toHexString(areaId), areaIdConfig.getMaxValue());
1076 
1077         }
1078 
1079         if (!areaIdConfig.getSupportedEnumValues().isEmpty()) {
1080             Preconditions.checkArgument(areaIdConfig.getSupportedEnumValues().contains(valueToSet),
1081                     "setProperty: value to set must exist in set of supported enum values. - "
1082                             + "property ID: %s area ID: 0x%s supported enum values: %s",
1083                     VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()),
1084                     toHexString(areaId), areaIdConfig.getSupportedEnumValues());
1085         }
1086 
1087         if (PROPERTY_ID_TO_UNWRITABLE_STATES.contains(carPropertyConfig.getPropertyId())) {
1088             Preconditions.checkArgument(!(PROPERTY_ID_TO_UNWRITABLE_STATES
1089                     .get(carPropertyConfig.getPropertyId()).contains(valueToSet)),
1090                     "setProperty: value to set: %s must not be an unwritable state value. - "
1091                             + "property ID: %s area ID: 0x%s unwritable states: %s",
1092                     valueToSet,
1093                     VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()),
1094                     toHexString(areaId),
1095                     PROPERTY_ID_TO_UNWRITABLE_STATES.get(carPropertyConfig.getPropertyId()));
1096         }
1097     }
1098 }
1099