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 static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
20
21import android.app.AlarmManager;
22import android.content.Context;
23import android.os.Handler;
24import android.os.SystemClock;
25import android.text.format.Formatter;
26import android.util.Log;
27
28import com.android.internal.annotations.VisibleForTesting;
29import com.android.keyguard.KeyguardUpdateMonitor;
30import com.android.keyguard.KeyguardUpdateMonitorCallback;
31import com.android.systemui.statusbar.phone.DozeParameters;
32import com.android.systemui.util.AlarmTimeout;
33import com.android.systemui.util.wakelock.WakeLock;
34
35import java.util.Calendar;
36import java.util.GregorianCalendar;
37
38/**
39 * The policy controlling doze.
40 */
41public class DozeUi implements DozeMachine.Part {
42
43    private static final long TIME_TICK_DEADLINE_MILLIS = 90 * 1000; // 1.5min
44    private final Context mContext;
45    private final DozeHost mHost;
46    private final Handler mHandler;
47    private final WakeLock mWakeLock;
48    private final DozeMachine mMachine;
49    private final AlarmTimeout mTimeTicker;
50    private final boolean mCanAnimateTransition;
51    private final DozeParameters mDozeParameters;
52
53    private boolean mKeyguardShowing;
54    private final KeyguardUpdateMonitorCallback mKeyguardVisibilityCallback =
55            new KeyguardUpdateMonitorCallback() {
56
57                @Override
58                public void onKeyguardVisibilityChanged(boolean showing) {
59                    mKeyguardShowing = showing;
60                    updateAnimateScreenOff();
61                }
62            };
63
64    private long mLastTimeTickElapsed = 0;
65
66    public DozeUi(Context context, AlarmManager alarmManager, DozeMachine machine,
67            WakeLock wakeLock, DozeHost host, Handler handler,
68            DozeParameters params, KeyguardUpdateMonitor keyguardUpdateMonitor) {
69        mContext = context;
70        mMachine = machine;
71        mWakeLock = wakeLock;
72        mHost = host;
73        mHandler = handler;
74        mCanAnimateTransition = !params.getDisplayNeedsBlanking();
75        mDozeParameters = params;
76        mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick", handler);
77        keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
78    }
79
80    /**
81     * Decide if we're taking over the screen-off animation
82     * when the device was configured to skip doze after screen off.
83     */
84    private void updateAnimateScreenOff() {
85        if (mCanAnimateTransition) {
86            final boolean controlScreenOff = mDozeParameters.getAlwaysOn() && mKeyguardShowing;
87            mDozeParameters.setControlScreenOffAnimation(controlScreenOff);
88            mHost.setAnimateScreenOff(controlScreenOff);
89        }
90    }
91
92    private void pulseWhileDozing(int reason) {
93        mHost.pulseWhileDozing(
94                new DozeHost.PulseCallback() {
95                    @Override
96                    public void onPulseStarted() {
97                        mMachine.requestState(DozeMachine.State.DOZE_PULSING);
98                    }
99
100                    @Override
101                    public void onPulseFinished() {
102                        mMachine.requestState(DozeMachine.State.DOZE_PULSE_DONE);
103                    }
104                }, reason);
105    }
106
107    @Override
108    public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
109        switch (newState) {
110            case DOZE_AOD:
111                if (oldState == DOZE_AOD_PAUSED) {
112                    // Whenever turning on the display, it's necessary to push a new frame.
113                    // The display buffers will be empty and need to be filled.
114                    mHost.dozeTimeTick();
115                    // The first frame may arrive when the display isn't ready yet.
116                    mHandler.postDelayed(mWakeLock.wrap(mHost::dozeTimeTick), 100);
117                    // The the delayed frame may arrive when the display isn't ready yet either.
118                    mHandler.postDelayed(mWakeLock.wrap(mHost::dozeTimeTick), 1000);
119                }
120                scheduleTimeTick();
121                break;
122            case DOZE_AOD_PAUSING:
123                scheduleTimeTick();
124                break;
125            case DOZE:
126            case DOZE_AOD_PAUSED:
127                unscheduleTimeTick();
128                break;
129            case DOZE_REQUEST_PULSE:
130                pulseWhileDozing(mMachine.getPulseReason());
131                break;
132            case INITIALIZED:
133                mHost.startDozing();
134                break;
135            case FINISH:
136                mHost.stopDozing();
137                unscheduleTimeTick();
138                break;
139        }
140        updateAnimateWakeup(newState);
141    }
142
143    private void updateAnimateWakeup(DozeMachine.State state) {
144        switch (state) {
145            case DOZE_REQUEST_PULSE:
146            case DOZE_PULSING:
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
159    private void scheduleTimeTick() {
160        if (mTimeTicker.isScheduled()) {
161            return;
162        }
163
164        long delta = roundToNextMinute(System.currentTimeMillis()) - System.currentTimeMillis();
165        mTimeTicker.schedule(delta, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
166        mLastTimeTickElapsed = SystemClock.elapsedRealtime();
167    }
168
169    private void unscheduleTimeTick() {
170        if (!mTimeTicker.isScheduled()) {
171            return;
172        }
173        verifyLastTimeTick();
174        mTimeTicker.cancel();
175    }
176
177    private void verifyLastTimeTick() {
178        long millisSinceLastTick = SystemClock.elapsedRealtime() - mLastTimeTickElapsed;
179        if (millisSinceLastTick > TIME_TICK_DEADLINE_MILLIS) {
180            String delay = Formatter.formatShortElapsedTime(mContext, millisSinceLastTick);
181            DozeLog.traceMissedTick(delay);
182            Log.e(DozeMachine.TAG, "Missed AOD time tick by " + delay);
183        }
184    }
185
186    private long roundToNextMinute(long timeInMillis) {
187        Calendar calendar = GregorianCalendar.getInstance();
188        calendar.setTimeInMillis(timeInMillis);
189        calendar.set(Calendar.MILLISECOND, 0);
190        calendar.set(Calendar.SECOND, 0);
191        calendar.add(Calendar.MINUTE, 1);
192
193        return calendar.getTimeInMillis();
194    }
195
196    private void onTimeTick() {
197        verifyLastTimeTick();
198
199        mHost.dozeTimeTick();
200
201        // Keep wakelock until a frame has been pushed.
202        mHandler.post(mWakeLock.wrap(() -> {}));
203
204        scheduleTimeTick();
205    }
206
207    @VisibleForTesting
208    KeyguardUpdateMonitorCallback getKeyguardCallback() {
209        return mKeyguardVisibilityCallback;
210    }
211}
212