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 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 */ 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 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 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 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 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 181 private void unscheduleTimeTick() { 182 if (!mTimeTicker.isScheduled()) { 183 return; 184 } 185 verifyLastTimeTick(); 186 mTimeTicker.cancel(); 187 } 188 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 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 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 220 KeyguardUpdateMonitorCallback getKeyguardCallback() { 221 return mKeyguardVisibilityCallback; 222 } 223 } 224