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