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