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