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