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