aboutsummaryrefslogtreecommitdiff
path: root/experimental/service/src/com/android/experimentalcar/GazeDriverAwarenessSupplier.java
blob: 7fbacbf5de5beb1f17248bb541384130f66ff724 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.experimentalcar;

import android.annotation.NonNull;
import android.car.Car;
import android.car.experimental.DriverAwarenessEvent;
import android.car.experimental.DriverAwarenessSupplierService;
import android.car.occupantawareness.OccupantAwarenessDetection;
import android.car.occupantawareness.OccupantAwarenessManager;
import android.car.occupantawareness.SystemStatusEvent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.IBinder;
import android.util.Log;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;

/**
 * A Driver Awareness Supplier that estimates the driver's current level of awareness based on gaze
 * history.
 *
 * <p>Attention is facilitated via {@link OccupantAwarenessManager}, which is an optional component
 * and may not be available on every platform.
 */
public class GazeDriverAwarenessSupplier extends DriverAwarenessSupplierService {
    private static final String TAG = "Car.OAS.GazeAwarenessSupplier";

    /* Maximum allowable staleness before gaze data should be considered unreliable for attention
     * monitoring, in milliseconds. */
    private static final long MAX_STALENESS_MILLIS = 500;

    private final Context mContext;
    private final Object mLock = new Object();
    private final ITimeSource mTimeSource;
    private final GazeAttentionProcessor.Configuration mConfiguration;

    @GuardedBy("mLock")
    private Car mCar;

    @GuardedBy("mLock")
    private OccupantAwarenessManager mOasManager;

    @GuardedBy("mLock")
    private final GazeAttentionProcessor mProcessor;

    public GazeDriverAwarenessSupplier(Context context) {
        this(context, new SystemTimeSource());
    }

    @VisibleForTesting
    GazeDriverAwarenessSupplier(Context context, ITimeSource timeSource) {
        mContext = context;
        mTimeSource = timeSource;
        mConfiguration = loadConfiguration();
        mProcessor = new GazeAttentionProcessor(mConfiguration);
    }

    /**
     * Gets the self-reported maximum allowable staleness before the supplier should be considered
     * failed, in milliseconds.
     */
    @Override
    public long getMaxStalenessMillis() {
        return MAX_STALENESS_MILLIS;
    }

    @Override
    public IBinder onBind(Intent intent) {
        logd("onBind with intent: " + intent);
        return super.onBind(intent);
    }

    @Override
    public void onReady() {
        synchronized (mLock) {
            mCar = Car.createCar(mContext);

            if (mCar != null) {
                if (mOasManager == null && mCar.isFeatureEnabled(Car.OCCUPANT_AWARENESS_SERVICE)) {
                    mOasManager =
                            (OccupantAwarenessManager)
                                    mCar.getCarManager(Car.OCCUPANT_AWARENESS_SERVICE);

                    if (mOasManager == null) {
                        Log.e(TAG, "Failed to get OccupantAwarenessManager.");
                    }
                }

                if (mOasManager != null) {
                    logd("Registering callback with OAS manager");
                    mOasManager.registerChangeCallback(new ChangeCallback());
                }
            }
        }

        // Send an initial value once the provider is ready, as required by {link
        // IDriverAwarenessSupplierCallback}.
        emitAwarenessEvent(
                new DriverAwarenessEvent(
                        mTimeSource.elapsedRealtime(), mConfiguration.initialValue));
    }

    /**
     * Returns whether this car supports gaze based awareness.
     *
     * <p>The underlying Occupant Awareness System is optional and may not be supported on every
     * vehicle. This method must be called *after* onReady().
     */
    public boolean supportsGaze() throws IllegalStateException {
        synchronized (mLock) {
            if (mCar == null) {
                throw new IllegalStateException(
                        "Must call onReady() before querying for gaze support");
            }

            // Occupant Awareness is optional and may not be available on this machine. If not
            // available, the returned manager will be null.
            if (mOasManager == null) {
                logd("Occupant Awareness System is not available on this machine");
                return false;
            }

            int driver_capability =
                    mOasManager.getCapabilityForRole(
                            OccupantAwarenessDetection.VEHICLE_OCCUPANT_DRIVER);

            logd("Driver capabilities flags: " + driver_capability);

            return (driver_capability & SystemStatusEvent.DETECTION_TYPE_DRIVER_MONITORING) > 0;
        }
    }

    /** Builds {@link GazeAttentionProcessor.Configuration} using context resources. */
    private GazeAttentionProcessor.Configuration loadConfiguration() {
        Resources resources = mContext.getResources();

        return new GazeAttentionProcessor.Configuration(
                resources.getFloat(R.fraction.driverAwarenessGazeModelInitialValue),
                resources.getFloat(R.fraction.driverAwarenessGazeModelDecayRate),
                resources.getFloat(R.fraction.driverAwarenessGazeModelGrowthRate));
    }

    /** Processes a new detection event, updating the current attention value. */
    @VisibleForTesting
    void processDetectionEvent(@NonNull OccupantAwarenessDetection event) {
        if (event.role == OccupantAwarenessDetection.VEHICLE_OCCUPANT_DRIVER
                && event.gazeDetection != null) {
            float awarenessValue;
            synchronized (mLock) {
                awarenessValue =
                        mProcessor.updateAttention(event.gazeDetection, event.timestampMillis);
            }

            emitAwarenessEvent(new DriverAwarenessEvent(event.timestampMillis, awarenessValue));
        }
    }

    @VisibleForTesting
    void emitAwarenessEvent(@NonNull DriverAwarenessEvent event) {
        logd("Emitting new event: " + event);
        onDriverAwarenessUpdated(event);
    }

    private static void logd(String message) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, message);
        }
    }

    /** Callback when the system status changes or a new detection is made. */
    private class ChangeCallback extends OccupantAwarenessManager.ChangeCallback {
        /** Called when the system state changes changes. */
        @Override
        public void onSystemStateChanged(@NonNull SystemStatusEvent status) {
            logd("New status: " + status);
        }

        /** Called when a detection event is generated. */
        @Override
        public void onDetectionEvent(@NonNull OccupantAwarenessDetection event) {
            logd("New detection: " + event);
            processDetectionEvent(event);
        }
    }
}