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