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