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