• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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