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