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