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