1 /* 2 * Copyright (C) 2014 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 android.app.ActivityManager; 20 import android.app.UiModeManager; 21 import android.content.BroadcastReceiver; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.IntentFilter; 25 import android.content.res.Configuration; 26 import android.database.ContentObserver; 27 import android.hardware.Sensor; 28 import android.hardware.SensorEvent; 29 import android.hardware.SensorEventListener; 30 import android.hardware.SensorManager; 31 import android.hardware.TriggerEvent; 32 import android.hardware.TriggerEventListener; 33 import android.media.AudioAttributes; 34 import android.net.Uri; 35 import android.os.Handler; 36 import android.os.PowerManager; 37 import android.os.SystemClock; 38 import android.os.UserHandle; 39 import android.os.Vibrator; 40 import android.provider.Settings; 41 import android.service.dreams.DreamService; 42 import android.text.TextUtils; 43 import android.util.Log; 44 import android.view.Display; 45 46 import com.android.internal.hardware.AmbientDisplayConfiguration; 47 import com.android.internal.logging.MetricsLogger; 48 import com.android.internal.logging.MetricsProto.MetricsEvent; 49 import com.android.systemui.SystemUIApplication; 50 import com.android.systemui.statusbar.phone.DozeParameters; 51 52 import java.io.FileDescriptor; 53 import java.io.PrintWriter; 54 import java.util.Date; 55 import java.util.List; 56 57 public class DozeService extends DreamService { 58 private static final String TAG = "DozeService"; 59 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 60 61 private static final String ACTION_BASE = "com.android.systemui.doze"; 62 private static final String PULSE_ACTION = ACTION_BASE + ".pulse"; 63 64 /** 65 * If true, reregisters all trigger sensors when the screen turns off. 66 */ 67 private static final boolean REREGISTER_ALL_SENSORS_ON_SCREEN_OFF = true; 68 69 private final String mTag = String.format(TAG + ".%08x", hashCode()); 70 private final Context mContext = this; 71 private final DozeParameters mDozeParameters = new DozeParameters(mContext); 72 private final Handler mHandler = new Handler(); 73 74 private DozeHost mHost; 75 private SensorManager mSensorManager; 76 private TriggerSensor[] mSensors; 77 private TriggerSensor mPickupSensor; 78 private PowerManager mPowerManager; 79 private PowerManager.WakeLock mWakeLock; 80 private UiModeManager mUiModeManager; 81 private boolean mDreaming; 82 private boolean mPulsing; 83 private boolean mBroadcastReceiverRegistered; 84 private boolean mDisplayStateSupported; 85 private boolean mPowerSaveActive; 86 private boolean mCarMode; 87 private long mNotificationPulseTime; 88 89 private AmbientDisplayConfiguration mConfig; 90 91 public DozeService() { 92 if (DEBUG) Log.d(mTag, "new DozeService()"); 93 setDebug(DEBUG); 94 } 95 96 @Override 97 protected void dumpOnHandler(FileDescriptor fd, PrintWriter pw, String[] args) { 98 super.dumpOnHandler(fd, pw, args); 99 pw.print(" mDreaming: "); pw.println(mDreaming); 100 pw.print(" mPulsing: "); pw.println(mPulsing); 101 pw.print(" mWakeLock: held="); pw.println(mWakeLock.isHeld()); 102 pw.print(" mHost: "); pw.println(mHost); 103 pw.print(" mBroadcastReceiverRegistered: "); pw.println(mBroadcastReceiverRegistered); 104 for (TriggerSensor s : mSensors) { 105 pw.print(" sensor: "); 106 pw.println(s); 107 } 108 pw.print(" mDisplayStateSupported: "); pw.println(mDisplayStateSupported); 109 pw.print(" mPowerSaveActive: "); pw.println(mPowerSaveActive); 110 pw.print(" mCarMode: "); pw.println(mCarMode); 111 pw.print(" mNotificationPulseTime: "); pw.println( 112 DozeLog.FORMAT.format(new Date(mNotificationPulseTime 113 - SystemClock.elapsedRealtime() + System.currentTimeMillis()))); 114 mDozeParameters.dump(pw); 115 } 116 117 @Override 118 public void onCreate() { 119 if (DEBUG) Log.d(mTag, "onCreate"); 120 super.onCreate(); 121 122 if (getApplication() instanceof SystemUIApplication) { 123 final SystemUIApplication app = (SystemUIApplication) getApplication(); 124 mHost = app.getComponent(DozeHost.class); 125 } 126 if (mHost == null) Log.w(TAG, "No doze service host found."); 127 128 setWindowless(true); 129 130 mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE); 131 mConfig = new AmbientDisplayConfiguration(mContext); 132 mSensors = new TriggerSensor[] { 133 new TriggerSensor( 134 mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION), 135 null /* setting */, 136 mDozeParameters.getPulseOnSigMotion(), 137 mDozeParameters.getVibrateOnSigMotion(), 138 DozeLog.PULSE_REASON_SENSOR_SIGMOTION), 139 mPickupSensor = new TriggerSensor( 140 mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE), 141 Settings.Secure.DOZE_PULSE_ON_PICK_UP, 142 mConfig.pulseOnPickupAvailable(), mDozeParameters.getVibrateOnPickup(), 143 DozeLog.PULSE_REASON_SENSOR_PICKUP), 144 new TriggerSensor( 145 findSensorWithType(mConfig.doubleTapSensorType()), 146 Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP, true, 147 mDozeParameters.getVibrateOnPickup(), 148 DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP) 149 }; 150 mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 151 mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); 152 mWakeLock.setReferenceCounted(true); 153 mDisplayStateSupported = mDozeParameters.getDisplayStateSupported(); 154 mUiModeManager = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE); 155 turnDisplayOff(); 156 } 157 158 @Override 159 public void onAttachedToWindow() { 160 if (DEBUG) Log.d(mTag, "onAttachedToWindow"); 161 super.onAttachedToWindow(); 162 } 163 164 @Override 165 public void onDreamingStarted() { 166 super.onDreamingStarted(); 167 168 if (mHost == null) { 169 finish(); 170 return; 171 } 172 173 mPowerSaveActive = mHost.isPowerSaveActive(); 174 mCarMode = mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR; 175 if (DEBUG) Log.d(mTag, "onDreamingStarted canDoze=" + canDoze() + " mPowerSaveActive=" 176 + mPowerSaveActive + " mCarMode=" + mCarMode); 177 if (mPowerSaveActive) { 178 finishToSavePower(); 179 return; 180 } 181 if (mCarMode) { 182 finishForCarMode(); 183 return; 184 } 185 186 mDreaming = true; 187 listenForPulseSignals(true); 188 189 // Ask the host to get things ready to start dozing. 190 // Once ready, we call startDozing() at which point the CPU may suspend 191 // and we will need to acquire a wakelock to do work. 192 mHost.startDozing(mWakeLock.wrap(() -> { 193 if (mDreaming) { 194 startDozing(); 195 196 // From this point until onDreamingStopped we will need to hold a 197 // wakelock whenever we are doing work. Note that we never call 198 // stopDozing because can we just keep dozing until the bitter end. 199 } 200 })); 201 } 202 203 @Override 204 public void onDreamingStopped() { 205 if (DEBUG) Log.d(mTag, "onDreamingStopped isDozing=" + isDozing()); 206 super.onDreamingStopped(); 207 208 if (mHost == null) { 209 return; 210 } 211 212 mDreaming = false; 213 listenForPulseSignals(false); 214 215 // Tell the host that it's over. 216 mHost.stopDozing(); 217 } 218 219 private void requestPulse(final int reason) { 220 requestPulse(reason, false /* performedProxCheck */); 221 } 222 223 private void requestPulse(final int reason, boolean performedProxCheck) { 224 if (mHost != null && mDreaming && !mPulsing) { 225 // Let the host know we want to pulse. Wait for it to be ready, then 226 // turn the screen on. When finished, turn the screen off again. 227 // Here we need a wakelock to stay awake until the pulse is finished. 228 mWakeLock.acquire(); 229 mPulsing = true; 230 if (!mDozeParameters.getProxCheckBeforePulse()) { 231 // skip proximity check 232 continuePulsing(reason); 233 return; 234 } 235 final long start = SystemClock.uptimeMillis(); 236 if (performedProxCheck) { 237 // the caller already performed a successful proximity check; we'll only do one to 238 // capture statistics, continue pulsing immediately. 239 continuePulsing(reason); 240 } 241 // perform a proximity check 242 new ProximityCheck() { 243 @Override 244 public void onProximityResult(int result) { 245 final boolean isNear = result == RESULT_NEAR; 246 final long end = SystemClock.uptimeMillis(); 247 DozeLog.traceProximityResult(mContext, isNear, end - start, reason); 248 if (performedProxCheck) { 249 // we already continued 250 return; 251 } 252 // avoid pulsing in pockets 253 if (isNear) { 254 mPulsing = false; 255 mWakeLock.release(); 256 return; 257 } 258 259 // not in-pocket, continue pulsing 260 continuePulsing(reason); 261 } 262 }.check(); 263 } 264 } 265 266 private void continuePulsing(int reason) { 267 if (mHost.isPulsingBlocked()) { 268 mPulsing = false; 269 mWakeLock.release(); 270 return; 271 } 272 mHost.pulseWhileDozing(new DozeHost.PulseCallback() { 273 @Override 274 public void onPulseStarted() { 275 if (mPulsing && mDreaming) { 276 turnDisplayOn(); 277 } 278 } 279 280 @Override 281 public void onPulseFinished() { 282 if (mPulsing && mDreaming) { 283 mPulsing = false; 284 if (REREGISTER_ALL_SENSORS_ON_SCREEN_OFF) { 285 reregisterAllSensors(); 286 } 287 turnDisplayOff(); 288 } 289 mWakeLock.release(); // needs to be unconditional to balance acquire 290 } 291 }, reason); 292 } 293 294 private void turnDisplayOff() { 295 if (DEBUG) Log.d(mTag, "Display off"); 296 setDozeScreenState(Display.STATE_OFF); 297 } 298 299 private void turnDisplayOn() { 300 if (DEBUG) Log.d(mTag, "Display on"); 301 setDozeScreenState(mDisplayStateSupported ? Display.STATE_DOZE : Display.STATE_ON); 302 } 303 304 private void finishToSavePower() { 305 Log.w(mTag, "Exiting ambient mode due to low power battery saver"); 306 finish(); 307 } 308 309 private void finishForCarMode() { 310 Log.w(mTag, "Exiting ambient mode, not allowed in car mode"); 311 finish(); 312 } 313 314 private void listenForPulseSignals(boolean listen) { 315 if (DEBUG) Log.d(mTag, "listenForPulseSignals: " + listen); 316 for (TriggerSensor s : mSensors) { 317 s.setListening(listen); 318 } 319 listenForBroadcasts(listen); 320 listenForNotifications(listen); 321 } 322 323 private void reregisterAllSensors() { 324 for (TriggerSensor s : mSensors) { 325 s.setListening(false); 326 } 327 for (TriggerSensor s : mSensors) { 328 s.setListening(true); 329 } 330 } 331 332 private void listenForBroadcasts(boolean listen) { 333 if (listen) { 334 final IntentFilter filter = new IntentFilter(PULSE_ACTION); 335 filter.addAction(UiModeManager.ACTION_ENTER_CAR_MODE); 336 filter.addAction(Intent.ACTION_USER_SWITCHED); 337 mContext.registerReceiver(mBroadcastReceiver, filter); 338 339 for (TriggerSensor s : mSensors) { 340 if (s.mConfigured && !TextUtils.isEmpty(s.mSetting)) { 341 mContext.getContentResolver().registerContentObserver( 342 Settings.Secure.getUriFor(s.mSetting), false /* descendants */, 343 mSettingsObserver, UserHandle.USER_ALL); 344 } 345 } 346 mBroadcastReceiverRegistered = true; 347 } else { 348 if (mBroadcastReceiverRegistered) { 349 mContext.unregisterReceiver(mBroadcastReceiver); 350 mContext.getContentResolver().unregisterContentObserver(mSettingsObserver); 351 } 352 mBroadcastReceiverRegistered = false; 353 } 354 } 355 356 private void listenForNotifications(boolean listen) { 357 if (listen) { 358 mHost.addCallback(mHostCallback); 359 } else { 360 mHost.removeCallback(mHostCallback); 361 } 362 } 363 364 private void requestNotificationPulse() { 365 if (DEBUG) Log.d(mTag, "requestNotificationPulse"); 366 if (!mConfig.pulseOnNotificationEnabled(UserHandle.USER_CURRENT)) return; 367 mNotificationPulseTime = SystemClock.elapsedRealtime(); 368 requestPulse(DozeLog.PULSE_REASON_NOTIFICATION); 369 } 370 371 private static String triggerEventToString(TriggerEvent event) { 372 if (event == null) return null; 373 final StringBuilder sb = new StringBuilder("TriggerEvent[") 374 .append(event.timestamp).append(',') 375 .append(event.sensor.getName()); 376 if (event.values != null) { 377 for (int i = 0; i < event.values.length; i++) { 378 sb.append(',').append(event.values[i]); 379 } 380 } 381 return sb.append(']').toString(); 382 } 383 384 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 385 @Override 386 public void onReceive(Context context, Intent intent) { 387 if (PULSE_ACTION.equals(intent.getAction())) { 388 if (DEBUG) Log.d(mTag, "Received pulse intent"); 389 requestPulse(DozeLog.PULSE_REASON_INTENT); 390 } 391 if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) { 392 mCarMode = true; 393 if (mCarMode && mDreaming) { 394 finishForCarMode(); 395 } 396 } 397 if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) { 398 for (TriggerSensor s : mSensors) { 399 s.updateListener(); 400 } 401 } 402 } 403 }; 404 405 private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { 406 @Override 407 public void onChange(boolean selfChange, Uri uri, int userId) { 408 if (userId != ActivityManager.getCurrentUser()) { 409 return; 410 } 411 for (TriggerSensor s : mSensors) { 412 s.updateListener(); 413 } 414 } 415 }; 416 417 private final DozeHost.Callback mHostCallback = new DozeHost.Callback() { 418 @Override 419 public void onNewNotifications() { 420 if (DEBUG) Log.d(mTag, "onNewNotifications (noop)"); 421 // noop for now 422 } 423 424 @Override 425 public void onBuzzBeepBlinked() { 426 if (DEBUG) Log.d(mTag, "onBuzzBeepBlinked"); 427 requestNotificationPulse(); 428 } 429 430 @Override 431 public void onNotificationLight(boolean on) { 432 if (DEBUG) Log.d(mTag, "onNotificationLight (noop) on=" + on); 433 // noop for now 434 } 435 436 @Override 437 public void onPowerSaveChanged(boolean active) { 438 mPowerSaveActive = active; 439 if (mPowerSaveActive && mDreaming) { 440 finishToSavePower(); 441 } 442 } 443 }; 444 445 private Sensor findSensorWithType(String type) { 446 if (TextUtils.isEmpty(type)) { 447 return null; 448 } 449 List<Sensor> sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL); 450 for (Sensor s : sensorList) { 451 if (type.equals(s.getStringType())) { 452 return s; 453 } 454 } 455 return null; 456 } 457 458 private class TriggerSensor extends TriggerEventListener { 459 final Sensor mSensor; 460 final boolean mConfigured; 461 final boolean mDebugVibrate; 462 final int mPulseReason; 463 final String mSetting; 464 465 private boolean mRequested; 466 private boolean mRegistered; 467 private boolean mDisabled; 468 469 public TriggerSensor(Sensor sensor, String setting, boolean configured, 470 boolean debugVibrate, int pulseReason) { 471 mSensor = sensor; 472 mSetting = setting; 473 mConfigured = configured; 474 mDebugVibrate = debugVibrate; 475 mPulseReason = pulseReason; 476 } 477 478 public void setListening(boolean listen) { 479 if (mRequested == listen) return; 480 mRequested = listen; 481 updateListener(); 482 } 483 484 public void setDisabled(boolean disabled) { 485 if (mDisabled == disabled) return; 486 mDisabled = disabled; 487 updateListener(); 488 } 489 490 public void updateListener() { 491 if (!mConfigured || mSensor == null) return; 492 if (mRequested && !mDisabled && enabledBySetting() && !mRegistered) { 493 mRegistered = mSensorManager.requestTriggerSensor(this, mSensor); 494 if (DEBUG) Log.d(mTag, "requestTriggerSensor " + mRegistered); 495 } else if (mRegistered) { 496 final boolean rt = mSensorManager.cancelTriggerSensor(this, mSensor); 497 if (DEBUG) Log.d(mTag, "cancelTriggerSensor " + rt); 498 mRegistered = false; 499 } 500 } 501 502 private boolean enabledBySetting() { 503 if (TextUtils.isEmpty(mSetting)) { 504 return true; 505 } 506 return Settings.Secure.getIntForUser(mContext.getContentResolver(), mSetting, 1, 507 UserHandle.USER_CURRENT) != 0; 508 } 509 510 @Override 511 public String toString() { 512 return new StringBuilder("{mRegistered=").append(mRegistered) 513 .append(", mRequested=").append(mRequested) 514 .append(", mDisabled=").append(mDisabled) 515 .append(", mConfigured=").append(mConfigured) 516 .append(", mDebugVibrate=").append(mDebugVibrate) 517 .append(", mSensor=").append(mSensor).append("}").toString(); 518 } 519 520 @Override 521 public void onTrigger(TriggerEvent event) { 522 mWakeLock.acquire(); 523 try { 524 if (DEBUG) Log.d(mTag, "onTrigger: " + triggerEventToString(event)); 525 boolean sensorPerformsProxCheck = false; 526 if (mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) { 527 int subType = (int) event.values[0]; 528 MetricsLogger.action(mContext, MetricsEvent.ACTION_AMBIENT_GESTURE, subType); 529 sensorPerformsProxCheck = mDozeParameters.getPickupSubtypePerformsProxCheck( 530 subType); 531 } 532 if (mDebugVibrate) { 533 final Vibrator v = (Vibrator) mContext.getSystemService( 534 Context.VIBRATOR_SERVICE); 535 if (v != null) { 536 v.vibrate(1000, new AudioAttributes.Builder() 537 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 538 .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()); 539 } 540 } 541 542 mRegistered = false; 543 requestPulse(mPulseReason, sensorPerformsProxCheck); 544 updateListener(); // reregister, this sensor only fires once 545 546 // record pickup gesture, also keep track of whether we might have been triggered 547 // by recent vibration. 548 final long timeSinceNotification = SystemClock.elapsedRealtime() 549 - mNotificationPulseTime; 550 final boolean withinVibrationThreshold = 551 timeSinceNotification < mDozeParameters.getPickupVibrationThreshold(); 552 if (mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) { 553 DozeLog.tracePickupPulse(mContext, withinVibrationThreshold); 554 } 555 } finally { 556 mWakeLock.release(); 557 } 558 } 559 } 560 561 private abstract class ProximityCheck implements SensorEventListener, Runnable { 562 private static final int TIMEOUT_DELAY_MS = 500; 563 564 protected static final int RESULT_UNKNOWN = 0; 565 protected static final int RESULT_NEAR = 1; 566 protected static final int RESULT_FAR = 2; 567 568 private final String mTag = DozeService.this.mTag + ".ProximityCheck"; 569 570 private boolean mRegistered; 571 private boolean mFinished; 572 private float mMaxRange; 573 574 abstract public void onProximityResult(int result); 575 576 public void check() { 577 if (mFinished || mRegistered) return; 578 final Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); 579 if (sensor == null) { 580 if (DEBUG) Log.d(mTag, "No sensor found"); 581 finishWithResult(RESULT_UNKNOWN); 582 return; 583 } 584 // the pickup sensor interferes with the prox event, disable it until we have a result 585 mPickupSensor.setDisabled(true); 586 587 mMaxRange = sensor.getMaximumRange(); 588 mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL, 0, 589 mHandler); 590 mHandler.postDelayed(this, TIMEOUT_DELAY_MS); 591 mRegistered = true; 592 } 593 594 @Override 595 public void onSensorChanged(SensorEvent event) { 596 if (event.values.length == 0) { 597 if (DEBUG) Log.d(mTag, "Event has no values!"); 598 finishWithResult(RESULT_UNKNOWN); 599 } else { 600 if (DEBUG) Log.d(mTag, "Event: value=" + event.values[0] + " max=" + mMaxRange); 601 final boolean isNear = event.values[0] < mMaxRange; 602 finishWithResult(isNear ? RESULT_NEAR : RESULT_FAR); 603 } 604 } 605 606 @Override 607 public void run() { 608 if (DEBUG) Log.d(mTag, "No event received before timeout"); 609 finishWithResult(RESULT_UNKNOWN); 610 } 611 612 private void finishWithResult(int result) { 613 if (mFinished) return; 614 if (mRegistered) { 615 mHandler.removeCallbacks(this); 616 mSensorManager.unregisterListener(this); 617 // we're done - reenable the pickup sensor 618 mPickupSensor.setDisabled(false); 619 mRegistered = false; 620 } 621 onProximityResult(result); 622 mFinished = true; 623 } 624 625 @Override 626 public void onAccuracyChanged(Sensor sensor, int accuracy) { 627 // noop 628 } 629 } 630 } 631