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 23 import static java.lang.Integer.toHexString; 24 25 import android.annotation.CheckResult; 26 import android.hardware.automotive.vehicle.V2_0.IVehicle; 27 import android.hardware.automotive.vehicle.V2_0.IVehicleCallback; 28 import android.hardware.automotive.vehicle.V2_0.SubscribeFlags; 29 import android.hardware.automotive.vehicle.V2_0.SubscribeOptions; 30 import android.hardware.automotive.vehicle.V2_0.VehicleAreaConfig; 31 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig; 32 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue; 33 import android.hardware.automotive.vehicle.V2_0.VehicleProperty; 34 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess; 35 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode; 36 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType; 37 import android.os.HandlerThread; 38 import android.os.RemoteException; 39 import android.os.SystemClock; 40 import android.util.ArraySet; 41 import android.util.Log; 42 import android.util.SparseArray; 43 44 import com.android.car.CarLog; 45 import com.android.internal.annotations.VisibleForTesting; 46 47 import com.google.android.collect.Lists; 48 49 import java.io.PrintWriter; 50 import java.lang.ref.WeakReference; 51 import java.util.ArrayList; 52 import java.util.Arrays; 53 import java.util.Collection; 54 import java.util.HashMap; 55 import java.util.HashSet; 56 import java.util.List; 57 import java.util.Set; 58 59 /** 60 * Abstraction for vehicle HAL. This class handles interface with native HAL and do basic parsing 61 * of received data (type check). Then each event is sent to corresponding {@link HalServiceBase} 62 * implementation. It is responsibility of {@link HalServiceBase} to convert data to corresponding 63 * Car*Service for Car*Manager API. 64 */ 65 public class VehicleHal extends IVehicleCallback.Stub { 66 67 private static final boolean DBG = false; 68 69 private static final int NO_AREA = -1; 70 71 private final HandlerThread mHandlerThread; 72 private final PowerHalService mPowerHal; 73 private final PropertyHalService mPropertyHal; 74 private final InputHalService mInputHal; 75 private final VmsHalService mVmsHal; 76 private DiagnosticHalService mDiagnosticHal = null; 77 78 /** Might be re-assigned if Vehicle HAL is reconnected. */ 79 private volatile HalClient mHalClient; 80 81 /** Stores handler for each HAL property. Property events are sent to handler. */ 82 private final SparseArray<HalServiceBase> mPropertyHandlers = new SparseArray<>(); 83 /** This is for iterating all HalServices with fixed order. */ 84 private final ArrayList<HalServiceBase> mAllServices = new ArrayList<>(); 85 private final HashMap<Integer, SubscribeOptions> mSubscribedProperties = new HashMap<>(); 86 private final HashMap<Integer, VehiclePropConfig> mAllProperties = new HashMap<>(); 87 private final HashMap<Integer, VehiclePropertyEventInfo> mEventLog = new HashMap<>(); 88 89 // Used by injectVHALEvent for testing purposes. Delimiter for an array of data 90 private static final String DATA_DELIMITER = ","; 91 92 public VehicleHal(IVehicle vehicle) { 93 mHandlerThread = new HandlerThread("VEHICLE-HAL"); 94 mHandlerThread.start(); 95 // passing this should be safe as long as it is just kept and not used in constructor 96 mPowerHal = new PowerHalService(this); 97 mPropertyHal = new PropertyHalService(this); 98 mInputHal = new InputHalService(this); 99 mVmsHal = new VmsHalService(this); 100 mDiagnosticHal = new DiagnosticHalService(this); 101 mAllServices.addAll(Arrays.asList(mPowerHal, 102 mInputHal, 103 mPropertyHal, 104 mDiagnosticHal, 105 mVmsHal)); 106 107 mHalClient = new HalClient(vehicle, mHandlerThread.getLooper(), this /*IVehicleCallback*/); 108 } 109 110 /** Dummy version only for testing */ 111 @VisibleForTesting 112 public VehicleHal(PowerHalService powerHal, DiagnosticHalService diagnosticHal, 113 HalClient halClient, PropertyHalService propertyHal) { 114 mHandlerThread = null; 115 mPowerHal = powerHal; 116 mPropertyHal = propertyHal; 117 mDiagnosticHal = diagnosticHal; 118 mInputHal = null; 119 mVmsHal = null; 120 mHalClient = halClient; 121 mDiagnosticHal = diagnosticHal; 122 } 123 124 public void vehicleHalReconnected(IVehicle vehicle) { 125 synchronized (this) { 126 mHalClient = new HalClient(vehicle, mHandlerThread.getLooper(), 127 this /*IVehicleCallback*/); 128 129 SubscribeOptions[] options = mSubscribedProperties.values() 130 .toArray(new SubscribeOptions[0]); 131 132 try { 133 mHalClient.subscribe(options); 134 } catch (RemoteException e) { 135 throw new RuntimeException("Failed to subscribe: " + Arrays.asList(options), e); 136 } 137 } 138 } 139 140 public void init() { 141 Set<VehiclePropConfig> properties; 142 try { 143 properties = new HashSet<>(mHalClient.getAllPropConfigs()); 144 } catch (RemoteException e) { 145 throw new RuntimeException("Unable to retrieve vehicle property configuration", e); 146 } 147 148 synchronized (this) { 149 // Create map of all properties 150 for (VehiclePropConfig p : properties) { 151 mAllProperties.put(p.prop, p); 152 } 153 } 154 155 for (HalServiceBase service: mAllServices) { 156 Collection<VehiclePropConfig> taken = service.takeSupportedProperties(properties); 157 if (taken == null) { 158 continue; 159 } 160 if (DBG) { 161 Log.i(CarLog.TAG_HAL, "HalService " + service + " take properties " + taken.size()); 162 } 163 synchronized (this) { 164 for (VehiclePropConfig p: taken) { 165 mPropertyHandlers.append(p.prop, service); 166 } 167 } 168 properties.removeAll(taken); 169 service.init(); 170 } 171 } 172 173 public void release() { 174 // release in reverse order from init 175 for (int i = mAllServices.size() - 1; i >= 0; i--) { 176 mAllServices.get(i).release(); 177 } 178 synchronized (this) { 179 for (int p : mSubscribedProperties.keySet()) { 180 try { 181 mHalClient.unsubscribe(p); 182 } catch (RemoteException e) { 183 // Ignore exceptions on shutdown path. 184 Log.w(CarLog.TAG_HAL, "Failed to unsubscribe", e); 185 } 186 } 187 mSubscribedProperties.clear(); 188 mAllProperties.clear(); 189 } 190 // keep the looper thread as should be kept for the whole life cycle. 191 } 192 193 public DiagnosticHalService getDiagnosticHal() { return mDiagnosticHal; } 194 195 public PowerHalService getPowerHal() { 196 return mPowerHal; 197 } 198 199 public PropertyHalService getPropertyHal() { 200 return mPropertyHal; 201 } 202 203 public InputHalService getInputHal() { 204 return mInputHal; 205 } 206 207 public VmsHalService getVmsHal() { return mVmsHal; } 208 209 private void assertServiceOwnerLocked(HalServiceBase service, int property) { 210 if (service != mPropertyHandlers.get(property)) { 211 throw new IllegalArgumentException("Property 0x" + toHexString(property) 212 + " is not owned by service: " + service); 213 } 214 } 215 216 /** 217 * Subscribes given properties with sampling rate defaults to 0 and no special flags provided. 218 * 219 * @see #subscribeProperty(HalServiceBase, int, float, int) 220 */ 221 public void subscribeProperty(HalServiceBase service, int property) 222 throws IllegalArgumentException { 223 subscribeProperty(service, property, 0f, SubscribeFlags.EVENTS_FROM_CAR); 224 } 225 226 /** 227 * Subscribes given properties with default subscribe flag. 228 * 229 * @see #subscribeProperty(HalServiceBase, int, float, int) 230 */ 231 public void subscribeProperty(HalServiceBase service, int property, float sampleRateHz) 232 throws IllegalArgumentException { 233 subscribeProperty(service, property, sampleRateHz, SubscribeFlags.EVENTS_FROM_CAR); 234 } 235 236 /** 237 * Subscribe given property. Only Hal service owning the property can subscribe it. 238 * 239 * @param service HalService that owns this property 240 * @param property property id (VehicleProperty) 241 * @param samplingRateHz sampling rate in Hz for continuous properties 242 * @param flags flags from {@link android.hardware.automotive.vehicle.V2_0.SubscribeFlags} 243 * @throws IllegalArgumentException thrown if property is not supported by VHAL 244 */ 245 public void subscribeProperty(HalServiceBase service, int property, 246 float samplingRateHz, int flags) throws IllegalArgumentException { 247 if (DBG) { 248 Log.i(CarLog.TAG_HAL, "subscribeProperty, service:" + service 249 + ", property: 0x" + toHexString(property)); 250 } 251 VehiclePropConfig config; 252 synchronized (this) { 253 config = mAllProperties.get(property); 254 } 255 256 if (config == null) { 257 throw new IllegalArgumentException("subscribe error: config is null for property 0x" + 258 toHexString(property)); 259 } else if (isPropertySubscribable(config)) { 260 SubscribeOptions opts = new SubscribeOptions(); 261 opts.propId = property; 262 opts.sampleRate = samplingRateHz; 263 opts.flags = flags; 264 synchronized (this) { 265 assertServiceOwnerLocked(service, property); 266 mSubscribedProperties.put(property, opts); 267 } 268 try { 269 mHalClient.subscribe(opts); 270 } catch (RemoteException e) { 271 Log.e(CarLog.TAG_HAL, "Failed to subscribe to property: 0x" + property, e); 272 } 273 } else { 274 Log.e(CarLog.TAG_HAL, "Cannot subscribe to property: " + property); 275 } 276 } 277 278 public void unsubscribeProperty(HalServiceBase service, int property) { 279 if (DBG) { 280 Log.i(CarLog.TAG_HAL, "unsubscribeProperty, service:" + service 281 + ", property: 0x" + toHexString(property)); 282 } 283 VehiclePropConfig config; 284 synchronized (this) { 285 config = mAllProperties.get(property); 286 } 287 288 if (config == null) { 289 Log.e(CarLog.TAG_HAL, "unsubscribeProperty: property " + property + " does not exist"); 290 } else if (isPropertySubscribable(config)) { 291 synchronized (this) { 292 assertServiceOwnerLocked(service, property); 293 mSubscribedProperties.remove(property); 294 } 295 try { 296 mHalClient.unsubscribe(property); 297 } catch (RemoteException e) { 298 Log.e(CarLog.TAG_SERVICE, "Failed to unsubscribe from property: 0x" 299 + toHexString(property), e); 300 } 301 } else { 302 Log.e(CarLog.TAG_HAL, "Cannot unsubscribe property: " + property); 303 } 304 } 305 306 public boolean isPropertySupported(int propertyId) { 307 return mAllProperties.containsKey(propertyId); 308 } 309 310 public Collection<VehiclePropConfig> getAllPropConfigs() { 311 return mAllProperties.values(); 312 } 313 314 public VehiclePropValue get(int propertyId) throws PropertyTimeoutException { 315 return get(propertyId, NO_AREA); 316 } 317 318 public VehiclePropValue get(int propertyId, int areaId) throws PropertyTimeoutException { 319 if (DBG) { 320 Log.i(CarLog.TAG_HAL, "get, property: 0x" + toHexString(propertyId) 321 + ", areaId: 0x" + toHexString(areaId)); 322 } 323 VehiclePropValue propValue = new VehiclePropValue(); 324 propValue.prop = propertyId; 325 propValue.areaId = areaId; 326 return mHalClient.getValue(propValue); 327 } 328 329 public <T> T get(Class clazz, int propertyId) throws PropertyTimeoutException { 330 return get(clazz, createPropValue(propertyId, NO_AREA)); 331 } 332 333 public <T> T get(Class clazz, int propertyId, int areaId) throws PropertyTimeoutException { 334 return get(clazz, createPropValue(propertyId, areaId)); 335 } 336 337 @SuppressWarnings("unchecked") 338 public <T> T get(Class clazz, VehiclePropValue requestedPropValue) 339 throws PropertyTimeoutException { 340 VehiclePropValue propValue; 341 propValue = mHalClient.getValue(requestedPropValue); 342 343 if (clazz == Integer.class || clazz == int.class) { 344 return (T) propValue.value.int32Values.get(0); 345 } else if (clazz == Boolean.class || clazz == boolean.class) { 346 return (T) Boolean.valueOf(propValue.value.int32Values.get(0) == 1); 347 } else if (clazz == Float.class || clazz == float.class) { 348 return (T) propValue.value.floatValues.get(0); 349 } else if (clazz == Integer[].class) { 350 Integer[] intArray = new Integer[propValue.value.int32Values.size()]; 351 return (T) propValue.value.int32Values.toArray(intArray); 352 } else if (clazz == Float[].class) { 353 Float[] floatArray = new Float[propValue.value.floatValues.size()]; 354 return (T) propValue.value.floatValues.toArray(floatArray); 355 } else if (clazz == int[].class) { 356 return (T) toIntArray(propValue.value.int32Values); 357 } else if (clazz == float[].class) { 358 return (T) toFloatArray(propValue.value.floatValues); 359 } else if (clazz == byte[].class) { 360 return (T) toByteArray(propValue.value.bytes); 361 } else if (clazz == String.class) { 362 return (T) propValue.value.stringValue; 363 } else { 364 throw new IllegalArgumentException("Unexpected type: " + clazz); 365 } 366 } 367 368 public VehiclePropValue get(VehiclePropValue requestedPropValue) 369 throws PropertyTimeoutException { 370 return mHalClient.getValue(requestedPropValue); 371 } 372 373 /** 374 * 375 * @param propId Property ID to return the current sample rate for. 376 * 377 * @return float Returns the current sample rate of the specified propId, or -1 if the 378 * property is not currently subscribed. 379 */ 380 public float getSampleRate(int propId) { 381 SubscribeOptions opts = mSubscribedProperties.get(propId); 382 if (opts == null) { 383 // No sample rate for this property 384 return -1; 385 } else { 386 return opts.sampleRate; 387 } 388 } 389 390 void set(VehiclePropValue propValue) throws PropertyTimeoutException { 391 mHalClient.setValue(propValue); 392 } 393 394 @CheckResult 395 VehiclePropValueSetter set(int propId) { 396 return new VehiclePropValueSetter(mHalClient, propId, NO_AREA); 397 } 398 399 @CheckResult 400 VehiclePropValueSetter set(int propId, int areaId) { 401 return new VehiclePropValueSetter(mHalClient, propId, areaId); 402 } 403 404 static boolean isPropertySubscribable(VehiclePropConfig config) { 405 if ((config.access & VehiclePropertyAccess.READ) == 0 || 406 (config.changeMode == VehiclePropertyChangeMode.STATIC)) { 407 return false; 408 } 409 return true; 410 } 411 412 static void dumpProperties(PrintWriter writer, Collection<VehiclePropConfig> configs) { 413 for (VehiclePropConfig config : configs) { 414 writer.println(String.format("property 0x%x", config.prop)); 415 } 416 } 417 418 private final ArraySet<HalServiceBase> mServicesToDispatch = new ArraySet<>(); 419 420 @Override 421 public void onPropertyEvent(ArrayList<VehiclePropValue> propValues) { 422 synchronized (this) { 423 for (VehiclePropValue v : propValues) { 424 HalServiceBase service = mPropertyHandlers.get(v.prop); 425 if(service == null) { 426 Log.e(CarLog.TAG_HAL, "HalService not found for prop: 0x" 427 + toHexString(v.prop)); 428 continue; 429 } 430 service.getDispatchList().add(v); 431 mServicesToDispatch.add(service); 432 VehiclePropertyEventInfo info = mEventLog.get(v.prop); 433 if (info == null) { 434 info = new VehiclePropertyEventInfo(v); 435 mEventLog.put(v.prop, info); 436 } else { 437 info.addNewEvent(v); 438 } 439 } 440 } 441 for (HalServiceBase s : mServicesToDispatch) { 442 s.handleHalEvents(s.getDispatchList()); 443 s.getDispatchList().clear(); 444 } 445 mServicesToDispatch.clear(); 446 } 447 448 @Override 449 public void onPropertySet(VehiclePropValue value) { 450 // No need to handle on-property-set events in HAL service yet. 451 } 452 453 @Override 454 public void onPropertySetError(int errorCode, int propId, int areaId) { 455 Log.e(CarLog.TAG_HAL, String.format("onPropertySetError, errorCode: %d, prop: 0x%x, " 456 + "area: 0x%x", errorCode, propId, areaId)); 457 if (propId != VehicleProperty.INVALID) { 458 HalServiceBase service = mPropertyHandlers.get(propId); 459 if (service != null) { 460 service.handlePropertySetError(propId, areaId); 461 } 462 } 463 } 464 465 public void dump(PrintWriter writer) { 466 writer.println("**dump HAL services**"); 467 for (HalServiceBase service: mAllServices) { 468 service.dump(writer); 469 } 470 471 List<VehiclePropConfig> configList; 472 synchronized (this) { 473 configList = new ArrayList<>(mAllProperties.values()); 474 } 475 476 writer.println("**All properties**"); 477 for (VehiclePropConfig config : configList) { 478 StringBuilder builder = new StringBuilder() 479 .append("Property:0x").append(toHexString(config.prop)) 480 .append(",Property name:").append(VehicleProperty.toString(config.prop)) 481 .append(",access:0x").append(toHexString(config.access)) 482 .append(",changeMode:0x").append(toHexString(config.changeMode)) 483 .append(",config:0x").append(Arrays.toString(config.configArray.toArray())) 484 .append(",fs min:").append(config.minSampleRate) 485 .append(",fs max:").append(config.maxSampleRate); 486 for (VehicleAreaConfig area : config.areaConfigs) { 487 builder.append(",areaId :").append(toHexString(area.areaId)) 488 .append(",f min:").append(area.minFloatValue) 489 .append(",f max:").append(area.maxFloatValue) 490 .append(",i min:").append(area.minInt32Value) 491 .append(",i max:").append(area.maxInt32Value) 492 .append(",i64 min:").append(area.minInt64Value) 493 .append(",i64 max:").append(area.maxInt64Value); 494 } 495 writer.println(builder.toString()); 496 } 497 writer.println(String.format("**All Events, now ns:%d**", 498 SystemClock.elapsedRealtimeNanos())); 499 for (VehiclePropertyEventInfo info : mEventLog.values()) { 500 writer.println(String.format("event count:%d, lastEvent:%s", 501 info.eventCount, dumpVehiclePropValue(info.lastEvent))); 502 } 503 504 writer.println("**Property handlers**"); 505 for (int i = 0; i < mPropertyHandlers.size(); i++) { 506 int propId = mPropertyHandlers.keyAt(i); 507 HalServiceBase service = mPropertyHandlers.valueAt(i); 508 writer.println(String.format("Prop: 0x%08X, service: %s", propId, service)); 509 } 510 } 511 512 /** 513 * Inject a VHAL event 514 * 515 * @param property the Vehicle property Id as defined in the HAL 516 * @param zone Zone that this event services 517 * @param value Data value of the event 518 */ 519 public void injectVhalEvent(String property, String zone, String value) 520 throws NumberFormatException { 521 if (value == null || zone == null || property == null) { 522 return; 523 } 524 int propId = Integer.decode(property); 525 int zoneId = Integer.decode(zone); 526 VehiclePropValue v = createPropValue(propId, zoneId); 527 int propertyType = propId & VehiclePropertyType.MASK; 528 // Values can be comma separated list 529 List<String> dataList = new ArrayList<>(Arrays.asList(value.split(DATA_DELIMITER))); 530 switch (propertyType) { 531 case VehiclePropertyType.BOOLEAN: 532 boolean boolValue = Boolean.valueOf(value); 533 v.value.int32Values.add(boolValue ? 1 : 0); 534 break; 535 case VehiclePropertyType.INT32: 536 case VehiclePropertyType.INT32_VEC: 537 for (String s : dataList) { 538 v.value.int32Values.add(Integer.decode(s)); 539 } 540 break; 541 case VehiclePropertyType.FLOAT: 542 case VehiclePropertyType.FLOAT_VEC: 543 for (String s : dataList) { 544 v.value.floatValues.add(Float.parseFloat(s)); 545 } 546 break; 547 default: 548 Log.e(CarLog.TAG_HAL, "Property type unsupported:" + propertyType); 549 return; 550 } 551 v.timestamp = SystemClock.elapsedRealtimeNanos(); 552 onPropertyEvent(Lists.newArrayList(v)); 553 } 554 555 private static class VehiclePropertyEventInfo { 556 private int eventCount; 557 private VehiclePropValue lastEvent; 558 559 private VehiclePropertyEventInfo(VehiclePropValue event) { 560 eventCount = 1; 561 lastEvent = event; 562 } 563 564 private void addNewEvent(VehiclePropValue event) { 565 eventCount++; 566 lastEvent = event; 567 } 568 } 569 570 final class VehiclePropValueSetter { 571 final WeakReference<HalClient> mClient; 572 final VehiclePropValue mPropValue; 573 574 private VehiclePropValueSetter(HalClient client, int propId, int areaId) { 575 mClient = new WeakReference<>(client); 576 mPropValue = new VehiclePropValue(); 577 mPropValue.prop = propId; 578 mPropValue.areaId = areaId; 579 } 580 581 void to(boolean value) throws PropertyTimeoutException { 582 to(value ? 1 : 0); 583 } 584 585 void to(int value) throws PropertyTimeoutException { 586 mPropValue.value.int32Values.add(value); 587 submit(); 588 } 589 590 void to(int[] values) throws PropertyTimeoutException { 591 for (int value : values) { 592 mPropValue.value.int32Values.add(value); 593 } 594 submit(); 595 } 596 597 void to(Collection<Integer> values) throws PropertyTimeoutException { 598 mPropValue.value.int32Values.addAll(values); 599 submit(); 600 } 601 602 void submit() throws PropertyTimeoutException { 603 HalClient client = mClient.get(); 604 if (client != null) { 605 if (DBG) { 606 Log.i(CarLog.TAG_HAL, "set, property: 0x" + toHexString(mPropValue.prop) 607 + ", areaId: 0x" + toHexString(mPropValue.areaId)); 608 } 609 client.setValue(mPropValue); 610 } 611 } 612 } 613 614 private static String dumpVehiclePropValue(VehiclePropValue value) { 615 final int MAX_BYTE_SIZE = 20; 616 617 StringBuilder sb = new StringBuilder() 618 .append("Property:0x").append(toHexString(value.prop)) 619 .append(",timestamp:").append(value.timestamp) 620 .append(",zone:0x").append(toHexString(value.areaId)) 621 .append(",floatValues: ").append(Arrays.toString(value.value.floatValues.toArray())) 622 .append(",int32Values: ").append(Arrays.toString(value.value.int32Values.toArray())) 623 .append(",int64Values: ") 624 .append(Arrays.toString(value.value.int64Values.toArray())); 625 626 if (value.value.bytes.size() > MAX_BYTE_SIZE) { 627 Object[] bytes = Arrays.copyOf(value.value.bytes.toArray(), MAX_BYTE_SIZE); 628 sb.append(",bytes: ").append(Arrays.toString(bytes)); 629 } else { 630 sb.append(",bytes: ").append(Arrays.toString(value.value.bytes.toArray())); 631 } 632 sb.append(",string: ").append(value.value.stringValue); 633 634 return sb.toString(); 635 } 636 637 private static VehiclePropValue createPropValue(int propId, int areaId) { 638 VehiclePropValue propValue = new VehiclePropValue(); 639 propValue.prop = propId; 640 propValue.areaId = areaId; 641 return propValue; 642 } 643 } 644