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