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