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