1 /* 2 * Copyright (C) 2018 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; 18 19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 20 21 import android.annotation.NonNull; 22 import android.car.Car; 23 import android.car.VehicleAreaType; 24 import android.car.builtin.os.BinderHelper; 25 import android.car.builtin.util.Slogf; 26 import android.car.drivingstate.CarDrivingStateEvent; 27 import android.car.drivingstate.CarDrivingStateEvent.CarDrivingState; 28 import android.car.drivingstate.ICarDrivingState; 29 import android.car.drivingstate.ICarDrivingStateChangeListener; 30 import android.car.hardware.CarPropertyConfig; 31 import android.car.hardware.CarPropertyValue; 32 import android.car.hardware.property.CarPropertyEvent; 33 import android.car.hardware.property.ICarPropertyEventListener; 34 import android.content.Context; 35 import android.hardware.automotive.vehicle.VehicleGear; 36 import android.hardware.automotive.vehicle.VehicleIgnitionState; 37 import android.hardware.automotive.vehicle.VehicleProperty; 38 import android.os.Handler; 39 import android.os.HandlerThread; 40 import android.os.RemoteCallbackList; 41 import android.os.RemoteException; 42 import android.os.SystemClock; 43 import android.util.Log; 44 import android.util.SparseBooleanArray; 45 import android.util.proto.ProtoOutputStream; 46 47 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 48 import com.android.car.internal.util.IndentingPrintWriter; 49 import com.android.car.util.TransitionLog; 50 import com.android.internal.annotations.GuardedBy; 51 import com.android.internal.annotations.VisibleForTesting; 52 53 import java.util.Collections; 54 import java.util.LinkedList; 55 import java.util.List; 56 57 /** 58 * A service that infers the current driving state of the vehicle. It computes the driving state 59 * from listening to relevant properties from {@link CarPropertyService} 60 */ 61 public class CarDrivingStateService extends ICarDrivingState.Stub implements CarServiceBase { 62 private static final String TAG = CarLog.tagFor(CarDrivingStateService.class); 63 private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG); 64 private static final int MAX_TRANSITION_LOG_SIZE = 20; 65 private static final int PROPERTY_UPDATE_RATE = 5; // Update rate in Hz 66 private final Context mContext; 67 private final CarPropertyService mPropertyService; 68 // List of clients listening to driving state events. 69 private final RemoteCallbackList<ICarDrivingStateChangeListener> mDrivingStateClients = 70 new RemoteCallbackList<>(); 71 // Array of properties that the service needs to listen to from CarPropertyService for deriving 72 // the driving state. 73 private static final int[] REQUIRED_PROPERTIES = { 74 VehicleProperty.PERF_VEHICLE_SPEED, 75 VehicleProperty.GEAR_SELECTION, 76 VehicleProperty.PARKING_BRAKE_ON, 77 }; 78 // Array of optional properties that the service needs to listen to from CarPropertyService 79 // for deriving the driving state. 80 private static final int[] OPTIONAL_PROPERTIES = { 81 VehicleProperty.IGNITION_STATE 82 }; 83 private final HandlerThread mClientDispatchThread = CarServiceUtils.getHandlerThread( 84 getClass().getSimpleName()); 85 private final Handler mClientDispatchHandler = new Handler(mClientDispatchThread.getLooper()); 86 private final Object mLock = new Object(); 87 88 // For dumpsys logging 89 @GuardedBy("mLock") 90 private final LinkedList<TransitionLog> mTransitionLogs = new LinkedList<>(); 91 92 /** 93 * A set of optional {@link VehicleProperty} that are supported. 94 */ 95 @GuardedBy("mLock") 96 private SparseBooleanArray mOptionalPropertiesSupported; 97 @GuardedBy("mLock") 98 private CarPropertyValue mLastParkingBrake; 99 @GuardedBy("mLock") 100 private CarPropertyValue mLastGear; 101 @GuardedBy("mLock") 102 private CarPropertyValue mLastSpeed; 103 @GuardedBy("mLock") 104 private CarPropertyValue mLastIgnitionState; 105 106 @GuardedBy("mLock") 107 private List<Integer> mSupportedGears; 108 109 @GuardedBy("mLock") 110 private CarDrivingStateEvent mCurrentDrivingState; 111 CarDrivingStateService(Context context, CarPropertyService propertyService)112 public CarDrivingStateService(Context context, CarPropertyService propertyService) { 113 mContext = context; 114 mPropertyService = propertyService; 115 mCurrentDrivingState = createDrivingStateEvent(CarDrivingStateEvent.DRIVING_STATE_UNKNOWN); 116 } 117 118 @Override init()119 public void init() { 120 if (!checkPropertySupport()) { 121 Slogf.e(TAG, "init failure. Driving state will always be fully restrictive"); 122 return; 123 } 124 // Gets the boot state first, before getting any events from car. 125 synchronized (mLock) { 126 mCurrentDrivingState = createDrivingStateEvent(inferDrivingStateLocked()); 127 addTransitionLogLocked(TAG + " Boot", CarDrivingStateEvent.DRIVING_STATE_UNKNOWN, 128 mCurrentDrivingState.eventValue, mCurrentDrivingState.timeStamp); 129 } 130 subscribeToProperties(); 131 } 132 133 @Override release()134 public void release() { 135 for (int property : REQUIRED_PROPERTIES) { 136 mPropertyService.unregisterListenerSafe(property, mICarPropertyEventListener); 137 } 138 for (int property : OPTIONAL_PROPERTIES) { 139 synchronized (mLock) { 140 if (isOptionalPropertySupportedLocked(property)) { 141 mPropertyService.unregisterListenerSafe(property, mICarPropertyEventListener); 142 } 143 } 144 } 145 while (mDrivingStateClients.getRegisteredCallbackCount() > 0) { 146 for (int i = mDrivingStateClients.getRegisteredCallbackCount() - 1; i >= 0; i--) { 147 ICarDrivingStateChangeListener client = 148 mDrivingStateClients.getRegisteredCallbackItem(i); 149 if (client == null) { 150 continue; 151 } 152 mDrivingStateClients.unregister(client); 153 } 154 } 155 synchronized (mLock) { 156 mCurrentDrivingState = createDrivingStateEvent( 157 CarDrivingStateEvent.DRIVING_STATE_UNKNOWN); 158 if (mOptionalPropertiesSupported != null) { 159 mOptionalPropertiesSupported.clear(); 160 } 161 } 162 } 163 164 /** 165 * Checks if the {@link CarPropertyService} supports the required properties. 166 * 167 * @return {@code true} if supported, {@code false} if not 168 */ checkPropertySupport()169 private boolean checkPropertySupport() { 170 List<CarPropertyConfig> configs = mPropertyService 171 .getPropertyConfigList(REQUIRED_PROPERTIES).carPropertyConfigList.getConfigs(); 172 for (int propertyId : REQUIRED_PROPERTIES) { 173 boolean found = false; 174 for (CarPropertyConfig config : configs) { 175 if (config.getPropertyId() == propertyId) { 176 found = true; 177 break; 178 } 179 } 180 if (!found) { 181 Slogf.e(TAG, "Required property not supported: " + propertyId); 182 return false; 183 } 184 } 185 configs = mPropertyService 186 .getPropertyConfigList(OPTIONAL_PROPERTIES).carPropertyConfigList.getConfigs(); 187 if (configs != null) { 188 SparseBooleanArray optionalPropertiesSupported = new SparseBooleanArray(); 189 for (CarPropertyConfig config : configs) { 190 optionalPropertiesSupported.put(config.getPropertyId(), true); 191 } 192 synchronized (mLock) { 193 mOptionalPropertiesSupported = optionalPropertiesSupported; 194 } 195 } 196 return true; 197 } 198 199 /** 200 * Check if the optional property is supported. 201 */ 202 @GuardedBy("mLock") isOptionalPropertySupportedLocked(int propertyId)203 private boolean isOptionalPropertySupportedLocked(int propertyId) { 204 if (mOptionalPropertiesSupported != null) { 205 return mOptionalPropertiesSupported.get(propertyId); 206 } 207 return false; 208 } 209 210 /** 211 * Subscribe to the {@link CarPropertyService} for required sensors. 212 */ subscribeToProperties()213 private void subscribeToProperties() { 214 for (int propertyId : REQUIRED_PROPERTIES) { 215 mPropertyService.registerListenerSafe(propertyId, PROPERTY_UPDATE_RATE, 216 mICarPropertyEventListener); 217 } 218 for (int propertyId : OPTIONAL_PROPERTIES) { 219 synchronized (mLock) { 220 if (isOptionalPropertySupportedLocked(propertyId)) { 221 mPropertyService.registerListenerSafe(propertyId, PROPERTY_UPDATE_RATE, 222 mICarPropertyEventListener); 223 } 224 } 225 } 226 } 227 228 // Binder methods 229 230 /** 231 * Register a {@link ICarDrivingStateChangeListener} to be notified for changes to the driving 232 * state. 233 * 234 * @param listener {@link ICarDrivingStateChangeListener} 235 */ 236 @Override registerDrivingStateChangeListener(ICarDrivingStateChangeListener listener)237 public void registerDrivingStateChangeListener(ICarDrivingStateChangeListener listener) { 238 if (listener == null) { 239 if (DBG) { 240 Slogf.e(TAG, "registerDrivingStateChangeListener(): listener null"); 241 } 242 throw new IllegalArgumentException("Listener is null"); 243 } 244 mDrivingStateClients.register(listener); 245 } 246 247 /** 248 * Unregister the given Driving State Change listener 249 * 250 * @param listener client to unregister 251 */ 252 @Override unregisterDrivingStateChangeListener(ICarDrivingStateChangeListener listener)253 public void unregisterDrivingStateChangeListener(ICarDrivingStateChangeListener listener) { 254 if (listener == null) { 255 Slogf.e(TAG, "unregisterDrivingStateChangeListener(): listener null"); 256 throw new IllegalArgumentException("Listener is null"); 257 } 258 259 mDrivingStateClients.unregister(listener); 260 } 261 262 /** 263 * Gets the current driving state 264 * 265 * @return {@link CarDrivingStateEvent} for the given event type 266 */ 267 @Override 268 @NonNull getCurrentDrivingState()269 public CarDrivingStateEvent getCurrentDrivingState() { 270 synchronized (mLock) { 271 return mCurrentDrivingState; 272 } 273 } 274 275 @Override injectDrivingState(CarDrivingStateEvent event)276 public void injectDrivingState(CarDrivingStateEvent event) { 277 CarServiceUtils.assertPermission(mContext, Car.PERMISSION_CONTROL_APP_BLOCKING); 278 279 dispatchEventToClients(event); 280 } 281 dispatchEventToClients(CarDrivingStateEvent event)282 private void dispatchEventToClients(CarDrivingStateEvent event) { 283 boolean success = mClientDispatchHandler.post(() -> { 284 int numClients = mDrivingStateClients.beginBroadcast(); 285 for (int i = 0; i < numClients; i++) { 286 ICarDrivingStateChangeListener callback = mDrivingStateClients.getBroadcastItem(i); 287 try { 288 callback.onDrivingStateChanged(event); 289 } catch (RemoteException e) { 290 Slogf.e(TAG, "Dispatch to listener %s failed for event (%s)", callback, event); 291 } 292 } 293 mDrivingStateClients.finishBroadcast(); 294 }); 295 296 if (!success) { 297 Slogf.e(TAG, "Unable to post (" + event + ") event to dispatch handler"); 298 } 299 } 300 301 @Override 302 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)303 public void dump(IndentingPrintWriter writer) { 304 writer.println("*CarDrivingStateService*"); 305 306 writer.println("Driving State Clients:"); 307 writer.increaseIndent(); 308 BinderHelper.dumpRemoteCallbackList(mDrivingStateClients, writer); 309 writer.decreaseIndent(); 310 311 writer.println("Driving state change log:"); 312 synchronized (mLock) { 313 for (TransitionLog tLog : mTransitionLogs) { 314 writer.println(tLog); 315 } 316 writer.println("Current Driving State: " + mCurrentDrivingState.eventValue); 317 if (mSupportedGears != null) { 318 writer.print("Supported gears:"); 319 for (Integer gear : mSupportedGears) { 320 writer.print(' '); 321 writer.print(gear); 322 } 323 writer.println(); 324 } 325 } 326 } 327 328 @Override 329 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dumpProto(ProtoOutputStream proto)330 public void dumpProto(ProtoOutputStream proto) {} 331 332 /** 333 * {@link CarPropertyEvent} listener registered with the {@link CarPropertyService} for getting 334 * property change notifications. 335 */ 336 private final ICarPropertyEventListener mICarPropertyEventListener = 337 new ICarPropertyEventListener.Stub() { 338 @Override 339 public void onEvent(List<CarPropertyEvent> events) throws RemoteException { 340 synchronized (mLock) { 341 for (CarPropertyEvent event : events) { 342 handlePropertyEventLocked(event); 343 } 344 } 345 } 346 }; 347 348 /** 349 * Handle events coming from {@link CarPropertyService}. Compute the driving state, map it to 350 * the corresponding UX Restrictions and dispatch the events to the registered clients. 351 */ 352 @GuardedBy("mLock") 353 @VisibleForTesting handlePropertyEventLocked(CarPropertyEvent event)354 void handlePropertyEventLocked(CarPropertyEvent event) { 355 if (event.getEventType() != CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE) { 356 return; 357 } 358 CarPropertyValue value = event.getCarPropertyValue(); 359 if (DBG) { 360 Slogf.d(TAG, "Property Changed: " + value); 361 } 362 switch (value.getPropertyId()) { 363 case VehicleProperty.PERF_VEHICLE_SPEED: 364 mLastSpeed = value; 365 if (DBG) { 366 Slogf.d(TAG, "mLastSpeed: " + mLastSpeed); 367 } 368 break; 369 case VehicleProperty.GEAR_SELECTION: 370 if (mSupportedGears == null) { 371 mSupportedGears = getSupportedGears(); 372 } 373 mLastGear = value; 374 if (DBG) { 375 Slogf.d(TAG, "mLastGear: " + mLastGear); 376 } 377 break; 378 case VehicleProperty.PARKING_BRAKE_ON: 379 mLastParkingBrake = value; 380 if (DBG) { 381 Slogf.d(TAG, "mLastParkingBrake: " + mLastParkingBrake); 382 } 383 break; 384 case VehicleProperty.IGNITION_STATE: 385 mLastIgnitionState = value; 386 if (DBG) { 387 Slogf.d(TAG, "mLastIgnitionState: " + mLastIgnitionState); 388 } 389 break; 390 default: 391 Slogf.e(TAG, 392 "Received property event for unhandled propId=" + value.getPropertyId()); 393 break; 394 } 395 396 int drivingState = inferDrivingStateLocked(); 397 // Check if the driving state has changed. If it has, update our records and 398 // dispatch the new events to the listeners. 399 if (DBG) { 400 Slogf.d(TAG, "Driving state new->old " + drivingState + "->" 401 + mCurrentDrivingState.eventValue); 402 } 403 if (drivingState != mCurrentDrivingState.eventValue) { 404 addTransitionLogLocked(TAG, mCurrentDrivingState.eventValue, drivingState, 405 System.currentTimeMillis()); 406 // Update if there is a change in state. 407 mCurrentDrivingState = createDrivingStateEvent(drivingState); 408 if (DBG) { 409 Slogf.d(TAG, "dispatching to " + mDrivingStateClients.getRegisteredCallbackCount() 410 + " clients"); 411 } 412 // Dispatch to clients on a separate thread to prevent a deadlock 413 final CarDrivingStateEvent currentDrivingStateEvent = mCurrentDrivingState; 414 dispatchEventToClients(currentDrivingStateEvent); 415 } 416 } 417 getSupportedGears()418 private List<Integer> getSupportedGears() { 419 List<CarPropertyConfig> propertyList = mPropertyService 420 .getPropertyConfigList(REQUIRED_PROPERTIES).carPropertyConfigList.getConfigs(); 421 for (CarPropertyConfig p : propertyList) { 422 if (p.getPropertyId() == VehicleProperty.GEAR_SELECTION) { 423 return p.getConfigArray(); 424 } 425 } 426 return Collections.emptyList(); 427 } 428 429 @GuardedBy("mLock") addTransitionLogLocked(String name, int from, int to, long timestamp)430 private void addTransitionLogLocked(String name, int from, int to, long timestamp) { 431 if (mTransitionLogs.size() >= MAX_TRANSITION_LOG_SIZE) { 432 mTransitionLogs.remove(); 433 } 434 435 TransitionLog tLog = new TransitionLog(name, from, to, timestamp); 436 mTransitionLogs.add(tLog); 437 } 438 isPropertyStatusAvailable(CarPropertyValue<?> carPropertyValue)439 private static boolean isPropertyStatusAvailable(CarPropertyValue<?> carPropertyValue) { 440 return carPropertyValue != null 441 && carPropertyValue.getStatus() == CarPropertyValue.STATUS_AVAILABLE; 442 } 443 444 /** 445 * Infers the current driving state of the car from the other Car Sensor properties like 446 * Current Gear, Speed etc. 447 * 448 * @return Current driving state 449 */ 450 @GuardedBy("mLock") 451 @CarDrivingState inferDrivingStateLocked()452 private int inferDrivingStateLocked() { 453 updateVehiclePropertiesIfNeededLocked(); 454 if (DBG) { 455 Slogf.d(TAG, 456 "inferDrivingStateLocked mLastGear: " + mLastGear + "mLastSpeed: " + mLastSpeed 457 + "mLastIgnitionState: " + mLastIgnitionState); 458 } 459 460 /* 461 Logic to start off deriving driving state: 462 1. If gear == parked, then Driving State is parked. 463 2. If gear != parked, 464 2a. if parking brake is applied, then Driving state is parked. 465 2b. if parking brake is not applied or unknown/unavailable, then driving state 466 is still unknown. 467 3. If driving state is unknown at the end of step 2, 468 3a. if speed == 0, then driving state is idling 469 3b. if speed != 0, then driving state is moving 470 3c. if speed is unavailable, 471 3ca. If ignition state is supported and ignition state == lock or acc or 472 off, then driving state is parked 473 3cb. else driving state is unknown 474 */ 475 476 if (isVehicleKnownToBeParkedLocked()) { 477 return CarDrivingStateEvent.DRIVING_STATE_PARKED; 478 } 479 480 // We don't know if the vehicle is parked, let's look at the speed. 481 if (!isPropertyStatusAvailable(mLastSpeed)) { 482 if (isOptionalPropertySupportedLocked(VehicleProperty.IGNITION_STATE) 483 && isVehicleKnownToBeInLockOrAccOrOffIgnitionStateLocked()) { 484 return CarDrivingStateEvent.DRIVING_STATE_PARKED; 485 } 486 return CarDrivingStateEvent.DRIVING_STATE_UNKNOWN; 487 } else if (mLastSpeed.getValue().equals(0f)) { 488 return CarDrivingStateEvent.DRIVING_STATE_IDLING; 489 } else { 490 return CarDrivingStateEvent.DRIVING_STATE_MOVING; 491 } 492 } 493 494 /** 495 * Find if we have signals to know if the vehicle is in ignition state lock or acc or off. 496 * 497 * @return true if we have enough information to say the vehicle is in ignition state acc or 498 * off, false if the vehicle is either not in ignition state acc or off or if we don't have any 499 * information. 500 */ 501 @GuardedBy("mLock") isVehicleKnownToBeInLockOrAccOrOffIgnitionStateLocked()502 private boolean isVehicleKnownToBeInLockOrAccOrOffIgnitionStateLocked() { 503 return isPropertyStatusAvailable(mLastIgnitionState) 504 && (mLastIgnitionState.getValue().equals(VehicleIgnitionState.ACC) 505 || mLastIgnitionState.getValue().equals(VehicleIgnitionState.OFF) 506 || mLastIgnitionState.getValue().equals(VehicleIgnitionState.LOCK)); 507 } 508 509 /** 510 * Find if we have signals to know if the vehicle is parked 511 * 512 * @return true if we have enough information to say the vehicle is parked. 513 * false, if the vehicle is either not parked or if we don't have any information. 514 */ 515 @GuardedBy("mLock") isVehicleKnownToBeParkedLocked()516 private boolean isVehicleKnownToBeParkedLocked() { 517 // If we know the gear is in park, return true 518 if (isPropertyStatusAvailable(mLastGear) && mLastGear.getValue() 519 .equals(VehicleGear.GEAR_PARK)) { 520 return true; 521 } else if (isPropertyStatusAvailable(mLastParkingBrake)) { 522 // if gear is not in park or unknown, look for status of parking brake if transmission 523 // type is manual. 524 if (isCarManualTransmissionTypeLocked()) { 525 return (boolean) mLastParkingBrake.getValue(); 526 } 527 } 528 // if neither information is available, return false to indicate we can't determine 529 // if the vehicle is parked. 530 return false; 531 } 532 533 /** 534 * If Supported gears information is available and GEAR_PARK is not one of the supported gears, 535 * transmission type is considered to be Manual. Automatic transmission is assumed otherwise. 536 */ 537 @GuardedBy("mLock") isCarManualTransmissionTypeLocked()538 private boolean isCarManualTransmissionTypeLocked() { 539 return mSupportedGears != null 540 && !mSupportedGears.isEmpty() 541 && !mSupportedGears.contains(VehicleGear.GEAR_PARK); 542 } 543 544 /** 545 * Try querying the gear selection and parking brake if we haven't received the event yet. 546 * This could happen if the gear change occurred before car service booted up like in the 547 * case of a HU restart in the middle of a drive. Since gear and parking brake are 548 * on-change only properties, we could be in this situation where we will have to query 549 * VHAL. 550 */ 551 @GuardedBy("mLock") updateVehiclePropertiesIfNeededLocked()552 private void updateVehiclePropertiesIfNeededLocked() { 553 if (mLastGear == null) { 554 CarPropertyValue propertyValue = mPropertyService.getPropertySafe( 555 VehicleProperty.GEAR_SELECTION, 556 VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL); 557 if (propertyValue != null) { 558 mLastGear = propertyValue; 559 if (DBG) { 560 Slogf.d(TAG, "updateVehiclePropertiesIfNeeded: gear:" + mLastGear); 561 } 562 } 563 } 564 565 if (mLastParkingBrake == null) { 566 CarPropertyValue propertyValue = mPropertyService.getPropertySafe( 567 VehicleProperty.PARKING_BRAKE_ON, 568 VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL); 569 if (propertyValue != null) { 570 mLastParkingBrake = propertyValue; 571 if (DBG) { 572 Slogf.d(TAG, "updateVehiclePropertiesIfNeeded: brake:" + mLastParkingBrake); 573 } 574 } 575 } 576 577 if (mLastSpeed == null) { 578 CarPropertyValue propertyValue = mPropertyService.getPropertySafe( 579 VehicleProperty.PERF_VEHICLE_SPEED, 580 VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL); 581 if (propertyValue != null) { 582 mLastSpeed = propertyValue; 583 if (DBG) { 584 Slogf.d(TAG, "updateVehiclePropertiesIfNeeded: speed:" + mLastSpeed); 585 } 586 } 587 } 588 589 if (mLastIgnitionState == null) { 590 CarPropertyValue propertyValue = mPropertyService.getPropertySafe( 591 VehicleProperty.IGNITION_STATE, 592 VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL); 593 if (propertyValue != null) { 594 mLastIgnitionState = propertyValue; 595 if (DBG) { 596 Slogf.d(TAG, "updateVehiclePropertiesIfNeeded: ignition state:" 597 + mLastIgnitionState); 598 } 599 } 600 } 601 } 602 createDrivingStateEvent(int eventValue)603 private static CarDrivingStateEvent createDrivingStateEvent(int eventValue) { 604 return new CarDrivingStateEvent(eventValue, SystemClock.elapsedRealtimeNanos()); 605 } 606 } 607