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