1 /* 2 * Copyright (C) 2015 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.hal; 18 19 import static android.os.SystemClock.uptimeMillis; 20 21 import static com.android.car.hal.property.HalPropertyDebugUtils.toAccessString; 22 import static com.android.car.hal.property.HalPropertyDebugUtils.toAreaIdString; 23 import static com.android.car.hal.property.HalPropertyDebugUtils.toAreaTypeString; 24 import static com.android.car.hal.property.HalPropertyDebugUtils.toChangeModeString; 25 import static com.android.car.hal.property.HalPropertyDebugUtils.toGroupString; 26 import static com.android.car.hal.property.HalPropertyDebugUtils.toHalPropIdAreaIdString; 27 import static com.android.car.hal.property.HalPropertyDebugUtils.toHalPropIdAreaIdsString; 28 import static com.android.car.hal.property.HalPropertyDebugUtils.toPropertyIdString; 29 import static com.android.car.hal.property.HalPropertyDebugUtils.toValueTypeString; 30 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 31 32 import android.annotation.CheckResult; 33 import android.annotation.Nullable; 34 import android.car.VehiclePropertyIds; 35 import android.car.builtin.os.BuildHelper; 36 import android.car.builtin.os.TraceHelper; 37 import android.car.builtin.util.Slogf; 38 import android.car.feature.FeatureFlags; 39 import android.car.feature.FeatureFlagsImpl; 40 import android.car.hardware.CarPropertyValue; 41 import android.car.hardware.property.CarPropertyEvent; 42 import android.car.hardware.property.ICarPropertyEventListener; 43 import android.content.Context; 44 import android.hardware.automotive.vehicle.RawPropValues; 45 import android.hardware.automotive.vehicle.StatusCode; 46 import android.hardware.automotive.vehicle.SubscribeOptions; 47 import android.hardware.automotive.vehicle.VehiclePropError; 48 import android.hardware.automotive.vehicle.VehicleProperty; 49 import android.hardware.automotive.vehicle.VehiclePropertyAccess; 50 import android.hardware.automotive.vehicle.VehiclePropertyChangeMode; 51 import android.hardware.automotive.vehicle.VehiclePropertyStatus; 52 import android.hardware.automotive.vehicle.VehiclePropertyType; 53 import android.os.Handler; 54 import android.os.HandlerThread; 55 import android.os.IBinder; 56 import android.os.ParcelFileDescriptor; 57 import android.os.RemoteException; 58 import android.os.ServiceSpecificException; 59 import android.os.SystemClock; 60 import android.os.Trace; 61 import android.util.ArrayMap; 62 import android.util.ArraySet; 63 import android.util.Log; 64 import android.util.SparseArray; 65 66 import com.android.car.CarLog; 67 import com.android.car.CarServiceUtils; 68 import com.android.car.CarSystemService; 69 import com.android.car.VehicleStub; 70 import com.android.car.VehicleStub.MinMaxSupportedRawPropValues; 71 import com.android.car.VehicleStub.SubscriptionClient; 72 import com.android.car.hal.fakevhal.SimulationVehicleStub; 73 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 74 import com.android.car.internal.common.DispatchList; 75 import com.android.car.internal.property.PropIdAreaId; 76 import com.android.car.internal.util.IndentingPrintWriter; 77 import com.android.car.internal.util.Lists; 78 import com.android.car.internal.util.PairSparseArray; 79 import com.android.car.systeminterface.DisplayHelperInterface; 80 import com.android.internal.annotations.GuardedBy; 81 import com.android.internal.annotations.VisibleForTesting; 82 83 import java.io.PrintWriter; 84 import java.util.ArrayList; 85 import java.util.Arrays; 86 import java.util.Collection; 87 import java.util.List; 88 import java.util.Map; 89 import java.util.Objects; 90 import java.util.Timer; 91 import java.util.TimerTask; 92 import java.util.concurrent.TimeUnit; 93 import java.util.concurrent.atomic.AtomicReference; 94 95 /** 96 * Abstraction for vehicle HAL. This class handles interface with native HAL and does basic parsing 97 * of received data (type check). Then each event is sent to corresponding {@link HalServiceBase} 98 * implementation. It is the responsibility of {@link HalServiceBase} to convert data to 99 * corresponding Car*Service for Car*Manager API. 100 */ 101 public class VehicleHal implements VehicleHalCallback, CarSystemService { 102 private static final boolean DBG = Slogf.isLoggable(CarLog.TAG_HAL, Log.DEBUG); 103 private static final long TRACE_TAG = TraceHelper.TRACE_TAG_CAR_SERVICE; 104 105 private static final int GLOBAL_AREA_ID = 0; 106 107 /** 108 * If call to vehicle HAL returns StatusCode.TRY_AGAIN, we will retry to invoke that method 109 * again for this amount of milliseconds. 110 */ 111 private static final int MAX_DURATION_FOR_RETRIABLE_RESULT_MS = 2000; 112 113 private static final int SLEEP_BETWEEN_RETRIABLE_INVOKES_MS = 100; 114 private static final float PRECISION_THRESHOLD = 0.001f; 115 116 private final HandlerThread mHandlerThread; 117 private final Handler mHandler; 118 private final SubscriptionClient mSubscriptionClient; 119 120 private final PowerHalService mPowerHal; 121 private final PropertyHalService mPropertyHal; 122 private final InputHalService mInputHal; 123 private final VmsHalService mVmsHal; 124 private final UserHalService mUserHal; 125 private final DiagnosticHalService mDiagnosticHal; 126 private final ClusterHalService mClusterHalService; 127 private final EvsHalService mEvsHal; 128 private final TimeHalService mTimeHalService; 129 private final HalPropValueBuilder mPropValueBuilder; 130 private AtomicReference<VehicleStub> mVehicleStub; 131 132 private final Object mLock = new Object(); 133 134 private FeatureFlags mFeatureFlags = new FeatureFlagsImpl(); 135 136 // Only changed for test. 137 private int mMaxDurationForRetryMs = MAX_DURATION_FOR_RETRIABLE_RESULT_MS; 138 // Only changed for test. 139 private int mSleepBetweenRetryMs = SLEEP_BETWEEN_RETRIABLE_INVOKES_MS; 140 141 /** Stores handler for each HAL property. Property events are sent to handler. */ 142 @GuardedBy("mLock") 143 private final SparseArray<HalServiceBase> mPropertyHandlers = new SparseArray<>(); 144 /** This is for iterating all HalServices with fixed order. */ 145 @GuardedBy("mLock") 146 private final List<HalServiceBase> mAllServices; 147 @GuardedBy("mLock") 148 private PairSparseArray<RateInfo> mRateInfoByPropIdAreaId = new PairSparseArray<>(); 149 @GuardedBy("mLock") 150 private final SparseArray<HalPropConfig> mAllProperties = new SparseArray<>(); 151 @GuardedBy("mLock") 152 private final PairSparseArray<Integer> mAccessByPropIdAreaId = new PairSparseArray<Integer>(); 153 @GuardedBy("mLock") 154 private final ArrayMap<HalServiceBase, ArraySet<PropIdAreaId>> 155 mSupportedValuesChangePropIdAreaIdsByService = new ArrayMap<>(); 156 157 @GuardedBy("mLock") 158 private final SparseArray<VehiclePropertyEventInfo> mEventLog = new SparseArray<>(); 159 160 // Used by injectVHALEvent for testing purposes. Delimiter for an array of data 161 private static final String DATA_DELIMITER = ","; 162 @GuardedBy("mLock") 163 private RecordingListenerHandler mListenerHandler; 164 @GuardedBy("mLock") 165 private ArraySet<Integer> mPropertyIdsFromRealHardware = new ArraySet<>(); 166 167 /** A structure to store update rate in hz and whether to enable VUR. */ 168 private static final class RateInfo { 169 public float updateRateHz; 170 public boolean enableVariableUpdateRate; 171 public float resolution; 172 RateInfo(float updateRateHz, boolean enableVariableUpdateRate, float resolution)173 RateInfo(float updateRateHz, boolean enableVariableUpdateRate, float resolution) { 174 this.updateRateHz = updateRateHz; 175 this.enableVariableUpdateRate = enableVariableUpdateRate; 176 this.resolution = resolution; 177 } 178 } 179 180 /* package */ static final class HalSubscribeOptions { 181 private final int mHalPropId; 182 private final int[] mAreaIds; 183 private final float mUpdateRateHz; 184 private final boolean mEnableVariableUpdateRate; 185 private final float mResolution; 186 HalSubscribeOptions(int halPropId, int[] areaIds, float updateRateHz)187 HalSubscribeOptions(int halPropId, int[] areaIds, float updateRateHz) { 188 this(halPropId, areaIds, updateRateHz, /* enableVariableUpdateRate= */ false, 189 /* resolution= */ 0.0f); 190 } 191 HalSubscribeOptions(int halPropId, int[] areaIds, float updateRateHz, boolean enableVariableUpdateRate)192 HalSubscribeOptions(int halPropId, int[] areaIds, float updateRateHz, 193 boolean enableVariableUpdateRate) { 194 this(halPropId, areaIds, updateRateHz, enableVariableUpdateRate, 195 /* resolution= */ 0.0f); 196 } 197 HalSubscribeOptions(int halPropId, int[] areaIds, float updateRateHz, boolean enableVariableUpdateRate, float resolution)198 HalSubscribeOptions(int halPropId, int[] areaIds, float updateRateHz, 199 boolean enableVariableUpdateRate, float resolution) { 200 mHalPropId = halPropId; 201 mAreaIds = areaIds; 202 mUpdateRateHz = updateRateHz; 203 mEnableVariableUpdateRate = enableVariableUpdateRate; 204 mResolution = resolution; 205 } 206 getHalPropId()207 int getHalPropId() { 208 return mHalPropId; 209 } 210 getAreaId()211 int[] getAreaId() { 212 return mAreaIds; 213 } 214 getUpdateRateHz()215 float getUpdateRateHz() { 216 return mUpdateRateHz; 217 } 218 isVariableUpdateRateEnabled()219 boolean isVariableUpdateRateEnabled() { 220 return mEnableVariableUpdateRate; 221 } getResolution()222 float getResolution() { 223 return mResolution; 224 } 225 226 @Override equals(Object other)227 public boolean equals(Object other) { 228 if (other == this) { 229 return true; 230 } 231 232 if (!(other instanceof VehicleHal.HalSubscribeOptions)) { 233 return false; 234 } 235 236 VehicleHal.HalSubscribeOptions o = (VehicleHal.HalSubscribeOptions) other; 237 238 return mHalPropId == o.getHalPropId() && mUpdateRateHz == o.getUpdateRateHz() 239 && Arrays.equals(mAreaIds, o.getAreaId()) 240 && mEnableVariableUpdateRate == o.isVariableUpdateRateEnabled() 241 && mResolution == o.getResolution(); 242 } 243 244 @Override toString()245 public String toString() { 246 return "HalSubscribeOptions{" 247 + "PropertyId: " + mHalPropId 248 + ", AreaId: " + Arrays.toString(mAreaIds) 249 + ", UpdateRateHz: " + mUpdateRateHz 250 + ", enableVariableUpdateRate: " + mEnableVariableUpdateRate 251 + ", Resolution: " + mResolution 252 + "}"; 253 } 254 255 @Override hashCode()256 public int hashCode() { 257 return Objects.hash(mHalPropId, Arrays.hashCode(mAreaIds), mUpdateRateHz, 258 mEnableVariableUpdateRate, mResolution); 259 } 260 } 261 262 /** 263 * Constructs a new {@link VehicleHal} object given the {@link Context} and {@link IVehicle} 264 * both passed as parameters. 265 */ VehicleHal(Context context, VehicleStub vehicle)266 public VehicleHal(Context context, VehicleStub vehicle) { 267 this(context, /* powerHal= */ null, /* propertyHal= */ null, 268 /* inputHal= */ null, /* vmsHal= */ null, /* userHal= */ null, 269 /* diagnosticHal= */ null, /* clusterHalService= */ null, 270 /* timeHalService= */ null, 271 CarServiceUtils.getHandlerThread(VehicleHal.class.getSimpleName()), 272 vehicle); 273 } 274 275 /** 276 * Constructs a new {@link VehicleHal} object given the services passed as parameters. 277 * This method must be used by tests only. 278 */ 279 @VisibleForTesting VehicleHal(Context context, PowerHalService powerHal, PropertyHalService propertyHal, InputHalService inputHal, VmsHalService vmsHal, UserHalService userHal, DiagnosticHalService diagnosticHal, ClusterHalService clusterHalService, TimeHalService timeHalService, HandlerThread handlerThread, VehicleStub vehicle)280 public VehicleHal(Context context, 281 PowerHalService powerHal, 282 PropertyHalService propertyHal, 283 InputHalService inputHal, 284 VmsHalService vmsHal, 285 UserHalService userHal, 286 DiagnosticHalService diagnosticHal, 287 ClusterHalService clusterHalService, 288 TimeHalService timeHalService, 289 HandlerThread handlerThread, 290 VehicleStub vehicle) { 291 // Must be initialized before HalService so that HalService could use this. 292 mPropValueBuilder = vehicle.getHalPropValueBuilder(); 293 mHandlerThread = handlerThread; 294 mHandler = new Handler(mHandlerThread.getLooper()); 295 mPowerHal = powerHal != null ? powerHal : new PowerHalService(context, mFeatureFlags, this, 296 new DisplayHelperInterface.DefaultImpl()); 297 mPropertyHal = propertyHal != null ? propertyHal : new PropertyHalService(this); 298 mInputHal = inputHal != null ? inputHal : new InputHalService(this); 299 mVmsHal = vmsHal != null ? vmsHal : new VmsHalService(context, this); 300 mUserHal = userHal != null ? userHal : new UserHalService(this); 301 mDiagnosticHal = diagnosticHal != null ? diagnosticHal : new DiagnosticHalService(this); 302 mClusterHalService = clusterHalService != null 303 ? clusterHalService : new ClusterHalService(context, this); 304 mEvsHal = new EvsHalService(this); 305 mTimeHalService = timeHalService != null 306 ? timeHalService : new TimeHalService(context, this); 307 mAllServices = List.of( 308 mPowerHal, 309 mInputHal, 310 mDiagnosticHal, 311 mVmsHal, 312 mUserHal, 313 mClusterHalService, 314 mEvsHal, 315 mTimeHalService, 316 // mPropertyHal must be the last so that on init/release it can be used for all 317 // other HAL services properties. 318 mPropertyHal); 319 mVehicleStub = new AtomicReference<>(vehicle); 320 mSubscriptionClient = vehicle.newSubscriptionClient(this); 321 } 322 323 /** 324 * Gets the current vehicle stub 325 * @return The current vehicle stub 326 */ 327 @VisibleForTesting getVehicleStub()328 public VehicleStub getVehicleStub() { 329 return mVehicleStub.get(); 330 } 331 332 /** Sets fake feature flag for unit testing. */ 333 @VisibleForTesting setFeatureFlags(FeatureFlags fakeFeatureFlags)334 public void setFeatureFlags(FeatureFlags fakeFeatureFlags) { 335 mFeatureFlags = fakeFeatureFlags; 336 } 337 338 @VisibleForTesting setMaxDurationForRetryMs(int maxDurationForRetryMs)339 void setMaxDurationForRetryMs(int maxDurationForRetryMs) { 340 mMaxDurationForRetryMs = maxDurationForRetryMs; 341 } 342 343 @VisibleForTesting setSleepBetweenRetryMs(int sleepBetweenRetryMs)344 void setSleepBetweenRetryMs(int sleepBetweenRetryMs) { 345 mSleepBetweenRetryMs = sleepBetweenRetryMs; 346 } 347 348 @VisibleForTesting fetchAllPropConfigs()349 void fetchAllPropConfigs() { 350 synchronized (mLock) { 351 if (mAllProperties.size() != 0) { // already set 352 Slogf.i(CarLog.TAG_HAL, "fetchAllPropConfigs already fetched"); 353 return; 354 } 355 } 356 HalPropConfig[] configs; 357 try { 358 configs = getAllPropConfigs(); 359 if (configs == null || configs.length == 0) { 360 Slogf.e(CarLog.TAG_HAL, "getAllPropConfigs returned empty configs"); 361 return; 362 } 363 } catch (RemoteException | ServiceSpecificException e) { 364 throw new RuntimeException("Unable to retrieve vehicle property configuration", e); 365 } 366 367 synchronized (mLock) { 368 // Create map of all properties 369 for (HalPropConfig p : configs) { 370 if (DBG) { 371 Slogf.d(CarLog.TAG_HAL, "Add config for prop: 0x%x config: %s", p.getPropId(), 372 p.toString()); 373 } 374 mAllProperties.put(p.getPropId(), p); 375 if (p.getAreaConfigs().length == 0) { 376 mAccessByPropIdAreaId.put(p.getPropId(), /* areaId */ 0, p.getAccess()); 377 } else { 378 for (HalAreaConfig areaConfig : p.getAreaConfigs()) { 379 mAccessByPropIdAreaId.put(p.getPropId(), areaConfig.getAreaId(), 380 areaConfig.getAccess()); 381 } 382 } 383 } 384 } 385 } 386 handleOnPropertyEvent(List<HalPropValue> propValues)387 private void handleOnPropertyEvent(List<HalPropValue> propValues) { 388 maybeHandleRecording(propValues); 389 synchronized (mLock) { 390 if (isVehiclePropertyInjectionModeEnabled()) { 391 List<HalPropValue> filteredPropValues = SimulationVehicleStub.filterProperties( 392 propValues, 393 HalPropValue::getPropId, mPropertyIdsFromRealHardware); 394 if (filteredPropValues.isEmpty()) { 395 Slogf.d(CarLog.TAG_HAL, "All onPropertyEvent properties filtered: %s", 396 Arrays.toString(propValues.toArray())); 397 return; 398 } 399 propValues = filteredPropValues; 400 } 401 } 402 dispatchPropertyEvents(propValues); 403 } 404 dispatchPropertyEvents(List<HalPropValue> propValues)405 private void dispatchPropertyEvents(List<HalPropValue> propValues) { 406 ArraySet<HalServiceBase> servicesToDispatch = new ArraySet<>(); 407 synchronized (mLock) { 408 for (int i = 0; i < propValues.size(); i++) { 409 HalPropValue v = propValues.get(i); 410 int propId = v.getPropId(); 411 HalServiceBase service = mPropertyHandlers.get(propId); 412 if (service == null) { 413 Slogf.e(CarLog.TAG_HAL, "handleOnPropertyEvent: HalService not found for %s", 414 v); 415 continue; 416 } 417 service.getDispatchList().add(v); 418 servicesToDispatch.add(service); 419 VehiclePropertyEventInfo info = mEventLog.get(propId); 420 if (info == null) { 421 info = new VehiclePropertyEventInfo(v); 422 mEventLog.put(propId, info); 423 } else { 424 info.addNewEvent(v); 425 } 426 } 427 } 428 for (HalServiceBase s : servicesToDispatch) { 429 s.onHalEvents(s.getDispatchList()); 430 s.getDispatchList().clear(); 431 } 432 } 433 handleOnPropertySetError(List<VehiclePropError> errors)434 private void handleOnPropertySetError(List<VehiclePropError> errors) { 435 if (isVehiclePropertyInjectionModeEnabled()) { 436 List<VehiclePropError> filteredErrors; 437 synchronized (mLock) { 438 filteredErrors = SimulationVehicleStub.filterProperties(errors, 439 (VehiclePropError err) -> err.propId, mPropertyIdsFromRealHardware); 440 } 441 if (filteredErrors.isEmpty()) { 442 Slogf.d(CarLog.TAG_HAL, "All onPropertySetError events filtered: %s", 443 Arrays.toString(errors.toArray())); 444 return; 445 } 446 errors = filteredErrors; 447 } 448 SparseArray<ArrayList<VehiclePropError>> errorsByPropId = 449 new SparseArray<ArrayList<VehiclePropError>>(); 450 for (int i = 0; i < errors.size(); i++) { 451 VehiclePropError error = errors.get(i); 452 int errorCode = error.errorCode; 453 int propId = error.propId; 454 int areaId = error.areaId; 455 Slogf.w(CarLog.TAG_HAL, "onPropertySetError, errorCode: %d, prop: 0x%x, area: 0x%x", 456 errorCode, propId, areaId); 457 if (propId == VehicleProperty.INVALID) { 458 continue; 459 } 460 461 ArrayList<VehiclePropError> propErrors; 462 if (errorsByPropId.get(propId) == null) { 463 propErrors = new ArrayList<VehiclePropError>(); 464 errorsByPropId.put(propId, propErrors); 465 } else { 466 propErrors = errorsByPropId.get(propId); 467 } 468 propErrors.add(error); 469 } 470 471 for (int i = 0; i < errorsByPropId.size(); i++) { 472 int propId = errorsByPropId.keyAt(i); 473 HalServiceBase service; 474 synchronized (mLock) { 475 service = mPropertyHandlers.get(propId); 476 } 477 if (service == null) { 478 Slogf.e(CarLog.TAG_HAL, 479 "handleOnPropertySetError: HalService not found for prop: 0x%x", propId); 480 continue; 481 } 482 483 ArrayList<VehiclePropError> propErrors = errorsByPropId.get(propId); 484 service.onPropertySetError(propErrors); 485 } 486 } 487 errorMessage(String action, HalPropValue propValue, String errorMsg)488 private static String errorMessage(String action, HalPropValue propValue, String errorMsg) { 489 return String.format("Failed to %s value for: %s, error: %s", action, 490 propValue, errorMsg); 491 } 492 getValueWithRetry(HalPropValue value)493 private HalPropValue getValueWithRetry(HalPropValue value) { 494 return getValueWithRetry(value, /* maxRetries= */ 0); 495 } 496 getValueWithRetry(HalPropValue value, int maxRetries)497 private HalPropValue getValueWithRetry(HalPropValue value, int maxRetries) { 498 HalPropValue result; 499 Trace.traceBegin(TRACE_TAG, "VehicleStub#getValueWithRetry"); 500 try { 501 result = invokeRetriable((requestValue) -> { 502 Trace.traceBegin(TRACE_TAG, "VehicleStub#get"); 503 try { 504 return mVehicleStub.get().get(requestValue); 505 } finally { 506 Trace.traceEnd(TRACE_TAG); 507 } 508 }, "get", value, mMaxDurationForRetryMs, mSleepBetweenRetryMs, maxRetries); 509 } finally { 510 Trace.traceEnd(TRACE_TAG); 511 } 512 513 if (result == null) { 514 // If VHAL returns null result, but the status is OKAY. We treat that as NOT_AVAILABLE. 515 throw new ServiceSpecificException(StatusCode.NOT_AVAILABLE, 516 errorMessage("get", value, "VHAL returns null for property value")); 517 } 518 return result; 519 } 520 setValueWithRetry(HalPropValue value)521 private void setValueWithRetry(HalPropValue value) { 522 invokeRetriable((requestValue) -> { 523 Trace.traceBegin(TRACE_TAG, "VehicleStub#set"); 524 mVehicleStub.get().set(requestValue); 525 Trace.traceEnd(TRACE_TAG); 526 return null; 527 }, "set", value, mMaxDurationForRetryMs, mSleepBetweenRetryMs, /* maxRetries= */ 0); 528 } 529 530 /** 531 * Inits the vhal configurations. 532 */ 533 @Override init()534 public void init() { 535 // nothing to init as everything was done on priorityInit 536 } 537 538 /** 539 * PriorityInit for the vhal configurations. 540 */ priorityInit()541 public void priorityInit() { 542 fetchAllPropConfigs(); 543 544 // PropertyHalService will take most properties, so make it big enough. 545 ArrayMap<HalServiceBase, ArrayList<HalPropConfig>> configsForAllServices; 546 synchronized (mLock) { 547 configsForAllServices = new ArrayMap<>(mAllServices.size()); 548 for (int i = 0; i < mAllServices.size(); i++) { 549 ArrayList<HalPropConfig> configsForService = new ArrayList(); 550 HalServiceBase service = mAllServices.get(i); 551 configsForAllServices.put(service, configsForService); 552 int[] supportedProps = service.getAllSupportedProperties(); 553 if (supportedProps.length == 0) { 554 for (int j = 0; j < mAllProperties.size(); j++) { 555 Integer propId = mAllProperties.keyAt(j); 556 if (service.isSupportedProperty(propId)) { 557 HalPropConfig config = mAllProperties.get(propId); 558 mPropertyHandlers.append(propId, service); 559 configsForService.add(config); 560 } 561 } 562 } else { 563 for (int prop : supportedProps) { 564 HalPropConfig config = mAllProperties.get(prop); 565 if (config == null) { 566 continue; 567 } 568 mPropertyHandlers.append(prop, service); 569 configsForService.add(config); 570 } 571 } 572 } 573 } 574 575 for (Map.Entry<HalServiceBase, ArrayList<HalPropConfig>> entry 576 : configsForAllServices.entrySet()) { 577 HalServiceBase service = entry.getKey(); 578 ArrayList<HalPropConfig> configsForService = entry.getValue(); 579 service.takeProperties(configsForService); 580 service.init(); 581 } 582 } 583 584 /** 585 * Releases all connected services (power management service, input service, etc). 586 */ 587 @Override release()588 public void release() { 589 ArraySet<Integer> subscribedProperties = new ArraySet<>(); 590 synchronized (mLock) { 591 // release in reverse order from init 592 for (int i = mAllServices.size() - 1; i >= 0; i--) { 593 mAllServices.get(i).release(); 594 } 595 for (int i = 0; i < mRateInfoByPropIdAreaId.size(); i++) { 596 int propertyId = mRateInfoByPropIdAreaId.keyPairAt(i)[0]; 597 subscribedProperties.add(propertyId); 598 } 599 mRateInfoByPropIdAreaId.clear(); 600 mAllProperties.clear(); 601 mAccessByPropIdAreaId.clear(); 602 } 603 for (int i = 0; i < subscribedProperties.size(); i++) { 604 try { 605 mSubscriptionClient.unsubscribe(subscribedProperties.valueAt(i)); 606 } catch (RemoteException | ServiceSpecificException e) { 607 // Ignore exceptions on shutdown path. 608 Slogf.w(CarLog.TAG_HAL, "Failed to unsubscribe", e); 609 } 610 } 611 // keep the looper thread as should be kept for the whole life cycle. 612 } 613 getDiagnosticHal()614 public DiagnosticHalService getDiagnosticHal() { 615 return mDiagnosticHal; 616 } 617 getPowerHal()618 public PowerHalService getPowerHal() { 619 return mPowerHal; 620 } 621 getPropertyHal()622 public PropertyHalService getPropertyHal() { 623 return mPropertyHal; 624 } 625 getInputHal()626 public InputHalService getInputHal() { 627 return mInputHal; 628 } 629 getUserHal()630 public UserHalService getUserHal() { 631 return mUserHal; 632 } 633 getVmsHal()634 public VmsHalService getVmsHal() { 635 return mVmsHal; 636 } 637 getClusterHal()638 public ClusterHalService getClusterHal() { 639 return mClusterHalService; 640 } 641 getEvsHal()642 public EvsHalService getEvsHal() { 643 return mEvsHal; 644 } 645 getTimeHalService()646 public TimeHalService getTimeHalService() { 647 return mTimeHalService; 648 } 649 getHalPropValueBuilder()650 public HalPropValueBuilder getHalPropValueBuilder() { 651 return mPropValueBuilder; 652 } 653 654 @GuardedBy("mLock") assertServiceOwnerLocked(HalServiceBase service, int property)655 private void assertServiceOwnerLocked(HalServiceBase service, int property) { 656 if (service != mPropertyHandlers.get(property)) { 657 throw new IllegalArgumentException(String.format( 658 "Property 0x%x is not owned by service: %s", property, service)); 659 } 660 } 661 662 /** 663 * Subscribes given properties with sampling rate defaults to 0 and no special flags provided. 664 * 665 * @throws IllegalArgumentException thrown if property is not supported by VHAL 666 * @throws ServiceSpecificException if VHAL returns error or lost connection with VHAL. 667 * @see #subscribeProperty(HalServiceBase, int, float) 668 */ subscribeProperty(HalServiceBase service, int property)669 public void subscribeProperty(HalServiceBase service, int property) 670 throws IllegalArgumentException, ServiceSpecificException { 671 subscribeProperty(service, property, /* samplingRateHz= */ 0f); 672 } 673 674 /** 675 * Similar to {@link #subscribeProperty(HalServiceBase, int)} except that all exceptions 676 * are caught and are logged. 677 */ subscribePropertySafe(HalServiceBase service, int property)678 public void subscribePropertySafe(HalServiceBase service, int property) { 679 try { 680 subscribeProperty(service, property); 681 } catch (IllegalArgumentException | ServiceSpecificException e) { 682 Slogf.w(CarLog.TAG_HAL, "Failed to subscribe for property: " 683 + VehiclePropertyIds.toString(property), e); 684 } 685 } 686 687 /** 688 * Subscribe given property. Only Hal service owning the property can subscribe it. 689 * 690 * @param service HalService that owns this property 691 * @param property property id (VehicleProperty) 692 * @param samplingRateHz sampling rate in Hz for continuous properties 693 * @throws IllegalArgumentException thrown if property is not supported by VHAL 694 * @throws ServiceSpecificException if VHAL returns error or lost connection with VHAL. 695 */ subscribeProperty(HalServiceBase service, int property, float samplingRateHz)696 public void subscribeProperty(HalServiceBase service, int property, float samplingRateHz) 697 throws IllegalArgumentException, ServiceSpecificException { 698 HalSubscribeOptions options = new HalSubscribeOptions(property, new int[0], samplingRateHz); 699 subscribeProperty(service, List.of(options)); 700 } 701 702 /** 703 * Similar to {@link #subscribeProperty(HalServiceBase, int, float)} except that all exceptions 704 * are caught and converted to logs. 705 */ subscribePropertySafe(HalServiceBase service, int property, float sampleRateHz)706 public void subscribePropertySafe(HalServiceBase service, int property, float sampleRateHz) { 707 try { 708 subscribeProperty(service, property, sampleRateHz); 709 } catch (IllegalArgumentException | ServiceSpecificException e) { 710 Slogf.w(CarLog.TAG_HAL, e, "Failed to subscribe for property: %s, sample rate: %f hz", 711 VehiclePropertyIds.toString(property), sampleRateHz); 712 } 713 } 714 715 /** 716 * Subscribe given property. Only Hal service owning the property can subscribe it. 717 * 718 * @param service HalService that owns this property 719 * @param halSubscribeOptions Information needed to subscribe to VHAL 720 * @throws IllegalArgumentException thrown if property is not supported by VHAL 721 * @throws ServiceSpecificException if VHAL returns error or lost connection with VHAL. 722 */ subscribeProperty(HalServiceBase service, List<HalSubscribeOptions> halSubscribeOptions)723 public void subscribeProperty(HalServiceBase service, List<HalSubscribeOptions> 724 halSubscribeOptions) throws IllegalArgumentException, ServiceSpecificException { 725 synchronized (mLock) { 726 PairSparseArray<RateInfo> previousState = cloneState(mRateInfoByPropIdAreaId); 727 SubscribeOptions[] subscribeOptions = createVhalSubscribeOptionsLocked( 728 service, halSubscribeOptions); 729 if (subscribeOptions.length == 0) { 730 if (DBG) { 731 Slogf.d(CarLog.TAG_HAL, 732 "Ignore the subscribeProperty request, SubscribeOptions is length 0"); 733 } 734 return; 735 } 736 try { 737 mSubscriptionClient.subscribe(subscribeOptions); 738 } catch (RemoteException e) { 739 mRateInfoByPropIdAreaId = previousState; 740 Slogf.w(CarLog.TAG_HAL, "Failed to subscribe, connection to VHAL failed", e); 741 // Convert RemoteException to ServiceSpecificException so that it could be passed 742 // back to the client. 743 throw new ServiceSpecificException(StatusCode.INTERNAL_ERROR, 744 "Failed to subscribe, connection to VHAL failed, error: " + e); 745 } catch (ServiceSpecificException e) { 746 mRateInfoByPropIdAreaId = previousState; 747 Slogf.w(CarLog.TAG_HAL, "Failed to subscribe, received error from VHAL", e); 748 throw e; 749 } 750 } 751 } 752 753 /** 754 * Converts {@link HalSubscribeOptions} to {@link SubscribeOptions} which is the data structure 755 * used by VHAL. 756 */ 757 @GuardedBy("mLock") createVhalSubscribeOptionsLocked(HalServiceBase service, List<HalSubscribeOptions> halSubscribeOptions)758 private SubscribeOptions[] createVhalSubscribeOptionsLocked(HalServiceBase service, 759 List<HalSubscribeOptions> halSubscribeOptions) throws IllegalArgumentException { 760 if (DBG) { 761 Slogf.d(CarLog.TAG_HAL, "creating subscribeOptions from HalSubscribeOptions of size: " 762 + halSubscribeOptions.size()); 763 } 764 List<SubscribeOptions> subscribeOptionsList = new ArrayList<>(); 765 for (int i = 0; i < halSubscribeOptions.size(); i++) { 766 HalSubscribeOptions halSubscribeOption = halSubscribeOptions.get(i); 767 int property = halSubscribeOption.getHalPropId(); 768 int[] areaIds = halSubscribeOption.getAreaId(); 769 float samplingRateHz = halSubscribeOption.getUpdateRateHz(); 770 boolean enableVariableUpdateRate = halSubscribeOption.isVariableUpdateRateEnabled(); 771 float resolution = halSubscribeOption.getResolution(); 772 773 HalPropConfig config; 774 config = mAllProperties.get(property); 775 776 if (config == null) { 777 throw new IllegalArgumentException("subscribe error: " 778 + toPropertyIdString(property) + " is not supported"); 779 } 780 781 if (enableVariableUpdateRate) { 782 if (config.getChangeMode() != VehiclePropertyChangeMode.CONTINUOUS) { 783 // enableVur should be ignored if property is not continuous, but we set it to 784 // false to be safe. 785 enableVariableUpdateRate = false; 786 Slogf.w(CarLog.TAG_HAL, "VUR is always off for non-continuous property: " 787 + toPropertyIdString(property)); 788 } 789 if (!mFeatureFlags.variableUpdateRate()) { 790 enableVariableUpdateRate = false; 791 Slogf.w(CarLog.TAG_HAL, "VUR feature is not enabled, VUR is always off"); 792 } 793 } 794 795 if (resolution != 0.0f) { 796 if (config.getChangeMode() != VehiclePropertyChangeMode.CONTINUOUS) { 797 // resolution should be ignored if property is not continuous, but we set it to 798 // 0 to be safe. 799 resolution = 0.0f; 800 Slogf.w(CarLog.TAG_HAL, "resolution is always 0 for non-continuous property: " 801 + toPropertyIdString(property)); 802 } 803 if (!mFeatureFlags.subscriptionWithResolution()) { 804 resolution = 0.0f; 805 Slogf.w(CarLog.TAG_HAL, 806 "Resolution feature is not enabled, resolution is always 0"); 807 } 808 } 809 810 if (isStaticProperty(config)) { 811 Slogf.w(CarLog.TAG_HAL, "Ignore subscribing to static property: " 812 + toPropertyIdString(property)); 813 continue; 814 } 815 816 if (areaIds.length == 0) { 817 if (!isPropertySubscribable(config)) { 818 throw new IllegalArgumentException("Property: " + toPropertyIdString(property) 819 + " is not subscribable"); 820 } 821 areaIds = getAllAreaIdsFromPropertyId(config); 822 } else { 823 for (int j = 0; j < areaIds.length; j++) { 824 Integer access = mAccessByPropIdAreaId.get(config.getPropId(), areaIds[j]); 825 if (access == null) { 826 throw new IllegalArgumentException( 827 "Cannot subscribe to " + toPropertyIdString(property) 828 + " at areaId " + toAreaIdString(property, areaIds[j]) 829 + " the property does not have the requested areaId"); 830 } 831 if (!isPropIdAreaIdReadable(config, access.intValue())) { 832 throw new IllegalArgumentException( 833 "Cannot subscribe to " + toPropertyIdString(property) 834 + " at areaId " + toAreaIdString(property, areaIds[j]) 835 + " the property's access mode does not contain READ"); 836 } 837 } 838 } 839 SubscribeOptions opts = new SubscribeOptions(); 840 opts.propId = property; 841 opts.sampleRate = samplingRateHz; 842 opts.enableVariableUpdateRate = enableVariableUpdateRate; 843 opts.resolution = resolution; 844 RateInfo rateInfo = new RateInfo(samplingRateHz, enableVariableUpdateRate, resolution); 845 int[] filteredAreaIds = filterAreaIdsWithSameRateInfo(property, areaIds, rateInfo); 846 opts.areaIds = filteredAreaIds; 847 if (opts.areaIds.length == 0) { 848 if (DBG) { 849 Slogf.d(CarLog.TAG_HAL, "property: " + VehiclePropertyIds.toString(property) 850 + " is already subscribed at rate: " + samplingRateHz + " hz"); 851 } 852 continue; 853 } 854 assertServiceOwnerLocked(service, property); 855 for (int j = 0; j < filteredAreaIds.length; j++) { 856 if (DBG) { 857 Slogf.d(CarLog.TAG_HAL, "Update subscription rate for propertyId:" 858 + " %s, areaId: %d, SampleRateHz: %f, enableVur: %b," 859 + " resolution: %f", 860 VehiclePropertyIds.toString(opts.propId), filteredAreaIds[j], 861 samplingRateHz, enableVariableUpdateRate, resolution); 862 } 863 mRateInfoByPropIdAreaId.put(property, filteredAreaIds[j], rateInfo); 864 } 865 subscribeOptionsList.add(opts); 866 } 867 return subscribeOptionsList.toArray(new SubscribeOptions[0]); 868 } 869 filterAreaIdsWithSameRateInfo(int property, int[] areaIds, RateInfo rateInfo)870 private int[] filterAreaIdsWithSameRateInfo(int property, int[] areaIds, RateInfo rateInfo) { 871 List<Integer> areaIdList = new ArrayList<>(); 872 synchronized (mLock) { 873 for (int i = 0; i < areaIds.length; i++) { 874 RateInfo savedRateInfo = mRateInfoByPropIdAreaId.get(property, areaIds[i]); 875 876 // Strict equality (==) is used here for comparing resolutions. This approach does 877 // not introduce a margin of error through PRECISION_THRESHOLD, and thus can allow 878 // clients to request the highest possible resolution without being limited by a 879 // predefined threshold. This approach is assumed to be feasible under the 880 // hypothesis that the floating point representation of numbers is consistent 881 // across the system. That is, if two clients specify a resolution of 0.01f, 882 // their internal representations will match, enabling an exact comparison despite 883 // floating point inaccuracies. If this is inaccurate, we must introduce a margin 884 // of error (ideally 1e-7 as floats can reliably represent up to 7 significant 885 // figures, but can be higher if necessary), and update the documentation in {@link 886 // android.car.hardware.property.Subscription.Builder#setResolution(float)} 887 // appropriately. 888 if (savedRateInfo != null 889 && (Math.abs(savedRateInfo.updateRateHz - rateInfo.updateRateHz) 890 < PRECISION_THRESHOLD) 891 && (savedRateInfo.enableVariableUpdateRate 892 == rateInfo.enableVariableUpdateRate) 893 && savedRateInfo.resolution == rateInfo.resolution) { 894 if (DBG) { 895 Slogf.d(CarLog.TAG_HAL, "Property: %s is already subscribed at rate: %f hz" 896 + ", enableVur: %b, resolution: %f", 897 toPropertyIdString(property), rateInfo.updateRateHz, 898 rateInfo.enableVariableUpdateRate, rateInfo.resolution); 899 } 900 continue; 901 } 902 areaIdList.add(areaIds[i]); 903 } 904 } 905 return CarServiceUtils.toIntArray(areaIdList); 906 } 907 getAllAreaIdsFromPropertyId(HalPropConfig config)908 private int[] getAllAreaIdsFromPropertyId(HalPropConfig config) { 909 HalAreaConfig[] allAreaConfigs = config.getAreaConfigs(); 910 if (allAreaConfigs.length == 0) { 911 return new int[]{/* areaId= */ 0}; 912 } 913 int[] areaId = new int[allAreaConfigs.length]; 914 for (int i = 0; i < allAreaConfigs.length; i++) { 915 areaId[i] = allAreaConfigs[i].getAreaId(); 916 } 917 return areaId; 918 } 919 920 /** 921 * Like {@link unsubscribeProperty} except that exceptions are logged. 922 */ unsubscribePropertySafe(HalServiceBase service, int property)923 public void unsubscribePropertySafe(HalServiceBase service, int property) { 924 try { 925 unsubscribeProperty(service, property); 926 } catch (ServiceSpecificException e) { 927 Slogf.w(CarLog.TAG_SERVICE, "Failed to unsubscribe: " 928 + toPropertyIdString(property), e); 929 } 930 } 931 932 /** 933 * Unsubscribes from receiving notifications for the property and HAL services passed 934 * as parameters. 935 */ unsubscribeProperty(HalServiceBase service, int property)936 public void unsubscribeProperty(HalServiceBase service, int property) 937 throws ServiceSpecificException { 938 if (DBG) { 939 Slogf.d(CarLog.TAG_HAL, "unsubscribeProperty, service:" + service 940 + ", " + toPropertyIdString(property)); 941 } 942 synchronized (mLock) { 943 HalPropConfig config = mAllProperties.get(property); 944 if (config == null) { 945 Slogf.w(CarLog.TAG_HAL, "unsubscribeProperty " + toPropertyIdString(property) 946 + " does not exist"); 947 return; 948 } 949 if (isStaticProperty(config)) { 950 Slogf.w(CarLog.TAG_HAL, "Unsubscribe to a static property: " 951 + toPropertyIdString(property) + ", do nothing"); 952 return; 953 } 954 assertServiceOwnerLocked(service, property); 955 HalAreaConfig[] halAreaConfigs = config.getAreaConfigs(); 956 boolean isSubscribed = false; 957 PairSparseArray<RateInfo> previousState = cloneState(mRateInfoByPropIdAreaId); 958 if (halAreaConfigs.length == 0) { 959 int index = mRateInfoByPropIdAreaId.indexOfKeyPair(property, 0); 960 if (hasReadAccess(config.getAccess()) && index >= 0) { 961 mRateInfoByPropIdAreaId.removeAt(index); 962 isSubscribed = true; 963 } 964 } else { 965 for (int i = 0; i < halAreaConfigs.length; i++) { 966 if (!isPropIdAreaIdReadable(config, halAreaConfigs[i].getAccess())) { 967 Slogf.w(CarLog.TAG_HAL, 968 "Cannot unsubscribe to " + toPropertyIdString(property) 969 + " at areaId " + toAreaIdString(property, 970 halAreaConfigs[i].getAreaId()) 971 + " the property's access mode does not contain READ"); 972 continue; 973 } 974 int index = mRateInfoByPropIdAreaId.indexOfKeyPair(property, 975 halAreaConfigs[i].getAreaId()); 976 if (index >= 0) { 977 mRateInfoByPropIdAreaId.removeAt(index); 978 isSubscribed = true; 979 } 980 } 981 } 982 if (!isSubscribed) { 983 if (DBG) { 984 Slogf.d(CarLog.TAG_HAL, "Property " + toPropertyIdString(property) 985 + " was not subscribed, do nothing"); 986 } 987 return; 988 } 989 try { 990 mSubscriptionClient.unsubscribe(property); 991 } catch (RemoteException e) { 992 mRateInfoByPropIdAreaId = previousState; 993 Slogf.w(CarLog.TAG_HAL, "Failed to unsubscribe, connection to VHAL failed", e); 994 throw new ServiceSpecificException(StatusCode.INTERNAL_ERROR, 995 "Failed to unsubscribe, connection to VHAL failed, error: " + e); 996 } catch (ServiceSpecificException e) { 997 mRateInfoByPropIdAreaId = previousState; 998 Slogf.w(CarLog.TAG_HAL, "Failed to unsubscribe, received error from VHAL", e); 999 throw e; 1000 } 1001 } 1002 } 1003 1004 /** 1005 * Indicates if the property passed as parameter is supported. 1006 */ isPropertySupported(int propertyId)1007 public boolean isPropertySupported(int propertyId) { 1008 synchronized (mLock) { 1009 return mAllProperties.contains(propertyId); 1010 } 1011 } 1012 1013 /** 1014 * Gets given property with retries. 1015 * 1016 * <p>If getting the property fails after all retries, it will throw 1017 * {@code IllegalStateException}. If the property is not supported, it will simply return 1018 * {@code null}. 1019 */ 1020 @Nullable getIfSupportedOrFail(int propertyId, int maxRetries)1021 public HalPropValue getIfSupportedOrFail(int propertyId, int maxRetries) { 1022 if (!isPropertySupported(propertyId)) { 1023 return null; 1024 } 1025 try { 1026 return getValueWithRetry(mPropValueBuilder.build(propertyId, GLOBAL_AREA_ID), 1027 maxRetries); 1028 } catch (Exception e) { 1029 throw new IllegalStateException(e); 1030 } 1031 } 1032 1033 /** 1034 * This works similar to {@link #getIfSupportedOrFail(int, int)} except that this can be called 1035 * before {@code init()} is called. 1036 * 1037 * <p>This call will check if requested vhal property is supported by querying directly to vhal 1038 * and can have worse performance. Use this only for accessing vhal properties before 1039 * {@code ICarImpl.init()} phase. 1040 */ 1041 @Nullable getIfSupportedOrFailForEarlyStage(int propertyId, int maxRetries)1042 public HalPropValue getIfSupportedOrFailForEarlyStage(int propertyId, int maxRetries) { 1043 fetchAllPropConfigs(); 1044 return getIfSupportedOrFail(propertyId, maxRetries); 1045 } 1046 1047 /** 1048 * Returns the property's {@link HalPropValue} for the property id passed as parameter and 1049 * not specified area. 1050 * 1051 * @throws IllegalArgumentException if argument is invalid 1052 * @throws ServiceSpecificException if VHAL returns error 1053 */ get(int propertyId)1054 public HalPropValue get(int propertyId) 1055 throws IllegalArgumentException, ServiceSpecificException { 1056 return get(propertyId, GLOBAL_AREA_ID); 1057 } 1058 1059 /** 1060 * Returns the property's {@link HalPropValue} for the property id and area id passed as 1061 * parameters. 1062 * 1063 * @throws IllegalArgumentException if argument is invalid 1064 * @throws ServiceSpecificException if VHAL returns error 1065 */ get(int propertyId, int areaId)1066 public HalPropValue get(int propertyId, int areaId) 1067 throws IllegalArgumentException, ServiceSpecificException { 1068 if (DBG) { 1069 Slogf.d(CarLog.TAG_HAL, "get, " + toPropertyIdString(propertyId) 1070 + toAreaIdString(propertyId, areaId)); 1071 } 1072 return getValueWithRetry(mPropValueBuilder.build(propertyId, areaId)); 1073 } 1074 1075 /** 1076 * Returns the property object value for the class and property id passed as parameter and 1077 * no area specified. 1078 * 1079 * @throws IllegalArgumentException if argument is invalid 1080 * @throws ServiceSpecificException if VHAL returns error 1081 */ get(Class clazz, int propertyId)1082 public <T> T get(Class clazz, int propertyId) 1083 throws IllegalArgumentException, ServiceSpecificException { 1084 return get(clazz, propertyId, GLOBAL_AREA_ID); 1085 } 1086 1087 /** 1088 * Returns the property object value for the class, property id, and area id passed as 1089 * parameter. 1090 * 1091 * @throws IllegalArgumentException if argument is invalid 1092 * @throws ServiceSpecificException if VHAL returns error 1093 */ get(Class clazz, int propertyId, int areaId)1094 public <T> T get(Class clazz, int propertyId, int areaId) 1095 throws IllegalArgumentException, ServiceSpecificException { 1096 return get(clazz, mPropValueBuilder.build(propertyId, areaId)); 1097 } 1098 1099 /** 1100 * Returns the property object value for the class and requested property value passed as 1101 * parameter. 1102 * 1103 * @throws IllegalArgumentException if argument is invalid 1104 * @throws ServiceSpecificException if VHAL returns error 1105 */ 1106 @SuppressWarnings("unchecked") get(Class clazz, HalPropValue requestedPropValue)1107 public <T> T get(Class clazz, HalPropValue requestedPropValue) 1108 throws IllegalArgumentException, ServiceSpecificException { 1109 HalPropValue propValue; 1110 propValue = getValueWithRetry(requestedPropValue); 1111 1112 if (clazz == Long.class || clazz == long.class) { 1113 Long value = propValue.getInt64Value(0); 1114 return (T) value; 1115 } else if (clazz == Integer.class || clazz == int.class) { 1116 Integer value = propValue.getInt32Value(0); 1117 return (T) value; 1118 } else if (clazz == Boolean.class || clazz == boolean.class) { 1119 Boolean value = Boolean.valueOf(propValue.getInt32Value(0) == 1); 1120 return (T) value; 1121 } else if (clazz == Float.class || clazz == float.class) { 1122 Float value = propValue.getFloatValue(0); 1123 return (T) value; 1124 } else if (clazz == Long[].class) { 1125 int size = propValue.getInt64ValuesSize(); 1126 Long[] longArray = new Long[size]; 1127 for (int i = 0; i < size; i++) { 1128 longArray[i] = propValue.getInt64Value(i); 1129 } 1130 return (T) longArray; 1131 } else if (clazz == Integer[].class) { 1132 int size = propValue.getInt32ValuesSize(); 1133 Integer[] intArray = new Integer[size]; 1134 for (int i = 0; i < size; i++) { 1135 intArray[i] = propValue.getInt32Value(i); 1136 } 1137 return (T) intArray; 1138 } else if (clazz == Float[].class) { 1139 int size = propValue.getFloatValuesSize(); 1140 Float[] floatArray = new Float[size]; 1141 for (int i = 0; i < size; i++) { 1142 floatArray[i] = propValue.getFloatValue(i); 1143 } 1144 return (T) floatArray; 1145 } else if (clazz == long[].class) { 1146 int size = propValue.getInt64ValuesSize(); 1147 long[] longArray = new long[size]; 1148 for (int i = 0; i < size; i++) { 1149 longArray[i] = propValue.getInt64Value(i); 1150 } 1151 return (T) longArray; 1152 } else if (clazz == int[].class) { 1153 int size = propValue.getInt32ValuesSize(); 1154 int[] intArray = new int[size]; 1155 for (int i = 0; i < size; i++) { 1156 intArray[i] = propValue.getInt32Value(i); 1157 } 1158 return (T) intArray; 1159 } else if (clazz == float[].class) { 1160 int size = propValue.getFloatValuesSize(); 1161 float[] floatArray = new float[size]; 1162 for (int i = 0; i < size; i++) { 1163 floatArray[i] = propValue.getFloatValue(i); 1164 } 1165 return (T) floatArray; 1166 } else if (clazz == byte[].class) { 1167 return (T) propValue.getByteArray(); 1168 } else if (clazz == String.class) { 1169 return (T) propValue.getStringValue(); 1170 } else { 1171 throw new IllegalArgumentException("Unexpected type: " + clazz); 1172 } 1173 } 1174 1175 /** 1176 * Returns the vehicle's {@link HalPropValue} for the requested property value passed 1177 * as parameter. 1178 * 1179 * @throws IllegalArgumentException if argument is invalid 1180 * @throws ServiceSpecificException if VHAL returns error 1181 */ get(HalPropValue requestedPropValue)1182 public HalPropValue get(HalPropValue requestedPropValue) 1183 throws IllegalArgumentException, ServiceSpecificException { 1184 return getValueWithRetry(requestedPropValue); 1185 } 1186 1187 /** 1188 * Set property. 1189 * 1190 * @throws IllegalArgumentException if argument is invalid 1191 * @throws ServiceSpecificException if VHAL returns error 1192 */ set(HalPropValue propValue)1193 public void set(HalPropValue propValue) 1194 throws IllegalArgumentException, ServiceSpecificException { 1195 setValueWithRetry(propValue); 1196 } 1197 1198 @CheckResult set(int propId)1199 HalPropValueSetter set(int propId) { 1200 return set(propId, GLOBAL_AREA_ID); 1201 } 1202 1203 @CheckResult set(int propId, int areaId)1204 HalPropValueSetter set(int propId, int areaId) { 1205 return new HalPropValueSetter(propId, areaId); 1206 } 1207 hasReadAccess(int accessLevel)1208 private static boolean hasReadAccess(int accessLevel) { 1209 return accessLevel == VehiclePropertyAccess.READ 1210 || accessLevel == VehiclePropertyAccess.READ_WRITE; 1211 } 1212 isPropIdAreaIdReadable(HalPropConfig config, int areaIdAccess)1213 private static boolean isPropIdAreaIdReadable(HalPropConfig config, int areaIdAccess) { 1214 return (areaIdAccess == VehiclePropertyAccess.NONE) 1215 ? hasReadAccess(config.getAccess()) : hasReadAccess(areaIdAccess); 1216 } 1217 1218 /** 1219 * Returns whether the property is readable and not static. 1220 */ isPropertySubscribable(HalPropConfig config)1221 static boolean isPropertySubscribable(HalPropConfig config) { 1222 if (isStaticProperty(config)) { 1223 Slogf.w(CarLog.TAG_HAL, "Subscribe to a static property: " 1224 + toPropertyIdString(config.getPropId()) + ", do nothing"); 1225 return false; 1226 } 1227 if (config.getAreaConfigs().length == 0) { 1228 boolean hasReadAccess = hasReadAccess(config.getAccess()); 1229 if (!hasReadAccess) { 1230 Slogf.w(CarLog.TAG_HAL, "Cannot subscribe to " 1231 + toPropertyIdString(config.getPropId()) 1232 + " the property's access mode does not contain READ"); 1233 } 1234 return hasReadAccess; 1235 } 1236 for (HalAreaConfig halAreaConfig : config.getAreaConfigs()) { 1237 if (!isPropIdAreaIdReadable(config, halAreaConfig.getAccess())) { 1238 Slogf.w(CarLog.TAG_HAL, "Cannot subscribe to " 1239 + toPropertyIdString(config.getPropId()) + " at areaId " 1240 + toAreaIdString(config.getPropId(), halAreaConfig.getAreaId()) 1241 + " the property's access mode does not contain READ"); 1242 return false; 1243 } 1244 } 1245 return true; 1246 } 1247 1248 /** 1249 * Sets a passed propertyId+areaId from the shell command. 1250 * 1251 * @param propertyId Property ID 1252 * @param areaId Area ID 1253 * @param data Comma-separated value. 1254 */ setPropertyFromCommand(int propertyId, int areaId, String data, IndentingPrintWriter writer)1255 public void setPropertyFromCommand(int propertyId, int areaId, String data, 1256 IndentingPrintWriter writer) throws IllegalArgumentException, ServiceSpecificException { 1257 long timestampNanos = SystemClock.elapsedRealtimeNanos(); 1258 HalPropValue halPropValue = createPropValueForInjecting(mPropValueBuilder, propertyId, 1259 areaId, List.of(data.split(DATA_DELIMITER)), timestampNanos); 1260 if (halPropValue == null) { 1261 throw new IllegalArgumentException( 1262 "Unsupported property type: propertyId=" + toPropertyIdString(propertyId) 1263 + ", areaId=" + toAreaIdString(propertyId, areaId)); 1264 } 1265 set(halPropValue); 1266 } 1267 1268 1269 @Override onPropertyEvent(List<HalPropValue> propValues)1270 public void onPropertyEvent(List<HalPropValue> propValues) { 1271 mHandler.post(() -> handleOnPropertyEvent(propValues)); 1272 } 1273 1274 @Override onInjectionPropertyEvent(List<HalPropValue> propValues)1275 public void onInjectionPropertyEvent(List<HalPropValue> propValues) { 1276 mHandler.post(() -> dispatchPropertyEvents(propValues)); 1277 } 1278 1279 @Override onPropertySetError(List<VehiclePropError> errors)1280 public void onPropertySetError(List<VehiclePropError> errors) { 1281 mHandler.post(() -> handleOnPropertySetError(errors)); 1282 } 1283 1284 @Override 1285 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)1286 public void dump(IndentingPrintWriter writer) { 1287 synchronized (mLock) { 1288 writer.println("**dump HAL services**"); 1289 for (int i = 0; i < mAllServices.size(); i++) { 1290 mAllServices.get(i).dump(writer); 1291 } 1292 // Dump all VHAL property configure. 1293 dumpPropertyConfigs(writer, -1); 1294 writer.printf("**All Events, now ns:%d**\n", 1295 SystemClock.elapsedRealtimeNanos()); 1296 for (int i = 0; i < mEventLog.size(); i++) { 1297 VehiclePropertyEventInfo info = mEventLog.valueAt(i); 1298 writer.printf("event count:%d, lastEvent: ", info.mEventCount); 1299 dumpPropValue(writer, info.mLastEvent); 1300 } 1301 writer.println("**Property handlers**"); 1302 for (int i = 0; i < mPropertyHandlers.size(); i++) { 1303 int propId = mPropertyHandlers.keyAt(i); 1304 HalServiceBase service = mPropertyHandlers.valueAt(i); 1305 writer.printf("Property Id: %d // 0x%x name: %s, service: %s\n", propId, propId, 1306 VehiclePropertyIds.toString(propId), service); 1307 } 1308 } 1309 } 1310 1311 private final class RecordingListenerHandler implements IBinder.DeathRecipient { 1312 1313 private ICarPropertyEventListener mCallback; 1314 RecordingListenerHandler(ICarPropertyEventListener callback)1315 private RecordingListenerHandler(ICarPropertyEventListener callback) { 1316 mCallback = callback; 1317 } 1318 onEvent(List<CarPropertyEvent> events)1319 private void onEvent(List<CarPropertyEvent> events) { 1320 try { 1321 mCallback.onEvent(events); 1322 } catch (RemoteException e) { 1323 Slogf.e(CarLog.TAG_HAL, "onEvent failed", e); 1324 } 1325 } 1326 linkToDeath()1327 private boolean linkToDeath() { 1328 IBinder binder = mCallback.asBinder(); 1329 try { 1330 binder.linkToDeath(this, 0); 1331 return true; 1332 } catch (RemoteException e) { 1333 mCallback = null; 1334 Slogf.w(CarLog.TAG_HAL, e, "Linking to binder death recipient failed"); 1335 } 1336 return false; 1337 } 1338 unlinkToDeath()1339 private void unlinkToDeath() { 1340 if (mCallback == null) { 1341 return; 1342 } 1343 IBinder binder = mCallback.asBinder(); 1344 binder.unlinkToDeath(this, 0); 1345 } 1346 1347 @Override binderDied()1348 public void binderDied() { 1349 Slogf.w(CarLog.TAG_HAL, "Recording listener died"); 1350 stopRecordingVehicleProperties(mCallback); 1351 } 1352 } 1353 maybeHandleRecording(List<HalPropValue> halPropValues)1354 private void maybeHandleRecording(List<HalPropValue> halPropValues) { 1355 RecordingListenerHandler recordingListenerHandler; 1356 List<CarPropertyEvent> events = new ArrayList<>(); 1357 synchronized (mLock) { 1358 if (mListenerHandler == null || !BuildHelper.isDebuggableBuild()) { 1359 return; 1360 } 1361 for (int i = 0; i < halPropValues.size(); i++) { 1362 HalPropValue halPropValue = halPropValues.get(i); 1363 HalPropConfig halPropConfig = mAllProperties.get(halPropValue.getPropId()); 1364 if (halPropConfig == null) { 1365 Slogf.w(CarLog.TAG_HAL, "No HalPropConfig associated with property %d", 1366 halPropValue.getPropId()); 1367 continue; 1368 } 1369 CarPropertyValue<?> carPropertyvalue = halPropValues.get(i).toCarPropertyValue( 1370 halPropValue.getPropId(), halPropConfig, /* isVhalPropId= */ true); 1371 events.add(new CarPropertyEvent( 1372 CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, carPropertyvalue)); 1373 } 1374 recordingListenerHandler = mListenerHandler; 1375 } 1376 if (events.isEmpty()) { 1377 return; 1378 } 1379 recordingListenerHandler.onEvent(events); 1380 } 1381 1382 /** 1383 * Registers a recording listener. 1384 * 1385 * @param callback The callback to register 1386 * @return A list of CarPropertyConfigs that are being recorded 1387 */ registerRecordingListener(ICarPropertyEventListener callback)1388 public List<HalPropConfig> registerRecordingListener(ICarPropertyEventListener callback) { 1389 synchronized (mLock) { 1390 if (mListenerHandler != null) { 1391 throw new IllegalStateException("Recording already in progress"); 1392 } 1393 mListenerHandler = new RecordingListenerHandler(callback); 1394 if (!mListenerHandler.linkToDeath()) { 1395 throw new IllegalStateException("Failed to link to death, the client is probably" 1396 + " already dead."); 1397 } 1398 1399 List<HalPropConfig> allHalPropConfigs = new ArrayList<>(); 1400 for (int i = 0; i < mAllProperties.size(); i++) { 1401 allHalPropConfigs.add(mAllProperties.valueAt(i)); 1402 } 1403 return allHalPropConfigs; 1404 } 1405 } 1406 1407 /** 1408 * @return {@code true} If currently recording vehicle properties 1409 */ isRecordingVehicleProperties()1410 public boolean isRecordingVehicleProperties() { 1411 synchronized (mLock) { 1412 return isRecordingVehiclePropertiesLocked(); 1413 } 1414 } 1415 1416 @GuardedBy("mLock") isRecordingVehiclePropertiesLocked()1417 private boolean isRecordingVehiclePropertiesLocked() { 1418 return mListenerHandler != null; 1419 } 1420 1421 /** 1422 * Stops the recording. If no recording is present, treat as no-op. 1423 * 1424 * @param callback The callback to stop recording. 1425 */ stopRecordingVehicleProperties(ICarPropertyEventListener callback)1426 public void stopRecordingVehicleProperties(ICarPropertyEventListener callback) { 1427 synchronized (mLock) { 1428 if (mListenerHandler == null) { 1429 Slogf.w(CarLog.TAG_HAL, "No recording was started"); 1430 return; 1431 } 1432 if (mListenerHandler.mCallback.asBinder() != callback.asBinder()) { 1433 Slogf.w(CarLog.TAG_HAL, "ICarPropertyEventListener are not the same"); 1434 return; 1435 } 1436 mListenerHandler.unlinkToDeath(); 1437 mListenerHandler = null; 1438 } 1439 } 1440 1441 /** 1442 * Disables injection mode. 1443 */ disableInjectionMode()1444 public void disableInjectionMode() { 1445 synchronized (mLock) { 1446 if (!isVehiclePropertyInjectionModeEnabled()) { 1447 Slogf.w(CarLog.TAG_HAL, "Cannot disable injection mode, injection mode is" 1448 + " not enabled"); 1449 return; 1450 } 1451 mPropertyIdsFromRealHardware.clear(); 1452 mVehicleStub.set(mVehicleStub.get().getRealVehicleStub()); 1453 } 1454 } 1455 1456 /** 1457 * Enables Injection mode with the list of properties to allow to come from the real VHAL. 1458 * @param propertyIdsFromRealHardware THe list of properties to allow to come from real VHAL. 1459 */ enableInjectionMode(List<Integer> propertyIdsFromRealHardware)1460 public long enableInjectionMode(List<Integer> propertyIdsFromRealHardware) { 1461 synchronized (mLock) { 1462 if (isRecordingVehiclePropertiesLocked()) { 1463 throw new IllegalStateException("Cannot enable injection mode while recording is in" 1464 + " progress"); 1465 } 1466 if (isVehiclePropertyInjectionModeEnabled()) { 1467 Slogf.w(CarLog.TAG_HAL, "Cannot enable injection mode, it is already in" 1468 + " progress"); 1469 return -1L; 1470 } 1471 // Creation of SimulationVehicleStub needs to be inside lock because 1472 // isVehiclePropertyInjectionModeEnabled can return false and cause another creation of 1473 // an SimulationVehicleStub before mVehicleStub is actually set. 1474 try { 1475 mVehicleStub.set(new SimulationVehicleStub( 1476 mVehicleStub.get(), propertyIdsFromRealHardware, this)); 1477 mPropertyIdsFromRealHardware.addAll(propertyIdsFromRealHardware); 1478 } catch (RemoteException e) { 1479 throw new IllegalStateException("Failed to create SimulationVehicleStub", e); 1480 } 1481 } 1482 return mVehicleStub.get().getSimulationStartTimestampNanos(); 1483 } 1484 1485 /** 1486 * @return {@code true} if Vehicle property injection mode is enabled, {@code false} otherwise. 1487 */ isVehiclePropertyInjectionModeEnabled()1488 public boolean isVehiclePropertyInjectionModeEnabled() { 1489 return mVehicleStub.get().isSimulatedModeEnabled(); 1490 } 1491 1492 /** 1493 * Gets the last injected vehicle property for the propertyId. 1494 * 1495 * @param propertyId The propertyId that was last injected. 1496 * @return The {@link CarPropertyValue} that was last injected. 1497 */ 1498 @Nullable getLastInjectedVehicleProperty(int propertyId)1499 public CarPropertyValue getLastInjectedVehicleProperty(int propertyId) { 1500 if (!isVehiclePropertyInjectionModeEnabled()) { 1501 throw new IllegalStateException("Vehicle property injection mode is not enabled!"); 1502 } 1503 return mVehicleStub.get().getLastInjectedVehicleProperty(propertyId); 1504 } 1505 1506 /** 1507 * Injects the CarPropertyValues. 1508 * @param carPropertyValues The carPropertyValues to inject. 1509 */ injectVehicleProperties(List<CarPropertyValue> carPropertyValues)1510 public void injectVehicleProperties(List<CarPropertyValue> carPropertyValues) { 1511 if (!isVehiclePropertyInjectionModeEnabled()) { 1512 throw new IllegalStateException("Vehicle property injection mode is not enabled!"); 1513 } 1514 mVehicleStub.get().injectVehicleProperties(carPropertyValues); 1515 } 1516 1517 /** 1518 * Dumps or debug VHAL. 1519 */ 1520 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dumpVhal(ParcelFileDescriptor fd, List<String> options)1521 public void dumpVhal(ParcelFileDescriptor fd, List<String> options) throws RemoteException { 1522 VehicleStub vehicleStub = mVehicleStub.get(); 1523 vehicleStub.dump(fd.getFileDescriptor(), options); 1524 } 1525 1526 /** 1527 * Dumps the list of HALs. 1528 */ dumpListHals(PrintWriter writer)1529 public void dumpListHals(PrintWriter writer) { 1530 synchronized (mLock) { 1531 for (int i = 0; i < mAllServices.size(); i++) { 1532 writer.println(mAllServices.get(i).getClass().getName()); 1533 } 1534 } 1535 } 1536 1537 /** 1538 * Dumps the given HALs. 1539 */ dumpSpecificHals(PrintWriter writer, String... halNames)1540 public void dumpSpecificHals(PrintWriter writer, String... halNames) { 1541 synchronized (mLock) { 1542 ArrayMap<String, HalServiceBase> byName = new ArrayMap<>(); 1543 for (int index = 0; index < mAllServices.size(); index++) { 1544 HalServiceBase halService = mAllServices.get(index); 1545 byName.put(halService.getClass().getSimpleName(), halService); 1546 } 1547 for (String halName : halNames) { 1548 HalServiceBase service = byName.get(halName); 1549 if (service == null) { 1550 writer.printf("No HAL named %s. Valid options are: %s\n", 1551 halName, byName.keySet()); 1552 continue; 1553 } 1554 service.dump(writer); 1555 } 1556 } 1557 } 1558 1559 /** 1560 * Dumps vehicle property values. 1561 * 1562 * @param propertyId property id, dump all properties' value if it is {@code -1}. 1563 * @param areaId areaId of the property, dump the property for all areaIds in the config 1564 * if it is {@code -1} 1565 */ dumpPropertyValueByCommand(PrintWriter writer, int propertyId, int areaId)1566 public void dumpPropertyValueByCommand(PrintWriter writer, int propertyId, int areaId) { 1567 if (propertyId == -1) { 1568 writer.println("**All property values**"); 1569 synchronized (mLock) { 1570 for (int i = 0; i < mAllProperties.size(); i++) { 1571 HalPropConfig config = mAllProperties.valueAt(i); 1572 dumpPropertyValueByConfig(writer, config); 1573 } 1574 } 1575 } else if (areaId == -1) { 1576 synchronized (mLock) { 1577 HalPropConfig config = mAllProperties.get(propertyId); 1578 if (config == null) { 1579 writer.printf("Property: %s not supported by HAL\n", 1580 toPropertyIdString(propertyId)); 1581 return; 1582 } 1583 dumpPropertyValueByConfig(writer, config); 1584 } 1585 } else { 1586 try { 1587 HalPropValue value = get(propertyId, areaId); 1588 dumpPropValue(writer, value); 1589 } catch (RuntimeException e) { 1590 writer.printf("Cannot get property value for property: %s in areaId: %s.\n", 1591 toPropertyIdString(propertyId), toAreaIdString(propertyId, areaId)); 1592 } 1593 } 1594 } 1595 1596 /** 1597 * Gets all property configs from VHAL. 1598 */ getAllPropConfigs()1599 public HalPropConfig[] getAllPropConfigs() throws RemoteException, ServiceSpecificException { 1600 return mVehicleStub.get().getAllPropConfigs(); 1601 } 1602 1603 /** 1604 * Gets the property config for a property, returns {@code null} if not supported. 1605 */ getPropConfig(int propId)1606 public @Nullable HalPropConfig getPropConfig(int propId) { 1607 synchronized (mLock) { 1608 return mAllProperties.get(propId); 1609 } 1610 } 1611 1612 /** 1613 * Checks whether we are connected to AIDL VHAL: {@code true} or HIDL VHAL: {@code false}. 1614 */ isAidlVhal()1615 public boolean isAidlVhal() { 1616 return mVehicleStub.get().isAidlVhal(); 1617 } 1618 1619 /** 1620 * Checks if fake VHAL mode is enabled. 1621 * 1622 * @return {@code true} if car service is connected to FakeVehicleStub. 1623 */ isFakeModeEnabled()1624 public boolean isFakeModeEnabled() { 1625 return mVehicleStub.get().isFakeModeEnabled(); 1626 } 1627 dumpPropertyValueByConfig(PrintWriter writer, HalPropConfig config)1628 private void dumpPropertyValueByConfig(PrintWriter writer, HalPropConfig config) { 1629 int propertyId = config.getPropId(); 1630 HalAreaConfig[] areaConfigs = config.getAreaConfigs(); 1631 if (areaConfigs == null || areaConfigs.length == 0) { 1632 try { 1633 HalPropValue value = get(config.getPropId()); 1634 dumpPropValue(writer, value); 1635 } catch (RuntimeException e) { 1636 writer.printf("Can not get property value for property: %s, areaId: %s\n", 1637 toPropertyIdString(propertyId), toAreaIdString(propertyId, /*areaId=*/0)); 1638 } 1639 } else { 1640 for (HalAreaConfig areaConfig : areaConfigs) { 1641 int areaId = areaConfig.getAreaId(); 1642 try { 1643 HalPropValue value = get(propertyId, areaId); 1644 dumpPropValue(writer, value); 1645 } catch (RuntimeException e) { 1646 writer.printf( 1647 "Can not get property value for property: %s in areaId: %s\n", 1648 toPropertyIdString(propertyId), toAreaIdString(propertyId, areaId)); 1649 } 1650 } 1651 } 1652 } 1653 1654 /** 1655 * Dump VHAL property configs. 1656 * Dump all properties if {@code propertyId} is equal to {@code -1}. 1657 * 1658 * @param propertyId the property ID 1659 */ dumpPropertyConfigs(PrintWriter writer, int propertyId)1660 public void dumpPropertyConfigs(PrintWriter writer, int propertyId) { 1661 HalPropConfig[] configs; 1662 synchronized (mLock) { 1663 configs = new HalPropConfig[mAllProperties.size()]; 1664 for (int i = 0; i < mAllProperties.size(); i++) { 1665 configs[i] = mAllProperties.valueAt(i); 1666 } 1667 } 1668 1669 if (propertyId == -1) { 1670 writer.println("**All properties**"); 1671 for (HalPropConfig config : configs) { 1672 dumpPropertyConfigsHelp(writer, config); 1673 } 1674 return; 1675 } 1676 for (HalPropConfig config : configs) { 1677 if (config.getPropId() == propertyId) { 1678 dumpPropertyConfigsHelp(writer, config); 1679 return; 1680 } 1681 } 1682 } 1683 1684 1685 /** Dumps VehiclePropertyConfigs */ dumpPropertyConfigsHelp(PrintWriter writer, HalPropConfig config)1686 private static void dumpPropertyConfigsHelp(PrintWriter writer, HalPropConfig config) { 1687 int propertyId = config.getPropId(); 1688 writer.printf( 1689 "Property:%s, group:%s, areaType:%s, valueType:%s,\n access:%s, changeMode:%s, " 1690 + "configArray:%s, minSampleRateHz:%f, maxSampleRateHz:%f\n", 1691 toPropertyIdString(propertyId), toGroupString(propertyId), 1692 toAreaTypeString(propertyId), toValueTypeString(propertyId), 1693 toAccessString(config.getAccess()), toChangeModeString(config.getChangeMode()), 1694 Arrays.toString(config.getConfigArray()), config.getMinSampleRate(), 1695 config.getMaxSampleRate()); 1696 if (config.getAreaConfigs() == null) { 1697 return; 1698 } 1699 for (HalAreaConfig area : config.getAreaConfigs()) { 1700 writer.printf(" areaId:%s, access:%s, f min:%f, f max:%f, i min:%d, i max:%d," 1701 + " i64 min:%d, i64 max:%d\n", toAreaIdString(propertyId, 1702 area.getAreaId()), toAccessString(area.getAccess()), 1703 area.getMinFloatValue(), area.getMaxFloatValue(), area.getMinInt32Value(), 1704 area.getMaxInt32Value(), area.getMinInt64Value(), area.getMaxInt64Value()); 1705 } 1706 } 1707 1708 /** 1709 * Inject a VHAL event 1710 * 1711 * @param propertyId the property ID as defined in the HAL 1712 * @param areaId the area ID that this event services 1713 * @param value the data value of the event 1714 * @param delayTimeSeconds add a certain duration to event timestamp 1715 */ injectVhalEvent(int propertyId, int areaId, String value, int delayTimeSeconds)1716 public void injectVhalEvent(int propertyId, int areaId, String value, int delayTimeSeconds) 1717 throws NumberFormatException { 1718 long timestampNanos = SystemClock.elapsedRealtimeNanos() + TimeUnit.SECONDS.toNanos( 1719 delayTimeSeconds); 1720 HalPropValue v = createPropValueForInjecting(mPropValueBuilder, propertyId, areaId, 1721 Arrays.asList(value.split(DATA_DELIMITER)), timestampNanos); 1722 if (v == null) { 1723 return; 1724 } 1725 mHandler.post(() -> handleOnPropertyEvent(Lists.newArrayList(v))); 1726 } 1727 1728 /** 1729 * Injects continuous VHAL events. 1730 * 1731 * @param property the Vehicle property Id as defined in the HAL 1732 * @param zone the zone that this event services 1733 * @param value the data value of the event 1734 * @param sampleRate the sample rate for events in Hz 1735 * @param timeDurationInSec the duration for injecting events in seconds 1736 */ injectContinuousVhalEvent(int property, int zone, String value, float sampleRate, long timeDurationInSec)1737 public void injectContinuousVhalEvent(int property, int zone, String value, 1738 float sampleRate, long timeDurationInSec) { 1739 1740 HalPropValue v = createPropValueForInjecting(mPropValueBuilder, property, zone, 1741 new ArrayList<>(Arrays.asList(value.split(DATA_DELIMITER))), 0); 1742 if (v == null) { 1743 return; 1744 } 1745 // rate in Hz 1746 if (sampleRate <= 0) { 1747 Slogf.e(CarLog.TAG_HAL, "Inject events at an invalid sample rate: " + sampleRate); 1748 return; 1749 } 1750 long period = (long) (1000 / sampleRate); 1751 long stopTime = timeDurationInSec * 1000 + SystemClock.elapsedRealtime(); 1752 Timer timer = new Timer(); 1753 timer.schedule(new TimerTask() { 1754 @Override 1755 public void run() { 1756 if (stopTime < SystemClock.elapsedRealtime()) { 1757 timer.cancel(); 1758 timer.purge(); 1759 } else { 1760 // Avoid the fake events be covered by real Event 1761 long timestamp = SystemClock.elapsedRealtimeNanos() 1762 + TimeUnit.SECONDS.toNanos(timeDurationInSec); 1763 HalPropValue v = createPropValueForInjecting(mPropValueBuilder, property, zone, 1764 new ArrayList<>(Arrays.asList(value.split(DATA_DELIMITER))), timestamp); 1765 mHandler.post(() -> handleOnPropertyEvent(Lists.newArrayList(v))); 1766 } 1767 } 1768 }, /* delay= */0, period); 1769 } 1770 1771 // Returns null if the property type is unsupported. 1772 @Nullable createPropValueForInjecting(HalPropValueBuilder builder, int propId, int zoneId, List<String> dataList, long timestamp)1773 private static HalPropValue createPropValueForInjecting(HalPropValueBuilder builder, 1774 int propId, int zoneId, List<String> dataList, long timestamp) { 1775 int propertyType = propId & VehiclePropertyType.MASK; 1776 // Values can be comma separated list 1777 switch (propertyType) { 1778 case VehiclePropertyType.BOOLEAN: 1779 boolean boolValue = Boolean.parseBoolean(dataList.get(0)); 1780 return builder.build(propId, zoneId, timestamp, VehiclePropertyStatus.AVAILABLE, 1781 boolValue ? 1 : 0); 1782 case VehiclePropertyType.INT64: 1783 case VehiclePropertyType.INT64_VEC: 1784 long[] longValues = new long[dataList.size()]; 1785 for (int i = 0; i < dataList.size(); i++) { 1786 longValues[i] = Long.decode(dataList.get(i)); 1787 } 1788 return builder.build(propId, zoneId, timestamp, VehiclePropertyStatus.AVAILABLE, 1789 longValues); 1790 case VehiclePropertyType.INT32: 1791 case VehiclePropertyType.INT32_VEC: 1792 int[] intValues = new int[dataList.size()]; 1793 for (int i = 0; i < dataList.size(); i++) { 1794 intValues[i] = Integer.decode(dataList.get(i)); 1795 } 1796 return builder.build(propId, zoneId, timestamp, VehiclePropertyStatus.AVAILABLE, 1797 intValues); 1798 case VehiclePropertyType.FLOAT: 1799 case VehiclePropertyType.FLOAT_VEC: 1800 float[] floatValues = new float[dataList.size()]; 1801 for (int i = 0; i < dataList.size(); i++) { 1802 floatValues[i] = Float.parseFloat(dataList.get(i)); 1803 } 1804 return builder.build(propId, zoneId, timestamp, VehiclePropertyStatus.AVAILABLE, 1805 floatValues); 1806 default: 1807 Slogf.e(CarLog.TAG_HAL, "Property type unsupported:" + propertyType); 1808 return null; 1809 } 1810 } 1811 1812 private static class VehiclePropertyEventInfo { 1813 private int mEventCount; 1814 private HalPropValue mLastEvent; 1815 VehiclePropertyEventInfo(HalPropValue event)1816 private VehiclePropertyEventInfo(HalPropValue event) { 1817 mEventCount = 1; 1818 mLastEvent = event; 1819 } 1820 addNewEvent(HalPropValue event)1821 private void addNewEvent(HalPropValue event) { 1822 mEventCount++; 1823 mLastEvent = event; 1824 } 1825 } 1826 1827 final class HalPropValueSetter { 1828 final int mPropId; 1829 final int mAreaId; 1830 HalPropValueSetter(int propId, int areaId)1831 private HalPropValueSetter(int propId, int areaId) { 1832 mPropId = propId; 1833 mAreaId = areaId; 1834 } 1835 1836 /** 1837 * Set the property to the given value. 1838 * 1839 * @throws IllegalArgumentException if argument is invalid 1840 * @throws ServiceSpecificException if VHAL returns error 1841 */ to(boolean value)1842 void to(boolean value) throws IllegalArgumentException, ServiceSpecificException { 1843 to(value ? 1 : 0); 1844 } 1845 1846 /** 1847 * Set the property to the given value. 1848 * 1849 * @throws IllegalArgumentException if argument is invalid 1850 * @throws ServiceSpecificException if VHAL returns error 1851 */ to(int value)1852 void to(int value) throws IllegalArgumentException, ServiceSpecificException { 1853 HalPropValue propValue = mPropValueBuilder.build(mPropId, mAreaId, value); 1854 submit(propValue); 1855 } 1856 1857 /** 1858 * Set the property to the given values. 1859 * 1860 * @throws IllegalArgumentException if argument is invalid 1861 * @throws ServiceSpecificException if VHAL returns error 1862 */ to(int[] values)1863 void to(int[] values) throws IllegalArgumentException, ServiceSpecificException { 1864 HalPropValue propValue = mPropValueBuilder.build(mPropId, mAreaId, values); 1865 submit(propValue); 1866 } 1867 1868 /** 1869 * Set the property to the given values. 1870 * 1871 * @throws IllegalArgumentException if argument is invalid 1872 * @throws ServiceSpecificException if VHAL returns error 1873 */ to(Collection<Integer> values)1874 void to(Collection<Integer> values) 1875 throws IllegalArgumentException, ServiceSpecificException { 1876 int[] intValues = new int[values.size()]; 1877 int i = 0; 1878 for (int value : values) { 1879 intValues[i] = value; 1880 i++; 1881 } 1882 HalPropValue propValue = mPropValueBuilder.build(mPropId, mAreaId, intValues); 1883 submit(propValue); 1884 } 1885 submit(HalPropValue propValue)1886 void submit(HalPropValue propValue) 1887 throws IllegalArgumentException, ServiceSpecificException { 1888 if (DBG) { 1889 Slogf.d(CarLog.TAG_HAL, "set - " + propValue); 1890 } 1891 setValueWithRetry(propValue); 1892 } 1893 } 1894 dumpPropValue(PrintWriter writer, HalPropValue value)1895 private static void dumpPropValue(PrintWriter writer, HalPropValue value) { 1896 writer.println(value); 1897 } 1898 1899 interface RetriableAction { run(HalPropValue requestValue)1900 @Nullable HalPropValue run(HalPropValue requestValue) 1901 throws ServiceSpecificException, RemoteException; 1902 } 1903 invokeRetriable(RetriableAction action, String operation, HalPropValue requestValue, long maxDurationForRetryMs, long sleepBetweenRetryMs, int maxRetries)1904 private static HalPropValue invokeRetriable(RetriableAction action, 1905 String operation, HalPropValue requestValue, long maxDurationForRetryMs, 1906 long sleepBetweenRetryMs, int maxRetries) 1907 throws ServiceSpecificException, IllegalArgumentException { 1908 Retrier retrier = new Retrier(action, operation, requestValue, maxDurationForRetryMs, 1909 sleepBetweenRetryMs, maxRetries); 1910 HalPropValue result = retrier.invokeAction(); 1911 if (DBG) { 1912 Slogf.d(CarLog.TAG_HAL, 1913 "Invoked retriable action for %s - RequestValue: %s - ResultValue: %s, for " 1914 + "retrier: %s", 1915 operation, requestValue, result, retrier); 1916 } 1917 return result; 1918 } 1919 cloneState(PairSparseArray<RateInfo> state)1920 private PairSparseArray<RateInfo> cloneState(PairSparseArray<RateInfo> state) { 1921 PairSparseArray<RateInfo> cloned = new PairSparseArray<>(); 1922 for (int i = 0; i < state.size(); i++) { 1923 int[] keyPair = state.keyPairAt(i); 1924 cloned.put(keyPair[0], keyPair[1], state.valueAt(i)); 1925 } 1926 return cloned; 1927 } 1928 isStaticProperty(HalPropConfig config)1929 private static boolean isStaticProperty(HalPropConfig config) { 1930 return config.getChangeMode() == VehiclePropertyChangeMode.STATIC; 1931 } 1932 1933 private static final class Retrier { 1934 private final RetriableAction mAction; 1935 private final String mOperation; 1936 private final HalPropValue mRequestValue; 1937 private final long mMaxDurationForRetryMs; 1938 private final long mSleepBetweenRetryMs; 1939 private final int mMaxRetries; 1940 private final long mStartTime; 1941 private int mRetryCount = 0; 1942 Retrier(RetriableAction action, String operation, HalPropValue requestValue, long maxDurationForRetryMs, long sleepBetweenRetryMs, int maxRetries)1943 Retrier(RetriableAction action, 1944 String operation, HalPropValue requestValue, long maxDurationForRetryMs, 1945 long sleepBetweenRetryMs, int maxRetries) { 1946 mAction = action; 1947 mOperation = operation; 1948 mRequestValue = requestValue; 1949 mMaxDurationForRetryMs = maxDurationForRetryMs; 1950 mSleepBetweenRetryMs = sleepBetweenRetryMs; 1951 mMaxRetries = maxRetries; 1952 mStartTime = uptimeMillis(); 1953 } 1954 invokeAction()1955 HalPropValue invokeAction() 1956 throws ServiceSpecificException, IllegalArgumentException { 1957 mRetryCount++; 1958 1959 try { 1960 return mAction.run(mRequestValue); 1961 } catch (ServiceSpecificException e) { 1962 switch (e.errorCode) { 1963 case StatusCode.INVALID_ARG: 1964 throw new IllegalArgumentException(errorMessage(mOperation, mRequestValue, 1965 e.toString())); 1966 case StatusCode.TRY_AGAIN: 1967 return sleepAndTryAgain(e); 1968 default: 1969 throw e; 1970 } 1971 } catch (RemoteException e) { 1972 return sleepAndTryAgain(e); 1973 } 1974 } 1975 toString()1976 public String toString() { 1977 return "Retrier{" 1978 + ", Operation=" + mOperation 1979 + ", RequestValue=" + mRequestValue 1980 + ", MaxDurationForRetryMs=" + mMaxDurationForRetryMs 1981 + ", SleepBetweenRetriesMs=" + mSleepBetweenRetryMs 1982 + ", MaxRetries=" + mMaxDurationForRetryMs 1983 + ", StartTime=" + mStartTime 1984 + "}"; 1985 } 1986 sleepAndTryAgain(Exception e)1987 private HalPropValue sleepAndTryAgain(Exception e) 1988 throws ServiceSpecificException, IllegalArgumentException { 1989 Slogf.d(CarLog.TAG_HAL, "trying the request: " 1990 + toPropertyIdString(mRequestValue.getPropId()) + ", " 1991 + toAreaIdString(mRequestValue.getPropId(), mRequestValue.getAreaId()) 1992 + " again..."); 1993 try { 1994 Thread.sleep(mSleepBetweenRetryMs); 1995 } catch (InterruptedException interruptedException) { 1996 Thread.currentThread().interrupt(); 1997 Slogf.w(CarLog.TAG_HAL, "Thread was interrupted while waiting for vehicle HAL.", 1998 interruptedException); 1999 throw new ServiceSpecificException(StatusCode.INTERNAL_ERROR, 2000 errorMessage(mOperation, mRequestValue, interruptedException.toString())); 2001 } 2002 2003 if (mMaxRetries != 0) { 2004 // If mMaxRetries is specified, check the retry count. 2005 if (mMaxRetries == mRetryCount) { 2006 throw new ServiceSpecificException(StatusCode.TRY_AGAIN, 2007 errorMessage(mOperation, mRequestValue, 2008 "cannot get property after " + mRetryCount + " retires, " 2009 + "last exception: " + e)); 2010 } 2011 } else if ((uptimeMillis() - mStartTime) >= mMaxDurationForRetryMs) { 2012 // Otherwise, check whether we have reached timeout. 2013 throw new ServiceSpecificException(StatusCode.TRY_AGAIN, 2014 errorMessage(mOperation, mRequestValue, 2015 "cannot get property within " + mMaxDurationForRetryMs 2016 + "ms, last exception: " + e)); 2017 } 2018 return invokeAction(); 2019 } 2020 } 2021 2022 2023 /** 2024 * Queries HalPropValue with list of GetVehicleHalRequest objects. 2025 * 2026 * <p>This method gets the HalPropValue using async methods. 2027 */ getAsync(List<VehicleStub.AsyncGetSetRequest> getVehicleStubAsyncRequests, VehicleStub.VehicleStubCallbackInterface getVehicleStubAsyncCallback)2028 public void getAsync(List<VehicleStub.AsyncGetSetRequest> getVehicleStubAsyncRequests, 2029 VehicleStub.VehicleStubCallbackInterface getVehicleStubAsyncCallback) { 2030 mVehicleStub.get().getAsync(getVehicleStubAsyncRequests, getVehicleStubAsyncCallback); 2031 } 2032 2033 /** 2034 * Sets vehicle property value asynchronously. 2035 */ setAsync(List<VehicleStub.AsyncGetSetRequest> setVehicleStubAsyncRequests, VehicleStub.VehicleStubCallbackInterface setVehicleStubAsyncCallback)2036 public void setAsync(List<VehicleStub.AsyncGetSetRequest> setVehicleStubAsyncRequests, 2037 VehicleStub.VehicleStubCallbackInterface setVehicleStubAsyncCallback) { 2038 mVehicleStub.get().setAsync(setVehicleStubAsyncRequests, setVehicleStubAsyncCallback); 2039 } 2040 2041 /** 2042 * Cancels all the on-going async requests with the given request IDs. 2043 */ cancelRequests(List<Integer> vehicleStubRequestIds)2044 public void cancelRequests(List<Integer> vehicleStubRequestIds) { 2045 mVehicleStub.get().cancelRequests(vehicleStubRequestIds); 2046 } 2047 2048 /** 2049 * Whether the [propId, areaId] supports dynamic supported values API. 2050 * 2051 * This is only supported if VHAL AreaIdConfig for it has non-null 2052 * {@code hasSupportedValuesInfo}. 2053 */ isSupportedValuesImplemented(PropIdAreaId halPropIdAreaId)2054 public boolean isSupportedValuesImplemented(PropIdAreaId halPropIdAreaId) { 2055 HalPropConfig halPropConfig = getPropConfig(halPropIdAreaId.propId); 2056 if (halPropConfig == null) { 2057 Slogf.e(CarLog.TAG_HAL, 2058 "No property config found for: %s, assume isSupportedValuesImplemented to be " 2059 + "false", toHalPropIdAreaIdString(halPropIdAreaId)); 2060 return false; 2061 } 2062 var areaConfigs = halPropConfig.getAreaConfigs(); 2063 for (int i = 0; i < areaConfigs.length; i++) { 2064 var areaConfig = areaConfigs[i]; 2065 if (areaConfig.getAreaId() == halPropIdAreaId.areaId) { 2066 return mVehicleStub.get().isSupportedValuesImplemented(areaConfig); 2067 } 2068 } 2069 Slogf.i(CarLog.TAG_HAL, 2070 "No area config found for: %s, assume isSupportedValuesImplemented to be " 2071 + "false", toHalPropIdAreaIdString(halPropIdAreaId)); 2072 return false; 2073 2074 } 2075 2076 /** 2077 * Gets the min/max supported value. 2078 * 2079 * This should only be called if {@link #isSupportedValuesImplemented} is {@code true}. 2080 */ getMinMaxSupportedValue(int propertyId, int areaId)2081 public MinMaxSupportedRawPropValues getMinMaxSupportedValue(int propertyId, int areaId) 2082 throws ServiceSpecificException { 2083 return mVehicleStub.get().getMinMaxSupportedValue(propertyId, areaId); 2084 } 2085 2086 /** 2087 * Gets the supported values list. 2088 * 2089 * This should only be called if {@link #isSupportedValuesImplemented} is {@code true}. 2090 */ getSupportedValuesList(int propertyId, int areaId)2091 public @Nullable List<RawPropValues> getSupportedValuesList(int propertyId, int areaId) 2092 throws ServiceSpecificException { 2093 return mVehicleStub.get().getSupportedValuesList(propertyId, areaId); 2094 } 2095 2096 private static class SupportedValuesChangeDispatchList extends 2097 DispatchList<HalServiceBase, PropIdAreaId> { 2098 @Override dispatchToClient(HalServiceBase client, List<PropIdAreaId> events)2099 protected void dispatchToClient(HalServiceBase client, List<PropIdAreaId> events) { 2100 client.onSupportedValuesChange(events); 2101 } 2102 } 2103 2104 @Override onSupportedValuesChange(List<PropIdAreaId> propIdAreaIds)2105 public void onSupportedValuesChange(List<PropIdAreaId> propIdAreaIds) { 2106 if (DBG) { 2107 Slogf.i(CarLog.TAG_HAL, "onSupportedValuesChange called for: %s", 2108 toHalPropIdAreaIdsString(propIdAreaIds)); 2109 } 2110 var dispatchList = new SupportedValuesChangeDispatchList(); 2111 synchronized (mLock) { 2112 if (isVehiclePropertyInjectionModeEnabled()) { 2113 List<PropIdAreaId> filteredPropIdAreaIds = SimulationVehicleStub.filterProperties( 2114 propIdAreaIds, 2115 (PropIdAreaId propIdAreaId) -> propIdAreaId.propId, 2116 mPropertyIdsFromRealHardware); 2117 if (filteredPropIdAreaIds.isEmpty()) { 2118 Slogf.d(CarLog.TAG_HAL, "All onSupportedValuesChange events filtered %s", 2119 Arrays.toString(filteredPropIdAreaIds.toArray())); 2120 return; 2121 } 2122 propIdAreaIds = filteredPropIdAreaIds; 2123 } 2124 2125 for (int i = 0; i < propIdAreaIds.size(); i++) { 2126 var propIdAreaId = propIdAreaIds.get(i); 2127 HalServiceBase service = mPropertyHandlers.get(propIdAreaId.propId); 2128 if (service == null) { 2129 Slogf.e(CarLog.TAG_HAL, "onSupportedValuesChange: HalService not found for %s", 2130 toHalPropIdAreaIdString(propIdAreaId)); 2131 continue; 2132 } 2133 2134 var propIdAreaIdsForService = mSupportedValuesChangePropIdAreaIdsByService.get( 2135 service); 2136 2137 if (!propIdAreaIdsForService.contains(propIdAreaId)) { 2138 Slogf.e(CarLog.TAG_HAL, 2139 "onSupportedValuesChange: not registered for %s, ignore", 2140 toHalPropIdAreaIdString(propIdAreaId)); 2141 continue; 2142 } 2143 dispatchList.addEvent(service, propIdAreaId); 2144 } 2145 } 2146 dispatchList.dispatchToClients(); 2147 } 2148 2149 /** 2150 * Registers the callback to be called when the min/max supported value or supported values 2151 * list change. 2152 * 2153 * This should only be called if {@link #isSupportedValuesImplemented} is {@code true}. 2154 * 2155 * @throws ServiceSpecificException If VHAL returns error. 2156 * @throws IllegalArgumentException If the service does not own one of the requested property 2157 * ID. 2158 */ registerSupportedValuesChange(HalServiceBase service, List<PropIdAreaId> propIdAreaIds)2159 public void registerSupportedValuesChange(HalServiceBase service, 2160 List<PropIdAreaId> propIdAreaIds) { 2161 synchronized (mLock) { 2162 for (int i = 0; i < propIdAreaIds.size(); i++) { 2163 int propertyId = propIdAreaIds.get(i).propId; 2164 assertServiceOwnerLocked(service, propertyId); 2165 } 2166 2167 var registeredPropIdAreaIds = mSupportedValuesChangePropIdAreaIdsByService.get(service); 2168 if (registeredPropIdAreaIds == null) { 2169 registeredPropIdAreaIds = new ArraySet<PropIdAreaId>(); 2170 } 2171 2172 // Here we do not filter out already registered [propId, areaId]s, we expect each 2173 // service to filter out duplicate requests. 2174 mSubscriptionClient.registerSupportedValuesChange(propIdAreaIds); 2175 2176 for (int i = 0; i < propIdAreaIds.size(); i++) { 2177 registeredPropIdAreaIds.add(propIdAreaIds.get(i)); 2178 } 2179 mSupportedValuesChangePropIdAreaIdsByService.put(service, registeredPropIdAreaIds); 2180 } 2181 } 2182 2183 /** 2184 * Unregisters the [propId, areaId]s previously registered with 2185 * registerSupportedValuesChange. 2186 * 2187 * Do nothing if the [propId, areaId]s were not previously registered. 2188 * 2189 * This should only be called if {@link #isSupportedValuesImplemented} is {@code true}. 2190 * 2191 * @throws IllegalArgumentException If the service does not own one of the requested property 2192 * ID. 2193 */ unregisterSupportedValuesChange(HalServiceBase service, List<PropIdAreaId> propIdAreaIds)2194 public void unregisterSupportedValuesChange(HalServiceBase service, 2195 List<PropIdAreaId> propIdAreaIds) { 2196 synchronized (mLock) { 2197 for (int i = 0; i < propIdAreaIds.size(); i++) { 2198 int propertyId = propIdAreaIds.get(i).propId; 2199 assertServiceOwnerLocked(service, propertyId); 2200 } 2201 var registeredPropIdAreaIds = mSupportedValuesChangePropIdAreaIdsByService.get(service); 2202 if (registeredPropIdAreaIds == null) { 2203 return; 2204 } 2205 2206 List<PropIdAreaId> propIdAreaIdsToUnRegister = new ArrayList<>(); 2207 for (int i = 0; i < propIdAreaIds.size(); i++) { 2208 var propIdAreaId = propIdAreaIds.get(i); 2209 if (registeredPropIdAreaIds.remove(propIdAreaId)) { 2210 propIdAreaIdsToUnRegister.add(propIdAreaId); 2211 } 2212 if (registeredPropIdAreaIds.isEmpty()) { 2213 mSupportedValuesChangePropIdAreaIdsByService.remove(service); 2214 } 2215 } 2216 2217 if (propIdAreaIdsToUnRegister.isEmpty()) { 2218 return; 2219 } 2220 mSubscriptionClient.unregisterSupportedValuesChange(propIdAreaIdsToUnRegister); 2221 } 2222 } 2223 } 2224