1 /* 2 * Copyright (C) 2016 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.systemui.doze; 18 19 import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_DISPLAY; 20 import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN; 21 22 import android.annotation.AnyThread; 23 import android.app.ActivityManager; 24 import android.app.AlarmManager; 25 import android.content.ContentResolver; 26 import android.content.Context; 27 import android.database.ContentObserver; 28 import android.hardware.Sensor; 29 import android.hardware.SensorManager; 30 import android.hardware.TriggerEvent; 31 import android.hardware.TriggerEventListener; 32 import android.hardware.display.AmbientDisplayConfiguration; 33 import android.net.Uri; 34 import android.os.Handler; 35 import android.os.SystemClock; 36 import android.os.UserHandle; 37 import android.provider.Settings; 38 import android.text.TextUtils; 39 import android.util.Log; 40 41 import androidx.annotation.VisibleForTesting; 42 43 import com.android.internal.logging.MetricsLogger; 44 import com.android.internal.logging.UiEvent; 45 import com.android.internal.logging.UiEventLogger; 46 import com.android.internal.logging.UiEventLoggerImpl; 47 import com.android.internal.logging.nano.MetricsProto; 48 import com.android.systemui.plugins.SensorManagerPlugin; 49 import com.android.systemui.statusbar.phone.DozeParameters; 50 import com.android.systemui.util.sensors.AsyncSensorManager; 51 import com.android.systemui.util.sensors.ProximitySensor; 52 import com.android.systemui.util.wakelock.WakeLock; 53 54 import java.io.PrintWriter; 55 import java.util.Collection; 56 import java.util.List; 57 import java.util.function.Consumer; 58 59 public class DozeSensors { 60 61 private static final boolean DEBUG = DozeService.DEBUG; 62 private static final String TAG = "DozeSensors"; 63 private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl(); 64 65 private final Context mContext; 66 private final AlarmManager mAlarmManager; 67 private final AsyncSensorManager mSensorManager; 68 private final ContentResolver mResolver; 69 private final TriggerSensor mPickupSensor; 70 private final DozeParameters mDozeParameters; 71 private final AmbientDisplayConfiguration mConfig; 72 private final WakeLock mWakeLock; 73 private final Consumer<Boolean> mProxCallback; 74 private final Callback mCallback; 75 @VisibleForTesting 76 protected TriggerSensor[] mSensors; 77 78 private final Handler mHandler = new Handler(); 79 private final ProximitySensor mProximitySensor; 80 private long mDebounceFrom; 81 private boolean mSettingRegistered; 82 private boolean mListening; 83 private boolean mPaused; 84 85 @VisibleForTesting 86 public enum DozeSensorsUiEvent implements UiEventLogger.UiEventEnum { 87 @UiEvent(doc = "User performs pickup gesture that activates the ambient display") 88 ACTION_AMBIENT_GESTURE_PICKUP(459); 89 90 private final int mId; 91 DozeSensorsUiEvent(int id)92 DozeSensorsUiEvent(int id) { 93 mId = id; 94 } 95 96 @Override getId()97 public int getId() { 98 return mId; 99 } 100 } 101 DozeSensors(Context context, AlarmManager alarmManager, AsyncSensorManager sensorManager, DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock, Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog, ProximitySensor proximitySensor)102 public DozeSensors(Context context, AlarmManager alarmManager, AsyncSensorManager sensorManager, 103 DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock, 104 Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog, 105 ProximitySensor proximitySensor) { 106 mContext = context; 107 mAlarmManager = alarmManager; 108 mSensorManager = sensorManager; 109 mDozeParameters = dozeParameters; 110 mConfig = config; 111 mWakeLock = wakeLock; 112 mProxCallback = proxCallback; 113 mResolver = mContext.getContentResolver(); 114 mCallback = callback; 115 mProximitySensor = proximitySensor; 116 117 boolean alwaysOn = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT); 118 mSensors = new TriggerSensor[] { 119 new TriggerSensor( 120 mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION), 121 null /* setting */, 122 dozeParameters.getPulseOnSigMotion(), 123 DozeLog.PULSE_REASON_SENSOR_SIGMOTION, false /* touchCoords */, 124 false /* touchscreen */, dozeLog), 125 mPickupSensor = new TriggerSensor( 126 mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE), 127 Settings.Secure.DOZE_PICK_UP_GESTURE, 128 true /* settingDef */, 129 config.dozePickupSensorAvailable(), 130 DozeLog.REASON_SENSOR_PICKUP, false /* touchCoords */, 131 false /* touchscreen */, 132 false /* ignoresSetting */, 133 dozeLog), 134 new TriggerSensor( 135 findSensorWithType(config.doubleTapSensorType()), 136 Settings.Secure.DOZE_DOUBLE_TAP_GESTURE, 137 true /* configured */, 138 DozeLog.REASON_SENSOR_DOUBLE_TAP, 139 dozeParameters.doubleTapReportsTouchCoordinates(), 140 true /* touchscreen */, 141 dozeLog), 142 new TriggerSensor( 143 findSensorWithType(config.tapSensorType()), 144 Settings.Secure.DOZE_TAP_SCREEN_GESTURE, 145 true /* configured */, 146 DozeLog.REASON_SENSOR_TAP, 147 false /* reports touch coordinates */, 148 true /* touchscreen */, 149 dozeLog), 150 new TriggerSensor( 151 findSensorWithType(config.longPressSensorType()), 152 Settings.Secure.DOZE_PULSE_ON_LONG_PRESS, 153 false /* settingDef */, 154 true /* configured */, 155 DozeLog.PULSE_REASON_SENSOR_LONG_PRESS, 156 true /* reports touch coordinates */, 157 true /* touchscreen */, 158 dozeLog), 159 new PluginSensor( 160 new SensorManagerPlugin.Sensor(TYPE_WAKE_DISPLAY), 161 Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE, 162 mConfig.wakeScreenGestureAvailable() && alwaysOn, 163 DozeLog.REASON_SENSOR_WAKE_UP, 164 false /* reports touch coordinates */, 165 false /* touchscreen */, 166 dozeLog), 167 new PluginSensor( 168 new SensorManagerPlugin.Sensor(TYPE_WAKE_LOCK_SCREEN), 169 Settings.Secure.DOZE_WAKE_LOCK_SCREEN_GESTURE, 170 mConfig.wakeScreenGestureAvailable(), 171 DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN, 172 false /* reports touch coordinates */, 173 false /* touchscreen */, 174 mConfig.getWakeLockScreenDebounce(), 175 dozeLog), 176 }; 177 178 setProxListening(false); // Don't immediately start listening when we register. 179 mProximitySensor.register( 180 proximityEvent -> { 181 if (proximityEvent != null) { 182 mProxCallback.accept(!proximityEvent.getNear()); 183 } 184 }); 185 } 186 187 /** 188 * Unregister any sensors. 189 */ destroy()190 public void destroy() { 191 // Unregisters everything, which is enough to allow gc. 192 for (TriggerSensor triggerSensor : mSensors) { 193 triggerSensor.setListening(false); 194 } 195 mProximitySensor.pause(); 196 } 197 198 /** 199 * Temporarily disable some sensors to avoid turning on the device while the user is 200 * turning it off. 201 */ requestTemporaryDisable()202 public void requestTemporaryDisable() { 203 mDebounceFrom = SystemClock.uptimeMillis(); 204 } 205 findSensorWithType(String type)206 private Sensor findSensorWithType(String type) { 207 return findSensorWithType(mSensorManager, type); 208 } 209 findSensorWithType(SensorManager sensorManager, String type)210 static Sensor findSensorWithType(SensorManager sensorManager, String type) { 211 if (TextUtils.isEmpty(type)) { 212 return null; 213 } 214 List<Sensor> sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL); 215 for (Sensor s : sensorList) { 216 if (type.equals(s.getStringType())) { 217 return s; 218 } 219 } 220 return null; 221 } 222 223 /** 224 * If sensors should be registered and sending signals. 225 */ setListening(boolean listen)226 public void setListening(boolean listen) { 227 if (mListening == listen) { 228 return; 229 } 230 mListening = listen; 231 updateListening(); 232 } 233 234 /** 235 * Unregister sensors, when listening, unless they are prox gated. 236 * @see #setListening(boolean) 237 */ setPaused(boolean paused)238 public void setPaused(boolean paused) { 239 if (mPaused == paused) { 240 return; 241 } 242 mPaused = paused; 243 updateListening(); 244 } 245 246 /** 247 * Registers/unregisters sensors based on internal state. 248 */ updateListening()249 public void updateListening() { 250 boolean anyListening = false; 251 for (TriggerSensor s : mSensors) { 252 s.setListening(mListening); 253 if (mListening) { 254 anyListening = true; 255 } 256 } 257 258 if (!anyListening) { 259 mResolver.unregisterContentObserver(mSettingsObserver); 260 } else if (!mSettingRegistered) { 261 for (TriggerSensor s : mSensors) { 262 s.registerSettingsObserver(mSettingsObserver); 263 } 264 } 265 mSettingRegistered = anyListening; 266 } 267 268 /** Set the listening state of only the sensors that require the touchscreen. */ setTouchscreenSensorsListening(boolean listening)269 public void setTouchscreenSensorsListening(boolean listening) { 270 for (TriggerSensor sensor : mSensors) { 271 if (sensor.mRequiresTouchscreen) { 272 sensor.setListening(listening); 273 } 274 } 275 } 276 onUserSwitched()277 public void onUserSwitched() { 278 for (TriggerSensor s : mSensors) { 279 s.updateListening(); 280 } 281 } 282 setProxListening(boolean listen)283 public void setProxListening(boolean listen) { 284 if (mProximitySensor.isRegistered() && listen) { 285 mProximitySensor.alertListeners(); 286 } else { 287 if (listen) { 288 mProximitySensor.resume(); 289 } else { 290 mProximitySensor.pause(); 291 } 292 } 293 } 294 295 private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { 296 @Override 297 public void onChange(boolean selfChange, Collection<Uri> uris, int flags, int userId) { 298 if (userId != ActivityManager.getCurrentUser()) { 299 return; 300 } 301 for (TriggerSensor s : mSensors) { 302 s.updateListening(); 303 } 304 } 305 }; 306 setDisableSensorsInterferingWithProximity(boolean disable)307 public void setDisableSensorsInterferingWithProximity(boolean disable) { 308 mPickupSensor.setDisabled(disable); 309 } 310 311 /** Ignore the setting value of only the sensors that require the touchscreen. */ ignoreTouchScreenSensorsSettingInterferingWithDocking(boolean ignore)312 public void ignoreTouchScreenSensorsSettingInterferingWithDocking(boolean ignore) { 313 for (TriggerSensor sensor : mSensors) { 314 if (sensor.mRequiresTouchscreen) { 315 sensor.ignoreSetting(ignore); 316 } 317 } 318 } 319 320 /** Dump current state */ dump(PrintWriter pw)321 public void dump(PrintWriter pw) { 322 for (TriggerSensor s : mSensors) { 323 pw.println(" Sensor: " + s.toString()); 324 } 325 pw.println(" ProxSensor: " + mProximitySensor.toString()); 326 } 327 328 /** 329 * @return true if prox is currently near, false if far or null if unknown. 330 */ isProximityCurrentlyNear()331 public Boolean isProximityCurrentlyNear() { 332 return mProximitySensor.isNear(); 333 } 334 335 @VisibleForTesting 336 class TriggerSensor extends TriggerEventListener { 337 final Sensor mSensor; 338 final boolean mConfigured; 339 final int mPulseReason; 340 private final String mSetting; 341 private final boolean mReportsTouchCoordinates; 342 private final boolean mSettingDefault; 343 private final boolean mRequiresTouchscreen; 344 345 protected boolean mRequested; 346 protected boolean mRegistered; 347 protected boolean mDisabled; 348 protected boolean mIgnoresSetting; 349 protected final DozeLog mDozeLog; 350 TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen, DozeLog dozeLog)351 public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason, 352 boolean reportsTouchCoordinates, boolean requiresTouchscreen, DozeLog dozeLog) { 353 this(sensor, setting, true /* settingDef */, configured, pulseReason, 354 reportsTouchCoordinates, requiresTouchscreen, dozeLog); 355 } 356 TriggerSensor(Sensor sensor, String setting, boolean settingDef, boolean configured, int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen, DozeLog dozeLog)357 public TriggerSensor(Sensor sensor, String setting, boolean settingDef, 358 boolean configured, int pulseReason, boolean reportsTouchCoordinates, 359 boolean requiresTouchscreen, DozeLog dozeLog) { 360 this(sensor, setting, settingDef, configured, pulseReason, reportsTouchCoordinates, 361 requiresTouchscreen, false /* ignoresSetting */, dozeLog); 362 } 363 TriggerSensor(Sensor sensor, String setting, boolean settingDef, boolean configured, int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen, boolean ignoresSetting, DozeLog dozeLog)364 private TriggerSensor(Sensor sensor, String setting, boolean settingDef, 365 boolean configured, int pulseReason, boolean reportsTouchCoordinates, 366 boolean requiresTouchscreen, boolean ignoresSetting, DozeLog dozeLog) { 367 mSensor = sensor; 368 mSetting = setting; 369 mSettingDefault = settingDef; 370 mConfigured = configured; 371 mPulseReason = pulseReason; 372 mReportsTouchCoordinates = reportsTouchCoordinates; 373 mRequiresTouchscreen = requiresTouchscreen; 374 mIgnoresSetting = ignoresSetting; 375 mDozeLog = dozeLog; 376 } 377 setListening(boolean listen)378 public void setListening(boolean listen) { 379 if (mRequested == listen) return; 380 mRequested = listen; 381 updateListening(); 382 } 383 setDisabled(boolean disabled)384 public void setDisabled(boolean disabled) { 385 if (mDisabled == disabled) return; 386 mDisabled = disabled; 387 updateListening(); 388 } 389 ignoreSetting(boolean ignored)390 public void ignoreSetting(boolean ignored) { 391 if (mIgnoresSetting == ignored) return; 392 mIgnoresSetting = ignored; 393 updateListening(); 394 } 395 updateListening()396 public void updateListening() { 397 if (!mConfigured || mSensor == null) return; 398 if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting) 399 && !mRegistered) { 400 mRegistered = mSensorManager.requestTriggerSensor(this, mSensor); 401 if (DEBUG) Log.d(TAG, "requestTriggerSensor " + mRegistered); 402 } else if (mRegistered) { 403 final boolean rt = mSensorManager.cancelTriggerSensor(this, mSensor); 404 if (DEBUG) Log.d(TAG, "cancelTriggerSensor " + rt); 405 mRegistered = false; 406 } 407 } 408 enabledBySetting()409 protected boolean enabledBySetting() { 410 if (!mConfig.enabled(UserHandle.USER_CURRENT)) { 411 return false; 412 } else if (TextUtils.isEmpty(mSetting)) { 413 return true; 414 } 415 return Settings.Secure.getIntForUser(mResolver, mSetting, mSettingDefault ? 1 : 0, 416 UserHandle.USER_CURRENT) != 0; 417 } 418 419 @Override toString()420 public String toString() { 421 return new StringBuilder("{mRegistered=").append(mRegistered) 422 .append(", mRequested=").append(mRequested) 423 .append(", mDisabled=").append(mDisabled) 424 .append(", mConfigured=").append(mConfigured) 425 .append(", mIgnoresSetting=").append(mIgnoresSetting) 426 .append(", mSensor=").append(mSensor).append("}").toString(); 427 } 428 429 @Override 430 @AnyThread onTrigger(TriggerEvent event)431 public void onTrigger(TriggerEvent event) { 432 mDozeLog.traceSensor(mPulseReason); 433 mHandler.post(mWakeLock.wrap(() -> { 434 if (DEBUG) Log.d(TAG, "onTrigger: " + triggerEventToString(event)); 435 if (mSensor != null && mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) { 436 int subType = (int) event.values[0]; 437 MetricsLogger.action( 438 mContext, MetricsProto.MetricsEvent.ACTION_AMBIENT_GESTURE, 439 subType); 440 UI_EVENT_LOGGER.log(DozeSensorsUiEvent.ACTION_AMBIENT_GESTURE_PICKUP); 441 } 442 443 mRegistered = false; 444 float screenX = -1; 445 float screenY = -1; 446 if (mReportsTouchCoordinates && event.values.length >= 2) { 447 screenX = event.values[0]; 448 screenY = event.values[1]; 449 } 450 mCallback.onSensorPulse(mPulseReason, screenX, screenY, event.values); 451 if (!mRegistered) { 452 updateListening(); // reregister, this sensor only fires once 453 } 454 })); 455 } 456 registerSettingsObserver(ContentObserver settingsObserver)457 public void registerSettingsObserver(ContentObserver settingsObserver) { 458 if (mConfigured && !TextUtils.isEmpty(mSetting)) { 459 mResolver.registerContentObserver( 460 Settings.Secure.getUriFor(mSetting), false /* descendants */, 461 mSettingsObserver, UserHandle.USER_ALL); 462 } 463 } 464 triggerEventToString(TriggerEvent event)465 protected String triggerEventToString(TriggerEvent event) { 466 if (event == null) return null; 467 final StringBuilder sb = new StringBuilder("SensorEvent[") 468 .append(event.timestamp).append(',') 469 .append(event.sensor.getName()); 470 if (event.values != null) { 471 for (int i = 0; i < event.values.length; i++) { 472 sb.append(',').append(event.values[i]); 473 } 474 } 475 return sb.append(']').toString(); 476 } 477 } 478 479 /** 480 * A Sensor that is injected via plugin. 481 */ 482 @VisibleForTesting 483 class PluginSensor extends TriggerSensor implements SensorManagerPlugin.SensorEventListener { 484 485 final SensorManagerPlugin.Sensor mPluginSensor; 486 private long mDebounce; 487 PluginSensor(SensorManagerPlugin.Sensor sensor, String setting, boolean configured, int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen, DozeLog dozeLog)488 PluginSensor(SensorManagerPlugin.Sensor sensor, String setting, boolean configured, 489 int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen, 490 DozeLog dozeLog) { 491 this(sensor, setting, configured, pulseReason, reportsTouchCoordinates, 492 requiresTouchscreen, 0L /* debounce */, dozeLog); 493 } 494 PluginSensor(SensorManagerPlugin.Sensor sensor, String setting, boolean configured, int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen, long debounce, DozeLog dozeLog)495 PluginSensor(SensorManagerPlugin.Sensor sensor, String setting, boolean configured, 496 int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen, 497 long debounce, DozeLog dozeLog) { 498 super(null, setting, configured, pulseReason, reportsTouchCoordinates, 499 requiresTouchscreen, dozeLog); 500 mPluginSensor = sensor; 501 mDebounce = debounce; 502 } 503 504 @Override updateListening()505 public void updateListening() { 506 if (!mConfigured) return; 507 AsyncSensorManager asyncSensorManager = (AsyncSensorManager) mSensorManager; 508 if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting) 509 && !mRegistered) { 510 asyncSensorManager.registerPluginListener(mPluginSensor, this); 511 mRegistered = true; 512 if (DEBUG) Log.d(TAG, "registerPluginListener"); 513 } else if (mRegistered) { 514 asyncSensorManager.unregisterPluginListener(mPluginSensor, this); 515 mRegistered = false; 516 if (DEBUG) Log.d(TAG, "unregisterPluginListener"); 517 } 518 } 519 520 @Override toString()521 public String toString() { 522 return new StringBuilder("{mRegistered=").append(mRegistered) 523 .append(", mRequested=").append(mRequested) 524 .append(", mDisabled=").append(mDisabled) 525 .append(", mConfigured=").append(mConfigured) 526 .append(", mIgnoresSetting=").append(mIgnoresSetting) 527 .append(", mSensor=").append(mPluginSensor).append("}").toString(); 528 } 529 triggerEventToString(SensorManagerPlugin.SensorEvent event)530 private String triggerEventToString(SensorManagerPlugin.SensorEvent event) { 531 if (event == null) return null; 532 final StringBuilder sb = new StringBuilder("PluginTriggerEvent[") 533 .append(event.getSensor()).append(',') 534 .append(event.getVendorType()); 535 if (event.getValues() != null) { 536 for (int i = 0; i < event.getValues().length; i++) { 537 sb.append(',').append(event.getValues()[i]); 538 } 539 } 540 return sb.append(']').toString(); 541 } 542 543 @Override onSensorChanged(SensorManagerPlugin.SensorEvent event)544 public void onSensorChanged(SensorManagerPlugin.SensorEvent event) { 545 mDozeLog.traceSensor(mPulseReason); 546 mHandler.post(mWakeLock.wrap(() -> { 547 final long now = SystemClock.uptimeMillis(); 548 if (now < mDebounceFrom + mDebounce) { 549 Log.d(TAG, "onSensorEvent dropped: " + triggerEventToString(event)); 550 return; 551 } 552 if (DEBUG) Log.d(TAG, "onSensorEvent: " + triggerEventToString(event)); 553 mCallback.onSensorPulse(mPulseReason, -1, -1, event.getValues()); 554 })); 555 } 556 } 557 558 public interface Callback { 559 560 /** 561 * Called when a sensor requests a pulse 562 * @param pulseReason Requesting sensor, e.g. {@link DozeLog#REASON_SENSOR_PICKUP} 563 * @param screenX the location on the screen where the sensor fired or -1 564 * if the sensor doesn't support reporting screen locations. 565 * @param screenY the location on the screen where the sensor fired or -1 566 * @param rawValues raw values array from the event. 567 */ onSensorPulse(int pulseReason, float screenX, float screenY, float[] rawValues)568 void onSensorPulse(int pulseReason, float screenX, float screenY, float[] rawValues); 569 } 570 } 571