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