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 android.car.experimental.DriverAwarenessEvent;
20 import android.car.experimental.DriverAwarenessSupplierConfig;
21 import android.car.experimental.DriverAwarenessSupplierService;
22 import android.car.experimental.IDriverAwarenessSupplier;
23 import android.car.experimental.IDriverAwarenessSupplierCallback;
24 import android.content.Context;
25 import android.hardware.input.InputManager;
26 import android.os.Looper;
27 import android.os.RemoteException;
28 import android.os.SystemClock;
29 import android.util.Log;
30 import android.view.Display;
31 import android.view.InputChannel;
32 import android.view.InputEvent;
33 import android.view.InputEventReceiver;
34 import android.view.InputMonitor;
35 import android.view.MotionEvent;
36 
37 import com.android.internal.annotations.GuardedBy;
38 import com.android.internal.annotations.VisibleForTesting;
39 
40 import java.util.concurrent.Executors;
41 import java.util.concurrent.ScheduledExecutorService;
42 import java.util.concurrent.ScheduledFuture;
43 import java.util.concurrent.TimeUnit;
44 import java.util.concurrent.atomic.AtomicInteger;
45 
46 
47 /**
48  * A driver awareness supplier that estimates the driver's current awareness level based on touches
49  * on the headunit.
50  */
51 public class TouchDriverAwarenessSupplier extends IDriverAwarenessSupplier.Stub {
52 
53     private static final String TAG = "Car.TouchAwarenessSupplier";
54     private static final String TOUCH_INPUT_CHANNEL_NAME = "TouchDriverAwarenessInputChannel";
55 
56     private static final long MAX_STALENESS = DriverAwarenessSupplierService.NO_STALENESS;
57 
58     @VisibleForTesting
59     static final float INITIAL_DRIVER_AWARENESS_VALUE = 1.0f;
60 
61     private final AtomicInteger mCurrentPermits = new AtomicInteger();
62     private final ScheduledExecutorService mRefreshScheduler;
63     private final Looper mLooper;
64     private final Context mContext;
65     private final ITimeSource mTimeSource;
66     private final Runnable mRefreshPermitRunnable;
67     private final IDriverAwarenessSupplierCallback mDriverAwarenessSupplierCallback;
68 
69     private final Object mLock = new Object();
70 
71     @GuardedBy("mLock")
72     private long mLastEventMillis;
73 
74     @GuardedBy("mLock")
75     private ScheduledFuture<?> mRefreshScheduleHandle;
76 
77     @GuardedBy("mLock")
78     private Config mConfig;
79 
80     // Main thread only. Hold onto reference to avoid garbage collection
81     private InputMonitor mInputMonitor;
82 
83     // Main thread only. Hold onto reference to avoid garbage collection
84     private InputEventReceiver mInputEventReceiver;
85 
TouchDriverAwarenessSupplier(Context context, IDriverAwarenessSupplierCallback driverAwarenessSupplierCallback, Looper looper)86     TouchDriverAwarenessSupplier(Context context,
87             IDriverAwarenessSupplierCallback driverAwarenessSupplierCallback, Looper looper) {
88         this(context, driverAwarenessSupplierCallback, Executors.newScheduledThreadPool(1),
89                 looper, new SystemTimeSource());
90     }
91 
92     @VisibleForTesting
TouchDriverAwarenessSupplier( Context context, IDriverAwarenessSupplierCallback driverAwarenessSupplierCallback, ScheduledExecutorService refreshScheduler, Looper looper, ITimeSource timeSource)93     TouchDriverAwarenessSupplier(
94             Context context,
95             IDriverAwarenessSupplierCallback driverAwarenessSupplierCallback,
96             ScheduledExecutorService refreshScheduler,
97             Looper looper,
98             ITimeSource timeSource) {
99         mContext = context;
100         mDriverAwarenessSupplierCallback = driverAwarenessSupplierCallback;
101         mRefreshScheduler = refreshScheduler;
102         mLooper = looper;
103         mTimeSource = timeSource;
104         mRefreshPermitRunnable =
105                 () -> {
106                     synchronized (mLock) {
107                         handlePermitRefreshLocked(mTimeSource.elapsedRealtime());
108                     }
109                 };
110     }
111 
112 
113     @Override
onReady()114     public void onReady() {
115         try {
116             mDriverAwarenessSupplierCallback.onConfigLoaded(
117                     new DriverAwarenessSupplierConfig(MAX_STALENESS));
118         } catch (RemoteException e) {
119             Log.e(TAG, "Unable to send config - abandoning ready process", e);
120             return;
121         }
122         // send an initial event, as required by the IDriverAwarenessSupplierCallback spec
123         try {
124             mDriverAwarenessSupplierCallback.onDriverAwarenessUpdated(
125                     new DriverAwarenessEvent(mTimeSource.elapsedRealtime(),
126                             INITIAL_DRIVER_AWARENESS_VALUE));
127         } catch (RemoteException e) {
128             Log.e(TAG, "Unable to emit initial awareness event", e);
129         }
130         synchronized (mLock) {
131             mConfig = loadConfig();
132             logd("Config loaded: " + mConfig);
133             mCurrentPermits.set(mConfig.getMaxPermits());
134         }
135         startTouchMonitoring();
136     }
137 
138     @Override
setCallback(IDriverAwarenessSupplierCallback callback)139     public void setCallback(IDriverAwarenessSupplierCallback callback) {
140         // no-op - the callback is initialized in the constructor
141     }
142 
loadConfig()143     private Config loadConfig() {
144         int maxPermits = mContext.getResources().getInteger(
145                 R.integer.driverAwarenessTouchModelMaxPermits);
146         if (maxPermits <= 0) {
147             throw new IllegalArgumentException("driverAwarenessTouchModelMaxPermits must be >0");
148         }
149         int refreshIntervalMillis = mContext.getResources().getInteger(
150                 R.integer.driverAwarenessTouchModelPermitRefreshIntervalMs);
151         if (refreshIntervalMillis <= 0) {
152             throw new IllegalArgumentException(
153                     "driverAwarenessTouchModelPermitRefreshIntervalMs must be >0");
154         }
155         int throttleDurationMillis = mContext.getResources().getInteger(
156                 R.integer.driverAwarenessTouchModelThrottleMs);
157         if (throttleDurationMillis <= 0) {
158             throw new IllegalArgumentException("driverAwarenessTouchModelThrottleMs must be >0");
159         }
160         return new Config(maxPermits, refreshIntervalMillis, throttleDurationMillis);
161     }
162 
163     /**
164      * Starts monitoring touches.
165      */
166     @VisibleForTesting
167     // TODO(b/146802952) handle touch monitoring on multiple displays
startTouchMonitoring()168     void startTouchMonitoring() {
169         InputManager inputManager = (InputManager) mContext.getSystemService(Context.INPUT_SERVICE);
170         mInputMonitor = inputManager.monitorGestureInput(
171                 TOUCH_INPUT_CHANNEL_NAME,
172                 Display.DEFAULT_DISPLAY);
173         mInputEventReceiver = new TouchReceiver(
174                 mInputMonitor.getInputChannel(),
175                 mLooper);
176     }
177 
178     /**
179      * Refreshes permits on the interval specified by {@code R.integer
180      * .driverAwarenessTouchModelPermitRefreshIntervalMs}.
181      */
182     @GuardedBy("mLock")
schedulePermitRefreshLocked()183     private void schedulePermitRefreshLocked() {
184         logd("Scheduling permit refresh interval (ms): "
185                 + mConfig.getPermitRefreshIntervalMillis());
186         mRefreshScheduleHandle = mRefreshScheduler.scheduleAtFixedRate(
187                 mRefreshPermitRunnable,
188                 mConfig.getPermitRefreshIntervalMillis(),
189                 mConfig.getPermitRefreshIntervalMillis(),
190                 TimeUnit.MILLISECONDS);
191     }
192 
193     /**
194      * Stops the scheduler for refreshing the number of permits.
195      */
196     @GuardedBy("mLock")
stopPermitRefreshLocked()197     private void stopPermitRefreshLocked() {
198         logd("Stopping permit refresh");
199         if (mRefreshScheduleHandle != null) {
200             mRefreshScheduleHandle.cancel(true);
201             mRefreshScheduleHandle = null;
202         }
203     }
204 
205     /**
206      * Consume a single permit if the event should not be throttled.
207      */
208     @VisibleForTesting
209     @GuardedBy("mLock")
consumePermitLocked(long timestamp)210     void consumePermitLocked(long timestamp) {
211         long timeSinceLastEvent = timestamp - mLastEventMillis;
212         boolean isEventAccepted = timeSinceLastEvent >= mConfig.getThrottleDurationMillis();
213         if (!isEventAccepted) {
214             logd("Ignoring consumePermit request: event throttled");
215             return;
216         }
217         mLastEventMillis = timestamp;
218         int curPermits = mCurrentPermits.updateAndGet(cur -> Math.max(0, cur - 1));
219         logd("Permit consumed to: " + curPermits);
220 
221         if (mRefreshScheduleHandle == null) {
222             schedulePermitRefreshLocked();
223         }
224 
225         try {
226             mDriverAwarenessSupplierCallback.onDriverAwarenessUpdated(
227                     new DriverAwarenessEvent(timestamp,
228                             (float) curPermits / mConfig.getMaxPermits()));
229         } catch (RemoteException e) {
230             Log.e(TAG, "Unable to emit awareness event", e);
231         }
232     }
233 
234     @VisibleForTesting
235     @GuardedBy("mLock")
handlePermitRefreshLocked(long timestamp)236     void handlePermitRefreshLocked(long timestamp) {
237         int curPermits = mCurrentPermits.updateAndGet(
238                 cur -> Math.min(cur + 1, mConfig.getMaxPermits()));
239         logd("Permit refreshed to: " + curPermits);
240         if (curPermits == mConfig.getMaxPermits()) {
241             stopPermitRefreshLocked();
242         }
243         try {
244             mDriverAwarenessSupplierCallback.onDriverAwarenessUpdated(
245                     new DriverAwarenessEvent(timestamp,
246                             (float) curPermits / mConfig.getMaxPermits()));
247         } catch (RemoteException e) {
248             Log.e(TAG, "Unable to emit awareness event", e);
249         }
250     }
251 
logd(String message)252     private static void logd(String message) {
253         if (Log.isLoggable(TAG, Log.DEBUG)) {
254             Log.d(TAG, message);
255         }
256     }
257 
258     /**
259      * Receiver of all touch events. This receiver filters out all events except {@link
260      * MotionEvent#ACTION_UP} events.
261      */
262     private class TouchReceiver extends InputEventReceiver {
263 
264         /**
265          * Creates an input event receiver bound to the specified input channel.
266          *
267          * @param inputChannel The input channel.
268          * @param looper       The looper to use when invoking callbacks.
269          */
TouchReceiver(InputChannel inputChannel, Looper looper)270         TouchReceiver(InputChannel inputChannel, Looper looper) {
271             super(inputChannel, looper);
272         }
273 
274         @Override
onInputEvent(InputEvent event)275         public void onInputEvent(InputEvent event) {
276             boolean handled = false;
277             try {
278                 if (!(event instanceof MotionEvent)) {
279                     return;
280                 }
281 
282                 MotionEvent motionEvent = (MotionEvent) event;
283                 if (motionEvent.getActionMasked() == MotionEvent.ACTION_UP) {
284                     logd("ACTION_UP touch received");
285                     synchronized (mLock) {
286                         consumePermitLocked(SystemClock.elapsedRealtime());
287                     }
288                     handled = true;
289                 }
290             } finally {
291                 finishInputEvent(event, handled);
292             }
293         }
294     }
295 
296     /**
297      * Configuration for a {@link TouchDriverAwarenessSupplier}.
298      */
299     private static class Config {
300 
301         private final int mMaxPermits;
302         private final int mPermitRefreshIntervalMillis;
303         private final int mThrottleDurationMillis;
304 
305         /**
306          * Creates an instance of {@link Config}.
307          *
308          * @param maxPermits                  the maximum number of permits in the user's
309          *                                    attention buffer. A user's number of permits will
310          *                                    never refresh to a value higher than this.
311          * @param permitRefreshIntervalMillis the refresh interval in milliseconds for refreshing
312          *                                    permits
313          * @param throttleDurationMillis      the duration in milliseconds representing the window
314          *                                    that permit consumption is ignored after an event.
315          */
Config( int maxPermits, int permitRefreshIntervalMillis, int throttleDurationMillis)316         private Config(
317                 int maxPermits,
318                 int permitRefreshIntervalMillis,
319                 int throttleDurationMillis) {
320             mMaxPermits = maxPermits;
321             mPermitRefreshIntervalMillis = permitRefreshIntervalMillis;
322             mThrottleDurationMillis = throttleDurationMillis;
323         }
324 
getMaxPermits()325         int getMaxPermits() {
326             return mMaxPermits;
327         }
328 
getPermitRefreshIntervalMillis()329         int getPermitRefreshIntervalMillis() {
330             return mPermitRefreshIntervalMillis;
331         }
332 
getThrottleDurationMillis()333         int getThrottleDurationMillis() {
334             return mThrottleDurationMillis;
335         }
336 
337         @Override
toString()338         public String toString() {
339             return String.format(
340                     "Config{mMaxPermits=%s, mPermitRefreshIntervalMillis=%s, "
341                             + "mThrottleDurationMillis=%s}",
342                     mMaxPermits,
343                     mPermitRefreshIntervalMillis,
344                     mThrottleDurationMillis);
345         }
346     }
347 }
348