1 /*
2  * Copyright (C) 2019 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.experimentalcar;
18 
19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
20 
21 import android.annotation.FloatRange;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.car.Car;
25 import android.car.VehiclePropertyIds;
26 import android.car.experimental.DriverAwarenessEvent;
27 import android.car.experimental.DriverAwarenessSupplierConfig;
28 import android.car.experimental.DriverAwarenessSupplierService;
29 import android.car.experimental.DriverDistractionChangeEvent;
30 import android.car.experimental.ExperimentalCar;
31 import android.car.experimental.IDriverAwarenessSupplier;
32 import android.car.experimental.IDriverAwarenessSupplierCallback;
33 import android.car.experimental.IDriverDistractionChangeListener;
34 import android.car.experimental.IDriverDistractionManager;
35 import android.car.hardware.CarPropertyValue;
36 import android.car.hardware.property.CarPropertyEvent;
37 import android.car.hardware.property.CarPropertyManager;
38 import android.content.ComponentName;
39 import android.content.Context;
40 import android.content.Intent;
41 import android.content.ServiceConnection;
42 import android.os.Handler;
43 import android.os.HandlerThread;
44 import android.os.IBinder;
45 import android.os.Looper;
46 import android.os.RemoteCallbackList;
47 import android.os.RemoteException;
48 import android.os.UserHandle;
49 import android.util.Log;
50 import android.util.Pair;
51 import android.util.proto.ProtoOutputStream;
52 
53 import com.android.car.CarServiceBase;
54 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
55 import com.android.car.internal.util.IndentingPrintWriter;
56 import com.android.car.util.TransitionLog;
57 import com.android.internal.annotations.GuardedBy;
58 import com.android.internal.annotations.VisibleForTesting;
59 
60 import java.util.ArrayDeque;
61 import java.util.ArrayList;
62 import java.util.Comparator;
63 import java.util.HashMap;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.TimerTask;
67 
68 /**
69  * Driver Distraction Service for using the driver's awareness, the required awareness of the
70  * driving environment to expose APIs for the driver's current distraction level.
71  *
72  * <p>Allows the registration of multiple {@link IDriverAwarenessSupplier} so that higher accuracy
73  * signals can be used when possible, with a fallback to less accurate signals. The {@link
74  * TouchDriverAwarenessSupplier} is always set to the fallback implementation - it is configured
75  * to send change-events, so its data will not become stale.
76  */
77 public final class DriverDistractionExperimentalFeatureService extends
78         IDriverDistractionManager.Stub implements CarServiceBase {
79 
80     private static final String TAG = "CAR.DriverDistractionService";
81 
82     /**
83      * The minimum delay between dispatched distraction events, in milliseconds.
84      */
85     @VisibleForTesting
86     static final long DISPATCH_THROTTLE_MS = 50L;
87     private static final float DEFAULT_AWARENESS_VALUE_FOR_LOG = 1.0f;
88     private static final float MOVING_REQUIRED_AWARENESS = 1.0f;
89     private static final float STATIONARY_REQUIRED_AWARENESS = 0.0f;
90     private static final int MAX_EVENT_LOG_COUNT = 50;
91     private static final int PROPERTY_UPDATE_RATE_HZ = 5;
92     @VisibleForTesting
93     static final float DEFAULT_AWARENESS_PERCENTAGE = 1.0f;
94 
95     private final HandlerThread mClientDispatchHandlerThread;
96     private final Handler mClientDispatchHandler;
97 
98     private final Object mLock = new Object();
99 
100     @GuardedBy("mLock")
101     private final ArrayDeque<TransitionLog> mTransitionLogs = new ArrayDeque<>();
102 
103     /**
104      * All the active service connections.
105      */
106     @GuardedBy("mLock")
107     private final List<ServiceConnection> mServiceConnections = new ArrayList<>();
108 
109     /**
110      * The binder for each supplier.
111      */
112     @GuardedBy("mLock")
113     private final Map<ComponentName, IDriverAwarenessSupplier> mSupplierBinders = new HashMap<>();
114 
115     /**
116      * The configuration for each supplier.
117      */
118     @GuardedBy("mLock")
119     private final Map<IDriverAwarenessSupplier, DriverAwarenessSupplierConfig> mSupplierConfigs =
120             new HashMap<>();
121 
122     /**
123      * List of driver awareness suppliers that can be used to understand the current driver
124      * awareness level. Ordered from highest to lowest priority.
125      */
126     @GuardedBy("mLock")
127     private final List<IDriverAwarenessSupplier> mPrioritizedDriverAwarenessSuppliers =
128             new ArrayList<>();
129 
130     /**
131      * Helper map for looking up the priority rank of a supplier by name. A higher integer value
132      * represents a higher priority.
133      */
134     @GuardedBy("mLock")
135     private final Map<IDriverAwarenessSupplier, Integer> mDriverAwarenessSupplierPriorities =
136             new HashMap<>();
137 
138     /**
139      * List of clients listening to UX restriction events.
140      */
141     private final RemoteCallbackList<IDriverDistractionChangeListener> mDistractionClients =
142             new RemoteCallbackList<>();
143 
144     /**
145      * Comparator used to sort {@link #mDriverAwarenessSupplierPriorities}.
146      */
147     private final Comparator<IDriverAwarenessSupplier> mPrioritizedSuppliersComparator =
148             (left, right) -> {
149                 int leftPri = mDriverAwarenessSupplierPriorities.get(left);
150                 int rightPri = mDriverAwarenessSupplierPriorities.get(right);
151                 // sort descending
152                 return rightPri - leftPri;
153             };
154 
155     /**
156      * Keep track of the most recent awareness event for each supplier for use when the data from
157      * higher priority suppliers becomes stale. This is necessary in order to seamlessly handle
158      * fallback scenarios when data from preferred providers becomes stale.
159      */
160     @GuardedBy("mLock")
161     private final Map<IDriverAwarenessSupplier, DriverAwarenessEventWrapper>
162             mCurrentAwarenessEventsMap =
163             new HashMap<>();
164 
165     /**
166      * The awareness event that is currently being used to determine the driver awareness level.
167      *
168      * <p>This is null until it is set by the first awareness supplier to send an event
169      */
170     @GuardedBy("mLock")
171     @Nullable
172     private DriverAwarenessEventWrapper mCurrentDriverAwareness;
173 
174     /**
175      * Timer to alert when the current driver awareness event has become stale.
176      */
177     @GuardedBy("mLock")
178     private ITimer mExpiredDriverAwarenessTimer;
179 
180     /**
181      * The current, non-stale, driver distraction event. Defaults to 100% awareness.
182      */
183     @GuardedBy("mLock")
184     private DriverDistractionChangeEvent mCurrentDistractionEvent;
185 
186     /**
187      * The required driver awareness based on the current driving environment, where 1.0 means that
188      * full awareness is required and 0.0 means than no awareness is required.
189      */
190     @FloatRange(from = 0.0f, to = 1.0f)
191     @GuardedBy("mLock")
192     private float mRequiredAwareness = STATIONARY_REQUIRED_AWARENESS;
193 
194     @GuardedBy("mLock")
195     private Car mCar;
196 
197     @GuardedBy("mLock")
198     private CarPropertyManager mPropertyManager;
199 
200     /**
201      * The time that last event was emitted, measured in milliseconds since boot using the {@link
202      * android.os.SystemClock#uptimeMillis()} time-base.
203      */
204     @GuardedBy("mLock")
205     private long mLastDispatchUptimeMillis;
206 
207     /**
208      * Whether there is currently a pending dispatch to clients.
209      */
210     @GuardedBy("mLock")
211     private boolean mIsDispatchQueued;
212 
213     private final Context mContext;
214     private final ITimeSource mTimeSource;
215     private final Looper mLooper;
216 
217     private final Runnable mDispatchCurrentDistractionRunnable = () -> {
218         synchronized (mLock) {
219             // dispatch whatever the current value is at this time in the future
220             dispatchCurrentDistractionEventToClientsLocked(
221                     mCurrentDistractionEvent);
222             mIsDispatchQueued = false;
223         }
224     };
225 
226     /**
227      * Create an instance of {@link DriverDistractionExperimentalFeatureService}.
228      *
229      * @param context the context
230      */
DriverDistractionExperimentalFeatureService(Context context)231     DriverDistractionExperimentalFeatureService(Context context) {
232         this(context, new SystemTimeSource(), new SystemTimer(), Looper.myLooper(), null);
233     }
234 
235     @VisibleForTesting
DriverDistractionExperimentalFeatureService( Context context, ITimeSource timeSource, ITimer timer, Looper looper, Handler clientDispatchHandler)236     DriverDistractionExperimentalFeatureService(
237             Context context,
238             ITimeSource timeSource,
239             ITimer timer,
240             Looper looper,
241             Handler clientDispatchHandler) {
242         mContext = context;
243         mTimeSource = timeSource;
244         mExpiredDriverAwarenessTimer = timer;
245         mCurrentDistractionEvent = new DriverDistractionChangeEvent.Builder()
246                 .setElapsedRealtimeTimestamp(mTimeSource.elapsedRealtime())
247                 .setAwarenessPercentage(DEFAULT_AWARENESS_PERCENTAGE)
248                 .build();
249         mClientDispatchHandlerThread = new HandlerThread(TAG);
250         mClientDispatchHandlerThread.start();
251         if (clientDispatchHandler == null) {
252             mClientDispatchHandler = new Handler(mClientDispatchHandlerThread.getLooper());
253         } else {
254             mClientDispatchHandler = clientDispatchHandler;
255         }
256         mLooper = looper;
257     }
258 
259     @Override
init()260     public void init() {
261         // The touch supplier is an internal implementation, so it can be started initiated by its
262         // constructor, unlike other suppliers
263         ComponentName touchComponent = new ComponentName(mContext,
264                 TouchDriverAwarenessSupplier.class);
265         TouchDriverAwarenessSupplier touchSupplier = new TouchDriverAwarenessSupplier(mContext,
266                 new DriverAwarenessSupplierCallback(touchComponent), mLooper);
267         addDriverAwarenessSupplier(touchComponent, touchSupplier, /* priority= */ 0);
268         touchSupplier.onReady();
269 
270         String[] preferredDriverAwarenessSuppliers = mContext.getResources().getStringArray(
271                 R.array.preferredDriverAwarenessSuppliers);
272         for (int i = 0; i < preferredDriverAwarenessSuppliers.length; i++) {
273             String supplierStringName = preferredDriverAwarenessSuppliers[i];
274             ComponentName externalComponent = ComponentName.unflattenFromString(supplierStringName);
275             // the touch supplier has priority 0 and preferred suppliers are higher based on order
276             int priority = i + 1;
277             bindDriverAwarenessSupplierService(externalComponent, priority);
278         }
279 
280         synchronized (mLock) {
281             mCar = Car.createCar(mContext);
282             if (mCar != null) {
283                 mPropertyManager = (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE);
284             } else {
285                 Log.e(TAG, "Unable to connect to car in init");
286             }
287         }
288 
289         if (mPropertyManager != null) {
290             mPropertyManager.registerCallback(mSpeedPropertyEventCallback,
291                     VehiclePropertyIds.PERF_VEHICLE_SPEED,
292                     PROPERTY_UPDATE_RATE_HZ);
293         } else {
294             Log.e(TAG, "Unable to get car property service.");
295         }
296     }
297 
298     @Override
release()299     public void release() {
300         logd("release");
301         mDistractionClients.kill();
302         synchronized (mLock) {
303             mExpiredDriverAwarenessTimer.cancel();
304             mClientDispatchHandler.removeCallbacksAndMessages(null);
305             for (ServiceConnection serviceConnection : mServiceConnections) {
306                 mContext.unbindService(serviceConnection);
307             }
308             if (mPropertyManager != null) {
309                 mPropertyManager.unregisterCallback(mSpeedPropertyEventCallback);
310             }
311             if (mCar != null) {
312                 mCar.disconnect();
313             }
314         }
315     }
316 
317     @Override
dump(IndentingPrintWriter writer)318     public void dump(IndentingPrintWriter writer) {
319         writer.println("*DriverDistractionExperimentalFeatureService*");
320         mDistractionClients.dump(writer, "Distraction Clients ");
321         writer.println("Prioritized Driver Awareness Suppliers (highest to lowest priority):");
322         synchronized (mLock) {
323             for (int i = 0; i < mPrioritizedDriverAwarenessSuppliers.size(); i++) {
324                 writer.println(
325                         String.format("  %d: %s", i, mPrioritizedDriverAwarenessSuppliers.get(
326                                 i).getClass().getName()));
327             }
328             writer.println("Current Driver Awareness:");
329             writer.println("  Value: "
330                     + (mCurrentDriverAwareness == null ? "unknown"
331                     : mCurrentDriverAwareness.mAwarenessEvent.getAwarenessValue()));
332             writer.println("  Supplier: " + (mCurrentDriverAwareness == null ? "unknown"
333                     : mCurrentDriverAwareness.mSupplier.getClass().getSimpleName()));
334             writer.println("  Timestamp (ms since boot): "
335                     + (mCurrentDriverAwareness == null ? "unknown"
336                     : mCurrentDriverAwareness.mAwarenessEvent.getTimeStamp()));
337             writer.println("Current Required Awareness: " + mRequiredAwareness);
338             writer.println("Last Distraction Event:");
339             writer.println("  Value: "
340                     + (mCurrentDistractionEvent == null ? "unknown"
341                     : mCurrentDistractionEvent.getAwarenessPercentage()));
342             writer.println("  Timestamp (ms since boot): "
343                     + (mCurrentDistractionEvent == null ? "unknown"
344                     : mCurrentDistractionEvent.getElapsedRealtimeTimestamp()));
345             writer.println("Dispatch Status:");
346             writer.println("  mLastDispatchUptimeMillis: " + mLastDispatchUptimeMillis);
347             writer.println("  mIsDispatchQueued: " + mIsDispatchQueued);
348             writer.println("Change log:");
349             for (TransitionLog log : mTransitionLogs) {
350                 writer.println(log);
351             }
352         }
353     }
354 
355     @Override
356     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProto(ProtoOutputStream proto)357     public void dumpProto(ProtoOutputStream proto) {}
358 
359     /**
360      * Bind to a {@link DriverAwarenessSupplierService} by its component name.
361      *
362      * @param componentName the name of the {@link DriverAwarenessSupplierService} to bind to.
363      * @param priority      the priority rank of this supplier
364      */
bindDriverAwarenessSupplierService(ComponentName componentName, int priority)365     private void bindDriverAwarenessSupplierService(ComponentName componentName, int priority) {
366         Intent intent = new Intent();
367         intent.setComponent(componentName);
368         ServiceConnection connection = new DriverAwarenessServiceConnection(priority);
369         synchronized (mLock) {
370             mServiceConnections.add(connection);
371         }
372         if (!mContext.bindServiceAsUser(intent, connection,
373                 Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.SYSTEM)) {
374             Log.e(TAG, "Unable to bind with intent: " + intent);
375             // TODO(b/146471650) attempt to rebind
376         }
377     }
378 
379     @VisibleForTesting
handleDriverAwarenessEvent(DriverAwarenessEventWrapper awarenessEventWrapper)380     void handleDriverAwarenessEvent(DriverAwarenessEventWrapper awarenessEventWrapper) {
381         synchronized (mLock) {
382             handleDriverAwarenessEventLocked(awarenessEventWrapper);
383         }
384     }
385 
386     /**
387      * Handle the driver awareness event by:
388      * <ul>
389      *     <li>Cache the driver awareness event for its supplier</li>
390      *     <li>Update the current awareness value</li>
391      *     <li>Register to refresh the awareness value again when the new current expires</li>
392      * </ul>
393      *
394      * @param awarenessEventWrapper the driver awareness event that has occurred
395      */
396     @GuardedBy("mLock")
handleDriverAwarenessEventLocked( DriverAwarenessEventWrapper awarenessEventWrapper)397     private void handleDriverAwarenessEventLocked(
398             DriverAwarenessEventWrapper awarenessEventWrapper) {
399         // update the current awareness event for the supplier, checking that it is the newest event
400         IDriverAwarenessSupplier supplier = awarenessEventWrapper.mSupplier;
401         long timestamp = awarenessEventWrapper.mAwarenessEvent.getTimeStamp();
402         if (!mCurrentAwarenessEventsMap.containsKey(supplier)
403                 || mCurrentAwarenessEventsMap.get(supplier).mAwarenessEvent.getTimeStamp()
404                 < timestamp) {
405             mCurrentAwarenessEventsMap.put(awarenessEventWrapper.mSupplier, awarenessEventWrapper);
406         }
407 
408         int oldSupplierPriority = mDriverAwarenessSupplierPriorities.get(supplier);
409         float oldAwarenessValue = DEFAULT_AWARENESS_VALUE_FOR_LOG;
410         if (mCurrentDriverAwareness != null) {
411             oldAwarenessValue = mCurrentDriverAwareness.mAwarenessEvent.getAwarenessValue();
412         }
413 
414         updateCurrentAwarenessValueLocked();
415 
416         int newSupplierPriority = mDriverAwarenessSupplierPriorities.get(
417                 mCurrentDriverAwareness.mSupplier);
418         if (mSupplierConfigs.get(mCurrentDriverAwareness.mSupplier).getMaxStalenessMillis()
419                 != DriverAwarenessSupplierService.NO_STALENESS
420                 && newSupplierPriority >= oldSupplierPriority) {
421             // only reschedule an expiration if this is for a supplier that is the same or higher
422             // priority than the old value. If there is a higher priority supplier with non-stale
423             // data, then mCurrentDriverAwareness won't change even though we received a new event.
424             scheduleExpirationTimerLocked();
425         }
426 
427         if (oldAwarenessValue != mCurrentDriverAwareness.mAwarenessEvent.getAwarenessValue()) {
428             logd("Driver awareness updated: "
429                     + mCurrentDriverAwareness.mAwarenessEvent.getAwarenessValue());
430             addTransitionLogLocked(oldAwarenessValue,
431                     awarenessEventWrapper.mAwarenessEvent.getAwarenessValue(),
432                     "Driver awareness updated by "
433                             + awarenessEventWrapper.mSupplier.getClass().getSimpleName());
434         }
435 
436         updateCurrentDistractionEventLocked();
437     }
438 
439     /**
440      * Get the current awareness value.
441      */
442     @VisibleForTesting
getCurrentDriverAwareness()443     DriverAwarenessEventWrapper getCurrentDriverAwareness() {
444         return mCurrentDriverAwareness;
445     }
446 
447     /**
448      * Set the drier awareness suppliers. Allows circumventing the {@link #init()} logic.
449      */
450     @VisibleForTesting
setDriverAwarenessSuppliers( List<Pair<IDriverAwarenessSupplier, DriverAwarenessSupplierConfig>> suppliers)451     void setDriverAwarenessSuppliers(
452             List<Pair<IDriverAwarenessSupplier, DriverAwarenessSupplierConfig>> suppliers) {
453         mPrioritizedDriverAwarenessSuppliers.clear();
454         mDriverAwarenessSupplierPriorities.clear();
455         for (int i = 0; i < suppliers.size(); i++) {
456             Pair<IDriverAwarenessSupplier, DriverAwarenessSupplierConfig> pair = suppliers.get(i);
457             mSupplierConfigs.put(pair.first, pair.second);
458             mDriverAwarenessSupplierPriorities.put(pair.first, i);
459             mPrioritizedDriverAwarenessSuppliers.add(pair.first);
460         }
461         mPrioritizedDriverAwarenessSuppliers.sort(mPrioritizedSuppliersComparator);
462     }
463 
464     /**
465      * {@link CarPropertyEvent} listener registered with the {@link CarPropertyManager} for getting
466      * speed change notifications.
467      */
468     private final CarPropertyManager.CarPropertyEventCallback mSpeedPropertyEventCallback =
469             new CarPropertyManager.CarPropertyEventCallback() {
470                 @Override
471                 public void onChangeEvent(CarPropertyValue value) {
472                     synchronized (mLock) {
473                         handleSpeedEventLocked(value);
474                     }
475                 }
476 
477                 @Override
478                 public void onErrorEvent(int propId, int zone) {
479                     Log.e(TAG, "Error in callback for vehicle speed");
480                 }
481             };
482 
483 
484     @VisibleForTesting
485     @GuardedBy("mLock")
handleSpeedEventLocked(@onNull CarPropertyValue value)486     void handleSpeedEventLocked(@NonNull CarPropertyValue value) {
487         if (value.getPropertyId() != VehiclePropertyIds.PERF_VEHICLE_SPEED) {
488             Log.e(TAG, "Unexpected property id: " + value.getPropertyId());
489             return;
490         }
491 
492         float oldValue = mRequiredAwareness;
493         if ((Float) value.getValue() > 0) {
494             mRequiredAwareness = MOVING_REQUIRED_AWARENESS;
495         } else {
496             mRequiredAwareness = STATIONARY_REQUIRED_AWARENESS;
497         }
498 
499         if (Float.compare(oldValue, mRequiredAwareness) != 0) {
500             logd("Required awareness updated: " + mRequiredAwareness);
501             addTransitionLogLocked(oldValue, mRequiredAwareness, "Required awareness");
502             updateCurrentDistractionEventLocked();
503         }
504     }
505 
506     @GuardedBy("mLock")
updateCurrentDistractionEventLocked()507     private void updateCurrentDistractionEventLocked() {
508         if (mCurrentDriverAwareness == null) {
509             logd("Driver awareness level is not yet known");
510             return;
511         }
512         float awarenessPercentage;
513         if (mRequiredAwareness == 0) {
514             // avoid divide by 0 error - awareness percentage should be 100% when required
515             // awareness is 0
516             awarenessPercentage = 1.0f;
517         } else {
518             // Cap awareness percentage at 100%
519             awarenessPercentage = Math.min(
520                     mCurrentDriverAwareness.mAwarenessEvent.getAwarenessValue()
521                             / mRequiredAwareness, 1.0f);
522         }
523         if (Float.compare(mCurrentDistractionEvent.getAwarenessPercentage(), awarenessPercentage)
524                 == 0) {
525             // no need to dispatch unless there's a change
526             return;
527         }
528 
529         addTransitionLogLocked(mCurrentDistractionEvent.getAwarenessPercentage(),
530                 awarenessPercentage, "Awareness percentage");
531 
532         mCurrentDistractionEvent = new DriverDistractionChangeEvent.Builder()
533                 .setElapsedRealtimeTimestamp(mTimeSource.elapsedRealtime())
534                 .setAwarenessPercentage(awarenessPercentage)
535                 .build();
536 
537         long nowUptimeMillis = mTimeSource.uptimeMillis();
538         if (shouldThrottleDispatchEventLocked(nowUptimeMillis)) {
539             scheduleAwarenessDispatchLocked(nowUptimeMillis);
540         } else {
541             // if event doesn't need to be throttled, emit immediately
542             DriverDistractionChangeEvent changeEvent = mCurrentDistractionEvent;
543             mClientDispatchHandler.post(
544                     () -> dispatchCurrentDistractionEventToClientsLocked(changeEvent));
545         }
546     }
547 
548     @GuardedBy("mLock")
scheduleAwarenessDispatchLocked(long uptimeMillis)549     private void scheduleAwarenessDispatchLocked(long uptimeMillis) {
550         if (mIsDispatchQueued) {
551             logd("Dispatch event is throttled and already scheduled.");
552             return;
553         }
554 
555         // schedule a dispatch for when throttle window has passed
556         long delayMs = mLastDispatchUptimeMillis + DISPATCH_THROTTLE_MS - uptimeMillis;
557         if (delayMs < 0) {
558             Log.e(TAG, String.format(
559                     "Delay for (%s) calculated to be negative (%s), so dispatching immediately",
560                     mCurrentDistractionEvent, delayMs));
561             delayMs = 0;
562         }
563         logd(String.format("Dispatch event (%s) is throttled. Scheduled to emit in %sms",
564                 mCurrentDistractionEvent, delayMs));
565         mIsDispatchQueued = true;
566         mClientDispatchHandler.postDelayed(mDispatchCurrentDistractionRunnable, delayMs);
567     }
568 
569     @GuardedBy("mLock")
shouldThrottleDispatchEventLocked(long uptimeMillis)570     private boolean shouldThrottleDispatchEventLocked(long uptimeMillis) {
571         return uptimeMillis < mLastDispatchUptimeMillis + DISPATCH_THROTTLE_MS;
572     }
573 
574     @GuardedBy("mLock")
dispatchCurrentDistractionEventToClientsLocked( DriverDistractionChangeEvent changeEvent)575     private void dispatchCurrentDistractionEventToClientsLocked(
576             DriverDistractionChangeEvent changeEvent) {
577         mLastDispatchUptimeMillis = mTimeSource.uptimeMillis();
578         logd("Dispatching event to clients: " + changeEvent);
579         int numClients = mDistractionClients.beginBroadcast();
580         for (int i = 0; i < numClients; i++) {
581             IDriverDistractionChangeListener callback = mDistractionClients.getBroadcastItem(i);
582             try {
583                 callback.onDriverDistractionChange(changeEvent);
584             } catch (RemoteException ignores) {
585                 // ignore
586             }
587         }
588         mDistractionClients.finishBroadcast();
589     }
590 
591     /**
592      * Internally register the supplier with the specified priority.
593      */
addDriverAwarenessSupplier( ComponentName componentName, IDriverAwarenessSupplier awarenessSupplier, int priority)594     private void addDriverAwarenessSupplier(
595             ComponentName componentName,
596             IDriverAwarenessSupplier awarenessSupplier,
597             int priority) {
598         synchronized (mLock) {
599             mSupplierBinders.put(componentName, awarenessSupplier);
600             mDriverAwarenessSupplierPriorities.put(awarenessSupplier, priority);
601             mPrioritizedDriverAwarenessSuppliers.add(awarenessSupplier);
602             mPrioritizedDriverAwarenessSuppliers.sort(mPrioritizedSuppliersComparator);
603         }
604     }
605 
606     /**
607      * Remove references to a supplier.
608      */
removeDriverAwarenessSupplier(ComponentName componentName)609     private void removeDriverAwarenessSupplier(ComponentName componentName) {
610         synchronized (mLock) {
611             IDriverAwarenessSupplier supplier = mSupplierBinders.get(componentName);
612             mSupplierBinders.remove(componentName);
613             mDriverAwarenessSupplierPriorities.remove(supplier);
614             mPrioritizedDriverAwarenessSuppliers.remove(supplier);
615         }
616     }
617 
618     /**
619      * Update {@link #mCurrentDriverAwareness} based on the current driver awareness events for each
620      * supplier.
621      */
622     @GuardedBy("mLock")
updateCurrentAwarenessValueLocked()623     private void updateCurrentAwarenessValueLocked() {
624         for (IDriverAwarenessSupplier supplier : mPrioritizedDriverAwarenessSuppliers) {
625             long supplierMaxStaleness = mSupplierConfigs.get(supplier).getMaxStalenessMillis();
626             DriverAwarenessEventWrapper eventForSupplier = mCurrentAwarenessEventsMap.get(supplier);
627             if (eventForSupplier == null) {
628                 continue;
629             }
630             if (supplierMaxStaleness == DriverAwarenessSupplierService.NO_STALENESS) {
631                 // this supplier can't be stale, so use its information
632                 mCurrentDriverAwareness = eventForSupplier;
633                 return;
634             }
635 
636             long oldestFreshTimestamp = mTimeSource.elapsedRealtime() - supplierMaxStaleness;
637             if (eventForSupplier.mAwarenessEvent.getTimeStamp() > oldestFreshTimestamp) {
638                 // value is still fresh, so use it
639                 mCurrentDriverAwareness = eventForSupplier;
640                 return;
641             }
642         }
643 
644         if (mCurrentDriverAwareness == null) {
645             // There must always at least be a fallback supplier with NO_STALENESS configuration.
646             // Since we control this configuration, getting this exception represents a developer
647             // error in initialization.
648             throw new IllegalStateException(
649                     "Unable to determine the current driver awareness value");
650         }
651     }
652 
653     /**
654      * Sets a timer to update the refresh the awareness value once the current value has become
655      * stale.
656      */
657     @GuardedBy("mLock")
scheduleExpirationTimerLocked()658     private void scheduleExpirationTimerLocked() {
659         // reschedule the current awareness expiration task
660         mExpiredDriverAwarenessTimer.reset();
661         long delay = mCurrentDriverAwareness.mAwarenessEvent.getTimeStamp()
662                 - mTimeSource.elapsedRealtime()
663                 + mCurrentDriverAwareness.mMaxStaleness;
664         if (delay < 0) {
665             // somehow the event is already stale
666             synchronized (mLock) {
667                 updateCurrentAwarenessValueLocked();
668             }
669             return;
670         }
671         mExpiredDriverAwarenessTimer.schedule(new TimerTask() {
672             @Override
673             public void run() {
674                 logd("Driver awareness has become stale. Selecting new awareness level.");
675                 synchronized (mLock) {
676                     updateCurrentAwarenessValueLocked();
677                     updateCurrentDistractionEventLocked();
678                 }
679             }
680         }, delay);
681 
682         logd(String.format(
683                 "Current awareness value is stale after %sms and is scheduled to expire in %sms",
684                 mCurrentDriverAwareness.mMaxStaleness, delay));
685     }
686 
687     /**
688      * Add the state change to the transition log.
689      *
690      * @param oldValue the old value
691      * @param newValue the new value
692      * @param extra    name of the value being changed
693      */
694     @GuardedBy("mLock")
addTransitionLogLocked(float oldValue, float newValue, String extra)695     private void addTransitionLogLocked(float oldValue, float newValue, String extra) {
696         if (mTransitionLogs.size() >= MAX_EVENT_LOG_COUNT) {
697             mTransitionLogs.remove();
698         }
699 
700         TransitionLog tLog = new TransitionLog(TAG, oldValue, newValue,
701                 System.currentTimeMillis(), extra);
702         mTransitionLogs.add(tLog);
703     }
704 
logd(String message)705     private static void logd(String message) {
706         if (Log.isLoggable(TAG, Log.DEBUG)) {
707             Log.d(TAG, message);
708         }
709     }
710 
711     @Override
getLastDistractionEvent()712     public DriverDistractionChangeEvent getLastDistractionEvent() throws RemoteException {
713         IExperimentalCarImpl.assertPermission(mContext,
714                 ExperimentalCar.PERMISSION_READ_CAR_DRIVER_DISTRACTION);
715         synchronized (mLock) {
716             return mCurrentDistractionEvent;
717         }
718     }
719 
720     @Override
addDriverDistractionChangeListener(IDriverDistractionChangeListener listener)721     public void addDriverDistractionChangeListener(IDriverDistractionChangeListener listener)
722             throws RemoteException {
723         IExperimentalCarImpl.assertPermission(mContext,
724                 ExperimentalCar.PERMISSION_READ_CAR_DRIVER_DISTRACTION);
725         if (listener == null) {
726             throw new IllegalArgumentException("IDriverDistractionChangeListener is null");
727         }
728         mDistractionClients.register(listener);
729 
730         DriverDistractionChangeEvent changeEvent = mCurrentDistractionEvent;
731         mClientDispatchHandler.post(() -> {
732             try {
733                 listener.onDriverDistractionChange(changeEvent);
734             } catch (RemoteException ignores) {
735                 // ignore
736             }
737         });
738     }
739 
740 
741     @Override
removeDriverDistractionChangeListener(IDriverDistractionChangeListener listener)742     public void removeDriverDistractionChangeListener(IDriverDistractionChangeListener listener)
743             throws RemoteException {
744         IExperimentalCarImpl.assertPermission(mContext,
745                 ExperimentalCar.PERMISSION_READ_CAR_DRIVER_DISTRACTION);
746         if (listener == null) {
747             Log.e(TAG, "unregisterUxRestrictionsChangeListener(): listener null");
748             throw new IllegalArgumentException("Listener is null");
749         }
750         mDistractionClients.unregister(listener);
751     }
752 
753     /**
754      * The service connection between this distraction service and a {@link
755      * DriverAwarenessSupplierService}, communicated through {@link IDriverAwarenessSupplier}.
756      */
757     private class DriverAwarenessServiceConnection implements ServiceConnection {
758 
759         final int mPriority;
760 
761         /**
762          * Create an instance of {@link DriverAwarenessServiceConnection}.
763          *
764          * @param priority the priority of the {@link DriverAwarenessSupplierService} that this
765          *                 connection is for
766          */
DriverAwarenessServiceConnection(int priority)767         DriverAwarenessServiceConnection(int priority) {
768             mPriority = priority;
769         }
770 
771         @Override
onServiceConnected(ComponentName name, IBinder binder)772         public void onServiceConnected(ComponentName name, IBinder binder) {
773             logd("onServiceConnected, name: " + name + ", binder: " + binder);
774             IDriverAwarenessSupplier service = IDriverAwarenessSupplier.Stub.asInterface(
775                     binder);
776             addDriverAwarenessSupplier(name, service, mPriority);
777             try {
778                 service.setCallback(new DriverAwarenessSupplierCallback(name));
779                 service.onReady();
780             } catch (RemoteException e) {
781                 Log.e(TAG, "Unable to call onReady on supplier", e);
782             }
783         }
784 
785         @Override
onServiceDisconnected(ComponentName name)786         public void onServiceDisconnected(ComponentName name) {
787             logd("onServiceDisconnected, name: " + name);
788             removeDriverAwarenessSupplier(name);
789             // TODO(b/146471650) rebind to driver awareness suppliers on service disconnect
790         }
791     }
792 
793     /**
794      * Driver awareness listener that keeps a references to some attributes of the supplier.
795      */
796     private class DriverAwarenessSupplierCallback extends IDriverAwarenessSupplierCallback.Stub {
797 
798         private final ComponentName mComponentName;
799 
800         /**
801          * Construct an instance  of {@link DriverAwarenessSupplierCallback}.
802          *
803          * @param componentName the driver awareness supplier for this listener
804          */
DriverAwarenessSupplierCallback(ComponentName componentName)805         DriverAwarenessSupplierCallback(ComponentName componentName) {
806             mComponentName = componentName;
807         }
808 
809         @Override
onDriverAwarenessUpdated(DriverAwarenessEvent event)810         public void onDriverAwarenessUpdated(DriverAwarenessEvent event) {
811             IDriverAwarenessSupplier supplier;
812             long maxStaleness;
813             synchronized (mLock) {
814                 supplier = mSupplierBinders.get(mComponentName);
815                 maxStaleness = mSupplierConfigs.get(supplier).getMaxStalenessMillis();
816             }
817             if (supplier == null) {
818                 // this should never happen. Initialization process would not be correct.
819                 throw new IllegalStateException(
820                         "No supplier registered for component " + mComponentName);
821             }
822             logd(String.format("Driver awareness updated for %s: %s",
823                     supplier.getClass().getSimpleName(), event));
824             handleDriverAwarenessEvent(
825                     new DriverAwarenessEventWrapper(event, supplier, maxStaleness));
826         }
827 
828         @Override
onConfigLoaded(DriverAwarenessSupplierConfig config)829         public void onConfigLoaded(DriverAwarenessSupplierConfig config) throws RemoteException {
830             synchronized (mLock) {
831                 mSupplierConfigs.put(mSupplierBinders.get(mComponentName), config);
832             }
833         }
834     }
835 
836     /**
837      * Wrapper for {@link DriverAwarenessEvent} that includes some information from the supplier
838      * that emitted the event.
839      */
840     @VisibleForTesting
841     static class DriverAwarenessEventWrapper {
842         final DriverAwarenessEvent mAwarenessEvent;
843         final IDriverAwarenessSupplier mSupplier;
844         final long mMaxStaleness;
845 
846         /**
847          * Construct an instance of {@link DriverAwarenessEventWrapper}.
848          *
849          * @param awarenessEvent the driver awareness event being wrapped
850          * @param supplier       the driver awareness supplier for this listener
851          * @param maxStaleness   the max staleness of the supplier that emitted this event (included
852          *                       to avoid making a binder call)
853          */
DriverAwarenessEventWrapper( DriverAwarenessEvent awarenessEvent, IDriverAwarenessSupplier supplier, long maxStaleness)854         DriverAwarenessEventWrapper(
855                 DriverAwarenessEvent awarenessEvent,
856                 IDriverAwarenessSupplier supplier,
857                 long maxStaleness) {
858             mAwarenessEvent = awarenessEvent;
859             mSupplier = supplier;
860             mMaxStaleness = maxStaleness;
861         }
862 
863         @Override
toString()864         public String toString() {
865             return String.format(
866                     "DriverAwarenessEventWrapper{mAwarenessChangeEvent=%s, mSupplier=%s, "
867                             + "mMaxStaleness=%s}",
868                     mAwarenessEvent, mSupplier, mMaxStaleness);
869         }
870     }
871 }
872