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