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