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.VehicleProperty; 37 import android.os.Handler; 38 import android.os.HandlerThread; 39 import android.os.RemoteCallbackList; 40 import android.os.RemoteException; 41 import android.os.SystemClock; 42 43 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 44 import com.android.car.internal.util.IndentingPrintWriter; 45 import com.android.car.util.TransitionLog; 46 import com.android.internal.annotations.GuardedBy; 47 import com.android.internal.annotations.VisibleForTesting; 48 49 import java.util.Collections; 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.unregisterListenerSafe(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).getConfigs(); 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.registerListenerSafe(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.print("Supported gears:"); 275 for (Integer gear : mSupportedGears) { 276 writer.print(' '); 277 writer.print(gear); 278 } 279 writer.println(); 280 } 281 } 282 } 283 284 /** 285 * {@link CarPropertyEvent} listener registered with the {@link CarPropertyService} for getting 286 * property change notifications. 287 */ 288 private final ICarPropertyEventListener mICarPropertyEventListener = 289 new ICarPropertyEventListener.Stub() { 290 @Override 291 public void onEvent(List<CarPropertyEvent> events) throws RemoteException { 292 synchronized (mLock) { 293 for (CarPropertyEvent event : events) { 294 handlePropertyEventLocked(event); 295 } 296 } 297 } 298 }; 299 300 /** 301 * Handle events coming from {@link CarPropertyService}. Compute the driving state, map it to 302 * the corresponding UX Restrictions and dispatch the events to the registered clients. 303 */ 304 @GuardedBy("mLock") 305 @VisibleForTesting handlePropertyEventLocked(CarPropertyEvent event)306 void handlePropertyEventLocked(CarPropertyEvent event) { 307 if (event.getEventType() != CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE) { 308 return; 309 } 310 CarPropertyValue value = event.getCarPropertyValue(); 311 int propId = value.getPropertyId(); 312 long curTimestamp = value.getTimestamp(); 313 if (DBG) { 314 Slogf.d(TAG, "Property Changed: propId=" + propId); 315 } 316 switch (propId) { 317 case VehicleProperty.PERF_VEHICLE_SPEED: 318 float curSpeed = (Float) value.getValue(); 319 if (DBG) { 320 Slogf.d(TAG, "Speed: " + curSpeed + "@" + curTimestamp); 321 } 322 if (curTimestamp > mLastSpeedTimestamp) { 323 mLastSpeedTimestamp = curTimestamp; 324 mLastSpeed = curSpeed; 325 } else if (DBG) { 326 Slogf.d(TAG, "Ignoring speed with older timestamp:" + curTimestamp); 327 } 328 break; 329 case VehicleProperty.GEAR_SELECTION: 330 if (mSupportedGears == null) { 331 mSupportedGears = getSupportedGears(); 332 } 333 int curGear = (Integer) value.getValue(); 334 if (DBG) { 335 Slogf.d(TAG, "Gear: " + curGear + "@" + curTimestamp); 336 } 337 if (curTimestamp > mLastGearTimestamp) { 338 mLastGearTimestamp = curTimestamp; 339 mLastGear = (Integer) value.getValue(); 340 } else if (DBG) { 341 Slogf.d(TAG, "Ignoring Gear with older timestamp:" + curTimestamp); 342 } 343 break; 344 case VehicleProperty.PARKING_BRAKE_ON: 345 boolean curParkingBrake = (boolean) value.getValue(); 346 if (DBG) { 347 Slogf.d(TAG, "Parking Brake: " + curParkingBrake + "@" + curTimestamp); 348 } 349 if (curTimestamp > mLastParkingBrakeTimestamp) { 350 mLastParkingBrakeTimestamp = curTimestamp; 351 mLastParkingBrakeState = curParkingBrake; 352 } else if (DBG) { 353 Slogf.d(TAG, "Ignoring Parking Brake status with an older timestamp:" 354 + curTimestamp); 355 } 356 break; 357 default: 358 Slogf.e(TAG, "Received property event for unhandled propId=" + propId); 359 break; 360 } 361 362 int drivingState = inferDrivingStateLocked(); 363 // Check if the driving state has changed. If it has, update our records and 364 // dispatch the new events to the listeners. 365 if (DBG) { 366 Slogf.d(TAG, "Driving state new->old " + drivingState + "->" 367 + mCurrentDrivingState.eventValue); 368 } 369 if (drivingState != mCurrentDrivingState.eventValue) { 370 addTransitionLogLocked(TAG, mCurrentDrivingState.eventValue, drivingState, 371 System.currentTimeMillis()); 372 // Update if there is a change in state. 373 mCurrentDrivingState = createDrivingStateEvent(drivingState); 374 if (DBG) { 375 Slogf.d(TAG, "dispatching to " + mDrivingStateClients.getRegisteredCallbackCount() 376 + " clients"); 377 } 378 // Dispatch to clients on a separate thread to prevent a deadlock 379 final CarDrivingStateEvent currentDrivingStateEvent = mCurrentDrivingState; 380 dispatchEventToClients(currentDrivingStateEvent); 381 } 382 } 383 getSupportedGears()384 private List<Integer> getSupportedGears() { 385 List<CarPropertyConfig> propertyList = mPropertyService 386 .getPropertyConfigList(REQUIRED_PROPERTIES).getConfigs(); 387 for (CarPropertyConfig p : propertyList) { 388 if (p.getPropertyId() == VehicleProperty.GEAR_SELECTION) { 389 return p.getConfigArray(); 390 } 391 } 392 return Collections.emptyList(); 393 } 394 395 @GuardedBy("mLock") addTransitionLogLocked(String name, int from, int to, long timestamp)396 private void addTransitionLogLocked(String name, int from, int to, long timestamp) { 397 if (mTransitionLogs.size() >= MAX_TRANSITION_LOG_SIZE) { 398 mTransitionLogs.remove(); 399 } 400 401 TransitionLog tLog = new TransitionLog(name, from, to, timestamp); 402 mTransitionLogs.add(tLog); 403 } 404 405 /** 406 * Infers the current driving state of the car from the other Car Sensor properties like 407 * Current Gear, Speed etc. 408 * 409 * @return Current driving state 410 */ 411 @GuardedBy("mLock") 412 @CarDrivingState inferDrivingStateLocked()413 private int inferDrivingStateLocked() { 414 updateVehiclePropertiesIfNeededLocked(); 415 if (DBG) { 416 Slogf.d(TAG, "Last known Gear:" + mLastGear + " Last known speed:" + mLastSpeed); 417 } 418 419 /* 420 Logic to start off deriving driving state: 421 1. If gear == parked, then Driving State is parked. 422 2. If gear != parked, 423 2a. if parking brake is applied, then Driving state is parked. 424 2b. if parking brake is not applied or unknown/unavailable, then driving state 425 is still unknown. 426 3. If driving state is unknown at the end of step 2, 427 3a. if speed == 0, then driving state is idling 428 3b. if speed != 0, then driving state is moving 429 3c. if speed unavailable, then driving state is unknown 430 */ 431 432 if (isVehicleKnownToBeParkedLocked()) { 433 return CarDrivingStateEvent.DRIVING_STATE_PARKED; 434 } 435 436 // We don't know if the vehicle is parked, let's look at the speed. 437 if (mLastSpeedTimestamp == NOT_RECEIVED) { 438 return CarDrivingStateEvent.DRIVING_STATE_UNKNOWN; 439 } else if (mLastSpeed == 0f) { 440 return CarDrivingStateEvent.DRIVING_STATE_IDLING; 441 } else { 442 return CarDrivingStateEvent.DRIVING_STATE_MOVING; 443 } 444 } 445 446 /** 447 * Find if we have signals to know if the vehicle is parked 448 * 449 * @return true if we have enough information to say the vehicle is parked. 450 * false, if the vehicle is either not parked or if we don't have any information. 451 */ 452 @GuardedBy("mLock") isVehicleKnownToBeParkedLocked()453 private boolean isVehicleKnownToBeParkedLocked() { 454 // If we know the gear is in park, return true 455 if (mLastGearTimestamp != NOT_RECEIVED && mLastGear == VehicleGear.GEAR_PARK) { 456 return true; 457 } else if (mLastParkingBrakeTimestamp != NOT_RECEIVED) { 458 // if gear is not in park or unknown, look for status of parking brake if transmission 459 // type is manual. 460 if (isCarManualTransmissionTypeLocked()) { 461 return mLastParkingBrakeState; 462 } 463 } 464 // if neither information is available, return false to indicate we can't determine 465 // if the vehicle is parked. 466 return false; 467 } 468 469 /** 470 * If Supported gears information is available and GEAR_PARK is not one of the supported gears, 471 * transmission type is considered to be Manual. Automatic transmission is assumed otherwise. 472 */ 473 @GuardedBy("mLock") isCarManualTransmissionTypeLocked()474 private boolean isCarManualTransmissionTypeLocked() { 475 return mSupportedGears != null 476 && !mSupportedGears.isEmpty() 477 && !mSupportedGears.contains(VehicleGear.GEAR_PARK); 478 } 479 480 /** 481 * Try querying the gear selection and parking brake if we haven't received the event yet. 482 * This could happen if the gear change occurred before car service booted up like in the 483 * case of a HU restart in the middle of a drive. Since gear and parking brake are 484 * on-change only properties, we could be in this situation where we will have to query 485 * VHAL. 486 */ 487 @GuardedBy("mLock") updateVehiclePropertiesIfNeededLocked()488 private void updateVehiclePropertiesIfNeededLocked() { 489 if (mLastGearTimestamp == NOT_RECEIVED) { 490 CarPropertyValue propertyValue = mPropertyService.getPropertySafe( 491 VehicleProperty.GEAR_SELECTION, 492 VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL); 493 if (propertyValue != null) { 494 mLastGear = (Integer) propertyValue.getValue(); 495 mLastGearTimestamp = propertyValue.getTimestamp(); 496 if (DBG) { 497 Slogf.d(TAG, "updateVehiclePropertiesIfNeeded: gear:" + mLastGear); 498 } 499 } 500 } 501 502 if (mLastParkingBrakeTimestamp == NOT_RECEIVED) { 503 CarPropertyValue propertyValue = mPropertyService.getPropertySafe( 504 VehicleProperty.PARKING_BRAKE_ON, 505 VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL); 506 if (propertyValue != null) { 507 mLastParkingBrakeState = (boolean) propertyValue.getValue(); 508 mLastParkingBrakeTimestamp = propertyValue.getTimestamp(); 509 if (DBG) { 510 Slogf.d(TAG, "updateVehiclePropertiesIfNeeded: brake:" 511 + mLastParkingBrakeState); 512 } 513 } 514 } 515 516 if (mLastSpeedTimestamp == NOT_RECEIVED) { 517 CarPropertyValue propertyValue = mPropertyService.getPropertySafe( 518 VehicleProperty.PERF_VEHICLE_SPEED, 519 VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL); 520 if (propertyValue != null) { 521 mLastSpeed = (float) propertyValue.getValue(); 522 mLastSpeedTimestamp = propertyValue.getTimestamp(); 523 if (DBG) { 524 Slogf.d(TAG, "updateVehiclePropertiesIfNeeded: speed:" + mLastSpeed); 525 } 526 } 527 } 528 } 529 createDrivingStateEvent(int eventValue)530 private static CarDrivingStateEvent createDrivingStateEvent(int eventValue) { 531 return new CarDrivingStateEvent(eventValue, SystemClock.elapsedRealtimeNanos()); 532 } 533 534 } 535