DozeService.java revision 4d69e2219390bce567b0d2c986d0bd3a3182eda5
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 17package com.android.systemui.doze; 18 19import android.app.AlarmManager; 20import android.app.PendingIntent; 21import android.content.BroadcastReceiver; 22import android.content.Context; 23import android.content.Intent; 24import android.content.IntentFilter; 25import android.hardware.Sensor; 26import android.hardware.SensorManager; 27import android.hardware.TriggerEvent; 28import android.hardware.TriggerEventListener; 29import android.media.AudioAttributes; 30import android.os.PowerManager; 31import android.os.Vibrator; 32import android.service.dreams.DreamService; 33import android.util.Log; 34import android.view.Display; 35 36import com.android.systemui.SystemUIApplication; 37import com.android.systemui.statusbar.phone.DozeParameters; 38import com.android.systemui.statusbar.phone.DozeParameters.PulseSchedule; 39 40import java.io.FileDescriptor; 41import java.io.PrintWriter; 42import java.util.Date; 43 44public class DozeService extends DreamService { 45 private static final String TAG = "DozeService"; 46 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 47 48 private static final String ACTION_BASE = "com.android.systemui.doze"; 49 private static final String PULSE_ACTION = ACTION_BASE + ".pulse"; 50 private static final String NOTIFICATION_PULSE_ACTION = ACTION_BASE + ".notification_pulse"; 51 private static final String EXTRA_INSTANCE = "instance"; 52 53 private final String mTag = String.format(TAG + ".%08x", hashCode()); 54 private final Context mContext = this; 55 private final DozeParameters mDozeParameters = new DozeParameters(mContext); 56 57 private DozeHost mHost; 58 private SensorManager mSensors; 59 private TriggerSensor mSigMotionSensor; 60 private TriggerSensor mPickupSensor; 61 private PowerManager mPowerManager; 62 private PowerManager.WakeLock mWakeLock; 63 private AlarmManager mAlarmManager; 64 private boolean mDreaming; 65 private boolean mPulsing; 66 private boolean mBroadcastReceiverRegistered; 67 private boolean mDisplayStateSupported; 68 private boolean mNotificationLightOn; 69 private boolean mPowerSaveActive; 70 private long mNotificationPulseTime; 71 private int mScheduleResetsRemaining; 72 73 public DozeService() { 74 if (DEBUG) Log.d(mTag, "new DozeService()"); 75 setDebug(DEBUG); 76 } 77 78 @Override 79 protected void dumpOnHandler(FileDescriptor fd, PrintWriter pw, String[] args) { 80 super.dumpOnHandler(fd, pw, args); 81 pw.print(" mDreaming: "); pw.println(mDreaming); 82 pw.print(" mPulsing: "); pw.println(mPulsing); 83 pw.print(" mWakeLock: held="); pw.println(mWakeLock.isHeld()); 84 pw.print(" mHost: "); pw.println(mHost); 85 pw.print(" mBroadcastReceiverRegistered: "); pw.println(mBroadcastReceiverRegistered); 86 pw.print(" mSigMotionSensor: "); pw.println(mSigMotionSensor); 87 pw.print(" mPickupSensor:"); pw.println(mPickupSensor); 88 pw.print(" mDisplayStateSupported: "); pw.println(mDisplayStateSupported); 89 pw.print(" mNotificationLightOn: "); pw.println(mNotificationLightOn); 90 pw.print(" mPowerSaveActive: "); pw.println(mPowerSaveActive); 91 pw.print(" mNotificationPulseTime: "); pw.println(mNotificationPulseTime); 92 pw.print(" mScheduleResetsRemaining: "); pw.println(mScheduleResetsRemaining); 93 mDozeParameters.dump(pw); 94 } 95 96 @Override 97 public void onCreate() { 98 if (DEBUG) Log.d(mTag, "onCreate"); 99 super.onCreate(); 100 101 if (getApplication() instanceof SystemUIApplication) { 102 final SystemUIApplication app = (SystemUIApplication) getApplication(); 103 mHost = app.getComponent(DozeHost.class); 104 } 105 if (mHost == null) Log.w(TAG, "No doze service host found."); 106 107 setWindowless(true); 108 109 mSensors = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE); 110 mSigMotionSensor = new TriggerSensor(Sensor.TYPE_SIGNIFICANT_MOTION, 111 mDozeParameters.getPulseOnSigMotion(), mDozeParameters.getVibrateOnSigMotion()); 112 mPickupSensor = new TriggerSensor(Sensor.TYPE_PICK_UP_GESTURE, 113 mDozeParameters.getPulseOnPickup(), mDozeParameters.getVibrateOnPickup()); 114 mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 115 mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag); 116 mWakeLock.setReferenceCounted(true); 117 mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); 118 mDisplayStateSupported = mDozeParameters.getDisplayStateSupported(); 119 turnDisplayOff(); 120 } 121 122 @Override 123 public void onAttachedToWindow() { 124 if (DEBUG) Log.d(mTag, "onAttachedToWindow"); 125 super.onAttachedToWindow(); 126 } 127 128 @Override 129 public void onDreamingStarted() { 130 super.onDreamingStarted(); 131 132 if (mHost == null) { 133 finish(); 134 return; 135 } 136 137 mPowerSaveActive = mHost.isPowerSaveActive(); 138 if (DEBUG) Log.d(mTag, "onDreamingStarted canDoze=" + canDoze() + " mPowerSaveActive=" 139 + mPowerSaveActive); 140 if (mPowerSaveActive) { 141 finishToSavePower(); 142 return; 143 } 144 145 mDreaming = true; 146 listenForPulseSignals(true); 147 rescheduleNotificationPulse(false /*predicate*/); // cancel any pending pulse alarms 148 149 // Ask the host to get things ready to start dozing. 150 // Once ready, we call startDozing() at which point the CPU may suspend 151 // and we will need to acquire a wakelock to do work. 152 mHost.startDozing(new Runnable() { 153 @Override 154 public void run() { 155 if (mDreaming) { 156 startDozing(); 157 158 // From this point until onDreamingStopped we will need to hold a 159 // wakelock whenever we are doing work. Note that we never call 160 // stopDozing because can we just keep dozing until the bitter end. 161 } 162 } 163 }); 164 } 165 166 @Override 167 public void onDreamingStopped() { 168 if (DEBUG) Log.d(mTag, "onDreamingStopped isDozing=" + isDozing()); 169 super.onDreamingStopped(); 170 171 if (mHost == null) { 172 return; 173 } 174 175 mDreaming = false; 176 listenForPulseSignals(false); 177 178 // Tell the host that it's over. 179 mHost.stopDozing(); 180 } 181 182 private void requestPulse() { 183 if (mHost != null && mDreaming && !mPulsing) { 184 // Let the host know we want to pulse. Wait for it to be ready, then 185 // turn the screen on. When finished, turn the screen off again. 186 // Here we need a wakelock to stay awake until the pulse is finished. 187 mWakeLock.acquire(); 188 mPulsing = true; 189 mHost.pulseWhileDozing(new DozeHost.PulseCallback() { 190 @Override 191 public void onPulseStarted() { 192 if (mPulsing && mDreaming) { 193 turnDisplayOn(); 194 } 195 } 196 197 @Override 198 public void onPulseFinished() { 199 if (mPulsing && mDreaming) { 200 mPulsing = false; 201 turnDisplayOff(); 202 mWakeLock.release(); 203 } 204 } 205 }); 206 } 207 } 208 209 private void turnDisplayOff() { 210 if (DEBUG) Log.d(TAG, "Display off"); 211 setDozeScreenState(Display.STATE_OFF); 212 } 213 214 private void turnDisplayOn() { 215 if (DEBUG) Log.d(TAG, "Display on"); 216 setDozeScreenState(mDisplayStateSupported ? Display.STATE_DOZE : Display.STATE_ON); 217 } 218 219 private void finishToSavePower() { 220 Log.w(mTag, "Exiting ambient mode due to low power battery saver"); 221 finish(); 222 } 223 224 private void listenForPulseSignals(boolean listen) { 225 if (DEBUG) Log.d(mTag, "listenForPulseSignals: " + listen); 226 mSigMotionSensor.setListening(listen); 227 mPickupSensor.setListening(listen); 228 listenForBroadcasts(listen); 229 listenForNotifications(listen); 230 } 231 232 private void listenForBroadcasts(boolean listen) { 233 if (listen) { 234 final IntentFilter filter = new IntentFilter(PULSE_ACTION); 235 filter.addAction(NOTIFICATION_PULSE_ACTION); 236 mContext.registerReceiver(mBroadcastReceiver, filter); 237 mBroadcastReceiverRegistered = true; 238 } else { 239 if (mBroadcastReceiverRegistered) { 240 mContext.unregisterReceiver(mBroadcastReceiver); 241 } 242 mBroadcastReceiverRegistered = false; 243 } 244 } 245 246 private void listenForNotifications(boolean listen) { 247 if (listen) { 248 resetNotificationResets(); 249 mHost.addCallback(mHostCallback); 250 } else { 251 mHost.removeCallback(mHostCallback); 252 } 253 } 254 255 private void resetNotificationResets() { 256 if (DEBUG) Log.d(TAG, "resetNotificationResets"); 257 mScheduleResetsRemaining = mDozeParameters.getPulseScheduleResets(); 258 } 259 260 private void updateNotificationPulse() { 261 if (DEBUG) Log.d(TAG, "updateNotificationPulse"); 262 if (!mDozeParameters.getPulseOnNotifications()) return; 263 if (mScheduleResetsRemaining <= 0) { 264 if (DEBUG) Log.d(TAG, "No more schedule resets remaining"); 265 return; 266 } 267 final long now = System.currentTimeMillis(); 268 if ((now - mNotificationPulseTime) < mDozeParameters.getPulseDuration()) { 269 if (DEBUG) Log.d(TAG, "Recently updated, not resetting schedule"); 270 return; 271 } 272 mScheduleResetsRemaining--; 273 if (DEBUG) Log.d(TAG, "mScheduleResetsRemaining = " + mScheduleResetsRemaining); 274 mNotificationPulseTime = now; 275 rescheduleNotificationPulse(true /*predicate*/); 276 } 277 278 private PendingIntent notificationPulseIntent(long instance) { 279 return PendingIntent.getBroadcast(mContext, 0, 280 new Intent(NOTIFICATION_PULSE_ACTION) 281 .setPackage(getPackageName()) 282 .putExtra(EXTRA_INSTANCE, instance) 283 .setFlags(Intent.FLAG_RECEIVER_FOREGROUND), 284 PendingIntent.FLAG_UPDATE_CURRENT); 285 } 286 287 private void rescheduleNotificationPulse(boolean predicate) { 288 if (DEBUG) Log.d(TAG, "rescheduleNotificationPulse predicate=" + predicate); 289 final PendingIntent notificationPulseIntent = notificationPulseIntent(0); 290 mAlarmManager.cancel(notificationPulseIntent); 291 if (!predicate) { 292 if (DEBUG) Log.d(TAG, " don't reschedule: predicate is false"); 293 return; 294 } 295 final PulseSchedule schedule = mDozeParameters.getPulseSchedule(); 296 if (schedule == null) { 297 if (DEBUG) Log.d(TAG, " don't reschedule: schedule is null"); 298 return; 299 } 300 final long now = System.currentTimeMillis(); 301 final long time = schedule.getNextTime(now, mNotificationPulseTime); 302 if (time <= 0) { 303 if (DEBUG) Log.d(TAG, " don't reschedule: time is " + time); 304 return; 305 } 306 final long delta = time - now; 307 if (delta <= 0) { 308 if (DEBUG) Log.d(TAG, " don't reschedule: delta is " + delta); 309 return; 310 } 311 final long instance = time - mNotificationPulseTime; 312 if (DEBUG) Log.d(TAG, "Scheduling pulse " + instance + " in " + delta + "ms for " 313 + new Date(time)); 314 mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, time, notificationPulseIntent(instance)); 315 } 316 317 private static String triggerEventToString(TriggerEvent event) { 318 if (event == null) return null; 319 final StringBuilder sb = new StringBuilder("TriggerEvent[") 320 .append(event.timestamp).append(',') 321 .append(event.sensor.getName()); 322 if (event.values != null) { 323 for (int i = 0; i < event.values.length; i++) { 324 sb.append(',').append(event.values[i]); 325 } 326 } 327 return sb.append(']').toString(); 328 } 329 330 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 331 @Override 332 public void onReceive(Context context, Intent intent) { 333 if (PULSE_ACTION.equals(intent.getAction())) { 334 if (DEBUG) Log.d(mTag, "Received pulse intent"); 335 requestPulse(); 336 } 337 if (NOTIFICATION_PULSE_ACTION.equals(intent.getAction())) { 338 final long instance = intent.getLongExtra(EXTRA_INSTANCE, -1); 339 if (DEBUG) Log.d(mTag, "Received notification pulse intent instance=" + instance); 340 DozeLog.traceNotificationPulse(instance); 341 requestPulse(); 342 rescheduleNotificationPulse(mNotificationLightOn); 343 } 344 } 345 }; 346 347 private final DozeHost.Callback mHostCallback = new DozeHost.Callback() { 348 @Override 349 public void onNewNotifications() { 350 if (DEBUG) Log.d(mTag, "onNewNotifications"); 351 // noop for now 352 } 353 354 @Override 355 public void onBuzzBeepBlinked() { 356 if (DEBUG) Log.d(mTag, "onBuzzBeepBlinked"); 357 updateNotificationPulse(); 358 } 359 360 @Override 361 public void onNotificationLight(boolean on) { 362 if (DEBUG) Log.d(mTag, "onNotificationLight on=" + on); 363 if (mNotificationLightOn == on) return; 364 mNotificationLightOn = on; 365 if (mNotificationLightOn) { 366 updateNotificationPulse(); 367 } 368 } 369 370 @Override 371 public void onPowerSaveChanged(boolean active) { 372 mPowerSaveActive = active; 373 if (mPowerSaveActive && mDreaming) { 374 finishToSavePower(); 375 } 376 } 377 }; 378 379 private class TriggerSensor extends TriggerEventListener { 380 private final Sensor mSensor; 381 private final boolean mConfigured; 382 private final boolean mDebugVibrate; 383 384 private boolean mEnabled; 385 386 public TriggerSensor(int type, boolean configured, boolean debugVibrate) { 387 mSensor = mSensors.getDefaultSensor(type); 388 mConfigured = configured; 389 mDebugVibrate = debugVibrate; 390 } 391 392 public void setListening(boolean listen) { 393 if (!mConfigured || mSensor == null) return; 394 if (listen) { 395 mEnabled = mSensors.requestTriggerSensor(this, mSensor); 396 } else if (mEnabled) { 397 mSensors.cancelTriggerSensor(this, mSensor); 398 mEnabled = false; 399 } 400 } 401 402 @Override 403 public String toString() { 404 return new StringBuilder("{mEnabled=").append(mEnabled).append(", mConfigured=") 405 .append(mConfigured).append(", mDebugVibrate=").append(mDebugVibrate) 406 .append(", mSensor=").append(mSensor).append("}").toString(); 407 } 408 409 @Override 410 public void onTrigger(TriggerEvent event) { 411 mWakeLock.acquire(); 412 try { 413 if (DEBUG) Log.d(mTag, "onTrigger: " + triggerEventToString(event)); 414 if (mDebugVibrate) { 415 final Vibrator v = (Vibrator) mContext.getSystemService( 416 Context.VIBRATOR_SERVICE); 417 if (v != null) { 418 v.vibrate(1000, new AudioAttributes.Builder() 419 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 420 .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()); 421 } 422 } 423 424 requestPulse(); 425 setListening(true); // reregister, this sensor only fires once 426 427 // reset the notification pulse schedule, but only if we think we were not triggered 428 // by a notification-related vibration 429 final long timeSinceNotification = System.currentTimeMillis() - mNotificationPulseTime; 430 final boolean withinVibrationThreshold = 431 timeSinceNotification < mDozeParameters.getPickupVibrationThreshold(); 432 if (withinVibrationThreshold) { 433 if (DEBUG) Log.d(mTag, "Not resetting schedule, recent notification"); 434 } else { 435 resetNotificationResets(); 436 } 437 if (mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) { 438 DozeLog.tracePickupPulse(withinVibrationThreshold); 439 } 440 } finally { 441 mWakeLock.release(); 442 } 443 } 444 } 445} 446