• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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