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