• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.systemui.statusbar.phone
2 
3 import android.animation.Animator
4 import android.animation.AnimatorListenerAdapter
5 import android.animation.ValueAnimator
6 import android.content.Context
7 import android.content.res.Configuration
8 import android.os.Handler
9 import android.view.View
10 import com.android.systemui.animation.Interpolators
11 import com.android.systemui.dagger.SysUISingleton
12 import com.android.systemui.keyguard.KeyguardViewMediator
13 import com.android.systemui.keyguard.WakefulnessLifecycle
14 import com.android.systemui.statusbar.LightRevealScrim
15 import com.android.systemui.statusbar.StatusBarState
16 import com.android.systemui.statusbar.StatusBarStateControllerImpl
17 import com.android.systemui.statusbar.notification.AnimatableProperty
18 import com.android.systemui.statusbar.notification.PropertyAnimator
19 import com.android.systemui.statusbar.notification.stack.AnimationProperties
20 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
21 import com.android.systemui.statusbar.policy.KeyguardStateController
22 import javax.inject.Inject
23 
24 /**
25  * When to show the keyguard (AOD) view. This should be once the light reveal scrim is barely
26  * visible, because the transition to KEYGUARD causes brief jank.
27  */
28 private const val ANIMATE_IN_KEYGUARD_DELAY = 600L
29 
30 /**
31  * Duration for the light reveal portion of the animation.
32  */
33 private const val LIGHT_REVEAL_ANIMATION_DURATION = 750L
34 
35 /**
36  * Controller for the unlocked screen off animation, which runs when the device is going to sleep
37  * and we're unlocked.
38  *
39  * This animation uses a [LightRevealScrim] that lives in the status bar to hide the screen contents
40  * and then animates in the AOD UI.
41  */
42 @SysUISingleton
43 class UnlockedScreenOffAnimationController @Inject constructor(
44     private val context: Context,
45     private val wakefulnessLifecycle: WakefulnessLifecycle,
46     private val statusBarStateControllerImpl: StatusBarStateControllerImpl,
47     private val keyguardViewMediatorLazy: dagger.Lazy<KeyguardViewMediator>,
48     private val keyguardStateController: KeyguardStateController,
49     private val dozeParameters: dagger.Lazy<DozeParameters>
50 ) : WakefulnessLifecycle.Observer {
51     private val handler = Handler()
52 
53     private lateinit var statusBar: StatusBar
54     private lateinit var lightRevealScrim: LightRevealScrim
55 
56     private var lightRevealAnimationPlaying = false
57     private var aodUiAnimationPlaying = false
58 
59     /**
60      * The result of our decision whether to play the screen off animation in
61      * [onStartedGoingToSleep], or null if we haven't made that decision yet or aren't going to
62      * sleep.
63      */
64     private var decidedToAnimateGoingToSleep: Boolean? = null
65 
<lambda>null66     private val lightRevealAnimator = ValueAnimator.ofFloat(1f, 0f).apply {
67         duration = LIGHT_REVEAL_ANIMATION_DURATION
68         interpolator = Interpolators.LINEAR
69         addUpdateListener { lightRevealScrim.revealAmount = it.animatedValue as Float }
70         addListener(object : AnimatorListenerAdapter() {
71             override fun onAnimationCancel(animation: Animator?) {
72                 lightRevealScrim.revealAmount = 1f
73                 lightRevealAnimationPlaying = false
74             }
75 
76             override fun onAnimationEnd(animation: Animator?) {
77                 lightRevealAnimationPlaying = false
78             }
79         })
80     }
81 
initializenull82     fun initialize(
83         statusBar: StatusBar,
84         lightRevealScrim: LightRevealScrim
85     ) {
86         this.lightRevealScrim = lightRevealScrim
87         this.statusBar = statusBar
88 
89         wakefulnessLifecycle.addObserver(this)
90     }
91 
92     /**
93      * Animates in the provided keyguard view, ending in the same position that it will be in on
94      * AOD.
95      */
animateInKeyguardnull96     fun animateInKeyguard(keyguardView: View, after: Runnable) {
97         keyguardView.alpha = 0f
98         keyguardView.visibility = View.VISIBLE
99 
100         val currentY = keyguardView.y
101 
102         // Move the keyguard up by 10% so we can animate it back down.
103         keyguardView.y = currentY - keyguardView.height * 0.1f
104 
105         val duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP
106 
107         // We animate the Y properly separately using the PropertyAnimator, as the panel
108         // view also needs to update the end position.
109         PropertyAnimator.cancelAnimation(keyguardView, AnimatableProperty.Y)
110         PropertyAnimator.setProperty<View>(keyguardView, AnimatableProperty.Y, currentY,
111                 AnimationProperties().setDuration(duration.toLong()),
112                 true /* animate */)
113 
114         keyguardView.animate()
115                 .setDuration(duration.toLong())
116                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
117                 .alpha(1f)
118                 .withEndAction {
119                     aodUiAnimationPlaying = false
120 
121                     // Lock the keyguard if it was waiting for the screen off animation to end.
122                     keyguardViewMediatorLazy.get().maybeHandlePendingLock()
123 
124                     // Tell the StatusBar to become keyguard for real - we waited on that since it
125                     // is slow and would have caused the animation to jank.
126                     statusBar.updateIsKeyguard()
127 
128                     // Run the callback given to us by the KeyguardVisibilityHelper.
129                     after.run()
130 
131                     // Done going to sleep, reset this flag.
132                     decidedToAnimateGoingToSleep = null
133                 }
134                 .start()
135     }
136 
onStartedWakingUpnull137     override fun onStartedWakingUp() {
138         // Waking up, so reset this flag.
139         decidedToAnimateGoingToSleep = null
140 
141         lightRevealAnimator.cancel()
142         handler.removeCallbacksAndMessages(null)
143     }
144 
onFinishedWakingUpnull145     override fun onFinishedWakingUp() {
146         // Set this to false in onFinishedWakingUp rather than onStartedWakingUp so that other
147         // observers (such as StatusBar) can ask us whether we were playing the screen off animation
148         // and reset accordingly.
149         lightRevealAnimationPlaying = false
150         aodUiAnimationPlaying = false
151 
152         // If we can't control the screen off animation, we shouldn't mess with the StatusBar's
153         // keyguard state unnecessarily.
154         if (dozeParameters.get().canControlUnlockedScreenOff()) {
155             // Make sure the status bar is in the correct keyguard state, forcing it if necessary.
156             // This is required if the screen off animation is cancelled, since it might be
157             // incorrectly left in the KEYGUARD or SHADE states depending on when it was cancelled
158             // and whether 'lock instantly' is enabled. We need to force it so that the state is set
159             // even if we're going from SHADE to SHADE or KEYGUARD to KEYGUARD, since we might have
160             // changed parts of the UI (such as showing AOD in the shade) without actually changing
161             // the StatusBarState. This ensures that the UI definitely reflects the desired state.
162             statusBar.updateIsKeyguard(true /* force */)
163         }
164     }
165 
onStartedGoingToSleepnull166     override fun onStartedGoingToSleep() {
167         if (dozeParameters.get().shouldControlUnlockedScreenOff()) {
168             decidedToAnimateGoingToSleep = true
169 
170             lightRevealAnimationPlaying = true
171             lightRevealAnimator.start()
172 
173             handler.postDelayed({
174                 aodUiAnimationPlaying = true
175 
176                 // Show AOD. That'll cause the KeyguardVisibilityHelper to call #animateInKeyguard.
177                 statusBar.notificationPanelViewController.showAodUi()
178             }, ANIMATE_IN_KEYGUARD_DELAY)
179         } else {
180             decidedToAnimateGoingToSleep = false
181         }
182     }
183 
184     /**
185      * Whether we want to play the screen off animation when the phone starts going to sleep, based
186      * on the current state of the device.
187      */
shouldPlayUnlockedScreenOffAnimationnull188     fun shouldPlayUnlockedScreenOffAnimation(): Boolean {
189         // If we explicitly already decided not to play the screen off animation, then never change
190         // our mind.
191         if (decidedToAnimateGoingToSleep == false) {
192             return false
193         }
194 
195         if (!dozeParameters.get().canControlUnlockedScreenOff()) {
196             return false
197         }
198 
199         // We only play the unlocked screen off animation if we are... unlocked.
200         if (statusBarStateControllerImpl.state != StatusBarState.SHADE) {
201             return false
202         }
203 
204         // We currently draw both the light reveal scrim, and the AOD UI, in the shade. If it's
205         // already expanded and showing notifications/QS, the animation looks really messy. For now,
206         // disable it if the notification panel is not fully collapsed.
207         if (!this::statusBar.isInitialized ||
208                 !statusBar.notificationPanelViewController.isFullyCollapsed) {
209             return false
210         }
211 
212         // If we're not allowed to rotate the keyguard, then only do the screen off animation if
213         // we're in portrait. Otherwise, AOD will animate in sideways, which looks weird.
214         if (!keyguardStateController.isKeyguardScreenRotationAllowed &&
215                 context.resources.configuration.orientation != Configuration.ORIENTATION_PORTRAIT) {
216             return false
217         }
218 
219         // Otherwise, good to go.
220         return true
221     }
222 
223     /**
224      * Whether we're doing the light reveal animation or we're done with that and animating in the
225      * AOD UI.
226      */
isScreenOffAnimationPlayingnull227     fun isScreenOffAnimationPlaying(): Boolean {
228         return lightRevealAnimationPlaying || aodUiAnimationPlaying
229     }
230 
231     /**
232      * Whether the light reveal animation is playing. The second part of the screen off animation,
233      * where AOD animates in, might still be playing if this returns false.
234      */
isScreenOffLightRevealAnimationPlayingnull235     fun isScreenOffLightRevealAnimationPlaying(): Boolean {
236         return lightRevealAnimationPlaying
237     }
238 }