1 /*
2  * Copyright (C) 2020 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 android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.car.Car;
22 import android.car.experimental.DriverAwarenessEvent;
23 import android.car.experimental.DriverAwarenessSupplierService;
24 import android.car.occupantawareness.OccupantAwarenessDetection;
25 import android.car.occupantawareness.OccupantAwarenessManager;
26 import android.car.occupantawareness.SystemStatusEvent;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.res.Resources;
30 import android.os.IBinder;
31 import android.util.Log;
32 
33 import com.android.internal.annotations.GuardedBy;
34 import com.android.internal.annotations.VisibleForTesting;
35 
36 /**
37  * A Driver Awareness Supplier that estimates the driver's current level of awareness based on gaze
38  * history.
39  *
40  * <p>Attention is facilitated via {@link OccupantAwarenessManager}, which is an optional component
41  * and may not be available on every platform.
42  */
43 public class GazeDriverAwarenessSupplier extends DriverAwarenessSupplierService {
44     private static final String TAG = "Car.OAS.GazeAwarenessSupplier";
45 
46     /* Maximum allowable staleness before gaze data should be considered unreliable for attention
47      * monitoring, in milliseconds. */
48     @VisibleForTesting
49     static final long MAX_STALENESS_MILLIS = 500;
50 
51     private final Object mLock = new Object();
52     private final ITimeSource mTimeSource;
53 
54     @GuardedBy("mLock")
55     private GazeAttentionProcessor.Configuration mConfiguration;
56 
57     @Nullable
58     private Context mContext;
59 
60     @GuardedBy("mLock")
61     private Car mCar;
62 
63     @GuardedBy("mLock")
64     private OccupantAwarenessManager mOasManager;
65 
66     @GuardedBy("mLock")
67     private GazeAttentionProcessor mProcessor;
68 
69     /**
70      * Empty constructor allows system service creation.
71      */
GazeDriverAwarenessSupplier()72     public GazeDriverAwarenessSupplier() {
73         this(/* context= */ null, new SystemTimeSource());
74     }
75 
76     @VisibleForTesting
GazeDriverAwarenessSupplier(Context context, ITimeSource timeSource)77     GazeDriverAwarenessSupplier(Context context, ITimeSource timeSource) {
78         mContext = context;
79         mTimeSource = timeSource;
80     }
81 
82     @Override
onCreate()83     public void onCreate() {
84         super.onCreate();
85         if (mContext == null) {
86             mContext = this;
87         }
88     }
89 
90     @Override
onDestroy()91     public void onDestroy() {
92         synchronized (mLock) {
93             if (mCar != null && mCar.isConnected()) {
94                 mCar.disconnect();
95             }
96         }
97         super.onDestroy();
98     }
99 
100     /**
101      * Gets the self-reported maximum allowable staleness before the supplier should be considered
102      * failed, in milliseconds.
103      */
104     @Override
getMaxStalenessMillis()105     public long getMaxStalenessMillis() {
106         return MAX_STALENESS_MILLIS;
107     }
108 
109     @Override
onBind(Intent intent)110     public IBinder onBind(Intent intent) {
111         logd("onBind with intent: " + intent);
112         return super.onBind(intent);
113     }
114 
115     @Override
onReady()116     public void onReady() {
117         synchronized (mLock) {
118             mConfiguration = loadConfiguration();
119             mProcessor = new GazeAttentionProcessor(mConfiguration);
120             mCar = Car.createCar(mContext);
121             if (mCar != null) {
122                 if (mOasManager == null && mCar.isFeatureEnabled(Car.OCCUPANT_AWARENESS_SERVICE)) {
123                     mOasManager =
124                             (OccupantAwarenessManager)
125                                     mCar.getCarManager(Car.OCCUPANT_AWARENESS_SERVICE);
126 
127                     if (mOasManager == null) {
128                         Log.e(TAG, "Failed to get OccupantAwarenessManager.");
129                     }
130                 }
131 
132                 if (mOasManager != null) {
133                     logd("Registering callback with OAS manager");
134                     mOasManager.registerChangeCallback(new ChangeCallback());
135                 }
136             }
137         }
138 
139         // Send an initial value once the provider is ready, as required by {link
140         // IDriverAwarenessSupplierCallback}.
141         emitAwarenessEvent(
142                 new DriverAwarenessEvent(
143                         mTimeSource.elapsedRealtime(), mConfiguration.initialValue));
144     }
145 
146     /**
147      * Returns whether this car supports gaze based awareness.
148      *
149      * <p>The underlying Occupant Awareness System is optional and may not be supported on every
150      * vehicle. This method must be called *after* onReady().
151      */
supportsGaze()152     public boolean supportsGaze() throws IllegalStateException {
153         synchronized (mLock) {
154             if (mCar == null) {
155                 throw new IllegalStateException(
156                         "Must call onReady() before querying for gaze support");
157             }
158 
159             // Occupant Awareness is optional and may not be available on this machine. If not
160             // available, the returned manager will be null.
161             if (mOasManager == null) {
162                 logd("Occupant Awareness System is not available on this machine");
163                 return false;
164             }
165 
166             int driver_capability =
167                     mOasManager.getCapabilityForRole(
168                             OccupantAwarenessDetection.VEHICLE_OCCUPANT_DRIVER);
169 
170             logd("Driver capabilities flags: " + driver_capability);
171 
172             return (driver_capability & SystemStatusEvent.DETECTION_TYPE_DRIVER_MONITORING) > 0;
173         }
174     }
175 
176     /** Builds {@link GazeAttentionProcessor.Configuration} using context resources. */
loadConfiguration()177     private GazeAttentionProcessor.Configuration loadConfiguration() {
178         Resources resources = mContext.getResources();
179 
180         return new GazeAttentionProcessor.Configuration(
181                 resources.getFloat(R.fraction.driverAwarenessGazeModelInitialValue),
182                 resources.getFloat(R.fraction.driverAwarenessGazeModelDecayRate),
183                 resources.getFloat(R.fraction.driverAwarenessGazeModelGrowthRate));
184     }
185 
186     /** Processes a new detection event, updating the current attention value. */
187     @VisibleForTesting
processDetectionEvent(@onNull OccupantAwarenessDetection event)188     void processDetectionEvent(@NonNull OccupantAwarenessDetection event) {
189         if (event.role == OccupantAwarenessDetection.VEHICLE_OCCUPANT_DRIVER
190                 && event.gazeDetection != null) {
191             float awarenessValue;
192             synchronized (mLock) {
193                 awarenessValue =
194                         mProcessor.updateAttention(event.gazeDetection, event.timestampMillis);
195             }
196 
197             emitAwarenessEvent(new DriverAwarenessEvent(event.timestampMillis, awarenessValue));
198         }
199     }
200 
201     @VisibleForTesting
emitAwarenessEvent(@onNull DriverAwarenessEvent event)202     void emitAwarenessEvent(@NonNull DriverAwarenessEvent event) {
203         logd("Emitting new event: " + event);
204         onDriverAwarenessUpdated(event);
205     }
206 
logd(String message)207     private static void logd(String message) {
208         if (Log.isLoggable(TAG, Log.DEBUG)) {
209             Log.d(TAG, message);
210         }
211     }
212 
213     /** Callback when the system status changes or a new detection is made. */
214     private class ChangeCallback extends OccupantAwarenessManager.ChangeCallback {
215         /** Called when the system state changes changes. */
216         @Override
onSystemStateChanged(@onNull SystemStatusEvent status)217         public void onSystemStateChanged(@NonNull SystemStatusEvent status) {
218             logd("New status: " + status);
219         }
220 
221         /** Called when a detection event is generated. */
222         @Override
onDetectionEvent(@onNull OccupantAwarenessDetection event)223         public void onDetectionEvent(@NonNull OccupantAwarenessDetection event) {
224             logd("New detection: " + event);
225             processDetectionEvent(event);
226         }
227     }
228 }
229