DozeService.java revision 813552cc27cbeac366166cdda82fd813c8458bbf
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.Handler; 31import android.os.PowerManager; 32import android.os.Vibrator; 33import android.service.dreams.DreamService; 34import android.util.Log; 35import android.view.Display; 36 37import com.android.systemui.SystemUIApplication; 38import com.android.systemui.statusbar.phone.DozeParameters; 39import com.android.systemui.statusbar.phone.DozeParameters.PulseSchedule; 40 41import java.io.FileDescriptor; 42import java.io.PrintWriter; 43import java.util.Date; 44 45public class DozeService extends DreamService { 46 private static final String TAG = "DozeService"; 47 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 48 49 private static final String ACTION_BASE = "com.android.systemui.doze"; 50 private static final String PULSE_ACTION = ACTION_BASE + ".pulse"; 51 private static final String NOTIFICATION_PULSE_ACTION = ACTION_BASE + ".notification_pulse"; 52 private static final String EXTRA_INSTANCE = "instance"; 53 54 private final String mTag = String.format(TAG + ".%08x", hashCode()); 55 private final Context mContext = this; 56 private final Handler mHandler = new Handler(); 57 private final DozeParameters mDozeParameters = new DozeParameters(mContext); 58 59 private Host mHost; 60 private SensorManager mSensors; 61 private TriggerSensor mSigMotionSensor; 62 private TriggerSensor mPickupSensor; 63 private PowerManager mPowerManager; 64 private PowerManager.WakeLock mWakeLock; 65 private AlarmManager mAlarmManager; 66 private boolean mDreaming; 67 private boolean mBroadcastReceiverRegistered; 68 private boolean mDisplayStateSupported; 69 private int mDisplayStateWhenOn; 70 private boolean mNotificationLightOn; 71 private boolean mPowerSaveActive; 72 private long mNotificationPulseTime; 73 private int mScheduleResetsRemaining; 74 75 public DozeService() { 76 if (DEBUG) Log.d(mTag, "new DozeService()"); 77 setDebug(DEBUG); 78 } 79 80 @Override 81 protected void dumpOnHandler(FileDescriptor fd, PrintWriter pw, String[] args) { 82 super.dumpOnHandler(fd, pw, args); 83 pw.print(" mDreaming: "); pw.println(mDreaming); 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(Host.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 mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); 117 mDisplayStateSupported = mDozeParameters.getDisplayStateSupported(); 118 mDisplayStateWhenOn = mDisplayStateSupported ? Display.STATE_DOZE : Display.STATE_ON; 119 mDisplayOff.run(); 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 mPowerSaveActive = mHost != null && mHost.isPowerSaveActive(); 132 if (DEBUG) Log.d(mTag, "onDreamingStarted canDoze=" + canDoze() + " mPowerSaveActive=" 133 + mPowerSaveActive); 134 if (mPowerSaveActive) { 135 finishToSavePower(); 136 return; 137 } 138 mDreaming = true; 139 listenForPulseSignals(true); 140 rescheduleNotificationPulse(false /*predicate*/); // cancel any pending pulse alarms 141 requestDoze(); 142 } 143 144 public void stayAwake(long millis) { 145 if (mDreaming && millis > 0) { 146 if (DEBUG) Log.d(mTag, "stayAwake millis=" + millis); 147 mWakeLock.acquire(millis); 148 setDozeScreenState(mDisplayStateWhenOn); 149 rescheduleOff(millis); 150 } 151 } 152 153 private void rescheduleOff(long millis) { 154 if (DEBUG) Log.d(TAG, "rescheduleOff millis=" + millis); 155 mHandler.removeCallbacks(mDisplayOff); 156 mHandler.postDelayed(mDisplayOff, millis); 157 } 158 159 @Override 160 public void onDreamingStopped() { 161 if (DEBUG) Log.d(mTag, "onDreamingStopped isDozing=" + isDozing()); 162 super.onDreamingStopped(); 163 164 mDreaming = false; 165 if (mWakeLock.isHeld()) { 166 mWakeLock.release(); 167 } 168 listenForPulseSignals(false); 169 dozingStopped(); 170 mHandler.removeCallbacks(mDisplayOff); 171 } 172 173 @Override 174 public void startDozing() { 175 if (DEBUG) Log.d(mTag, "startDozing"); 176 super.startDozing(); 177 } 178 179 private void requestDoze() { 180 if (mHost != null) { 181 mHost.requestDoze(this); 182 } 183 } 184 185 private void requestPulse() { 186 if (mHost != null) { 187 mHost.requestPulse(this); 188 } 189 } 190 191 private void dozingStopped() { 192 if (mHost != null) { 193 mHost.dozingStopped(this); 194 } 195 } 196 197 private void finishToSavePower() { 198 Log.w(mTag, "Exiting ambient mode due to low power battery saver"); 199 finish(); 200 } 201 202 private void listenForPulseSignals(boolean listen) { 203 if (DEBUG) Log.d(mTag, "listenForPulseSignals: " + listen); 204 mSigMotionSensor.setListening(listen); 205 mPickupSensor.setListening(listen); 206 listenForBroadcasts(listen); 207 listenForNotifications(listen); 208 } 209 210 private void listenForBroadcasts(boolean listen) { 211 if (listen) { 212 final IntentFilter filter = new IntentFilter(PULSE_ACTION); 213 filter.addAction(NOTIFICATION_PULSE_ACTION); 214 mContext.registerReceiver(mBroadcastReceiver, filter); 215 mBroadcastReceiverRegistered = true; 216 } else { 217 if (mBroadcastReceiverRegistered) { 218 mContext.unregisterReceiver(mBroadcastReceiver); 219 } 220 mBroadcastReceiverRegistered = false; 221 } 222 } 223 224 private void listenForNotifications(boolean listen) { 225 if (mHost == null) return; 226 if (listen) { 227 resetNotificationResets(); 228 mHost.addCallback(mHostCallback); 229 } else { 230 mHost.removeCallback(mHostCallback); 231 } 232 } 233 234 private void resetNotificationResets() { 235 if (DEBUG) Log.d(TAG, "resetNotificationResets"); 236 mScheduleResetsRemaining = mDozeParameters.getPulseScheduleResets(); 237 } 238 239 private void updateNotificationPulse() { 240 if (DEBUG) Log.d(TAG, "updateNotificationPulse"); 241 if (!mDozeParameters.getPulseOnNotifications()) return; 242 if (mScheduleResetsRemaining <= 0) { 243 if (DEBUG) Log.d(TAG, "No more schedule resets remaining"); 244 return; 245 } 246 final long now = System.currentTimeMillis(); 247 if ((now - mNotificationPulseTime) < mDozeParameters.getPulseDuration()) { 248 if (DEBUG) Log.d(TAG, "Recently updated, not resetting schedule"); 249 return; 250 } 251 mScheduleResetsRemaining--; 252 if (DEBUG) Log.d(TAG, "mScheduleResetsRemaining = " + mScheduleResetsRemaining); 253 mNotificationPulseTime = now; 254 rescheduleNotificationPulse(true /*predicate*/); 255 } 256 257 private PendingIntent notificationPulseIntent(long instance) { 258 return PendingIntent.getBroadcast(mContext, 0, 259 new Intent(NOTIFICATION_PULSE_ACTION).setPackage(getPackageName()) 260 .putExtra(EXTRA_INSTANCE, instance), 261 PendingIntent.FLAG_UPDATE_CURRENT); 262 } 263 264 private void rescheduleNotificationPulse(boolean predicate) { 265 if (DEBUG) Log.d(TAG, "rescheduleNotificationPulse predicate=" + predicate); 266 final PendingIntent notificationPulseIntent = notificationPulseIntent(0); 267 mAlarmManager.cancel(notificationPulseIntent); 268 if (!predicate) { 269 if (DEBUG) Log.d(TAG, " don't reschedule: predicate is false"); 270 return; 271 } 272 final PulseSchedule schedule = mDozeParameters.getPulseSchedule(); 273 if (schedule == null) { 274 if (DEBUG) Log.d(TAG, " don't reschedule: schedule is null"); 275 return; 276 } 277 final long now = System.currentTimeMillis(); 278 final long time = schedule.getNextTime(now, mNotificationPulseTime); 279 if (time <= 0) { 280 if (DEBUG) Log.d(TAG, " don't reschedule: time is " + time); 281 return; 282 } 283 final long delta = time - now; 284 if (delta <= 0) { 285 if (DEBUG) Log.d(TAG, " don't reschedule: delta is " + delta); 286 return; 287 } 288 final long instance = time - mNotificationPulseTime; 289 if (DEBUG) Log.d(TAG, "Scheduling pulse " + instance + " in " + delta + "ms for " 290 + new Date(time)); 291 mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, time, notificationPulseIntent(instance)); 292 } 293 294 private static String triggerEventToString(TriggerEvent event) { 295 if (event == null) return null; 296 final StringBuilder sb = new StringBuilder("TriggerEvent[") 297 .append(event.timestamp).append(',') 298 .append(event.sensor.getName()); 299 if (event.values != null) { 300 for (int i = 0; i < event.values.length; i++) { 301 sb.append(',').append(event.values[i]); 302 } 303 } 304 return sb.append(']').toString(); 305 } 306 307 private final Runnable mDisplayOff = new Runnable() { 308 @Override 309 public void run() { 310 if (DEBUG) Log.d(TAG, "Display off"); 311 setDozeScreenState(Display.STATE_OFF); 312 } 313 }; 314 315 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 316 @Override 317 public void onReceive(Context context, Intent intent) { 318 if (PULSE_ACTION.equals(intent.getAction())) { 319 if (DEBUG) Log.d(mTag, "Received pulse intent"); 320 requestPulse(); 321 } 322 if (NOTIFICATION_PULSE_ACTION.equals(intent.getAction())) { 323 final long instance = intent.getLongExtra(EXTRA_INSTANCE, -1); 324 if (DEBUG) Log.d(mTag, "Received notification pulse intent instance=" + instance); 325 DozeLog.traceNotificationPulse(instance); 326 requestPulse(); 327 rescheduleNotificationPulse(mNotificationLightOn); 328 } 329 } 330 }; 331 332 private final Host.Callback mHostCallback = new Host.Callback() { 333 @Override 334 public void onNewNotifications() { 335 if (DEBUG) Log.d(mTag, "onNewNotifications"); 336 // noop for now 337 } 338 339 @Override 340 public void onBuzzBeepBlinked() { 341 if (DEBUG) Log.d(mTag, "onBuzzBeepBlinked"); 342 updateNotificationPulse(); 343 } 344 345 @Override 346 public void onNotificationLight(boolean on) { 347 if (DEBUG) Log.d(mTag, "onNotificationLight on=" + on); 348 if (mNotificationLightOn == on) return; 349 mNotificationLightOn = on; 350 if (mNotificationLightOn) { 351 updateNotificationPulse(); 352 } 353 } 354 355 @Override 356 public void onPowerSaveChanged(boolean active) { 357 mPowerSaveActive = active; 358 if (mPowerSaveActive && mDreaming) { 359 finishToSavePower(); 360 } 361 } 362 }; 363 364 public interface Host { 365 void addCallback(Callback callback); 366 void removeCallback(Callback callback); 367 void requestDoze(DozeService dozeService); 368 void requestPulse(DozeService dozeService); 369 void dozingStopped(DozeService dozeService); 370 boolean isPowerSaveActive(); 371 372 public interface Callback { 373 void onNewNotifications(); 374 void onBuzzBeepBlinked(); 375 void onNotificationLight(boolean on); 376 void onPowerSaveChanged(boolean active); 377 } 378 } 379 380 private class TriggerSensor extends TriggerEventListener { 381 private final Sensor mSensor; 382 private final boolean mConfigured; 383 private final boolean mDebugVibrate; 384 385 private boolean mEnabled; 386 387 public TriggerSensor(int type, boolean configured, boolean debugVibrate) { 388 mSensor = mSensors.getDefaultSensor(type); 389 mConfigured = configured; 390 mDebugVibrate = debugVibrate; 391 } 392 393 public void setListening(boolean listen) { 394 if (!mConfigured || mSensor == null) return; 395 if (listen) { 396 mEnabled = mSensors.requestTriggerSensor(this, mSensor); 397 } else if (mEnabled) { 398 mSensors.cancelTriggerSensor(this, mSensor); 399 mEnabled = false; 400 } 401 } 402 403 @Override 404 public String toString() { 405 return new StringBuilder("{mEnabled=").append(mEnabled).append(", mConfigured=") 406 .append(mConfigured).append(", mDebugVibrate=").append(mDebugVibrate) 407 .append(", mSensor=").append(mSensor).append("}").toString(); 408 } 409 410 @Override 411 public void onTrigger(TriggerEvent event) { 412 if (DEBUG) Log.d(mTag, "onTrigger: " + triggerEventToString(event)); 413 if (mDebugVibrate) { 414 final Vibrator v = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); 415 if (v != null) { 416 v.vibrate(1000, new AudioAttributes.Builder() 417 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 418 .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()); 419 } 420 } 421 requestPulse(); 422 setListening(true); // reregister, this sensor only fires once 423 424 // reset the notification pulse schedule, but only if we think we were not triggered 425 // by a notification-related vibration 426 final long timeSinceNotification = System.currentTimeMillis() - mNotificationPulseTime; 427 final boolean withinVibrationThreshold = 428 timeSinceNotification < mDozeParameters.getPickupVibrationThreshold(); 429 if (withinVibrationThreshold) { 430 if (DEBUG) Log.d(mTag, "Not resetting schedule, recent notification"); 431 } else { 432 resetNotificationResets(); 433 } 434 if (mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) { 435 DozeLog.tracePickupPulse(withinVibrationThreshold); 436 } 437 } 438 } 439} 440