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