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