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