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