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.notification 18 19 import android.util.FloatProperty 20 import android.view.animation.Interpolator 21 import androidx.annotation.VisibleForTesting 22 import androidx.core.animation.ObjectAnimator 23 import com.android.systemui.Dumpable 24 import com.android.app.animation.Interpolators 25 import com.android.app.animation.InterpolatorsAndroidX 26 import com.android.systemui.dagger.SysUISingleton 27 import com.android.systemui.dump.DumpManager 28 import com.android.systemui.plugins.statusbar.StatusBarStateController 29 import com.android.systemui.shade.ShadeExpansionChangeEvent 30 import com.android.systemui.shade.ShadeExpansionListener 31 import com.android.systemui.shade.ShadeViewController 32 import com.android.systemui.statusbar.StatusBarState 33 import com.android.systemui.statusbar.notification.collection.NotificationEntry 34 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController 35 import com.android.systemui.statusbar.notification.stack.StackStateAnimator 36 import com.android.systemui.statusbar.phone.DozeParameters 37 import com.android.systemui.statusbar.phone.KeyguardBypassController 38 import com.android.systemui.statusbar.phone.KeyguardBypassController.OnBypassStateChangedListener 39 import com.android.systemui.statusbar.phone.ScreenOffAnimationController 40 import com.android.systemui.statusbar.policy.HeadsUpManager 41 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener 42 import com.android.systemui.util.doOnEnd 43 import com.android.systemui.util.doOnStart 44 import java.io.PrintWriter 45 import javax.inject.Inject 46 import kotlin.math.max 47 import kotlin.math.min 48 49 @SysUISingleton 50 class NotificationWakeUpCoordinator 51 @Inject 52 constructor( 53 dumpManager: DumpManager, 54 private val mHeadsUpManager: HeadsUpManager, 55 private val statusBarStateController: StatusBarStateController, 56 private val bypassController: KeyguardBypassController, 57 private val dozeParameters: DozeParameters, 58 private val screenOffAnimationController: ScreenOffAnimationController, 59 private val logger: NotificationWakeUpCoordinatorLogger, 60 ) : 61 OnHeadsUpChangedListener, 62 StatusBarStateController.StateListener, 63 ShadeExpansionListener, 64 Dumpable { 65 private lateinit var mStackScrollerController: NotificationStackScrollLayoutController 66 private var mVisibilityInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE 67 68 private var inputLinearDozeAmount: Float = 0.0f 69 private var inputEasedDozeAmount: Float = 0.0f 70 private var delayedDozeAmountOverride: Float = 0.0f 71 private var delayedDozeAmountAnimator: ObjectAnimator? = null 72 /** Valid values: {1f, 0f, null} null => use input */ 73 private var hardDozeAmountOverride: Float? = null 74 private var hardDozeAmountOverrideSource: String = "n/a" 75 private var outputLinearDozeAmount: Float = 0.0f 76 private var outputEasedDozeAmount: Float = 0.0f 77 @VisibleForTesting val dozeAmountInterpolator: Interpolator = Interpolators.FAST_OUT_SLOW_IN 78 79 private var mNotificationVisibleAmount = 0.0f 80 private var mNotificationsVisible = false 81 private var mNotificationsVisibleForExpansion = false 82 private var mVisibilityAnimator: ObjectAnimator? = null 83 private var mVisibilityAmount = 0.0f 84 private var mLinearVisibilityAmount = 0.0f 85 private val mEntrySetToClearWhenFinished = mutableSetOf<NotificationEntry>() 86 private var pulseExpanding: Boolean = false 87 private val wakeUpListeners = arrayListOf<WakeUpListener>() 88 private var state: Int = StatusBarState.KEYGUARD 89 90 var fullyAwake: Boolean = false 91 92 var wakingUp = false 93 private set(value) { 94 field = value 95 willWakeUp = false 96 if (value) { 97 if ( 98 mNotificationsVisible && 99 !mNotificationsVisibleForExpansion && 100 !bypassController.bypassEnabled 101 ) { 102 // We're waking up while pulsing, let's make sure the animation looks nice 103 mStackScrollerController.wakeUpFromPulse() 104 } 105 if (bypassController.bypassEnabled && !mNotificationsVisible) { 106 // Let's make sure our huns become visible once we are waking up in case 107 // they were blocked by the proximity sensor 108 updateNotificationVisibility( 109 animate = shouldAnimateVisibility(), 110 increaseSpeed = false 111 ) 112 } 113 } 114 } 115 116 var willWakeUp = false 117 set(value) { 118 if (!value || outputLinearDozeAmount != 0.0f) { 119 field = value 120 } 121 } 122 123 private var collapsedEnoughToHide: Boolean = false 124 125 var pulsing: Boolean = false 126 set(value) { 127 field = value 128 if (value) { 129 // Only when setting pulsing to true we want an immediate update, since we get 130 // this already when the doze service finishes which is usually before we get 131 // the waking up callback 132 updateNotificationVisibility( 133 animate = shouldAnimateVisibility(), 134 increaseSpeed = false 135 ) 136 } 137 } 138 139 var notificationsFullyHidden: Boolean = false 140 private set(value) { 141 if (field != value) { 142 field = value 143 for (listener in wakeUpListeners) { 144 listener.onFullyHiddenChanged(value) 145 } 146 } 147 } 148 149 /** True if we can show pulsing heads up notifications */ 150 var canShowPulsingHuns: Boolean = false 151 private set 152 get() { 153 var canShow = pulsing 154 if (bypassController.bypassEnabled) { 155 // We also allow pulsing on the lock screen! 156 canShow = 157 canShow || 158 (wakingUp || willWakeUp || fullyAwake) && 159 statusBarStateController.state == StatusBarState.KEYGUARD 160 // We want to hide the notifications when collapsed too much 161 if (collapsedEnoughToHide) { 162 canShow = false 163 } 164 } 165 return canShow 166 } 167 168 private val bypassStateChangedListener = 169 object : OnBypassStateChangedListener { 170 override fun onBypassStateChanged(isEnabled: Boolean) { 171 // When the bypass state changes, we have to check whether we should re-show the 172 // notifications by clearing the doze amount override which hides them. 173 maybeClearHardDozeAmountOverrideHidingNotifs() 174 } 175 } 176 177 init { 178 dumpManager.registerDumpable(this) 179 mHeadsUpManager.addListener(this) 180 statusBarStateController.addCallback(this) 181 bypassController.registerOnBypassStateChangedListener(bypassStateChangedListener) 182 addListener( 183 object : WakeUpListener { 184 override fun onFullyHiddenChanged(isFullyHidden: Boolean) { 185 if (isFullyHidden && mNotificationsVisibleForExpansion) { 186 // When the notification becomes fully invisible, let's make sure our 187 // expansion 188 // flag also changes. This can happen if the bouncer shows when dragging 189 // down 190 // and then the screen turning off, where we don't reset this state. 191 setNotificationsVisibleForExpansion( 192 visible = false, 193 animate = false, 194 increaseSpeed = false 195 ) 196 } 197 } 198 } 199 ) 200 } 201 202 fun setStackScroller(stackScrollerController: NotificationStackScrollLayoutController) { 203 mStackScrollerController = stackScrollerController 204 pulseExpanding = stackScrollerController.isPulseExpanding 205 stackScrollerController.setOnPulseHeightChangedListener { 206 val nowExpanding = isPulseExpanding() 207 val changed = nowExpanding != pulseExpanding 208 pulseExpanding = nowExpanding 209 for (listener in wakeUpListeners) { 210 listener.onPulseExpansionChanged(changed) 211 } 212 } 213 } 214 215 fun isPulseExpanding(): Boolean = mStackScrollerController.isPulseExpanding 216 217 /** 218 * @param visible should notifications be visible 219 * @param animate should this change be animated 220 * @param increaseSpeed should the speed be increased of the animation 221 */ 222 fun setNotificationsVisibleForExpansion( 223 visible: Boolean, 224 animate: Boolean, 225 increaseSpeed: Boolean 226 ) { 227 mNotificationsVisibleForExpansion = visible 228 updateNotificationVisibility(animate, increaseSpeed) 229 if (!visible && mNotificationsVisible) { 230 // If we stopped expanding and we're still visible because we had a pulse that hasn't 231 // times out, let's release them all to make sure were not stuck in a state where 232 // notifications are visible 233 mHeadsUpManager.releaseAllImmediately() 234 } 235 } 236 237 fun addListener(listener: WakeUpListener) { 238 wakeUpListeners.add(listener) 239 } 240 241 fun removeListener(listener: WakeUpListener) { 242 wakeUpListeners.remove(listener) 243 } 244 245 private fun updateNotificationVisibility(animate: Boolean, increaseSpeed: Boolean) { 246 // TODO: handle Lockscreen wakeup for bypass when we're not pulsing anymore 247 var visible = mNotificationsVisibleForExpansion || mHeadsUpManager.hasNotifications() 248 visible = visible && canShowPulsingHuns 249 250 if ( 251 !visible && 252 mNotificationsVisible && 253 (wakingUp || willWakeUp) && 254 outputLinearDozeAmount != 0.0f 255 ) { 256 // let's not make notifications invisible while waking up, otherwise the animation 257 // is strange 258 return 259 } 260 setNotificationsVisible(visible, animate, increaseSpeed) 261 } 262 263 private fun setNotificationsVisible( 264 visible: Boolean, 265 animate: Boolean, 266 increaseSpeed: Boolean 267 ) { 268 if (mNotificationsVisible == visible) { 269 return 270 } 271 mNotificationsVisible = visible 272 mVisibilityAnimator?.cancel() 273 if (animate) { 274 notifyAnimationStart(visible) 275 startVisibilityAnimation(increaseSpeed) 276 } else { 277 setVisibilityAmount(if (visible) 1.0f else 0.0f) 278 } 279 } 280 281 override fun onDozeAmountChanged(linear: Float, eased: Float) { 282 logger.logOnDozeAmountChanged(linear = linear, eased = eased) 283 inputLinearDozeAmount = linear 284 inputEasedDozeAmount = eased 285 if (overrideDozeAmountIfAnimatingScreenOff()) { 286 return 287 } 288 289 if (overrideDozeAmountIfBypass()) { 290 return 291 } 292 293 if (clearHardDozeAmountOverride()) { 294 return 295 } 296 297 updateDozeAmount() 298 } 299 300 private fun setHardDozeAmountOverride(dozing: Boolean, source: String) { 301 logger.logSetDozeAmountOverride(dozing = dozing, source = source) 302 hardDozeAmountOverride = if (dozing) 1f else 0f 303 hardDozeAmountOverrideSource = source 304 updateDozeAmount() 305 } 306 307 private fun clearHardDozeAmountOverride(): Boolean { 308 if (hardDozeAmountOverride == null) return false 309 hardDozeAmountOverride = null 310 hardDozeAmountOverrideSource = "Cleared: $hardDozeAmountOverrideSource" 311 updateDozeAmount() 312 return true 313 } 314 315 private fun updateDozeAmount() { 316 // Calculate new doze amount (linear) 317 val newOutputLinearDozeAmount = 318 hardDozeAmountOverride ?: max(inputLinearDozeAmount, delayedDozeAmountOverride) 319 val changed = outputLinearDozeAmount != newOutputLinearDozeAmount 320 321 // notify when the animation is starting 322 if ( 323 newOutputLinearDozeAmount != 1.0f && 324 newOutputLinearDozeAmount != 0.0f && 325 (outputLinearDozeAmount == 0.0f || outputLinearDozeAmount == 1.0f) 326 ) { 327 // Let's notify the scroller that an animation started 328 notifyAnimationStart(outputLinearDozeAmount == 1.0f) 329 } 330 331 // Update output doze amount 332 outputLinearDozeAmount = newOutputLinearDozeAmount 333 outputEasedDozeAmount = dozeAmountInterpolator.getInterpolation(outputLinearDozeAmount) 334 logger.logUpdateDozeAmount( 335 inputLinear = inputLinearDozeAmount, 336 delayLinear = delayedDozeAmountOverride, 337 hardOverride = hardDozeAmountOverride, 338 outputLinear = outputLinearDozeAmount, 339 state = statusBarStateController.state, 340 changed = changed 341 ) 342 mStackScrollerController.setDozeAmount(outputEasedDozeAmount) 343 updateHideAmount() 344 if (changed && outputLinearDozeAmount == 0.0f) { 345 setNotificationsVisible(visible = false, animate = false, increaseSpeed = false) 346 setNotificationsVisibleForExpansion( 347 visible = false, 348 animate = false, 349 increaseSpeed = false 350 ) 351 } 352 } 353 354 /** 355 * Notifies the wakeup coordinator that we're waking up. 356 * 357 * [requestDelayedAnimation] is used to request that we delay the start of the wakeup animation 358 * in order to wait for a potential fingerprint authentication to arrive, since unlocking during 359 * the wakeup animation looks chaotic. 360 * 361 * If called with [wakingUp] and [requestDelayedAnimation] both `true`, the [WakeUpListener]s 362 * are guaranteed to receive at least one [WakeUpListener.onDelayedDozeAmountAnimationRunning] 363 * call with `false` at some point in the near future. A call with `true` before that will 364 * happen if the animation is not already running. 365 */ 366 fun setWakingUp( 367 wakingUp: Boolean, 368 requestDelayedAnimation: Boolean, 369 ) { 370 logger.logSetWakingUp(wakingUp, requestDelayedAnimation) 371 this.wakingUp = wakingUp 372 if (wakingUp && requestDelayedAnimation) { 373 scheduleDelayedDozeAmountAnimation() 374 } 375 } 376 377 private fun scheduleDelayedDozeAmountAnimation() { 378 val alreadyRunning = delayedDozeAmountAnimator != null 379 logger.logStartDelayedDozeAmountAnimation(alreadyRunning) 380 if (alreadyRunning) return 381 delayedDozeAmount.setValue(this, 1.0f) 382 delayedDozeAmountAnimator = 383 ObjectAnimator.ofFloat(this, delayedDozeAmount, 0.0f).apply { 384 interpolator = InterpolatorsAndroidX.LINEAR 385 duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP.toLong() 386 startDelay = ShadeViewController.WAKEUP_ANIMATION_DELAY_MS.toLong() 387 doOnStart { 388 wakeUpListeners.forEach { it.onDelayedDozeAmountAnimationRunning(true) } 389 } 390 doOnEnd { 391 delayedDozeAmountAnimator = null 392 wakeUpListeners.forEach { it.onDelayedDozeAmountAnimationRunning(false) } 393 } 394 start() 395 } 396 } 397 398 override fun onStateChanged(newState: Int) { 399 logger.logOnStateChanged(newState = newState, storedState = state) 400 if (state == StatusBarState.SHADE && newState == StatusBarState.SHADE) { 401 // The SHADE -> SHADE transition is only possible as part of cancelling the screen-off 402 // animation (e.g. by fingerprint unlock). This is done because the system is in an 403 // undefined state, so it's an indication that we should do state cleanup. We override 404 // the doze amount to 0f (not dozing) so that the notifications are no longer hidden. 405 // See: UnlockedScreenOffAnimationController.onFinishedWakingUp() 406 setHardDozeAmountOverride( 407 dozing = false, 408 source = "Override: Shade->Shade (lock cancelled by unlock)" 409 ) 410 this.state = newState 411 return 412 } 413 414 if (overrideDozeAmountIfAnimatingScreenOff()) { 415 this.state = newState 416 return 417 } 418 419 if (overrideDozeAmountIfBypass()) { 420 this.state = newState 421 return 422 } 423 424 maybeClearHardDozeAmountOverrideHidingNotifs() 425 426 this.state = newState 427 } 428 429 @VisibleForTesting 430 val statusBarState: Int 431 get() = state 432 433 override fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) { 434 val collapsedEnough = event.fraction <= 0.9f 435 if (collapsedEnough != this.collapsedEnoughToHide) { 436 val couldShowPulsingHuns = canShowPulsingHuns 437 this.collapsedEnoughToHide = collapsedEnough 438 if (couldShowPulsingHuns && !canShowPulsingHuns) { 439 updateNotificationVisibility(animate = true, increaseSpeed = true) 440 mHeadsUpManager.releaseAllImmediately() 441 } 442 } 443 } 444 445 /** 446 * @return Whether the doze amount was overridden because bypass is enabled. If true, the 447 * original doze amount should be ignored. 448 */ 449 private fun overrideDozeAmountIfBypass(): Boolean { 450 if (bypassController.bypassEnabled) { 451 if (statusBarStateController.state == StatusBarState.KEYGUARD) { 452 setHardDozeAmountOverride(dozing = true, source = "Override: bypass (keyguard)") 453 } else { 454 setHardDozeAmountOverride(dozing = false, source = "Override: bypass (shade)") 455 } 456 return true 457 } 458 return false 459 } 460 461 /** 462 * If the last [setDozeAmount] call was an override to hide notifications, then this call will 463 * check for the set of states that may have caused that override, and if none of them still 464 * apply, and the device is awake or not on the keyguard, then dozeAmount will be reset to 0. 465 * This fixes bugs where the bypass state changing could result in stale overrides, hiding 466 * notifications either on the inside screen or even after unlock. 467 */ 468 private fun maybeClearHardDozeAmountOverrideHidingNotifs() { 469 if (hardDozeAmountOverride == 1f) { 470 val onKeyguard = statusBarStateController.state == StatusBarState.KEYGUARD 471 val dozing = statusBarStateController.isDozing 472 val bypass = bypassController.bypassEnabled 473 val animating = 474 screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard() 475 // Overrides are set by [overrideDozeAmountIfAnimatingScreenOff] and 476 // [overrideDozeAmountIfBypass] based on 'animating' and 'bypass' respectively, so only 477 // clear the override if both those conditions are cleared. But also require either 478 // !dozing or !onKeyguard because those conditions should indicate that we intend 479 // notifications to be visible, and thus it is safe to unhide them. 480 val willRemove = (!onKeyguard || !dozing) && !bypass && !animating 481 logger.logMaybeClearHardDozeAmountOverrideHidingNotifs( 482 willRemove = willRemove, 483 onKeyguard = onKeyguard, 484 dozing = dozing, 485 bypass = bypass, 486 animating = animating, 487 ) 488 if (willRemove) { 489 clearHardDozeAmountOverride() 490 } 491 } 492 } 493 494 /** 495 * If we're playing the screen off animation, force the notification doze amount to be 1f (fully 496 * dozing). This is needed so that the notifications aren't briefly visible as the screen turns 497 * off and dozeAmount goes from 1f to 0f. 498 * 499 * @return Whether the doze amount was overridden because we are playing the screen off 500 * animation. If true, the original doze amount should be ignored. 501 */ 502 private fun overrideDozeAmountIfAnimatingScreenOff(): Boolean { 503 if (screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()) { 504 setHardDozeAmountOverride(dozing = true, source = "Override: animating screen off") 505 return true 506 } 507 508 return false 509 } 510 511 private fun startVisibilityAnimation(increaseSpeed: Boolean) { 512 if (mNotificationVisibleAmount == 0f || mNotificationVisibleAmount == 1f) { 513 mVisibilityInterpolator = 514 if (mNotificationsVisible) Interpolators.TOUCH_RESPONSE 515 else Interpolators.FAST_OUT_SLOW_IN_REVERSE 516 } 517 val target = if (mNotificationsVisible) 1.0f else 0.0f 518 val visibilityAnimator = ObjectAnimator.ofFloat(this, notificationVisibility, target) 519 visibilityAnimator.interpolator = InterpolatorsAndroidX.LINEAR 520 var duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP.toLong() 521 if (increaseSpeed) { 522 duration = (duration.toFloat() / 1.5F).toLong() 523 } 524 visibilityAnimator.duration = duration 525 visibilityAnimator.start() 526 mVisibilityAnimator = visibilityAnimator 527 } 528 529 private fun setVisibilityAmount(visibilityAmount: Float) { 530 logger.logSetVisibilityAmount(visibilityAmount) 531 mLinearVisibilityAmount = visibilityAmount 532 mVisibilityAmount = mVisibilityInterpolator.getInterpolation(visibilityAmount) 533 handleAnimationFinished() 534 updateHideAmount() 535 } 536 537 private fun handleAnimationFinished() { 538 if (outputLinearDozeAmount == 0.0f || mLinearVisibilityAmount == 0.0f) { 539 mEntrySetToClearWhenFinished.forEach { it.setHeadsUpAnimatingAway(false) } 540 mEntrySetToClearWhenFinished.clear() 541 } 542 } 543 544 private fun updateHideAmount() { 545 val linearAmount = min(1.0f - mLinearVisibilityAmount, outputLinearDozeAmount) 546 val amount = min(1.0f - mVisibilityAmount, outputEasedDozeAmount) 547 logger.logSetHideAmount(linearAmount) 548 mStackScrollerController.setHideAmount(linearAmount, amount) 549 notificationsFullyHidden = linearAmount == 1.0f 550 } 551 552 private fun notifyAnimationStart(awake: Boolean) { 553 mStackScrollerController.notifyHideAnimationStart(!awake) 554 } 555 556 override fun onDozingChanged(isDozing: Boolean) { 557 if (isDozing) { 558 setNotificationsVisible(visible = false, animate = false, increaseSpeed = false) 559 } 560 } 561 562 override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) { 563 var animate = shouldAnimateVisibility() 564 if (!isHeadsUp) { 565 if (outputLinearDozeAmount != 0.0f && mLinearVisibilityAmount != 0.0f) { 566 if (entry.isRowDismissed) { 567 // if we animate, we see the shelf briefly visible. Instead we fully animate 568 // the notification and its background out 569 animate = false 570 } else if (!wakingUp && !willWakeUp) { 571 // TODO: look that this is done properly and not by anyone else 572 entry.setHeadsUpAnimatingAway(true) 573 mEntrySetToClearWhenFinished.add(entry) 574 } 575 } 576 } else if (mEntrySetToClearWhenFinished.contains(entry)) { 577 mEntrySetToClearWhenFinished.remove(entry) 578 entry.setHeadsUpAnimatingAway(false) 579 } 580 updateNotificationVisibility(animate, increaseSpeed = false) 581 } 582 583 private fun shouldAnimateVisibility() = 584 dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking 585 586 override fun dump(pw: PrintWriter, args: Array<out String>) { 587 pw.println("inputLinearDozeAmount: $inputLinearDozeAmount") 588 pw.println("inputEasedDozeAmount: $inputEasedDozeAmount") 589 pw.println("delayedDozeAmountOverride: $delayedDozeAmountOverride") 590 pw.println("hardDozeAmountOverride: $hardDozeAmountOverride") 591 pw.println("hardDozeAmountOverrideSource: $hardDozeAmountOverrideSource") 592 pw.println("outputLinearDozeAmount: $outputLinearDozeAmount") 593 pw.println("outputEasedDozeAmount: $outputEasedDozeAmount") 594 pw.println("mNotificationVisibleAmount: $mNotificationVisibleAmount") 595 pw.println("mNotificationsVisible: $mNotificationsVisible") 596 pw.println("mNotificationsVisibleForExpansion: $mNotificationsVisibleForExpansion") 597 pw.println("mVisibilityAmount: $mVisibilityAmount") 598 pw.println("mLinearVisibilityAmount: $mLinearVisibilityAmount") 599 pw.println("pulseExpanding: $pulseExpanding") 600 pw.println("state: ${StatusBarState.toString(state)}") 601 pw.println("fullyAwake: $fullyAwake") 602 pw.println("wakingUp: $wakingUp") 603 pw.println("willWakeUp: $willWakeUp") 604 pw.println("collapsedEnoughToHide: $collapsedEnoughToHide") 605 pw.println("pulsing: $pulsing") 606 pw.println("notificationsFullyHidden: $notificationsFullyHidden") 607 pw.println("canShowPulsingHuns: $canShowPulsingHuns") 608 } 609 610 fun logDelayingClockWakeUpAnimation(delayingAnimation: Boolean) { 611 logger.logDelayingClockWakeUpAnimation(delayingAnimation) 612 } 613 614 interface WakeUpListener { 615 /** Called whenever the notifications are fully hidden or shown */ 616 @JvmDefault fun onFullyHiddenChanged(isFullyHidden: Boolean) {} 617 618 /** 619 * Called whenever the pulseExpansion changes 620 * 621 * @param expandingChanged if the user has started or stopped expanding 622 */ 623 @JvmDefault fun onPulseExpansionChanged(expandingChanged: Boolean) {} 624 625 /** 626 * Called when the animator started by [scheduleDelayedDozeAmountAnimation] begins running 627 * after the start delay, or after it ends/is cancelled. 628 */ 629 @JvmDefault fun onDelayedDozeAmountAnimationRunning(running: Boolean) {} 630 } 631 632 companion object { 633 private val notificationVisibility = 634 object : FloatProperty<NotificationWakeUpCoordinator>("notificationVisibility") { 635 636 override fun setValue(coordinator: NotificationWakeUpCoordinator, value: Float) { 637 coordinator.setVisibilityAmount(value) 638 } 639 640 override fun get(coordinator: NotificationWakeUpCoordinator): Float { 641 return coordinator.mLinearVisibilityAmount 642 } 643 } 644 645 private val delayedDozeAmount = 646 object : FloatProperty<NotificationWakeUpCoordinator>("delayedDozeAmount") { 647 648 override fun setValue(coordinator: NotificationWakeUpCoordinator, value: Float) { 649 coordinator.delayedDozeAmountOverride = value 650 coordinator.logger.logSetDelayDozeAmountOverride(value) 651 coordinator.updateDozeAmount() 652 } 653 654 override fun get(coordinator: NotificationWakeUpCoordinator): Float { 655 return coordinator.delayedDozeAmountOverride 656 } 657 } 658 } 659 } 660