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