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 import static com.android.systemui.Flags.dozeuiSchedulingAlarmsBackgroundExecution;
22 
23 import android.app.AlarmManager;
24 import android.content.Context;
25 import android.os.Handler;
26 import android.os.SystemClock;
27 import android.text.format.Formatter;
28 import android.util.Log;
29 
30 import com.android.systemui.dagger.qualifiers.Background;
31 import com.android.systemui.dagger.qualifiers.Main;
32 import com.android.systemui.doze.dagger.DozeScope;
33 import com.android.systemui.statusbar.phone.DozeParameters;
34 import com.android.systemui.util.AlarmTimeout;
35 import com.android.systemui.util.concurrency.DelayableExecutor;
36 import com.android.systemui.util.wakelock.WakeLock;
37 
38 import java.util.Calendar;
39 
40 import javax.inject.Inject;
41 
42 /**
43  * The policy controlling doze.
44  */
45 @DozeScope
46 public class DozeUi implements DozeMachine.Part {
47     private static final long TIME_TICK_DEADLINE_MILLIS = 90 * 1000; // 1.5min
48     private final Context mContext;
49     private final DozeHost mHost;
50     private final Handler mHandler;
51     private final WakeLock mWakeLock;
52     private DozeMachine mMachine;
53     private final AlarmTimeout mTimeTicker;
54     private final boolean mCanAnimateTransition;
55     private final DozeParameters mDozeParameters;
56     private final DozeLog mDozeLog;
57     private final DelayableExecutor mBgExecutor;
58     private long mLastTimeTickElapsed = 0;
59     // If time tick is scheduled and there's not a pending runnable to cancel:
60     private volatile boolean mTimeTickScheduled;
61     private final Runnable mCancelTimeTickerRunnable =  new Runnable() {
62         @Override
63         public void run() {
64             mDozeLog.tracePendingUnscheduleTimeTick(false, mTimeTickScheduled);
65             if (!mTimeTickScheduled) {
66                 mTimeTicker.cancel();
67             }
68         }
69     };
70 
71     @Inject
DozeUi(Context context, AlarmManager alarmManager, WakeLock wakeLock, DozeHost host, @Main Handler handler, @Background Handler bgHandler, DozeParameters params, @Background DelayableExecutor bgExecutor, DozeLog dozeLog)72     public DozeUi(Context context, AlarmManager alarmManager,
73             WakeLock wakeLock, DozeHost host, @Main Handler handler,
74             @Background Handler bgHandler,
75             DozeParameters params,
76             @Background DelayableExecutor bgExecutor,
77             DozeLog dozeLog) {
78         mContext = context;
79         mWakeLock = wakeLock;
80         mHost = host;
81         mHandler = handler;
82         mBgExecutor = bgExecutor;
83         mCanAnimateTransition = !params.getDisplayNeedsBlanking();
84         mDozeParameters = params;
85         if (dozeuiSchedulingAlarmsBackgroundExecution()) {
86             mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick",
87                     bgHandler);
88         } else {
89             mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick",
90                     handler);
91         }
92         mDozeLog = dozeLog;
93     }
94 
95     @Override
setDozeMachine(DozeMachine dozeMachine)96     public void setDozeMachine(DozeMachine dozeMachine) {
97         mMachine = dozeMachine;
98     }
99 
pulseWhileDozing(int reason)100     private void pulseWhileDozing(int reason) {
101         mHost.pulseWhileDozing(
102                 new DozeHost.PulseCallback() {
103                     @Override
104                     public void onPulseStarted() {
105                         try {
106                             mMachine.requestState(
107                                     reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH
108                                             ? DozeMachine.State.DOZE_PULSING_BRIGHT
109                                             : DozeMachine.State.DOZE_PULSING);
110                         } catch (IllegalStateException e) {
111                             // It's possible that the pulse was asynchronously cancelled while
112                             // we were waiting for it to start (under stress conditions.)
113                             // In those cases we should just ignore it. b/127657926
114                         }
115                     }
116 
117                     @Override
118                     public void onPulseFinished() {
119                         mMachine.requestState(DozeMachine.State.DOZE_PULSE_DONE);
120                     }
121                 }, reason);
122     }
123 
124     @Override
transitionTo(DozeMachine.State oldState, DozeMachine.State newState)125     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
126         switch (newState) {
127             case DOZE_AOD:
128             case DOZE_AOD_DOCKED:
129                 if (oldState == DOZE_AOD_PAUSED || oldState == DOZE) {
130                     // Whenever turning on the display, it's necessary to push a new frame.
131                     // The display buffers will be empty and need to be filled.
132                     mHost.dozeTimeTick();
133                     // The first frame may arrive when the display isn't ready yet.
134                     mHandler.postDelayed(mWakeLock.wrap(mHost::dozeTimeTick), 500);
135                 }
136                 scheduleTimeTick();
137                 break;
138             case DOZE_AOD_PAUSING:
139                 scheduleTimeTick();
140                 break;
141             case DOZE:
142             case DOZE_AOD_PAUSED:
143             case DOZE_SUSPEND_TRIGGERS:
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 (mTimeTickScheduled) {
180             return;
181         }
182         mTimeTickScheduled = true;
183 
184         long time = System.currentTimeMillis();
185         long delta = roundToNextMinute(time) - System.currentTimeMillis();
186         boolean scheduled = mTimeTicker.schedule(delta, AlarmTimeout.MODE_RESCHEDULE_IF_SCHEDULED);
187         if (scheduled) {
188             mDozeLog.traceTimeTickScheduled(time, time + delta);
189         }
190         mLastTimeTickElapsed = SystemClock.elapsedRealtime();
191     }
192 
unscheduleTimeTick()193     private void unscheduleTimeTick() {
194         if (!mTimeTickScheduled) {
195             return;
196         }
197         mTimeTickScheduled = false;
198         mDozeLog.tracePendingUnscheduleTimeTick(true, mTimeTickScheduled);
199         mBgExecutor.execute(mCancelTimeTickerRunnable);
200     }
201 
verifyLastTimeTick()202     private void verifyLastTimeTick() {
203         long millisSinceLastTick = SystemClock.elapsedRealtime() - mLastTimeTickElapsed;
204         if (millisSinceLastTick > TIME_TICK_DEADLINE_MILLIS) {
205             String delay = Formatter.formatShortElapsedTime(mContext, millisSinceLastTick);
206             mDozeLog.traceMissedTick(delay);
207             Log.e(DozeMachine.TAG, "Missed AOD time tick by " + delay);
208         }
209     }
210 
roundToNextMinute(long timeInMillis)211     private long roundToNextMinute(long timeInMillis) {
212         Calendar calendar = Calendar.getInstance();
213         calendar.setTimeInMillis(timeInMillis);
214         calendar.set(Calendar.MILLISECOND, 0);
215         calendar.set(Calendar.SECOND, 0);
216         calendar.add(Calendar.MINUTE, 1);
217 
218         return calendar.getTimeInMillis();
219     }
220 
onTimeTick()221     private void onTimeTick() {
222         verifyLastTimeTick();
223 
224         mHost.dozeTimeTick();
225 
226         // Keep wakelock until a frame has been pushed.
227         mHandler.post(mWakeLock.wrap(() -> {}));
228 
229         mTimeTickScheduled = false;
230         scheduleTimeTick();
231     }
232 }
233