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