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