• 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 android.annotation.Nullable;
20 import android.car.drivingstate.CarDrivingStateEvent;
21 import android.car.drivingstate.CarDrivingStateEvent.CarDrivingState;
22 import android.car.drivingstate.CarUxRestrictions;
23 import android.car.drivingstate.ICarDrivingStateChangeListener;
24 import android.car.drivingstate.ICarUxRestrictionsChangeListener;
25 import android.car.drivingstate.ICarUxRestrictionsManager;
26 import android.car.hardware.CarPropertyValue;
27 import android.car.hardware.property.CarPropertyEvent;
28 import android.car.hardware.property.ICarPropertyEventListener;
29 import android.content.Context;
30 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
31 import android.os.IBinder;
32 import android.os.RemoteException;
33 import android.util.Log;
34 
35 import org.xmlpull.v1.XmlPullParserException;
36 
37 import java.io.IOException;
38 import java.io.PrintWriter;
39 import java.util.ArrayList;
40 import java.util.LinkedList;
41 import java.util.List;
42 
43 /**
44  * A service that listens to current driving state of the vehicle and maps it to the
45  * appropriate UX restrictions for that driving state.
46  */
47 public class CarUxRestrictionsManagerService extends ICarUxRestrictionsManager.Stub implements
48         CarServiceBase {
49     private static final String TAG = "CarUxR";
50     private static final boolean DBG = false;
51     private static final int MAX_TRANSITION_LOG_SIZE = 20;
52     private static final int PROPERTY_UPDATE_RATE = 5; // Update rate in Hz
53     private static final float SPEED_NOT_AVAILABLE = -1.0F;
54     private final Context mContext;
55     private final CarDrivingStateService mDrivingStateService;
56     private final CarPropertyService mCarPropertyService;
57     private final CarUxRestrictionsServiceHelper mHelper;
58     // List of clients listening to UX restriction events.
59     private final List<UxRestrictionsClient> mUxRClients = new ArrayList<>();
60     private CarUxRestrictions mCurrentUxRestrictions;
61     private float mCurrentMovingSpeed;
62     private boolean mFallbackToDefaults;
63     // For dumpsys logging
64     private final LinkedList<Utils.TransitionLog> mTransitionLogs = new LinkedList<>();
65 
66 
CarUxRestrictionsManagerService(Context context, CarDrivingStateService drvService, CarPropertyService propertyService)67     public CarUxRestrictionsManagerService(Context context, CarDrivingStateService drvService,
68             CarPropertyService propertyService) {
69         mContext = context;
70         mDrivingStateService = drvService;
71         mCarPropertyService = propertyService;
72         mHelper = new CarUxRestrictionsServiceHelper(mContext, R.xml.car_ux_restrictions_map);
73         // Unrestricted until driving state information is received. During boot up, we don't want
74         // everything to be blocked until data is available from CarPropertyManager.  If we start
75         // driving and we don't get speed or gear information, we have bigger problems.
76         mCurrentUxRestrictions = mHelper.createUxRestrictionsEvent(false,
77                 CarUxRestrictions.UX_RESTRICTIONS_BASELINE);
78     }
79 
80     @Override
init()81     public synchronized void init() {
82         try {
83             if (!mHelper.loadUxRestrictionsFromXml()) {
84                 Log.e(TAG, "Error reading Ux Restrictions Mapping. Falling back to defaults");
85                 mFallbackToDefaults = true;
86             }
87         } catch (IOException | XmlPullParserException e) {
88             Log.e(TAG, "Exception reading UX restrictions XML mapping", e);
89             mFallbackToDefaults = true;
90         }
91         // subscribe to driving State
92         mDrivingStateService.registerDrivingStateChangeListener(
93                 mICarDrivingStateChangeEventListener);
94         // subscribe to property service for speed
95         mCarPropertyService.registerListener(VehicleProperty.PERF_VEHICLE_SPEED,
96                 PROPERTY_UPDATE_RATE, mICarPropertyEventListener);
97         initializeUxRestrictions();
98     }
99 
100     // Update current restrictions by getting the current driving state and speed.
initializeUxRestrictions()101     private void initializeUxRestrictions() {
102         CarDrivingStateEvent currentDrivingStateEvent =
103                 mDrivingStateService.getCurrentDrivingState();
104         // if we don't have enough information from the CarPropertyService to compute the UX
105         // restrictions, then leave the UX restrictions unchanged from what it was initialized to
106         // in the constructor.
107         if (currentDrivingStateEvent == null || currentDrivingStateEvent.eventValue
108                 == CarDrivingStateEvent.DRIVING_STATE_UNKNOWN) {
109             return;
110         }
111         int currentDrivingState = currentDrivingStateEvent.eventValue;
112         Float currentSpeed = getCurrentSpeed();
113         if (currentSpeed == SPEED_NOT_AVAILABLE) {
114             return;
115         }
116         // At this point the underlying CarPropertyService has provided us enough information to
117         // compute the UX restrictions that could be potentially different from the initial UX
118         // restrictions.
119         handleDispatchUxRestrictions(currentDrivingState, currentSpeed);
120     }
121 
getCurrentSpeed()122     private Float getCurrentSpeed() {
123         CarPropertyValue value = mCarPropertyService.getProperty(VehicleProperty.PERF_VEHICLE_SPEED,
124                 0);
125         if (value != null) {
126             return (Float) value.getValue();
127         }
128         return SPEED_NOT_AVAILABLE;
129     }
130 
131     @Override
release()132     public synchronized void release() {
133         for (UxRestrictionsClient client : mUxRClients) {
134             client.listenerBinder.unlinkToDeath(client, 0);
135         }
136         mUxRClients.clear();
137         mDrivingStateService.unregisterDrivingStateChangeListener(
138                 mICarDrivingStateChangeEventListener);
139     }
140 
141     // Binder methods
142 
143     /**
144      * Register a {@link ICarUxRestrictionsChangeListener} to be notified for changes to the UX
145      * restrictions
146      *
147      * @param listener listener to register
148      */
149     @Override
registerUxRestrictionsChangeListener( ICarUxRestrictionsChangeListener listener)150     public synchronized void registerUxRestrictionsChangeListener(
151             ICarUxRestrictionsChangeListener listener) {
152         if (listener == null) {
153             if (DBG) {
154                 Log.e(TAG, "registerUxRestrictionsChangeListener(): listener null");
155             }
156             throw new IllegalArgumentException("Listener is null");
157         }
158         // If a new client is registering, create a new DrivingStateClient and add it to the list
159         // of listening clients.
160         UxRestrictionsClient client = findUxRestrictionsClient(listener);
161         if (client == null) {
162             client = new UxRestrictionsClient(listener);
163             try {
164                 listener.asBinder().linkToDeath(client, 0);
165             } catch (RemoteException e) {
166                 Log.e(TAG, "Cannot link death recipient to binder " + e);
167             }
168             mUxRClients.add(client);
169         }
170         return;
171     }
172 
173     /**
174      * Iterates through the list of registered UX Restrictions clients -
175      * {@link UxRestrictionsClient} and finds if the given client is already registered.
176      *
177      * @param listener Listener to look for.
178      * @return the {@link UxRestrictionsClient} if found, null if not
179      */
180     @Nullable
findUxRestrictionsClient( ICarUxRestrictionsChangeListener listener)181     private UxRestrictionsClient findUxRestrictionsClient(
182             ICarUxRestrictionsChangeListener listener) {
183         IBinder binder = listener.asBinder();
184         for (UxRestrictionsClient client : mUxRClients) {
185             if (client.isHoldingBinder(binder)) {
186                 return client;
187             }
188         }
189         return null;
190     }
191 
192     /**
193      * Unregister the given UX Restrictions listener
194      *
195      * @param listener client to unregister
196      */
197     @Override
unregisterUxRestrictionsChangeListener( ICarUxRestrictionsChangeListener listener)198     public synchronized void unregisterUxRestrictionsChangeListener(
199             ICarUxRestrictionsChangeListener listener) {
200         if (listener == null) {
201             Log.e(TAG, "unregisterUxRestrictionsChangeListener(): listener null");
202             throw new IllegalArgumentException("Listener is null");
203         }
204 
205         UxRestrictionsClient client = findUxRestrictionsClient(listener);
206         if (client == null) {
207             Log.e(TAG, "unregisterUxRestrictionsChangeListener(): listener was not previously "
208                     + "registered");
209             return;
210         }
211         listener.asBinder().unlinkToDeath(client, 0);
212         mUxRClients.remove(client);
213     }
214 
215     /**
216      * Gets the current UX restrictions
217      *
218      * @return {@link CarUxRestrictions} for the given event type
219      */
220     @Override
221     @Nullable
getCurrentUxRestrictions()222     public synchronized CarUxRestrictions getCurrentUxRestrictions() {
223         return mCurrentUxRestrictions;
224     }
225 
226     /**
227      * Class that holds onto client related information - listener interface, process that hosts the
228      * binder object etc.
229      * It also registers for death notifications of the host.
230      */
231     private class UxRestrictionsClient implements IBinder.DeathRecipient {
232         private final IBinder listenerBinder;
233         private final ICarUxRestrictionsChangeListener listener;
234 
UxRestrictionsClient(ICarUxRestrictionsChangeListener l)235         public UxRestrictionsClient(ICarUxRestrictionsChangeListener l) {
236             listener = l;
237             listenerBinder = l.asBinder();
238         }
239 
240         @Override
binderDied()241         public void binderDied() {
242             if (DBG) {
243                 Log.d(TAG, "Binder died " + listenerBinder);
244             }
245             listenerBinder.unlinkToDeath(this, 0);
246             synchronized (CarUxRestrictionsManagerService.this) {
247                 mUxRClients.remove(this);
248             }
249         }
250 
251         /**
252          * Returns if the given binder object matches to what this client info holds.
253          * Used to check if the listener asking to be registered is already registered.
254          *
255          * @return true if matches, false if not
256          */
isHoldingBinder(IBinder binder)257         public boolean isHoldingBinder(IBinder binder) {
258             return listenerBinder == binder;
259         }
260 
261         /**
262          * Dispatch the event to the listener
263          *
264          * @param event {@link CarUxRestrictions}.
265          */
dispatchEventToClients(CarUxRestrictions event)266         public void dispatchEventToClients(CarUxRestrictions event) {
267             if (event == null) {
268                 return;
269             }
270             try {
271                 listener.onUxRestrictionsChanged(event);
272             } catch (RemoteException e) {
273                 if (DBG) {
274                     Log.d(TAG, "Dispatch to listener failed");
275                 }
276             }
277         }
278     }
279 
280     @Override
dump(PrintWriter writer)281     public void dump(PrintWriter writer) {
282         writer.println(
283                 "Requires DO? " + mCurrentUxRestrictions.isRequiresDistractionOptimization());
284         writer.println("Current UXR: " + mCurrentUxRestrictions.getActiveRestrictions());
285         mHelper.dump(writer);
286         writer.println("UX Restriction change log:");
287         for (Utils.TransitionLog tlog : mTransitionLogs) {
288             writer.println(tlog);
289         }
290     }
291 
292     /**
293      * {@link CarDrivingStateEvent} listener registered with the {@link CarDrivingStateService}
294      * for getting driving state change notifications.
295      */
296     private final ICarDrivingStateChangeListener mICarDrivingStateChangeEventListener =
297             new ICarDrivingStateChangeListener.Stub() {
298                 @Override
299                 public void onDrivingStateChanged(CarDrivingStateEvent event) {
300                     if (DBG) {
301                         Log.d(TAG, "Driving State Changed:" + event.eventValue);
302                     }
303                     handleDrivingStateEvent(event);
304                 }
305             };
306 
307     /**
308      * Handle the driving state change events coming from the {@link CarDrivingStateService}.
309      * Map the driving state to the corresponding UX Restrictions and dispatch the
310      * UX Restriction change to the registered clients.
311      */
handleDrivingStateEvent(CarDrivingStateEvent event)312     private synchronized void handleDrivingStateEvent(CarDrivingStateEvent event) {
313         if (event == null) {
314             return;
315         }
316         int drivingState = event.eventValue;
317         Float speed = getCurrentSpeed();
318 
319         if (speed != SPEED_NOT_AVAILABLE) {
320             mCurrentMovingSpeed = speed;
321         } else if (drivingState == CarDrivingStateEvent.DRIVING_STATE_PARKED
322                 || drivingState == CarDrivingStateEvent.DRIVING_STATE_UNKNOWN) {
323             // If speed is unavailable, but the driving state is parked or unknown, it can still be
324             // handled.
325             if (DBG) {
326                 Log.d(TAG, "Speed null when driving state is: " + drivingState);
327             }
328             mCurrentMovingSpeed = 0;
329         } else {
330             // If we get here with driving state != parked or unknown && speed == null,
331             // something is wrong.  CarDrivingStateService could not have inferred idling or moving
332             // when speed is not available
333             Log.e(TAG, "Unexpected:  Speed null when driving state is: " + drivingState);
334             return;
335         }
336         handleDispatchUxRestrictions(drivingState, mCurrentMovingSpeed);
337     }
338 
339     /**
340      * {@link CarPropertyEvent} listener registered with the {@link CarPropertyService} for getting
341      * speed change notifications.
342      */
343     private final ICarPropertyEventListener mICarPropertyEventListener =
344             new ICarPropertyEventListener.Stub() {
345                 @Override
346                 public void onEvent(List<CarPropertyEvent> events) throws RemoteException {
347                     for (CarPropertyEvent event : events) {
348                         if ((event.getEventType()
349                                 == CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE)
350                                 && (event.getCarPropertyValue().getPropertyId()
351                                 == VehicleProperty.PERF_VEHICLE_SPEED)) {
352                             handleSpeedChange((Float) event.getCarPropertyValue().getValue());
353                         }
354                     }
355                 }
356             };
357 
handleSpeedChange(float newSpeed)358     private synchronized void handleSpeedChange(float newSpeed) {
359         if (newSpeed == mCurrentMovingSpeed) {
360             // Ignore if speed hasn't changed
361             return;
362         }
363         int currentDrivingState = mDrivingStateService.getCurrentDrivingState().eventValue;
364         if (currentDrivingState != CarDrivingStateEvent.DRIVING_STATE_MOVING) {
365             // Ignore speed changes if the vehicle is not moving
366             return;
367         }
368         mCurrentMovingSpeed = newSpeed;
369         handleDispatchUxRestrictions(currentDrivingState, newSpeed);
370     }
371 
372     /**
373      * Handle dispatching UX restrictions change.
374      *
375      * @param currentDrivingState driving state of the vehicle
376      * @param speed               speed of the vehicle
377      */
handleDispatchUxRestrictions(@arDrivingState int currentDrivingState, float speed)378     private synchronized void handleDispatchUxRestrictions(@CarDrivingState int currentDrivingState,
379             float speed) {
380         CarUxRestrictions uxRestrictions;
381         // Get UX restrictions from the parsed configuration XML or fall back to defaults if not
382         // available.
383         if (mFallbackToDefaults) {
384             uxRestrictions = getDefaultRestrictions(currentDrivingState);
385         } else {
386             uxRestrictions = mHelper.getUxRestrictions(currentDrivingState, speed);
387         }
388 
389         if (DBG) {
390             Log.d(TAG, String.format("DO old->new: %b -> %b",
391                     mCurrentUxRestrictions.isRequiresDistractionOptimization(),
392                     uxRestrictions.isRequiresDistractionOptimization()));
393             Log.d(TAG, String.format("UxR old->new: 0x%x -> 0x%x",
394                     mCurrentUxRestrictions.getActiveRestrictions(),
395                     uxRestrictions.getActiveRestrictions()));
396         }
397 
398         if (mCurrentUxRestrictions.isSameRestrictions(uxRestrictions)) {
399             // Ignore dispatching if the restrictions has not changed.
400             return;
401         }
402         // for dumpsys logging
403         StringBuilder extraInfo = new StringBuilder();
404         extraInfo.append(
405                 mCurrentUxRestrictions.isRequiresDistractionOptimization() ? "DO -> "
406                         : "No DO -> ");
407         extraInfo.append(
408                 uxRestrictions.isRequiresDistractionOptimization() ? "DO" : "No DO");
409         addTransitionLog(TAG, mCurrentUxRestrictions.getActiveRestrictions(),
410                 uxRestrictions.getActiveRestrictions(), System.currentTimeMillis(),
411                 extraInfo.toString());
412 
413         mCurrentUxRestrictions = uxRestrictions;
414         if (DBG) {
415             Log.d(TAG, "dispatching to " + mUxRClients.size() + " clients");
416         }
417         for (UxRestrictionsClient client : mUxRClients) {
418             client.dispatchEventToClients(uxRestrictions);
419         }
420     }
421 
getDefaultRestrictions(@arDrivingState int drivingState)422     private CarUxRestrictions getDefaultRestrictions(@CarDrivingState int drivingState) {
423         int restrictions;
424         boolean requiresOpt = false;
425         switch (drivingState) {
426             case CarDrivingStateEvent.DRIVING_STATE_PARKED:
427                 restrictions = CarUxRestrictions.UX_RESTRICTIONS_BASELINE;
428                 break;
429             case CarDrivingStateEvent.DRIVING_STATE_IDLING:
430                 restrictions = CarUxRestrictions.UX_RESTRICTIONS_BASELINE;
431                 requiresOpt = true;
432                 break;
433             case CarDrivingStateEvent.DRIVING_STATE_MOVING:
434             default:
435                 restrictions = CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED;
436                 requiresOpt = true;
437         }
438         return mHelper.createUxRestrictionsEvent(requiresOpt, restrictions);
439     }
440 
addTransitionLog(String name, int from, int to, long timestamp, String extra)441     private void addTransitionLog(String name, int from, int to, long timestamp, String extra) {
442         if (mTransitionLogs.size() >= MAX_TRANSITION_LOG_SIZE) {
443             mTransitionLogs.remove();
444         }
445 
446         Utils.TransitionLog tLog = new Utils.TransitionLog(name, from, to, timestamp, extra);
447         mTransitionLogs.add(tLog);
448     }
449 
450 }
451