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