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