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