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 com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE; 20 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 21 22 import static java.lang.Integer.toHexString; 23 24 import android.annotation.CheckResult; 25 import android.annotation.Nullable; 26 import android.car.VehiclePropertyIds; 27 import android.car.builtin.util.Slogf; 28 import android.content.Context; 29 import android.hardware.automotive.vehicle.SubscribeOptions; 30 import android.hardware.automotive.vehicle.VehiclePropError; 31 import android.hardware.automotive.vehicle.VehicleProperty; 32 import android.hardware.automotive.vehicle.VehiclePropertyAccess; 33 import android.hardware.automotive.vehicle.VehiclePropertyChangeMode; 34 import android.hardware.automotive.vehicle.VehiclePropertyStatus; 35 import android.hardware.automotive.vehicle.VehiclePropertyType; 36 import android.os.Handler; 37 import android.os.HandlerThread; 38 import android.os.RemoteException; 39 import android.os.ServiceSpecificException; 40 import android.os.SystemClock; 41 import android.util.ArrayMap; 42 import android.util.ArraySet; 43 import android.util.SparseArray; 44 45 import com.android.car.CarLog; 46 import com.android.car.CarServiceUtils; 47 import com.android.car.VehicleStub; 48 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 49 import com.android.car.internal.util.IndentingPrintWriter; 50 import com.android.car.internal.util.Lists; 51 import com.android.internal.annotations.GuardedBy; 52 import com.android.internal.annotations.VisibleForTesting; 53 54 import java.io.PrintWriter; 55 import java.lang.ref.WeakReference; 56 import java.util.ArrayList; 57 import java.util.Arrays; 58 import java.util.Collection; 59 import java.util.List; 60 import java.util.Map; 61 import java.util.Timer; 62 import java.util.TimerTask; 63 import java.util.concurrent.TimeUnit; 64 import java.util.stream.Collectors; 65 66 /** 67 * Abstraction for vehicle HAL. This class handles interface with native HAL and do basic parsing of 68 * received data (type check). Then each event is sent to corresponding {@link HalServiceBase} 69 * implementation. It is responsibility of {@link HalServiceBase} to convert data to corresponding 70 * Car*Service for Car*Manager API. 71 */ 72 public class VehicleHal implements HalClientCallback { 73 74 private static final boolean DBG = false; 75 76 /** 77 * Used in {@link VehicleHal#dumpPropValue} method when copying 78 * {@link HalPropValue}. 79 */ 80 private static final int MAX_BYTE_SIZE = 20; 81 82 public static final int NO_AREA = -1; 83 public static final float NO_SAMPLE_RATE = -1; 84 85 private final HandlerThread mHandlerThread; 86 private final Handler mHandler; 87 private final PowerHalService mPowerHal; 88 private final PropertyHalService mPropertyHal; 89 private final InputHalService mInputHal; 90 private final VmsHalService mVmsHal; 91 private final UserHalService mUserHal; 92 private final DiagnosticHalService mDiagnosticHal; 93 private final ClusterHalService mClusterHalService; 94 private final EvsHalService mEvsHal; 95 private final TimeHalService mTimeHalService; 96 private final HalPropValueBuilder mPropValueBuilder; 97 private final VehicleStub mVehicleStub; 98 99 private final Object mLock = new Object(); 100 101 /** Might be re-assigned if Vehicle HAL is reconnected. */ 102 private volatile HalClient mHalClient; 103 104 /** Stores handler for each HAL property. Property events are sent to handler. */ 105 @GuardedBy("mLock") 106 private final SparseArray<HalServiceBase> mPropertyHandlers = new SparseArray<>(); 107 /** This is for iterating all HalServices with fixed order. */ 108 @GuardedBy("mLock") 109 private final List<HalServiceBase> mAllServices; 110 @GuardedBy("mLock") 111 private final SparseArray<SubscribeOptions> mSubscribedProperties = new SparseArray<>(); 112 @GuardedBy("mLock") 113 private final SparseArray<HalPropConfig> mAllProperties = new SparseArray<>(); 114 115 @GuardedBy("mLock") 116 private final SparseArray<VehiclePropertyEventInfo> mEventLog = new SparseArray<>(); 117 118 // Used by injectVHALEvent for testing purposes. Delimiter for an array of data 119 private static final String DATA_DELIMITER = ","; 120 121 /** 122 * Constructs a new {@link VehicleHal} object given the {@link Context} and {@link IVehicle} 123 * both passed as parameters. 124 */ VehicleHal(Context context, VehicleStub vehicle)125 public VehicleHal(Context context, VehicleStub vehicle) { 126 this(context, /* powerHal= */ null, /* propertyHal= */ null, 127 /* inputHal= */ null, /* vmsHal= */ null, /* userHal= */ null, 128 /* diagnosticHal= */ null, /* clusterHalService= */ null, 129 /* timeHalService= */ null, /* halClient= */ null, 130 CarServiceUtils.getHandlerThread(VehicleHal.class.getSimpleName()), vehicle); 131 } 132 133 /** 134 * Constructs a new {@link VehicleHal} object given the services and {@link HalClient} factory 135 * function passed as parameters. This method must be used by tests only. 136 */ 137 @VisibleForTesting VehicleHal(Context context, PowerHalService powerHal, PropertyHalService propertyHal, InputHalService inputHal, VmsHalService vmsHal, UserHalService userHal, DiagnosticHalService diagnosticHal, ClusterHalService clusterHalService, TimeHalService timeHalService, HalClient halClient, HandlerThread handlerThread, VehicleStub vehicle)138 VehicleHal(Context context, 139 PowerHalService powerHal, 140 PropertyHalService propertyHal, 141 InputHalService inputHal, 142 VmsHalService vmsHal, 143 UserHalService userHal, 144 DiagnosticHalService diagnosticHal, 145 ClusterHalService clusterHalService, 146 TimeHalService timeHalService, 147 HalClient halClient, 148 HandlerThread handlerThread, 149 VehicleStub vehicle) { 150 // Must be initialized before HalService so that HalService could use this. 151 mPropValueBuilder = vehicle.getHalPropValueBuilder(); 152 mHandlerThread = handlerThread; 153 mHandler = new Handler(mHandlerThread.getLooper()); 154 mPowerHal = powerHal != null ? powerHal : new PowerHalService(this); 155 mPropertyHal = propertyHal != null ? propertyHal : new PropertyHalService(this); 156 mInputHal = inputHal != null ? inputHal : new InputHalService(this); 157 mVmsHal = vmsHal != null ? vmsHal : new VmsHalService(context, this); 158 mUserHal = userHal != null ? userHal : new UserHalService(this); 159 mDiagnosticHal = diagnosticHal != null ? diagnosticHal : new DiagnosticHalService(this); 160 mClusterHalService = clusterHalService != null 161 ? clusterHalService : new ClusterHalService(this); 162 mEvsHal = new EvsHalService(this); 163 mTimeHalService = timeHalService != null 164 ? timeHalService : new TimeHalService(context, this); 165 mAllServices = List.of( 166 mPowerHal, 167 mInputHal, 168 mDiagnosticHal, 169 mVmsHal, 170 mUserHal, 171 mClusterHalService, 172 mEvsHal, 173 mTimeHalService, 174 mPropertyHal); 175 // mPropertyHal must be the last so that on init/release 176 // it can be used for all other HAL services properties. 177 mHalClient = halClient != null 178 ? halClient : new HalClient(vehicle, mHandlerThread.getLooper(), 179 /* callback= */ this); 180 mVehicleStub = vehicle; 181 } 182 fetchAllPropConfigs()183 private void fetchAllPropConfigs() { 184 synchronized (mLock) { 185 if (mAllProperties.size() != 0) { // already set 186 Slogf.i(CarLog.TAG_HAL, "fetchAllPropConfigs already fetched"); 187 return; 188 } 189 } 190 HalPropConfig[] configs; 191 try { 192 configs = mHalClient.getAllPropConfigs(); 193 if (configs == null || configs.length == 0) { 194 Slogf.e(CarLog.TAG_HAL, "getAllPropConfigs returned empty configs"); 195 return; 196 } 197 } catch (RemoteException | ServiceSpecificException e) { 198 throw new RuntimeException("Unable to retrieve vehicle property configuration", e); 199 } 200 201 synchronized (mLock) { 202 // Create map of all properties 203 for (HalPropConfig p : configs) { 204 if (DBG) { 205 Slogf.i(CarLog.TAG_HAL, "Add config for prop:" 206 + Integer.toHexString(p.getPropId()) + " config:" + p.toString()); 207 } 208 mAllProperties.put(p.getPropId(), p); 209 } 210 } 211 } 212 213 /** 214 * Inits the vhal configurations. 215 * 216 * <p><Note that {@link #getIfAvailableOrFailForEarlyStage(int, int)} 217 * can be called before {@code init()}. 218 */ init()219 public void init() { 220 fetchAllPropConfigs(); 221 222 // PropertyHalService will take most properties, so make it big enough. 223 ArrayMap<HalServiceBase, ArrayList<HalPropConfig>> configsForAllServices; 224 synchronized (mLock) { 225 configsForAllServices = new ArrayMap<>(mAllServices.size()); 226 for (int i = 0; i < mAllServices.size(); i++) { 227 ArrayList<HalPropConfig> configsForService = new ArrayList(); 228 HalServiceBase service = mAllServices.get(i); 229 configsForAllServices.put(service, configsForService); 230 int[] supportedProps = service.getAllSupportedProperties(); 231 if (supportedProps.length == 0) { 232 for (int j = 0; j < mAllProperties.size(); j++) { 233 Integer propId = mAllProperties.keyAt(j); 234 if (service.isSupportedProperty(propId)) { 235 HalPropConfig config = mAllProperties.get(propId); 236 mPropertyHandlers.append(propId, service); 237 configsForService.add(config); 238 } 239 } 240 } else { 241 for (int prop : supportedProps) { 242 HalPropConfig config = mAllProperties.get(prop); 243 if (config == null) { 244 continue; 245 } 246 mPropertyHandlers.append(prop, service); 247 configsForService.add(config); 248 } 249 } 250 } 251 } 252 253 for (Map.Entry<HalServiceBase, ArrayList<HalPropConfig>> entry 254 : configsForAllServices.entrySet()) { 255 HalServiceBase service = entry.getKey(); 256 ArrayList<HalPropConfig> configsForService = entry.getValue(); 257 service.takeProperties(configsForService); 258 service.init(); 259 } 260 } 261 262 /** 263 * Releases all connected services (power management service, input service, etc). 264 */ release()265 public void release() { 266 ArrayList<Integer> subscribedProperties = new ArrayList<>(); 267 synchronized (mLock) { 268 // release in reverse order from init 269 for (int i = mAllServices.size() - 1; i >= 0; i--) { 270 mAllServices.get(i).release(); 271 } 272 for (int i = 0; i < mSubscribedProperties.size(); i++) { 273 subscribedProperties.add(mSubscribedProperties.keyAt(i)); 274 } 275 mSubscribedProperties.clear(); 276 mAllProperties.clear(); 277 } 278 for (int i = 0; i < subscribedProperties.size(); i++) { 279 try { 280 mHalClient.unsubscribe(subscribedProperties.get(i)); 281 } catch (RemoteException | ServiceSpecificException e) { 282 // Ignore exceptions on shutdown path. 283 Slogf.w(CarLog.TAG_HAL, "Failed to unsubscribe", e); 284 } 285 } 286 // keep the looper thread as should be kept for the whole life cycle. 287 } 288 getDiagnosticHal()289 public DiagnosticHalService getDiagnosticHal() { 290 return mDiagnosticHal; 291 } 292 getPowerHal()293 public PowerHalService getPowerHal() { 294 return mPowerHal; 295 } 296 getPropertyHal()297 public PropertyHalService getPropertyHal() { 298 return mPropertyHal; 299 } 300 getInputHal()301 public InputHalService getInputHal() { 302 return mInputHal; 303 } 304 getUserHal()305 public UserHalService getUserHal() { 306 return mUserHal; 307 } 308 getVmsHal()309 public VmsHalService getVmsHal() { 310 return mVmsHal; 311 } 312 getClusterHal()313 public ClusterHalService getClusterHal() { 314 return mClusterHalService; 315 } 316 getEvsHal()317 public EvsHalService getEvsHal() { 318 return mEvsHal; 319 } 320 getTimeHalService()321 public TimeHalService getTimeHalService() { 322 return mTimeHalService; 323 } 324 getHalPropValueBuilder()325 public HalPropValueBuilder getHalPropValueBuilder() { 326 return mPropValueBuilder; 327 } 328 329 @GuardedBy("mLock") assertServiceOwnerLocked(HalServiceBase service, int property)330 private void assertServiceOwnerLocked(HalServiceBase service, int property) { 331 if (service != mPropertyHandlers.get(property)) { 332 throw new IllegalArgumentException("Property 0x" + toHexString(property) 333 + " is not owned by service: " + service); 334 } 335 } 336 337 /** 338 * Subscribes given properties with sampling rate defaults to 0 and no special flags provided. 339 * 340 * @throws IllegalArgumentException thrown if property is not supported by VHAL 341 * @see #subscribeProperty(HalServiceBase, int, float) 342 */ subscribeProperty(HalServiceBase service, int property)343 public void subscribeProperty(HalServiceBase service, int property) 344 throws IllegalArgumentException { 345 subscribeProperty(service, property, /* samplingRateHz= */ 0f); 346 } 347 348 /** 349 * Subscribe given property. Only Hal service owning the property can subscribe it. 350 * 351 * @param service HalService that owns this property 352 * @param property property id (VehicleProperty) 353 * @param samplingRateHz sampling rate in Hz for continuous properties 354 * @throws IllegalArgumentException thrown if property is not supported by VHAL 355 */ subscribeProperty(HalServiceBase service, int property, float samplingRateHz)356 public void subscribeProperty(HalServiceBase service, int property, float samplingRateHz) 357 throws IllegalArgumentException { 358 if (DBG) { 359 Slogf.i(CarLog.TAG_HAL, "subscribeProperty, service:" + service 360 + ", " + toCarPropertyLog(property)); 361 } 362 HalPropConfig config; 363 synchronized (mLock) { 364 config = mAllProperties.get(property); 365 } 366 367 if (config == null) { 368 throw new IllegalArgumentException("subscribe error: config is null for property 0x" 369 + toHexString(property)); 370 } else if (isPropertySubscribable(config)) { 371 SubscribeOptions opts = new SubscribeOptions(); 372 opts.propId = property; 373 opts.sampleRate = samplingRateHz; 374 opts.areaIds = new int[0]; 375 synchronized (mLock) { 376 assertServiceOwnerLocked(service, property); 377 mSubscribedProperties.put(property, opts); 378 } 379 try { 380 mHalClient.subscribe(opts); 381 } catch (RemoteException | ServiceSpecificException e) { 382 Slogf.w(CarLog.TAG_HAL, "Failed to subscribe to " + toCarPropertyLog(property), 383 e); 384 } 385 } else { 386 Slogf.w(CarLog.TAG_HAL, "Cannot subscribe to " + toCarPropertyLog(property)); 387 } 388 } 389 390 /** 391 * Unsubscribes from receiving notifications for the property and HAL services passed 392 * as parameters. 393 */ unsubscribeProperty(HalServiceBase service, int property)394 public void unsubscribeProperty(HalServiceBase service, int property) { 395 if (DBG) { 396 Slogf.i(CarLog.TAG_HAL, "unsubscribeProperty, service:" + service 397 + ", " + toCarPropertyLog(property)); 398 } 399 HalPropConfig config; 400 synchronized (mLock) { 401 config = mAllProperties.get(property); 402 } 403 404 if (config == null) { 405 Slogf.w(CarLog.TAG_HAL, "unsubscribeProperty " + toCarPropertyLog(property) 406 + " does not exist"); 407 } else if (isPropertySubscribable(config)) { 408 synchronized (mLock) { 409 assertServiceOwnerLocked(service, property); 410 mSubscribedProperties.remove(property); 411 } 412 try { 413 mHalClient.unsubscribe(property); 414 } catch (RemoteException | ServiceSpecificException e) { 415 Slogf.w(CarLog.TAG_SERVICE, "Failed to unsubscribe: " 416 + toCarPropertyLog(property), e); 417 } 418 } else { 419 Slogf.w(CarLog.TAG_HAL, "Cannot unsubscribe " + toCarPropertyLog(property)); 420 } 421 } 422 423 /** 424 * Indicates if the property passed as parameter is supported. 425 */ isPropertySupported(int propertyId)426 public boolean isPropertySupported(int propertyId) { 427 synchronized (mLock) { 428 return mAllProperties.contains(propertyId); 429 } 430 } 431 432 /** 433 * Gets given property with retries. 434 * 435 * <p>If getting the property fails after all retries, it will throw 436 * {@code IllegalStateException}. If the property does not exist, it will simply return 437 * {@code null}. 438 */ 439 @Nullable getIfAvailableOrFail(int propertyId, int numberOfRetries)440 public HalPropValue getIfAvailableOrFail(int propertyId, int numberOfRetries) { 441 if (!isPropertySupported(propertyId)) { 442 return null; 443 } 444 Exception lastException = null; 445 for (int i = 0; i < numberOfRetries; i++) { 446 try { 447 return get(propertyId); 448 } catch (Exception e) { 449 Slogf.w(CarLog.TAG_HAL, "Cannot get " + toCarPropertyLog(propertyId), e); 450 lastException = e; 451 } 452 } 453 throw new IllegalStateException("Cannot get property: 0x" + toHexString(propertyId) 454 + " after " + numberOfRetries + " retries" + ", last exception: " 455 + lastException); 456 } 457 458 /** 459 * This works similar to {@link #getIfAvailableOrFail(int, int)} except that this can be called 460 * before {@code init()} is called. 461 * 462 * <p>This call will check if requested vhal property is supported by querying directly to vhal 463 * and can have worse performance. Use this only for accessing vhal properties before 464 * {@code ICarImpl.init()} phase. 465 */ 466 @Nullable getIfAvailableOrFailForEarlyStage(int propertyId, int numberOfRetries)467 public HalPropValue getIfAvailableOrFailForEarlyStage(int propertyId, 468 int numberOfRetries) { 469 fetchAllPropConfigs(); 470 return getIfAvailableOrFail(propertyId, numberOfRetries); 471 } 472 473 /** 474 * Returns the property's {@link HalPropValue} for the property id passed as parameter and 475 * not specified area. 476 * 477 * @throws IllegalArgumentException if argument is invalid 478 * @throws ServiceSpecificException if VHAL returns error 479 */ get(int propertyId)480 public HalPropValue get(int propertyId) 481 throws IllegalArgumentException, ServiceSpecificException { 482 return get(propertyId, NO_AREA); 483 } 484 485 /** 486 * Returns the property's {@link HalPropValue} for the property id and area id passed as 487 * parameters. 488 * 489 * @throws IllegalArgumentException if argument is invalid 490 * @throws ServiceSpecificException if VHAL returns error 491 */ get(int propertyId, int areaId)492 public HalPropValue get(int propertyId, int areaId) 493 throws IllegalArgumentException, ServiceSpecificException { 494 if (DBG) { 495 Slogf.i(CarLog.TAG_HAL, "get, " + toCarPropertyLog(propertyId) 496 + toCarAreaLog(areaId)); 497 } 498 return mHalClient.getValue(mPropValueBuilder.build(propertyId, areaId)); 499 } 500 501 /** 502 * Returns the property object value for the class and property id passed as parameter and 503 * no area specified. 504 * 505 * @throws IllegalArgumentException if argument is invalid 506 * @throws ServiceSpecificException if VHAL returns error 507 */ get(Class clazz, int propertyId)508 public <T> T get(Class clazz, int propertyId) 509 throws IllegalArgumentException, ServiceSpecificException { 510 return get(clazz, propertyId, NO_AREA); 511 } 512 513 /** 514 * Returns the property object value for the class, property id, and area id passed as 515 * parameter. 516 * 517 * @throws IllegalArgumentException if argument is invalid 518 * @throws ServiceSpecificException if VHAL returns error 519 */ get(Class clazz, int propertyId, int areaId)520 public <T> T get(Class clazz, int propertyId, int areaId) 521 throws IllegalArgumentException, ServiceSpecificException { 522 return get(clazz, mPropValueBuilder.build(propertyId, areaId)); 523 } 524 525 /** 526 * Returns the property object value for the class and requested property value passed as 527 * parameter. 528 * 529 * @throws IllegalArgumentException if argument is invalid 530 * @throws ServiceSpecificException if VHAL returns error 531 */ 532 @SuppressWarnings("unchecked") get(Class clazz, HalPropValue requestedPropValue)533 public <T> T get(Class clazz, HalPropValue requestedPropValue) 534 throws IllegalArgumentException, ServiceSpecificException { 535 HalPropValue propValue; 536 propValue = mHalClient.getValue(requestedPropValue); 537 538 if (clazz == Long.class || clazz == long.class) { 539 Long value = propValue.getInt64Value(0); 540 return (T) value; 541 } else if (clazz == Integer.class || clazz == int.class) { 542 Integer value = propValue.getInt32Value(0); 543 return (T) value; 544 } else if (clazz == Boolean.class || clazz == boolean.class) { 545 Boolean value = Boolean.valueOf(propValue.getInt32Value(0) == 1); 546 return (T) value; 547 } else if (clazz == Float.class || clazz == float.class) { 548 Float value = propValue.getFloatValue(0); 549 return (T) value; 550 } else if (clazz == Long[].class) { 551 int size = propValue.getInt64ValuesSize(); 552 Long[] longArray = new Long[size]; 553 for (int i = 0; i < size; i++) { 554 longArray[i] = propValue.getInt64Value(i); 555 } 556 return (T) longArray; 557 } else if (clazz == Integer[].class) { 558 int size = propValue.getInt32ValuesSize(); 559 Integer[] intArray = new Integer[size]; 560 for (int i = 0; i < size; i++) { 561 intArray[i] = propValue.getInt32Value(i); 562 } 563 return (T) intArray; 564 } else if (clazz == Float[].class) { 565 int size = propValue.getFloatValuesSize(); 566 Float[] floatArray = new Float[size]; 567 for (int i = 0; i < size; i++) { 568 floatArray[i] = propValue.getFloatValue(i); 569 } 570 return (T) floatArray; 571 } else if (clazz == long[].class) { 572 int size = propValue.getInt64ValuesSize(); 573 long[] longArray = new long[size]; 574 for (int i = 0; i < size; i++) { 575 longArray[i] = propValue.getInt64Value(i); 576 } 577 return (T) longArray; 578 } else if (clazz == int[].class) { 579 int size = propValue.getInt32ValuesSize(); 580 int[] intArray = new int[size]; 581 for (int i = 0; i < size; i++) { 582 intArray[i] = propValue.getInt32Value(i); 583 } 584 return (T) intArray; 585 } else if (clazz == float[].class) { 586 int size = propValue.getFloatValuesSize(); 587 float[] floatArray = new float[size]; 588 for (int i = 0; i < size; i++) { 589 floatArray[i] = propValue.getFloatValue(i); 590 } 591 return (T) floatArray; 592 } else if (clazz == byte[].class) { 593 return (T) propValue.getByteArray(); 594 } else if (clazz == String.class) { 595 return (T) propValue.getStringValue(); 596 } else { 597 throw new IllegalArgumentException("Unexpected type: " + clazz); 598 } 599 } 600 601 /** 602 * Returns the vehicle's {@link HalPropValue} for the requested property value passed 603 * as parameter. 604 * 605 * @throws IllegalArgumentException if argument is invalid 606 * @throws ServiceSpecificException if VHAL returns error 607 */ get(HalPropValue requestedPropValue)608 public HalPropValue get(HalPropValue requestedPropValue) 609 throws IllegalArgumentException, ServiceSpecificException { 610 return mHalClient.getValue(requestedPropValue); 611 } 612 613 /** 614 * Returns the sample rate for a subscribed property. Returns {@link VehicleHal#NO_SAMPLE_RATE} 615 * if the property id passed as parameter is not linked to any subscribed property. 616 */ getSampleRate(int propId)617 public float getSampleRate(int propId) { 618 SubscribeOptions opts; 619 synchronized (mLock) { 620 opts = mSubscribedProperties.get(propId); 621 } 622 623 if (opts == null) { 624 // No sample rate for this property 625 return NO_SAMPLE_RATE; 626 } else { 627 return opts.sampleRate; 628 } 629 } 630 631 /** 632 * Set property. 633 * 634 * @throws IllegalArgumentException if argument is invalid 635 * @throws ServiceSpecificException if VHAL returns error 636 */ set(HalPropValue propValue)637 public void set(HalPropValue propValue) 638 throws IllegalArgumentException, ServiceSpecificException { 639 mHalClient.setValue(propValue); 640 } 641 642 @CheckResult set(int propId)643 HalPropValueSetter set(int propId) { 644 return set(propId, NO_AREA); 645 } 646 647 @CheckResult set(int propId, int areaId)648 HalPropValueSetter set(int propId, int areaId) { 649 return new HalPropValueSetter(mHalClient, propId, areaId); 650 } 651 isPropertySubscribable(HalPropConfig config)652 static boolean isPropertySubscribable(HalPropConfig config) { 653 if ((config.getAccess() & VehiclePropertyAccess.READ) == 0 654 || (config.getChangeMode() == VehiclePropertyChangeMode.STATIC)) { 655 return false; 656 } 657 return true; 658 } 659 660 /** 661 * Sets a property passed from the shell command. 662 * 663 * @param property Property ID in hex or decimal. 664 * @param areaId Area ID 665 * @param data Comma-separated value. 666 */ setPropertyFromCommand(int property, int areaId, String data, IndentingPrintWriter writer)667 public void setPropertyFromCommand(int property, int areaId, String data, 668 IndentingPrintWriter writer) throws IllegalArgumentException, ServiceSpecificException { 669 long timestamp = SystemClock.elapsedRealtimeNanos(); 670 HalPropValue v = createPropValueForInjecting(mPropValueBuilder, property, areaId, 671 List.of(data.split(DATA_DELIMITER)), timestamp); 672 if (v == null) { 673 throw new IllegalArgumentException("Unsupported property type: property=" + property 674 + ", areaId=" + areaId); 675 } 676 set(v); 677 } 678 679 private final ArraySet<HalServiceBase> mServicesToDispatch = new ArraySet<>(); 680 681 // should be posted to the mHandlerThread 682 @Override onPropertyEvent(ArrayList<HalPropValue> propValues)683 public void onPropertyEvent(ArrayList<HalPropValue> propValues) { 684 synchronized (mLock) { 685 for (int i = 0; i < propValues.size(); i++) { 686 HalPropValue v = propValues.get(i); 687 int propId = v.getPropId(); 688 HalServiceBase service = mPropertyHandlers.get(propId); 689 if (service == null) { 690 Slogf.e(CarLog.TAG_HAL, "HalService not found for prop: 0x" 691 + toHexString(propId)); 692 continue; 693 } 694 service.getDispatchList().add(v); 695 mServicesToDispatch.add(service); 696 VehiclePropertyEventInfo info = mEventLog.get(propId); 697 if (info == null) { 698 info = new VehiclePropertyEventInfo(v); 699 mEventLog.put(propId, info); 700 } else { 701 info.addNewEvent(v); 702 } 703 } 704 } 705 for (HalServiceBase s : mServicesToDispatch) { 706 s.onHalEvents(s.getDispatchList()); 707 s.getDispatchList().clear(); 708 } 709 mServicesToDispatch.clear(); 710 } 711 712 // should be posted to the mHandlerThread 713 @Override onPropertySetError(ArrayList<VehiclePropError> errors)714 public void onPropertySetError(ArrayList<VehiclePropError> errors) { 715 SparseArray<ArrayList<VehiclePropError>> errorsByPropId = 716 new SparseArray<ArrayList<VehiclePropError>>(); 717 for (int i = 0; i < errors.size(); i++) { 718 VehiclePropError error = errors.get(i); 719 int errorCode = error.errorCode; 720 int propId = error.propId; 721 int areaId = error.areaId; 722 Slogf.w( 723 CarLog.TAG_HAL, 724 "onPropertySetError, errorCode: %d, prop: 0x%x, area: 0x%x", 725 errorCode, 726 propId, 727 areaId); 728 if (propId == VehicleProperty.INVALID) { 729 continue; 730 } 731 732 ArrayList<VehiclePropError> propErrors; 733 if (errorsByPropId.get(propId) == null) { 734 propErrors = new ArrayList<VehiclePropError>(); 735 errorsByPropId.put(propId, propErrors); 736 } else { 737 propErrors = errorsByPropId.get(propId); 738 } 739 propErrors.add(error); 740 } 741 742 for (int i = 0; i < errorsByPropId.size(); i++) { 743 int propId = errorsByPropId.keyAt(i); 744 HalServiceBase service; 745 synchronized (mLock) { 746 service = mPropertyHandlers.get(propId); 747 } 748 if (service == null) { 749 continue; 750 } 751 752 ArrayList<VehiclePropError> propErrors = errorsByPropId.get(propId); 753 service.onPropertySetError(propErrors); 754 } 755 } 756 757 /** 758 * Dumps HAL service info using the print writer passed as parameter. 759 */ 760 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(PrintWriter writer)761 public void dump(PrintWriter writer) { 762 synchronized (mLock) { 763 writer.println("**dump HAL services**"); 764 for (int i = 0; i < mAllServices.size(); i++) { 765 mAllServices.get(i).dump(writer); 766 } 767 // Dump all VHAL property configure. 768 dumpPropertyConfigs(writer, -1); 769 writer.printf("**All Events, now ns:%d**\n", 770 SystemClock.elapsedRealtimeNanos()); 771 for (int i = 0; i < mEventLog.size(); i++) { 772 VehiclePropertyEventInfo info = mEventLog.valueAt(i); 773 writer.printf("event count:%d, lastEvent: ", info.mEventCount); 774 dumpPropValue(writer, info.mLastEvent); 775 } 776 writer.println("**Property handlers**"); 777 for (int i = 0; i < mPropertyHandlers.size(); i++) { 778 int propId = mPropertyHandlers.keyAt(i); 779 HalServiceBase service = mPropertyHandlers.valueAt(i); 780 writer.printf("Property Id: %d // 0x%x name: %s, service: %s\n", propId, propId, 781 VehiclePropertyIds.toString(propId), service); 782 } 783 } 784 } 785 786 /** 787 * Dumps the list of HALs. 788 */ dumpListHals(PrintWriter writer)789 public void dumpListHals(PrintWriter writer) { 790 synchronized (mLock) { 791 for (int i = 0; i < mAllServices.size(); i++) { 792 writer.println(mAllServices.get(i).getClass().getName()); 793 } 794 } 795 } 796 797 /** 798 * Dumps the given HALs. 799 */ dumpSpecificHals(PrintWriter writer, String... halNames)800 public void dumpSpecificHals(PrintWriter writer, String... halNames) { 801 synchronized (mLock) { 802 Map<String, HalServiceBase> byName = mAllServices.stream() 803 .collect(Collectors.toMap(s -> s.getClass().getSimpleName(), s -> s)); 804 for (String halName : halNames) { 805 HalServiceBase service = byName.get(halName); 806 if (service == null) { 807 writer.printf("No HAL named %s. Valid options are: %s\n", 808 halName, byName.keySet()); 809 continue; 810 } 811 service.dump(writer); 812 } 813 } 814 } 815 816 /** 817 * Dumps vehicle property values. 818 * 819 * @param propId property id, dump all properties' value if it is empty string 820 * @param areaId areaId of the property, dump the property for all areaIds in the config 821 * if it is empty string 822 */ dumpPropertyValueByCommand(PrintWriter writer, int propId, int areaId)823 public void dumpPropertyValueByCommand(PrintWriter writer, int propId, int areaId) { 824 if (propId == -1) { 825 writer.println("**All property values**"); 826 synchronized (mLock) { 827 for (int i = 0; i < mAllProperties.size(); i++) { 828 HalPropConfig config = mAllProperties.valueAt(i); 829 dumpPropertyValueByConfig(writer, config); 830 } 831 } 832 } else if (areaId == -1) { 833 synchronized (mLock) { 834 HalPropConfig config = mAllProperties.get(propId); 835 if (config == null) { 836 writer.print("Property "); 837 dumpPropHelper(writer, propId); 838 writer.print(" not supported by HAL\n"); 839 return; 840 } 841 dumpPropertyValueByConfig(writer, config); 842 } 843 } else { 844 try { 845 HalPropValue value = get(propId, areaId); 846 dumpPropValue(writer, value); 847 } catch (RuntimeException e) { 848 writer.printf("Can not get property value for property: %d // 0x%x " 849 + "in areaId: %d // 0x%x.\n", propId, propId, areaId, areaId); 850 } 851 } 852 } 853 854 /** 855 * Gets all property configs from VHAL. 856 */ getAllPropConfigs()857 public HalPropConfig[] getAllPropConfigs() throws RemoteException, ServiceSpecificException { 858 return mVehicleStub.getAllPropConfigs(); 859 } 860 861 /** 862 * Checks whether we are connected to AIDL VHAL: {@code true} or HIDL VHAL: {@code false}. 863 */ isAidlVhal()864 public boolean isAidlVhal() { 865 return mVehicleStub.isAidlVhal(); 866 } 867 dumpPropHelper(PrintWriter pw, int propId)868 private static void dumpPropHelper(PrintWriter pw, int propId) { 869 pw.printf("Id: %d // 0x%x, name: %s ", propId, propId, VehiclePropertyIds.toString(propId)); 870 } 871 dumpPropertyValueByConfig(PrintWriter writer, HalPropConfig config)872 private void dumpPropertyValueByConfig(PrintWriter writer, HalPropConfig config) { 873 int propId = config.getPropId(); 874 HalAreaConfig[] areaConfigs = config.getAreaConfigs(); 875 if (areaConfigs == null || areaConfigs.length == 0) { 876 try { 877 HalPropValue value = get(config.getPropId()); 878 dumpPropValue(writer, value); 879 } catch (RuntimeException e) { 880 writer.printf("Can not get property value for property: %d // 0x%x," 881 + " areaId: 0 \n", propId, propId); 882 } 883 } else { 884 for (HalAreaConfig areaConfig : areaConfigs) { 885 int areaId = areaConfig.getAreaId(); 886 try { 887 HalPropValue value = get(propId, areaId); 888 dumpPropValue(writer, value); 889 } catch (RuntimeException e) { 890 writer.printf("Can not get property value for property: %d // 0x%x " 891 + "in areaId: %d // 0x%x\n", propId, propId, areaId, areaId); 892 } 893 } 894 } 895 } 896 897 /** 898 * Dump VHAL property configs. 899 * Dump all properties if propid param is empty. 900 * 901 * @param propId the property ID 902 */ dumpPropertyConfigs(PrintWriter writer, int propId)903 public void dumpPropertyConfigs(PrintWriter writer, int propId) { 904 HalPropConfig[] configs; 905 synchronized (mLock) { 906 configs = new HalPropConfig[mAllProperties.size()]; 907 for (int i = 0; i < mAllProperties.size(); i++) { 908 configs[i] = mAllProperties.valueAt(i); 909 } 910 } 911 912 if (propId == -1) { 913 writer.println("**All properties**"); 914 for (HalPropConfig config : configs) { 915 dumpPropertyConfigsHelp(writer, config); 916 } 917 return; 918 } 919 for (HalPropConfig config : configs) { 920 if (config.getPropId() == propId) { 921 dumpPropertyConfigsHelp(writer, config); 922 return; 923 } 924 } 925 926 } 927 928 /** Dumps VehiclePropertyConfigs */ dumpPropertyConfigsHelp(PrintWriter writer, HalPropConfig config)929 private static void dumpPropertyConfigsHelp(PrintWriter writer, HalPropConfig config) { 930 int propId = config.getPropId(); 931 writer.printf("Property:0x%x, Property name:%s, access:0x%x, changeMode:0x%x, " 932 + "config:%s, fs min:%f, fs max:%f\n", 933 propId, VehiclePropertyIds.toString(propId), config.getAccess(), 934 config.getChangeMode(), Arrays.toString(config.getConfigArray()), 935 config.getMinSampleRate(), config.getMaxSampleRate()); 936 if (config.getAreaConfigs() == null) { 937 return; 938 } 939 for (HalAreaConfig area : config.getAreaConfigs()) { 940 writer.printf("\tareaId:0x%x, f min:%f, f max:%f, i min:%d, i max:%d," 941 + " i64 min:%d, i64 max:%d\n", 942 area.getAreaId(), area.getMinFloatValue(), area.getMaxFloatValue(), 943 area.getMinInt32Value(), area.getMaxInt32Value(), area.getMinInt64Value(), 944 area.getMaxInt64Value()); 945 } 946 } 947 948 /** 949 * Inject a VHAL event 950 * 951 * @param property the Vehicle property Id as defined in the HAL 952 * @param zone the zone that this event services 953 * @param value the data value of the event 954 * @param delayTime add a certain duration to event timestamp 955 */ injectVhalEvent(int property, int zone, String value, int delayTime)956 public void injectVhalEvent(int property, int zone, String value, int delayTime) 957 throws NumberFormatException { 958 long timestamp = SystemClock.elapsedRealtimeNanos() + TimeUnit.SECONDS.toNanos(delayTime); 959 HalPropValue v = createPropValueForInjecting(mPropValueBuilder, property, zone, 960 Arrays.asList(value.split(DATA_DELIMITER)), timestamp); 961 if (v == null) { 962 return; 963 } 964 mHandler.post(() -> onPropertyEvent(Lists.newArrayList(v))); 965 } 966 967 /** 968 * Injects continuous VHAL events. 969 * 970 * @param property the Vehicle property Id as defined in the HAL 971 * @param zone the zone that this event services 972 * @param value the data value of the event 973 * @param sampleRate the sample rate for events in Hz 974 * @param timeDurationInSec the duration for injecting events in seconds 975 */ injectContinuousVhalEvent(int property, int zone, String value, float sampleRate, long timeDurationInSec)976 public void injectContinuousVhalEvent(int property, int zone, String value, 977 float sampleRate, long timeDurationInSec) { 978 979 HalPropValue v = createPropValueForInjecting(mPropValueBuilder, property, zone, 980 new ArrayList<>(Arrays.asList(value.split(DATA_DELIMITER))), 0); 981 if (v == null) { 982 return; 983 } 984 // rate in Hz 985 if (sampleRate <= 0) { 986 Slogf.e(CarLog.TAG_HAL, "Inject events at an invalid sample rate: " + sampleRate); 987 return; 988 } 989 long period = (long) (1000 / sampleRate); 990 long stopTime = timeDurationInSec * 1000 + SystemClock.elapsedRealtime(); 991 Timer timer = new Timer(); 992 timer.schedule(new TimerTask() { 993 @Override 994 public void run() { 995 if (stopTime < SystemClock.elapsedRealtime()) { 996 timer.cancel(); 997 timer.purge(); 998 } else { 999 // Avoid the fake events be covered by real Event 1000 long timestamp = SystemClock.elapsedRealtimeNanos() 1001 + TimeUnit.SECONDS.toNanos(timeDurationInSec); 1002 HalPropValue v = createPropValueForInjecting(mPropValueBuilder, property, zone, 1003 new ArrayList<>(Arrays.asList(value.split(DATA_DELIMITER))), timestamp); 1004 mHandler.post(() -> onPropertyEvent(Lists.newArrayList(v))); 1005 } 1006 } 1007 }, /* delay= */0, period); 1008 } 1009 1010 // Returns null if the property type is unsupported. 1011 @Nullable createPropValueForInjecting(HalPropValueBuilder builder, int propId, int zoneId, List<String> dataList, long timestamp)1012 private static HalPropValue createPropValueForInjecting(HalPropValueBuilder builder, 1013 int propId, int zoneId, List<String> dataList, long timestamp) { 1014 int propertyType = propId & VehiclePropertyType.MASK; 1015 // Values can be comma separated list 1016 switch (propertyType) { 1017 case VehiclePropertyType.BOOLEAN: 1018 boolean boolValue = Boolean.parseBoolean(dataList.get(0)); 1019 return builder.build(propId, zoneId, timestamp, VehiclePropertyStatus.AVAILABLE, 1020 boolValue ? 1 : 0); 1021 case VehiclePropertyType.INT64: 1022 case VehiclePropertyType.INT64_VEC: 1023 long[] longValues = new long[dataList.size()]; 1024 for (int i = 0; i < dataList.size(); i++) { 1025 longValues[i] = Long.decode(dataList.get(i)); 1026 } 1027 return builder.build(propId, zoneId, timestamp, VehiclePropertyStatus.AVAILABLE, 1028 longValues); 1029 case VehiclePropertyType.INT32: 1030 case VehiclePropertyType.INT32_VEC: 1031 int[] intValues = new int[dataList.size()]; 1032 for (int i = 0; i < dataList.size(); i++) { 1033 intValues[i] = Integer.decode(dataList.get(i)); 1034 } 1035 return builder.build(propId, zoneId, timestamp, VehiclePropertyStatus.AVAILABLE, 1036 intValues); 1037 case VehiclePropertyType.FLOAT: 1038 case VehiclePropertyType.FLOAT_VEC: 1039 float[] floatValues = new float[dataList.size()]; 1040 for (int i = 0; i < dataList.size(); i++) { 1041 floatValues[i] = Float.parseFloat(dataList.get(i)); 1042 } 1043 return builder.build(propId, zoneId, timestamp, VehiclePropertyStatus.AVAILABLE, 1044 floatValues); 1045 default: 1046 Slogf.e(CarLog.TAG_HAL, "Property type unsupported:" + propertyType); 1047 return null; 1048 } 1049 } 1050 1051 private static class VehiclePropertyEventInfo { 1052 private int mEventCount; 1053 private HalPropValue mLastEvent; 1054 VehiclePropertyEventInfo(HalPropValue event)1055 private VehiclePropertyEventInfo(HalPropValue event) { 1056 mEventCount = 1; 1057 mLastEvent = event; 1058 } 1059 addNewEvent(HalPropValue event)1060 private void addNewEvent(HalPropValue event) { 1061 mEventCount++; 1062 mLastEvent = event; 1063 } 1064 } 1065 1066 final class HalPropValueSetter { 1067 final WeakReference<HalClient> mClient; 1068 final int mPropId; 1069 final int mAreaId; 1070 HalPropValueSetter(HalClient client, int propId, int areaId)1071 private HalPropValueSetter(HalClient client, int propId, int areaId) { 1072 mClient = new WeakReference<>(client); 1073 mPropId = propId; 1074 mAreaId = areaId; 1075 } 1076 1077 /** 1078 * Set the property to the given value. 1079 * 1080 * @throws IllegalArgumentException if argument is invalid 1081 * @throws ServiceSpecificException if VHAL returns error 1082 */ to(boolean value)1083 void to(boolean value) throws IllegalArgumentException, ServiceSpecificException { 1084 to(value ? 1 : 0); 1085 } 1086 1087 /** 1088 * Set the property to the given value. 1089 * 1090 * @throws IllegalArgumentException if argument is invalid 1091 * @throws ServiceSpecificException if VHAL returns error 1092 */ to(int value)1093 void to(int value) throws IllegalArgumentException, ServiceSpecificException { 1094 HalPropValue propValue = mPropValueBuilder.build(mPropId, mAreaId, value); 1095 submit(propValue); 1096 } 1097 1098 /** 1099 * Set the property to the given values. 1100 * 1101 * @throws IllegalArgumentException if argument is invalid 1102 * @throws ServiceSpecificException if VHAL returns error 1103 */ to(int[] values)1104 void to(int[] values) throws IllegalArgumentException, ServiceSpecificException { 1105 HalPropValue propValue = mPropValueBuilder.build(mPropId, mAreaId, values); 1106 submit(propValue); 1107 } 1108 1109 /** 1110 * Set the property to the given values. 1111 * 1112 * @throws IllegalArgumentException if argument is invalid 1113 * @throws ServiceSpecificException if VHAL returns error 1114 */ to(Collection<Integer> values)1115 void to(Collection<Integer> values) 1116 throws IllegalArgumentException, ServiceSpecificException { 1117 int[] intValues = new int[values.size()]; 1118 int i = 0; 1119 for (int value : values) { 1120 intValues[i] = value; 1121 i++; 1122 } 1123 HalPropValue propValue = mPropValueBuilder.build(mPropId, mAreaId, intValues); 1124 submit(propValue); 1125 } 1126 submit(HalPropValue propValue)1127 void submit(HalPropValue propValue) 1128 throws IllegalArgumentException, ServiceSpecificException { 1129 HalClient client = mClient.get(); 1130 if (client != null) { 1131 if (DBG) { 1132 Slogf.i(CarLog.TAG_HAL, "set, " + toCarPropertyLog(mPropId) 1133 + toCarAreaLog(mAreaId)); 1134 } 1135 client.setValue(propValue); 1136 } 1137 } 1138 } 1139 dumpPropValue(PrintWriter writer, HalPropValue value)1140 private static void dumpPropValue(PrintWriter writer, HalPropValue value) { 1141 String bytesString = ""; 1142 byte[] byteValues = value.getByteArray(); 1143 if (byteValues.length > MAX_BYTE_SIZE) { 1144 byte[] bytes = Arrays.copyOf(byteValues, MAX_BYTE_SIZE); 1145 bytesString = Arrays.toString(bytes); 1146 } else { 1147 bytesString = Arrays.toString(byteValues); 1148 } 1149 1150 writer.printf("Property:0x%x, status: %d, timestamp: %d, zone: 0x%x, " 1151 + "floatValues: %s, int32Values: %s, int64Values: %s, bytes: %s, string: " 1152 + "%s\n", 1153 value.getPropId(), value.getStatus(), value.getTimestamp(), value.getAreaId(), 1154 value.dumpFloatValues(), value.dumpInt32Values(), value.dumpInt64Values(), 1155 bytesString, value.getStringValue()); 1156 } 1157 toCarPropertyLog(int propId)1158 private static String toCarPropertyLog(int propId) { 1159 return String.format("property Id: %d // 0x%x, property name: %s ", propId, propId, 1160 VehiclePropertyIds.toString(propId)); 1161 } 1162 1163 @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE) toCarAreaLog(int areaId)1164 private static String toCarAreaLog(int areaId) { 1165 return String.format("areaId: %d // 0x%x", areaId, areaId); 1166 } 1167 } 1168