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