• 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.database.ContentObserver
8 import android.os.Handler
9 import android.os.PowerManager
10 import android.provider.Settings
11 import android.view.Surface
12 import android.view.View
13 import android.view.WindowManager.fixScale
14 import com.android.internal.jank.InteractionJankMonitor
15 import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF
16 import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
17 import com.android.systemui.animation.Interpolators
18 import com.android.systemui.dagger.SysUISingleton
19 import com.android.systemui.keyguard.KeyguardViewMediator
20 import com.android.systemui.keyguard.WakefulnessLifecycle
21 import com.android.systemui.statusbar.CircleReveal
22 import com.android.systemui.statusbar.LightRevealScrim
23 import com.android.systemui.statusbar.StatusBarState
24 import com.android.systemui.statusbar.StatusBarStateControllerImpl
25 import com.android.systemui.statusbar.notification.AnimatableProperty
26 import com.android.systemui.statusbar.notification.PropertyAnimator
27 import com.android.systemui.statusbar.notification.stack.AnimationProperties
28 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
29 import com.android.systemui.statusbar.policy.KeyguardStateController
30 import com.android.systemui.util.settings.GlobalSettings
31 import javax.inject.Inject
32 
33 /**
34  * When to show the keyguard (AOD) view. This should be once the light reveal scrim is barely
35  * visible, because the transition to KEYGUARD causes brief jank.
36  */
37 private const val ANIMATE_IN_KEYGUARD_DELAY = 600L
38 
39 /**
40  * Duration for the light reveal portion of the animation.
41  */
42 private const val LIGHT_REVEAL_ANIMATION_DURATION = 750L
43 
44 /**
45  * Controller for the unlocked screen off animation, which runs when the device is going to sleep
46  * and we're unlocked.
47  *
48  * This animation uses a [LightRevealScrim] that lives in the status bar to hide the screen contents
49  * and then animates in the AOD UI.
50  */
51 @SysUISingleton
52 class UnlockedScreenOffAnimationController @Inject constructor(
53     private val context: Context,
54     private val wakefulnessLifecycle: WakefulnessLifecycle,
55     private val statusBarStateControllerImpl: StatusBarStateControllerImpl,
56     private val keyguardViewMediatorLazy: dagger.Lazy<KeyguardViewMediator>,
57     private val keyguardStateController: KeyguardStateController,
58     private val dozeParameters: dagger.Lazy<DozeParameters>,
59     private val globalSettings: GlobalSettings,
60     private val interactionJankMonitor: InteractionJankMonitor,
61     private val powerManager: PowerManager,
62     private val handler: Handler = Handler()
63 ) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
64     private lateinit var mCentralSurfaces: CentralSurfaces
65     /**
66      * Whether or not [initialize] has been called to provide us with the StatusBar,
67      * NotificationPanelViewController, and LightRevealSrim so that we can run the unlocked screen
68      * off animation.
69      */
70     private var initialized = false
71 
72     private lateinit var lightRevealScrim: LightRevealScrim
73 
74     private var animatorDurationScale = 1f
75     private var shouldAnimateInKeyguard = false
76     private var lightRevealAnimationPlaying = false
77     private var aodUiAnimationPlaying = false
78 
79     /**
80      * The result of our decision whether to play the screen off animation in
81      * [onStartedGoingToSleep], or null if we haven't made that decision yet or aren't going to
82      * sleep.
83      */
84     private var decidedToAnimateGoingToSleep: Boolean? = null
85 
<lambda>null86     private val lightRevealAnimator = ValueAnimator.ofFloat(1f, 0f).apply {
87         duration = LIGHT_REVEAL_ANIMATION_DURATION
88         interpolator = Interpolators.LINEAR
89         addUpdateListener {
90             if (lightRevealScrim.revealEffect !is CircleReveal) {
91                 lightRevealScrim.revealAmount = it.animatedValue as Float
92             }
93             if (lightRevealScrim.isScrimAlmostOccludes &&
94                     interactionJankMonitor.isInstrumenting(CUJ_SCREEN_OFF)) {
95                 // ends the instrument when the scrim almost occludes the screen.
96                 // because the following janky frames might not be perceptible.
97                 interactionJankMonitor.end(CUJ_SCREEN_OFF)
98             }
99         }
100         addListener(object : AnimatorListenerAdapter() {
101             override fun onAnimationCancel(animation: Animator?) {
102                 if (lightRevealScrim.revealEffect !is CircleReveal) {
103                     lightRevealScrim.revealAmount = 1f
104                 }
105             }
106 
107             override fun onAnimationEnd(animation: Animator?) {
108                 lightRevealAnimationPlaying = false
109                 interactionJankMonitor.end(CUJ_SCREEN_OFF)
110             }
111 
112             override fun onAnimationStart(animation: Animator?) {
113                 interactionJankMonitor.begin(
114                     mCentralSurfaces.notificationShadeWindowView, CUJ_SCREEN_OFF)
115             }
116         })
117     }
118 
119     val animatorDurationScaleObserver = object : ContentObserver(null) {
onChangenull120         override fun onChange(selfChange: Boolean) {
121             updateAnimatorDurationScale()
122         }
123     }
124 
initializenull125     override fun initialize(
126         centralSurfaces: CentralSurfaces,
127         lightRevealScrim: LightRevealScrim
128     ) {
129         this.initialized = true
130         this.lightRevealScrim = lightRevealScrim
131         this.mCentralSurfaces = centralSurfaces
132 
133         updateAnimatorDurationScale()
134         globalSettings.registerContentObserver(
135                 Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
136                 /* notify for descendants */ false,
137                 animatorDurationScaleObserver)
138         wakefulnessLifecycle.addObserver(this)
139     }
140 
updateAnimatorDurationScalenull141     fun updateAnimatorDurationScale() {
142         animatorDurationScale = fixScale(
143                 globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f))
144     }
145 
shouldDelayKeyguardShownull146     override fun shouldDelayKeyguardShow(): Boolean =
147         shouldPlayAnimation()
148 
149     override fun isKeyguardShowDelayed(): Boolean =
150         isAnimationPlaying()
151 
152     /**
153      * Animates in the provided keyguard view, ending in the same position that it will be in on
154      * AOD.
155      */
156     override fun animateInKeyguard(keyguardView: View, after: Runnable) {
157         shouldAnimateInKeyguard = false
158         keyguardView.alpha = 0f
159         keyguardView.visibility = View.VISIBLE
160 
161         val currentY = keyguardView.y
162 
163         // Move the keyguard up by 10% so we can animate it back down.
164         keyguardView.y = currentY - keyguardView.height * 0.1f
165 
166         val duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP
167 
168         // We animate the Y properly separately using the PropertyAnimator, as the panel
169         // view also needs to update the end position.
170         PropertyAnimator.cancelAnimation(keyguardView, AnimatableProperty.Y)
171         PropertyAnimator.setProperty<View>(keyguardView, AnimatableProperty.Y, currentY,
172                 AnimationProperties().setDuration(duration.toLong()),
173                 true /* animate */)
174 
175         keyguardView.animate()
176                 .setDuration(duration.toLong())
177                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
178                 .alpha(1f)
179                 .withEndAction {
180                     aodUiAnimationPlaying = false
181 
182                     // Lock the keyguard if it was waiting for the screen off animation to end.
183                     keyguardViewMediatorLazy.get().maybeHandlePendingLock()
184 
185                     // Tell the CentralSurfaces to become keyguard for real - we waited on that
186                     // since it is slow and would have caused the animation to jank.
187                     mCentralSurfaces.updateIsKeyguard()
188 
189                     // Run the callback given to us by the KeyguardVisibilityHelper.
190                     after.run()
191 
192                     // Done going to sleep, reset this flag.
193                     decidedToAnimateGoingToSleep = null
194 
195                     // We need to unset the listener. These are persistent for future animators
196                     keyguardView.animate().setListener(null)
197                     interactionJankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD)
198                 }
199                 .setListener(object : AnimatorListenerAdapter() {
200                     override fun onAnimationCancel(animation: Animator?) {
201                         // If we're cancelled, reset state flags/listeners. The end action above
202                         // will not be called, which is what we want since that will finish the
203                         // screen off animation and show the lockscreen, which we don't want if we
204                         // were cancelled.
205                         aodUiAnimationPlaying = false
206                         decidedToAnimateGoingToSleep = null
207                         keyguardView.animate().setListener(null)
208 
209                         interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
210                     }
211 
212                     override fun onAnimationStart(animation: Animator?) {
213                         interactionJankMonitor.begin(
214                                 mCentralSurfaces.notificationShadeWindowView,
215                                 CUJ_SCREEN_OFF_SHOW_AOD)
216                     }
217                 })
218                 .start()
219     }
220 
onStartedWakingUpnull221     override fun onStartedWakingUp() {
222         // Waking up, so reset this flag.
223         decidedToAnimateGoingToSleep = null
224 
225         shouldAnimateInKeyguard = false
226         lightRevealAnimator.cancel()
227         handler.removeCallbacksAndMessages(null)
228     }
229 
onFinishedWakingUpnull230     override fun onFinishedWakingUp() {
231         // Set this to false in onFinishedWakingUp rather than onStartedWakingUp so that other
232         // observers (such as CentralSurfaces) can ask us whether we were playing the screen off
233         // animation and reset accordingly.
234         aodUiAnimationPlaying = false
235 
236         // If we can't control the screen off animation, we shouldn't mess with the
237         // CentralSurfaces's keyguard state unnecessarily.
238         if (dozeParameters.get().canControlUnlockedScreenOff()) {
239             // Make sure the status bar is in the correct keyguard state, forcing it if necessary.
240             // This is required if the screen off animation is cancelled, since it might be
241             // incorrectly left in the KEYGUARD or SHADE states depending on when it was cancelled
242             // and whether 'lock instantly' is enabled. We need to force it so that the state is set
243             // even if we're going from SHADE to SHADE or KEYGUARD to KEYGUARD, since we might have
244             // changed parts of the UI (such as showing AOD in the shade) without actually changing
245             // the StatusBarState. This ensures that the UI definitely reflects the desired state.
246             mCentralSurfaces.updateIsKeyguard(true /* forceStateChange */)
247         }
248     }
249 
startAnimationnull250     override fun startAnimation(): Boolean {
251         if (shouldPlayUnlockedScreenOffAnimation()) {
252             decidedToAnimateGoingToSleep = true
253 
254             shouldAnimateInKeyguard = true
255             lightRevealAnimationPlaying = true
256             lightRevealAnimator.start()
257             handler.postDelayed({
258                 // Only run this callback if the device is sleeping (not interactive). This callback
259                 // is removed in onStartedWakingUp, but since that event is asynchronously
260                 // dispatched, a race condition could make it possible for this callback to be run
261                 // as the device is waking up. That results in the AOD UI being shown while we wake
262                 // up, with unpredictable consequences.
263                 if (!powerManager.isInteractive) {
264                     aodUiAnimationPlaying = true
265 
266                     // Show AOD. That'll cause the KeyguardVisibilityHelper to call
267                     // #animateInKeyguard.
268                     mCentralSurfaces.notificationPanelViewController.showAodUi()
269                 }
270             }, (ANIMATE_IN_KEYGUARD_DELAY * animatorDurationScale).toLong())
271 
272             return true
273         } else {
274             decidedToAnimateGoingToSleep = false
275             return false
276         }
277     }
278 
279     /**
280      * Whether we want to play the screen off animation when the phone starts going to sleep, based
281      * on the current state of the device.
282      */
shouldPlayUnlockedScreenOffAnimationnull283     fun shouldPlayUnlockedScreenOffAnimation(): Boolean {
284         // If we haven't been initialized yet, we don't have a StatusBar/LightRevealScrim yet, so we
285         // can't perform the animation.
286         if (!initialized) {
287             return false
288         }
289 
290         // If the device isn't in a state where we can control unlocked screen off (no AOD enabled,
291         // power save, etc.) then we shouldn't try to do so.
292         if (!dozeParameters.get().canControlUnlockedScreenOff()) {
293             return false
294         }
295 
296         // If we explicitly already decided not to play the screen off animation, then never change
297         // our mind.
298         if (decidedToAnimateGoingToSleep == false) {
299             return false
300         }
301 
302         // If animations are disabled system-wide, don't play this one either.
303         if (Settings.Global.getString(
304                 context.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE) == "0") {
305             return false
306         }
307 
308         // We only play the unlocked screen off animation if we are... unlocked.
309         if (statusBarStateControllerImpl.state != StatusBarState.SHADE) {
310             return false
311         }
312 
313         // We currently draw both the light reveal scrim, and the AOD UI, in the shade. If it's
314         // already expanded and showing notifications/QS, the animation looks really messy. For now,
315         // disable it if the notification panel is expanded.
316         if ((!this::mCentralSurfaces.isInitialized ||
317                 mCentralSurfaces.notificationPanelViewController.isPanelExpanded) &&
318                 // Status bar might be expanded because we have started
319                 // playing the animation already
320                 !isAnimationPlaying()
321         ) {
322             return false
323         }
324 
325         // If we're not allowed to rotate the keyguard, it can only be displayed in zero-degree
326         // portrait. If we're in another orientation, disable the screen off animation so we don't
327         // animate in the keyguard AOD UI sideways or upside down.
328         if (!keyguardStateController.isKeyguardScreenRotationAllowed &&
329             context.display.rotation != Surface.ROTATION_0) {
330             return false
331         }
332 
333         // Otherwise, good to go.
334         return true
335     }
336 
shouldDelayDisplayDozeTransitionnull337     override fun shouldDelayDisplayDozeTransition(): Boolean =
338         shouldPlayUnlockedScreenOffAnimation()
339 
340     /**
341      * Whether we're doing the light reveal animation or we're done with that and animating in the
342      * AOD UI.
343      */
344     override fun isAnimationPlaying(): Boolean {
345         return lightRevealAnimationPlaying || aodUiAnimationPlaying
346     }
347 
shouldAnimateInKeyguardnull348     override fun shouldAnimateInKeyguard(): Boolean =
349         shouldAnimateInKeyguard
350 
351     override fun shouldHideScrimOnWakeUp(): Boolean =
352         isScreenOffLightRevealAnimationPlaying()
353 
354     override fun overrideNotificationsDozeAmount(): Boolean =
355         shouldPlayUnlockedScreenOffAnimation() && isAnimationPlaying()
356 
357     override fun shouldShowAodIconsWhenShade(): Boolean =
358         isAnimationPlaying()
359 
360     override fun shouldAnimateAodIcons(): Boolean =
361         shouldPlayUnlockedScreenOffAnimation()
362 
363     override fun shouldPlayAnimation(): Boolean =
364         shouldPlayUnlockedScreenOffAnimation()
365 
366     /**
367      * Whether the light reveal animation is playing. The second part of the screen off animation,
368      * where AOD animates in, might still be playing if this returns false.
369      */
370     fun isScreenOffLightRevealAnimationPlaying(): Boolean {
371         return lightRevealAnimationPlaying
372     }
373 }
374