1 /*
2  * Copyright (C) 2019 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.statusbar.phone;
18 
19 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
20 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
21 
22 import android.annotation.NonNull;
23 import android.os.Bundle;
24 import android.os.PowerManager;
25 import android.os.SystemClock;
26 import android.os.SystemProperties;
27 import android.util.Log;
28 import android.view.MotionEvent;
29 import android.view.View;
30 
31 import com.android.internal.annotations.VisibleForTesting;
32 import com.android.keyguard.KeyguardUpdateMonitor;
33 import com.android.systemui.assist.AssistManager;
34 import com.android.systemui.biometrics.AuthController;
35 import com.android.systemui.dagger.SysUISingleton;
36 import com.android.systemui.doze.DozeHost;
37 import com.android.systemui.doze.DozeLog;
38 import com.android.systemui.doze.DozeReceiver;
39 import com.android.systemui.keyguard.KeyguardViewMediator;
40 import com.android.systemui.keyguard.WakefulnessLifecycle;
41 import com.android.systemui.statusbar.NotificationShadeWindowController;
42 import com.android.systemui.statusbar.PulseExpansionHandler;
43 import com.android.systemui.statusbar.StatusBarState;
44 import com.android.systemui.statusbar.SysuiStatusBarStateController;
45 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
46 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
47 import com.android.systemui.statusbar.policy.BatteryController;
48 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
49 
50 import java.util.ArrayList;
51 
52 import javax.inject.Inject;
53 
54 import dagger.Lazy;
55 
56 /**
57  * Implementation of DozeHost for SystemUI.
58  */
59 @SysUISingleton
60 public final class DozeServiceHost implements DozeHost {
61     private static final String TAG = "DozeServiceHost";
62     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
63     private final DozeLog mDozeLog;
64     private final PowerManager mPowerManager;
65     private boolean mAnimateWakeup;
66     private boolean mIgnoreTouchWhilePulsing;
67     private Runnable mPendingScreenOffCallback;
68     @VisibleForTesting
69     boolean mWakeLockScreenPerformsAuth = SystemProperties.getBoolean(
70             "persist.sysui.wake_performs_auth", true);
71     private boolean mDozingRequested;
72     private boolean mPulsing;
73     private final WakefulnessLifecycle mWakefulnessLifecycle;
74     private final SysuiStatusBarStateController mStatusBarStateController;
75     private final DeviceProvisionedController mDeviceProvisionedController;
76     private final HeadsUpManagerPhone mHeadsUpManagerPhone;
77     private final BatteryController mBatteryController;
78     private final ScrimController mScrimController;
79     private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
80     private final KeyguardViewMediator mKeyguardViewMediator;
81     private final Lazy<AssistManager> mAssistManagerLazy;
82     private final DozeScrimController mDozeScrimController;
83     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
84     private final PulseExpansionHandler mPulseExpansionHandler;
85     private final NotificationShadeWindowController mNotificationShadeWindowController;
86     private final NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
87     private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
88     private final AuthController mAuthController;
89     private final NotificationIconAreaController mNotificationIconAreaController;
90     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
91     private NotificationPanelViewController mNotificationPanel;
92     private View mAmbientIndicationContainer;
93     private StatusBar mStatusBar;
94     private boolean mSuppressed;
95 
96     @Inject
DozeServiceHost(DozeLog dozeLog, PowerManager powerManager, WakefulnessLifecycle wakefulnessLifecycle, SysuiStatusBarStateController statusBarStateController, DeviceProvisionedController deviceProvisionedController, HeadsUpManagerPhone headsUpManagerPhone, BatteryController batteryController, ScrimController scrimController, Lazy<BiometricUnlockController> biometricUnlockControllerLazy, KeyguardViewMediator keyguardViewMediator, Lazy<AssistManager> assistManagerLazy, DozeScrimController dozeScrimController, KeyguardUpdateMonitor keyguardUpdateMonitor, PulseExpansionHandler pulseExpansionHandler, NotificationShadeWindowController notificationShadeWindowController, NotificationWakeUpCoordinator notificationWakeUpCoordinator, AuthController authController, NotificationIconAreaController notificationIconAreaController)97     public DozeServiceHost(DozeLog dozeLog, PowerManager powerManager,
98             WakefulnessLifecycle wakefulnessLifecycle,
99             SysuiStatusBarStateController statusBarStateController,
100             DeviceProvisionedController deviceProvisionedController,
101             HeadsUpManagerPhone headsUpManagerPhone, BatteryController batteryController,
102             ScrimController scrimController,
103             Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
104             KeyguardViewMediator keyguardViewMediator,
105             Lazy<AssistManager> assistManagerLazy,
106             DozeScrimController dozeScrimController, KeyguardUpdateMonitor keyguardUpdateMonitor,
107             PulseExpansionHandler pulseExpansionHandler,
108             NotificationShadeWindowController notificationShadeWindowController,
109             NotificationWakeUpCoordinator notificationWakeUpCoordinator,
110             AuthController authController,
111             NotificationIconAreaController notificationIconAreaController) {
112         super();
113         mDozeLog = dozeLog;
114         mPowerManager = powerManager;
115         mWakefulnessLifecycle = wakefulnessLifecycle;
116         mStatusBarStateController = statusBarStateController;
117         mDeviceProvisionedController = deviceProvisionedController;
118         mHeadsUpManagerPhone = headsUpManagerPhone;
119         mBatteryController = batteryController;
120         mScrimController = scrimController;
121         mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
122         mKeyguardViewMediator = keyguardViewMediator;
123         mAssistManagerLazy = assistManagerLazy;
124         mDozeScrimController = dozeScrimController;
125         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
126         mPulseExpansionHandler = pulseExpansionHandler;
127         mNotificationShadeWindowController = notificationShadeWindowController;
128         mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
129         mAuthController = authController;
130         mNotificationIconAreaController = notificationIconAreaController;
131     }
132 
133     // TODO: we should try to not pass status bar in here if we can avoid it.
134 
135     /**
136      * Initialize instance with objects only available later during execution.
137      */
initialize( StatusBar statusBar, StatusBarKeyguardViewManager statusBarKeyguardViewManager, NotificationShadeWindowViewController notificationShadeWindowViewController, NotificationPanelViewController notificationPanel, View ambientIndicationContainer)138     public void initialize(
139             StatusBar statusBar,
140             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
141             NotificationShadeWindowViewController notificationShadeWindowViewController,
142             NotificationPanelViewController notificationPanel,
143             View ambientIndicationContainer) {
144         mStatusBar = statusBar;
145         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
146         mNotificationPanel = notificationPanel;
147         mNotificationShadeWindowViewController = notificationShadeWindowViewController;
148         mAmbientIndicationContainer = ambientIndicationContainer;
149     }
150 
151 
152     @Override
toString()153     public String toString() {
154         return "PSB.DozeServiceHost[mCallbacks=" + mCallbacks.size() + "]";
155     }
156 
firePowerSaveChanged(boolean active)157     void firePowerSaveChanged(boolean active) {
158         for (Callback callback : mCallbacks) {
159             callback.onPowerSaveChanged(active);
160         }
161     }
162 
fireNotificationPulse(NotificationEntry entry)163     void fireNotificationPulse(NotificationEntry entry) {
164         Runnable pulseSuppressedListener = () -> {
165             entry.setPulseSuppressed(true);
166             mNotificationIconAreaController.updateAodNotificationIcons();
167         };
168         for (Callback callback : mCallbacks) {
169             callback.onNotificationAlerted(pulseSuppressedListener);
170         }
171     }
172 
getDozingRequested()173     boolean getDozingRequested() {
174         return mDozingRequested;
175     }
176 
isPulsing()177     boolean isPulsing() {
178         return mPulsing;
179     }
180 
181 
182     @Override
addCallback(@onNull Callback callback)183     public void addCallback(@NonNull Callback callback) {
184         mCallbacks.add(callback);
185     }
186 
187     @Override
removeCallback(@onNull Callback callback)188     public void removeCallback(@NonNull Callback callback) {
189         mCallbacks.remove(callback);
190     }
191 
192     @Override
startDozing()193     public void startDozing() {
194         if (!mDozingRequested) {
195             mDozingRequested = true;
196             updateDozing();
197             mDozeLog.traceDozing(mStatusBarStateController.isDozing());
198             mStatusBar.updateIsKeyguard();
199         }
200     }
201 
updateDozing()202     void updateDozing() {
203         // When in wake-and-unlock while pulsing, keep dozing state until fully unlocked.
204         boolean
205                 dozing =
206                 mDozingRequested && mStatusBarStateController.getState() == StatusBarState.KEYGUARD
207                         || mBiometricUnlockControllerLazy.get().getMode()
208                         == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
209         // When in wake-and-unlock we may not have received a change to StatusBarState
210         // but we still should not be dozing, manually set to false.
211         if (mBiometricUnlockControllerLazy.get().getMode()
212                 == BiometricUnlockController.MODE_WAKE_AND_UNLOCK) {
213             dozing = false;
214         }
215 
216         mStatusBarStateController.setIsDozing(dozing);
217     }
218 
219     @Override
pulseWhileDozing(@onNull PulseCallback callback, int reason)220     public void pulseWhileDozing(@NonNull PulseCallback callback, int reason) {
221         if (reason == DozeLog.PULSE_REASON_SENSOR_LONG_PRESS) {
222             mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
223                                  "com.android.systemui:LONG_PRESS");
224             mAssistManagerLazy.get().startAssist(new Bundle());
225             return;
226         }
227 
228         if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH) {
229             mScrimController.setWakeLockScreenSensorActive(true);
230         }
231 
232         boolean passiveAuthInterrupt = reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH
233                         && mWakeLockScreenPerformsAuth;
234         // Set the state to pulsing, so ScrimController will know what to do once we ask it to
235         // execute the transition. The pulse callback will then be invoked when the scrims
236         // are black, indicating that StatusBar is ready to present the rest of the UI.
237         mPulsing = true;
238         mDozeScrimController.pulse(new PulseCallback() {
239             @Override
240             public void onPulseStarted() {
241                 callback.onPulseStarted();
242                 mStatusBar.updateNotificationPanelTouchState();
243                 setPulsing(true);
244             }
245 
246             @Override
247             public void onPulseFinished() {
248                 mPulsing = false;
249                 callback.onPulseFinished();
250                 mStatusBar.updateNotificationPanelTouchState();
251                 mScrimController.setWakeLockScreenSensorActive(false);
252                 setPulsing(false);
253             }
254 
255             private void setPulsing(boolean pulsing) {
256                 mStatusBarKeyguardViewManager.setPulsing(pulsing);
257                 mKeyguardViewMediator.setPulsing(pulsing);
258                 mNotificationPanel.setPulsing(pulsing);
259                 mStatusBarStateController.setPulsing(pulsing);
260                 mIgnoreTouchWhilePulsing = false;
261                 if (mKeyguardUpdateMonitor != null && passiveAuthInterrupt) {
262                     mKeyguardUpdateMonitor.onAuthInterruptDetected(pulsing /* active */);
263                 }
264                 mStatusBar.updateScrimController();
265                 mPulseExpansionHandler.setPulsing(pulsing);
266                 mNotificationWakeUpCoordinator.setPulsing(pulsing);
267             }
268         }, reason);
269         // DozeScrimController is in pulse state, now let's ask ScrimController to start
270         // pulsing and draw the black frame, if necessary.
271         mStatusBar.updateScrimController();
272     }
273 
274     @Override
stopDozing()275     public void stopDozing() {
276         if (mDozingRequested) {
277             mDozingRequested = false;
278             updateDozing();
279             mDozeLog.traceDozing(mStatusBarStateController.isDozing());
280         }
281     }
282 
283     @Override
onIgnoreTouchWhilePulsing(boolean ignore)284     public void onIgnoreTouchWhilePulsing(boolean ignore) {
285         if (ignore != mIgnoreTouchWhilePulsing) {
286             mDozeLog.tracePulseTouchDisabledByProx(ignore);
287         }
288         mIgnoreTouchWhilePulsing = ignore;
289         if (mStatusBarStateController.isDozing() && ignore) {
290             mNotificationShadeWindowViewController.cancelCurrentTouch();
291         }
292     }
293 
294     @Override
dozeTimeTick()295     public void dozeTimeTick() {
296         mNotificationPanel.dozeTimeTick();
297         mAuthController.dozeTimeTick();
298         if (mAmbientIndicationContainer instanceof DozeReceiver) {
299             ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick();
300         }
301     }
302 
303     @Override
isPowerSaveActive()304     public boolean isPowerSaveActive() {
305         return mBatteryController.isAodPowerSave();
306     }
307 
308     @Override
isPulsingBlocked()309     public boolean isPulsingBlocked() {
310         return mBiometricUnlockControllerLazy.get().getMode()
311                 == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
312     }
313 
314     @Override
isProvisioned()315     public boolean isProvisioned() {
316         return mDeviceProvisionedController.isDeviceProvisioned()
317                 && mDeviceProvisionedController.isCurrentUserSetup();
318     }
319 
320     @Override
isBlockingDoze()321     public boolean isBlockingDoze() {
322         if (mBiometricUnlockControllerLazy.get().hasPendingAuthentication()) {
323             Log.i(StatusBar.TAG, "Blocking AOD because fingerprint has authenticated");
324             return true;
325         }
326         return false;
327     }
328 
329     @Override
extendPulse(int reason)330     public void extendPulse(int reason) {
331         if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH) {
332             mScrimController.setWakeLockScreenSensorActive(true);
333         }
334         if (mDozeScrimController.isPulsing() && mHeadsUpManagerPhone.hasNotifications()) {
335             mHeadsUpManagerPhone.extendHeadsUp();
336         } else {
337             mDozeScrimController.extendPulse();
338         }
339     }
340 
341     @Override
stopPulsing()342     public void stopPulsing() {
343         if (mDozeScrimController.isPulsing()) {
344             mDozeScrimController.pulseOutNow();
345         }
346     }
347 
348     @Override
setAnimateWakeup(boolean animateWakeup)349     public void setAnimateWakeup(boolean animateWakeup) {
350         if (mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_AWAKE
351                 || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_WAKING) {
352             // Too late to change the wakeup animation.
353             return;
354         }
355         mAnimateWakeup = animateWakeup;
356     }
357 
358     @Override
onSlpiTap(float screenX, float screenY)359     public void onSlpiTap(float screenX, float screenY) {
360         if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null
361                 && mAmbientIndicationContainer.getVisibility() == View.VISIBLE) {
362             int[] locationOnScreen = new int[2];
363             mAmbientIndicationContainer.getLocationOnScreen(locationOnScreen);
364             float viewX = screenX - locationOnScreen[0];
365             float viewY = screenY - locationOnScreen[1];
366             if (0 <= viewX && viewX <= mAmbientIndicationContainer.getWidth()
367                     && 0 <= viewY && viewY <= mAmbientIndicationContainer.getHeight()) {
368 
369                 // Dispatch a tap
370                 long now = SystemClock.elapsedRealtime();
371                 MotionEvent ev = MotionEvent.obtain(
372                         now, now, MotionEvent.ACTION_DOWN, screenX, screenY, 0);
373                 mAmbientIndicationContainer.dispatchTouchEvent(ev);
374                 ev.recycle();
375                 ev = MotionEvent.obtain(
376                         now, now, MotionEvent.ACTION_UP, screenX, screenY, 0);
377                 mAmbientIndicationContainer.dispatchTouchEvent(ev);
378                 ev.recycle();
379             }
380         }
381     }
382 
383     @Override
setDozeScreenBrightness(int brightness)384     public void setDozeScreenBrightness(int brightness) {
385         mDozeLog.traceDozeScreenBrightness(brightness);
386         mNotificationShadeWindowController.setDozeScreenBrightness(brightness);
387     }
388 
389     @Override
setAodDimmingScrim(float scrimOpacity)390     public void setAodDimmingScrim(float scrimOpacity) {
391         mDozeLog.traceSetAodDimmingScrim(scrimOpacity);
392         mScrimController.setAodFrontScrimAlpha(scrimOpacity);
393     }
394 
395     @Override
prepareForGentleSleep(Runnable onDisplayOffCallback)396     public void prepareForGentleSleep(Runnable onDisplayOffCallback) {
397         if (mPendingScreenOffCallback != null) {
398             Log.w(TAG, "Overlapping onDisplayOffCallback. Ignoring previous one.");
399         }
400         mPendingScreenOffCallback = onDisplayOffCallback;
401         mStatusBar.updateScrimController();
402     }
403 
404     @Override
cancelGentleSleep()405     public void cancelGentleSleep() {
406         mPendingScreenOffCallback = null;
407         if (mScrimController.getState() == ScrimState.OFF) {
408             mStatusBar.updateScrimController();
409         }
410     }
411 
412     /**
413      * When the dozing host is waiting for scrims to fade out to change the display state.
414      */
hasPendingScreenOffCallback()415     boolean hasPendingScreenOffCallback() {
416         return mPendingScreenOffCallback != null;
417     }
418 
419     /**
420      * Executes an nullifies the pending display state callback.
421      *
422      * @see #hasPendingScreenOffCallback()
423      * @see #prepareForGentleSleep(Runnable)
424      */
executePendingScreenOffCallback()425     void executePendingScreenOffCallback() {
426         if (mPendingScreenOffCallback == null) {
427             return;
428         }
429         mPendingScreenOffCallback.run();
430         mPendingScreenOffCallback = null;
431     }
432 
shouldAnimateWakeup()433     boolean shouldAnimateWakeup() {
434         return mAnimateWakeup;
435     }
436 
getIgnoreTouchWhilePulsing()437     boolean getIgnoreTouchWhilePulsing() {
438         return mIgnoreTouchWhilePulsing;
439     }
440 
setDozeSuppressed(boolean suppressed)441     void setDozeSuppressed(boolean suppressed) {
442         if (suppressed == mSuppressed) {
443             return;
444         }
445         mSuppressed = suppressed;
446         mDozeLog.traceDozingSuppressed(mSuppressed);
447         for (Callback callback : mCallbacks) {
448             callback.onDozeSuppressedChanged(suppressed);
449         }
450     }
451 
isDozeSuppressed()452     public boolean isDozeSuppressed() {
453         return mSuppressed;
454     }
455 }
456