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