aboutsummaryrefslogtreecommitdiff
path: root/experimental/service/src/com/android/experimentalcar/GazeAttentionProcessor.java
blob: 66e3a481a36f85b3ee88587c822d6435196bc3ae (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
/*
 * 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.FloatRange;
import android.annotation.NonNull;
import android.car.occupantawareness.GazeDetection;
import android.util.Log;

/** {@link GazeAttentionProcessor} estimates driver attention based on a gaze history. */
class GazeAttentionProcessor {
    private static final String TAG = "Car.OAS.GazeAttentionProcessor";
    private static final int NOT_SET = -1;

    private final Configuration mConfig;

    /** Current attention value. */
    @FloatRange(from = 0.0f, to = 1.0f)
    private float mAttention;

    /** Timestamp of last frame received, in milliseconds since boot. */
    private long mLastTimestamp = NOT_SET;

    GazeAttentionProcessor(Configuration configuration) {
        mConfig = configuration;
        mAttention = mConfig.initialValue;
    }

    /** Gets the current attention awareness value. */
    @FloatRange(from = 0.0f, to = 1.0f)
    public float getAttention() {
        return mAttention;
    }

    /**
     * Updates the current attention with a new gaze detection.
     *
     * @param detection New {@link android.car.occupantawareness.GazeDetection} data.
     * @param timestamp Timestamp that the detection was detected, in milliseconds since boot.
     * @return Attention value [0, 1].
     */
    @FloatRange(from = 0.0f, to = 1.0f)
    public float updateAttention(@NonNull GazeDetection detection, long timestamp) {
        // When the first frame of data is received, no duration can be computed. Just capture the
        // timestamp for the next pass and return the initial buffer value.
        if (mLastTimestamp == NOT_SET) {
            logd("First pass, returning 'initialValue=" + mConfig.initialValue);
            mLastTimestamp = timestamp;
            return mConfig.initialValue;
        }

        float startingAttention = mAttention;

        // Compute the time since the last detection, in seconds.
        float dtSeconds = (float) (timestamp - mLastTimestamp) / 1000.0f;

        if (isOnRoadGaze(detection.gazeTarget)) {
            logd("Handling on-road gaze");
            mAttention += dtSeconds * mConfig.growthRate;
            mAttention = Math.min(mAttention, 1.0f); // Cap value to max of 1.
        } else {
            logd("Handling off-road gaze");
            mAttention -= dtSeconds * mConfig.decayRate;
            mAttention = Math.max(mAttention, 0.0f); // Cap value to min of 0.
        }

        // Save current timestamp for next pass.
        mLastTimestamp = timestamp;

        logd(String.format("updateAttention(): Time=%1.2f. Attention [%f]->[%f]. ",
                dtSeconds, startingAttention, mAttention));

        return mAttention;
    }

    /** Gets whether the gaze target is on-road. */
    private boolean isOnRoadGaze(@GazeDetection.VehicleRegion int gazeTarget) {
        switch (gazeTarget) {
            case GazeDetection.VEHICLE_REGION_FORWARD_ROADWAY:
            case GazeDetection.VEHICLE_REGION_LEFT_ROADWAY:
            case GazeDetection.VEHICLE_REGION_RIGHT_ROADWAY:
                return true;
            default:
                return false;
        }
    }

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

    /** Configuration settings for {@link GazeAttentionProcessor}. */
    public static class Configuration {
        /** Initial value for the attention buffer, [0, 1]. */
        @FloatRange(from = 0.0f, to = 1.0f)
        public final float initialValue;

        /**
         * Rate at which the attention decays when the driver is looking off-road (per second of
         * off-road looks)
         */
        public final float decayRate;

        /**
         * Rate at which the attention grows when the driver is looking on-road (per second of
         * on-road looks).
         */
        public final float growthRate;

        /**
         * Creates an instance of {@link Configuration}.
         *
         * @param initialValue the initial value that the attention buffer starts at.
         * @param decayRate the rate at which the attention buffer decreases when the driver is
         *     looking off-road.
         * @param growthRate the rate at which the attention buffer increases when the driver is
         *     looking on-road.
         */
        Configuration(float initialValue, float decayRate, float growthRate) {
            this.initialValue = initialValue;
            this.decayRate = decayRate;
            this.growthRate = growthRate;
        }

        @Override
        public String toString() {
            return String.format(
                    "Configuration{initialValue=%s, decayRate=%s, growthRate=%s}",
                    initialValue, decayRate, growthRate);
        }
    }
}