1 /*
2  * Copyright (C) 2017 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;
21 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
22 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
23 import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE;
24 
25 import android.os.Handler;
26 import android.util.Log;
27 import android.view.Display;
28 
29 import androidx.annotation.Nullable;
30 
31 import com.android.keyguard.KeyguardUpdateMonitor;
32 import com.android.systemui.biometrics.AuthController;
33 import com.android.systemui.biometrics.UdfpsController;
34 import com.android.systemui.dagger.qualifiers.Main;
35 import com.android.systemui.doze.dagger.DozeScope;
36 import com.android.systemui.doze.dagger.WrappedService;
37 import com.android.systemui.statusbar.phone.DozeParameters;
38 import com.android.systemui.util.wakelock.SettableWakeLock;
39 import com.android.systemui.util.wakelock.WakeLock;
40 
41 import javax.inject.Inject;
42 import javax.inject.Provider;
43 
44 /**
45  * Controls the screen when dozing.
46  */
47 @DozeScope
48 public class DozeScreenState implements DozeMachine.Part {
49 
50     private static final boolean DEBUG = DozeService.DEBUG;
51     private static final String TAG = "DozeScreenState";
52 
53     /**
54      * Delay entering low power mode when animating to make sure that we'll have
55      * time to move all elements into their final positions while still at 60 fps.
56      */
57     private static final int ENTER_DOZE_DELAY = 4000;
58     /**
59      * Hide wallpaper earlier when entering low power mode. The gap between
60      * hiding the wallpaper and changing the display mode is necessary to hide
61      * the black frame that's inherent to hardware specs.
62      */
63     public static final int ENTER_DOZE_HIDE_WALLPAPER_DELAY = 2500;
64 
65     /**
66      * Add an extra delay to the transition to DOZE when udfps is current activated before
67      * the display state transitions from ON => DOZE.
68      */
69     public static final int UDFPS_DISPLAY_STATE_DELAY = 1200;
70 
71     private final DozeMachine.Service mDozeService;
72     private final Handler mHandler;
73     private final Runnable mApplyPendingScreenState = this::applyPendingScreenState;
74     private final DozeParameters mParameters;
75     private final DozeHost mDozeHost;
76     private final AuthController mAuthController;
77     private final Provider<UdfpsController> mUdfpsControllerProvider;
78     @Nullable private UdfpsController mUdfpsController;
79     private final DozeLog mDozeLog;
80     private final DozeScreenBrightness mDozeScreenBrightness;
81 
82     private int mPendingScreenState = Display.STATE_UNKNOWN;
83     private SettableWakeLock mWakeLock;
84 
85     @Inject
DozeScreenState( @rappedService DozeMachine.Service service, @Main Handler handler, DozeHost host, DozeParameters parameters, WakeLock wakeLock, AuthController authController, Provider<UdfpsController> udfpsControllerProvider, DozeLog dozeLog, DozeScreenBrightness dozeScreenBrightness)86     public DozeScreenState(
87             @WrappedService DozeMachine.Service service,
88             @Main Handler handler,
89             DozeHost host,
90             DozeParameters parameters,
91             WakeLock wakeLock,
92             AuthController authController,
93             Provider<UdfpsController> udfpsControllerProvider,
94             DozeLog dozeLog,
95             DozeScreenBrightness dozeScreenBrightness) {
96         mDozeService = service;
97         mHandler = handler;
98         mParameters = parameters;
99         mDozeHost = host;
100         mWakeLock = new SettableWakeLock(wakeLock, TAG);
101         mAuthController = authController;
102         mUdfpsControllerProvider = udfpsControllerProvider;
103         mDozeLog = dozeLog;
104         mDozeScreenBrightness = dozeScreenBrightness;
105 
106         updateUdfpsController();
107         if (mUdfpsController == null) {
108             mAuthController.addCallback(mAuthControllerCallback);
109         }
110     }
111 
updateUdfpsController()112     private void updateUdfpsController() {
113         if (mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) {
114             mUdfpsController = mUdfpsControllerProvider.get();
115         } else {
116             mUdfpsController = null;
117         }
118     }
119 
120     @Override
destroy()121     public void destroy() {
122         mAuthController.removeCallback(mAuthControllerCallback);
123     }
124 
125     @Override
transitionTo(DozeMachine.State oldState, DozeMachine.State newState)126     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
127         int screenState = newState.screenState(mParameters);
128         mDozeHost.cancelGentleSleep();
129 
130         if (newState == DozeMachine.State.FINISH) {
131             // Make sure not to apply the screen state after DozeService was destroyed.
132             mPendingScreenState = Display.STATE_UNKNOWN;
133             mHandler.removeCallbacks(mApplyPendingScreenState);
134 
135             applyScreenState(screenState);
136             mWakeLock.setAcquired(false);
137             return;
138         }
139 
140         if (screenState == Display.STATE_UNKNOWN) {
141             // We'll keep it in the existing state
142             return;
143         }
144 
145         final boolean messagePending = mHandler.hasCallbacks(mApplyPendingScreenState);
146         final boolean pulseEnding = oldState == DOZE_PULSE_DONE && newState.isAlwaysOn();
147         final boolean turningOn = (oldState == DOZE_AOD_PAUSED || oldState == DOZE)
148                 && newState.isAlwaysOn();
149         final boolean turningOff = (oldState.isAlwaysOn() && newState == DOZE)
150                 || (oldState == DOZE_AOD_PAUSING && newState == DOZE_AOD_PAUSED);
151         final boolean justInitialized = oldState == DozeMachine.State.INITIALIZED;
152         if (messagePending || justInitialized || pulseEnding || turningOn) {
153             // During initialization, we hide the navigation bar. That is however only applied after
154             // a traversal; setting the screen state here is immediate however, so it can happen
155             // that the screen turns on again before the navigation bar is hidden. To work around
156             // that, wait for a traversal to happen before applying the initial screen state.
157             mPendingScreenState = screenState;
158 
159             // Delay screen state transitions even longer while animations are running.
160             boolean shouldDelayTransitionEnteringDoze = newState == DOZE_AOD
161                     && mParameters.shouldControlScreenOff() && !turningOn;
162 
163             // Delay screen state transition longer if UDFPS is actively authenticating a fp
164             boolean shouldDelayTransitionForUDFPS = newState == DOZE_AOD
165                     && mUdfpsController != null && mUdfpsController.isFingerDown();
166 
167             if (shouldDelayTransitionEnteringDoze || shouldDelayTransitionForUDFPS) {
168                 mWakeLock.setAcquired(true);
169             }
170 
171             if (!messagePending) {
172                 if (DEBUG) {
173                     Log.d(TAG, "Display state changed to " + screenState + " delayed by "
174                             + (shouldDelayTransitionEnteringDoze ? ENTER_DOZE_DELAY : 1));
175                 }
176 
177                 if (shouldDelayTransitionEnteringDoze) {
178                     mHandler.postDelayed(mApplyPendingScreenState, ENTER_DOZE_DELAY);
179                 } else if (shouldDelayTransitionForUDFPS) {
180                     mDozeLog.traceDisplayStateDelayedByUdfps(mPendingScreenState);
181                     mHandler.postDelayed(mApplyPendingScreenState, UDFPS_DISPLAY_STATE_DELAY);
182                 } else {
183                     mHandler.post(mApplyPendingScreenState);
184                 }
185             } else if (DEBUG) {
186                 Log.d(TAG, "Pending display state change to " + screenState);
187             }
188         } else if (turningOff) {
189             mDozeHost.prepareForGentleSleep(() -> applyScreenState(screenState));
190         } else {
191             applyScreenState(screenState);
192         }
193     }
194 
applyPendingScreenState()195     private void applyPendingScreenState() {
196         if (mUdfpsController != null && mUdfpsController.isFingerDown()) {
197             mDozeLog.traceDisplayStateDelayedByUdfps(mPendingScreenState);
198             mHandler.postDelayed(mApplyPendingScreenState, UDFPS_DISPLAY_STATE_DELAY);
199             return;
200         }
201 
202         applyScreenState(mPendingScreenState);
203         mPendingScreenState = Display.STATE_UNKNOWN;
204     }
205 
applyScreenState(int screenState)206     private void applyScreenState(int screenState) {
207         if (screenState != Display.STATE_UNKNOWN) {
208             if (DEBUG) Log.d(TAG, "setDozeScreenState(" + screenState + ")");
209             mDozeService.setDozeScreenState(screenState);
210             if (screenState == Display.STATE_DOZE) {
211                 // If we're entering doze, update the doze screen brightness. We might have been
212                 // clamping it to the dim brightness during the screen off animation, and we should
213                 // now change it to the brightness we actually want according to the sensor.
214                 mDozeScreenBrightness.updateBrightnessAndReady(false /* force */);
215             }
216             mPendingScreenState = Display.STATE_UNKNOWN;
217             mWakeLock.setAcquired(false);
218         }
219     }
220 
221     private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
222         @Override
223         public void onAllAuthenticatorsRegistered() {
224             updateUdfpsController();
225         }
226 
227         @Override
228         public void onEnrollmentsChanged() {
229             updateUdfpsController();
230         }
231     };
232 }
233