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