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 17package com.android.systemui.doze; 18 19import android.app.AlarmManager; 20import android.content.Context; 21import android.os.Handler; 22import android.os.SystemClock; 23import android.text.format.Formatter; 24import android.util.Log; 25 26import com.android.systemui.util.wakelock.WakeLock; 27 28import java.util.Calendar; 29import java.util.GregorianCalendar; 30 31/** 32 * The policy controlling doze. 33 */ 34public class DozeUi implements DozeMachine.Part { 35 36 private static final long TIME_TICK_DEADLINE_MILLIS = 90 * 1000; // 1.5min 37 private final Context mContext; 38 private final AlarmManager mAlarmManager; 39 private final DozeHost mHost; 40 private final Handler mHandler; 41 private final WakeLock mWakeLock; 42 private final DozeMachine mMachine; 43 private final AlarmManager.OnAlarmListener mTimeTick; 44 45 private boolean mTimeTickScheduled = false; 46 private long mLastTimeTickElapsed = 0; 47 48 public DozeUi(Context context, AlarmManager alarmManager, DozeMachine machine, 49 WakeLock wakeLock, DozeHost host, Handler handler) { 50 mContext = context; 51 mAlarmManager = alarmManager; 52 mMachine = machine; 53 mWakeLock = wakeLock; 54 mHost = host; 55 mHandler = handler; 56 57 mTimeTick = this::onTimeTick; 58 } 59 60 private void pulseWhileDozing(int reason) { 61 mHost.pulseWhileDozing( 62 new DozeHost.PulseCallback() { 63 @Override 64 public void onPulseStarted() { 65 mMachine.requestState(DozeMachine.State.DOZE_PULSING); 66 } 67 68 @Override 69 public void onPulseFinished() { 70 mMachine.requestState(DozeMachine.State.DOZE_PULSE_DONE); 71 } 72 }, reason); 73 } 74 75 @Override 76 public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { 77 switch (newState) { 78 case DOZE_AOD: 79 scheduleTimeTick(); 80 break; 81 case DOZE: 82 case DOZE_AOD_PAUSED: 83 unscheduleTimeTick(); 84 break; 85 case DOZE_REQUEST_PULSE: 86 pulseWhileDozing(mMachine.getPulseReason()); 87 break; 88 case DOZE_PULSE_DONE: 89 mHost.abortPulsing(); 90 case INITIALIZED: 91 mHost.startDozing(); 92 break; 93 case FINISH: 94 mHost.stopDozing(); 95 unscheduleTimeTick(); 96 break; 97 } 98 mHost.setAnimateWakeup(shouldAnimateWakeup(newState)); 99 } 100 101 private boolean shouldAnimateWakeup(DozeMachine.State state) { 102 switch (state) { 103 case DOZE_AOD: 104 case DOZE_REQUEST_PULSE: 105 case DOZE_PULSING: 106 case DOZE_PULSE_DONE: 107 return true; 108 default: 109 return false; 110 } 111 } 112 113 private void scheduleTimeTick() { 114 if (mTimeTickScheduled) { 115 return; 116 } 117 118 long delta = roundToNextMinute(System.currentTimeMillis()) - System.currentTimeMillis(); 119 mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, 120 SystemClock.elapsedRealtime() + delta, "doze_time_tick", mTimeTick, mHandler); 121 122 mTimeTickScheduled = true; 123 mLastTimeTickElapsed = SystemClock.elapsedRealtime(); 124 } 125 126 private void unscheduleTimeTick() { 127 if (!mTimeTickScheduled) { 128 return; 129 } 130 verifyLastTimeTick(); 131 mAlarmManager.cancel(mTimeTick); 132 } 133 134 private void verifyLastTimeTick() { 135 long millisSinceLastTick = SystemClock.elapsedRealtime() - mLastTimeTickElapsed; 136 if (millisSinceLastTick > TIME_TICK_DEADLINE_MILLIS) { 137 String delay = Formatter.formatShortElapsedTime(mContext, millisSinceLastTick); 138 DozeLog.traceMissedTick(delay); 139 Log.e(DozeMachine.TAG, "Missed AOD time tick by " + delay); 140 } 141 } 142 143 private long roundToNextMinute(long timeInMillis) { 144 Calendar calendar = GregorianCalendar.getInstance(); 145 calendar.setTimeInMillis(timeInMillis); 146 calendar.set(Calendar.MILLISECOND, 0); 147 calendar.set(Calendar.SECOND, 0); 148 calendar.add(Calendar.MINUTE, 1); 149 150 return calendar.getTimeInMillis(); 151 } 152 153 private void onTimeTick() { 154 if (!mTimeTickScheduled) { 155 // Alarm was canceled, but we still got the callback. Ignore. 156 return; 157 } 158 verifyLastTimeTick(); 159 160 mHost.dozeTimeTick(); 161 162 // Keep wakelock until a frame has been pushed. 163 mHandler.post(mWakeLock.wrap(() -> {})); 164 165 mTimeTickScheduled = false; 166 scheduleTimeTick(); 167 } 168} 169