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