1 /*
2  * Copyright (C) 2016 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 static com.android.systemui.doze.DozeMachine.State.DOZE;
20 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
21 
22 import android.app.AlarmManager;
23 import android.content.Context;
24 import android.os.Handler;
25 import android.os.SystemClock;
26 import android.text.format.Formatter;
27 import android.util.Log;
28 
29 import com.android.keyguard.KeyguardUpdateMonitor;
30 import com.android.keyguard.KeyguardUpdateMonitorCallback;
31 import com.android.systemui.dagger.qualifiers.Main;
32 import com.android.systemui.doze.dagger.DozeScope;
33 import com.android.systemui.plugins.statusbar.StatusBarStateController;
34 import com.android.systemui.statusbar.phone.DozeParameters;
35 import com.android.systemui.util.AlarmTimeout;
36 import com.android.systemui.util.wakelock.WakeLock;
37 
38 import java.util.Calendar;
39 
40 import javax.inject.Inject;
41 
42 import dagger.Lazy;
43 
44 /**
45  * The policy controlling doze.
46  */
47 @DozeScope
48 public class DozeUi implements DozeMachine.Part {
49     // if enabled, calls dozeTimeTick() whenever the time changes:
50     private static final boolean BURN_IN_TESTING_ENABLED = false;
51     private static final long TIME_TICK_DEADLINE_MILLIS = 90 * 1000; // 1.5min
52     private final Context mContext;
53     private final DozeHost mHost;
54     private final Handler mHandler;
55     private final WakeLock mWakeLock;
56     private DozeMachine mMachine;
57     private final AlarmTimeout mTimeTicker;
58     private final boolean mCanAnimateTransition;
59     private final DozeParameters mDozeParameters;
60     private final DozeLog mDozeLog;
61     private final Lazy<StatusBarStateController> mStatusBarStateController;
62     private final KeyguardUpdateMonitorCallback mKeyguardVisibilityCallback =
63             new KeyguardUpdateMonitorCallback() {
64                 @Override
65                 public void onTimeChanged() {
66                     if (BURN_IN_TESTING_ENABLED && mStatusBarStateController != null
67                             && mStatusBarStateController.get().isDozing()) {
68                         // update whenever the time changes for manual burn in testing
69                         mHost.dozeTimeTick();
70 
71                         // Keep wakelock until a frame has been pushed.
72                         mHandler.post(mWakeLock.wrap(() -> {}));
73                     }
74                 }
75             };
76 
77     private long mLastTimeTickElapsed = 0;
78 
79     @Inject
DozeUi(Context context, AlarmManager alarmManager, WakeLock wakeLock, DozeHost host, @Main Handler handler, DozeParameters params, KeyguardUpdateMonitor keyguardUpdateMonitor, DozeLog dozeLog, Lazy<StatusBarStateController> statusBarStateController)80     public DozeUi(Context context, AlarmManager alarmManager,
81             WakeLock wakeLock, DozeHost host, @Main Handler handler,
82             DozeParameters params, KeyguardUpdateMonitor keyguardUpdateMonitor,
83             DozeLog dozeLog, Lazy<StatusBarStateController> statusBarStateController) {
84         mContext = context;
85         mWakeLock = wakeLock;
86         mHost = host;
87         mHandler = handler;
88         mCanAnimateTransition = !params.getDisplayNeedsBlanking();
89         mDozeParameters = params;
90         mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick", handler);
91         keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
92         mDozeLog = dozeLog;
93         mStatusBarStateController = statusBarStateController;
94     }
95 
96     @Override
setDozeMachine(DozeMachine dozeMachine)97     public void setDozeMachine(DozeMachine dozeMachine) {
98         mMachine = dozeMachine;
99     }
100 
pulseWhileDozing(int reason)101     private void pulseWhileDozing(int reason) {
102         mHost.pulseWhileDozing(
103                 new DozeHost.PulseCallback() {
104                     @Override
105                     public void onPulseStarted() {
106                         try {
107                             mMachine.requestState(
108                                     reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH
109                                             ? DozeMachine.State.DOZE_PULSING_BRIGHT
110                                             : DozeMachine.State.DOZE_PULSING);
111                         } catch (IllegalStateException e) {
112                             // It's possible that the pulse was asynchronously cancelled while
113                             // we were waiting for it to start (under stress conditions.)
114                             // In those cases we should just ignore it. b/127657926
115                         }
116                     }
117 
118                     @Override
119                     public void onPulseFinished() {
120                         mMachine.requestState(DozeMachine.State.DOZE_PULSE_DONE);
121                     }
122                 }, reason);
123     }
124 
125     @Override
transitionTo(DozeMachine.State oldState, DozeMachine.State newState)126     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
127         switch (newState) {
128             case DOZE_AOD:
129             case DOZE_AOD_DOCKED:
130                 if (oldState == DOZE_AOD_PAUSED || oldState == DOZE) {
131                     // Whenever turning on the display, it's necessary to push a new frame.
132                     // The display buffers will be empty and need to be filled.
133                     mHost.dozeTimeTick();
134                     // The first frame may arrive when the display isn't ready yet.
135                     mHandler.postDelayed(mWakeLock.wrap(mHost::dozeTimeTick), 500);
136                 }
137                 scheduleTimeTick();
138                 break;
139             case DOZE_AOD_PAUSING:
140                 scheduleTimeTick();
141                 break;
142             case DOZE:
143             case DOZE_AOD_PAUSED:
144                 unscheduleTimeTick();
145                 break;
146             case DOZE_REQUEST_PULSE:
147                 scheduleTimeTick();
148                 pulseWhileDozing(mMachine.getPulseReason());
149                 break;
150             case INITIALIZED:
151                 mHost.startDozing();
152                 break;
153             case FINISH:
154                 mHost.stopDozing();
155                 unscheduleTimeTick();
156                 break;
157         }
158         updateAnimateWakeup(newState);
159     }
160 
updateAnimateWakeup(DozeMachine.State state)161     private void updateAnimateWakeup(DozeMachine.State state) {
162         switch (state) {
163             case DOZE_REQUEST_PULSE:
164             case DOZE_PULSING:
165             case DOZE_PULSING_BRIGHT:
166             case DOZE_PULSE_DONE:
167                 mHost.setAnimateWakeup(true);
168                 break;
169             case FINISH:
170                 // Keep current state.
171                 break;
172             default:
173                 mHost.setAnimateWakeup(mCanAnimateTransition && mDozeParameters.getAlwaysOn());
174                 break;
175         }
176     }
177 
scheduleTimeTick()178     private void scheduleTimeTick() {
179         if (mTimeTicker.isScheduled()) {
180             return;
181         }
182 
183         long time = System.currentTimeMillis();
184         long delta = roundToNextMinute(time) - System.currentTimeMillis();
185         boolean scheduled = mTimeTicker.schedule(delta, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
186         if (scheduled) {
187             mDozeLog.traceTimeTickScheduled(time, time + delta);
188         }
189         mLastTimeTickElapsed = SystemClock.elapsedRealtime();
190     }
191 
unscheduleTimeTick()192     private void unscheduleTimeTick() {
193         if (!mTimeTicker.isScheduled()) {
194             return;
195         }
196         verifyLastTimeTick();
197         mTimeTicker.cancel();
198     }
199 
verifyLastTimeTick()200     private void verifyLastTimeTick() {
201         long millisSinceLastTick = SystemClock.elapsedRealtime() - mLastTimeTickElapsed;
202         if (millisSinceLastTick > TIME_TICK_DEADLINE_MILLIS) {
203             String delay = Formatter.formatShortElapsedTime(mContext, millisSinceLastTick);
204             mDozeLog.traceMissedTick(delay);
205             Log.e(DozeMachine.TAG, "Missed AOD time tick by " + delay);
206         }
207     }
208 
roundToNextMinute(long timeInMillis)209     private long roundToNextMinute(long timeInMillis) {
210         Calendar calendar = Calendar.getInstance();
211         calendar.setTimeInMillis(timeInMillis);
212         calendar.set(Calendar.MILLISECOND, 0);
213         calendar.set(Calendar.SECOND, 0);
214         calendar.add(Calendar.MINUTE, 1);
215 
216         return calendar.getTimeInMillis();
217     }
218 
onTimeTick()219     private void onTimeTick() {
220         verifyLastTimeTick();
221 
222         mHost.dozeTimeTick();
223 
224         // Keep wakelock until a frame has been pushed.
225         mHandler.post(mWakeLock.wrap(() -> {}));
226 
227         scheduleTimeTick();
228     }
229 }
230