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