• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2021 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.unfold
18 
19 import android.annotation.BinderThread
20 import android.content.Context
21 import android.hardware.devicestate.DeviceStateManager
22 import android.os.PowerManager
23 import android.provider.Settings
24 import androidx.annotation.VisibleForTesting
25 import androidx.core.view.OneShotPreDrawListener
26 import androidx.lifecycle.Lifecycle
27 import androidx.lifecycle.repeatOnLifecycle
28 import com.android.internal.util.LatencyTracker
29 import com.android.systemui.dagger.qualifiers.Main
30 import com.android.systemui.keyguard.WakefulnessLifecycle
31 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
32 import com.android.systemui.lifecycle.repeatWhenAttached
33 import com.android.systemui.statusbar.LightRevealScrim
34 import com.android.systemui.statusbar.phone.CentralSurfaces
35 import com.android.systemui.statusbar.phone.ScreenOffAnimation
36 import com.android.systemui.statusbar.policy.CallbackController
37 import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus
38 import com.android.systemui.util.concurrency.DelayableExecutor
39 import com.android.systemui.util.settings.GlobalSettings
40 import dagger.Lazy
41 import java.util.function.Consumer
42 import javax.inject.Inject
43 import kotlinx.coroutines.CoroutineScope
44 import kotlinx.coroutines.Job
45 import kotlinx.coroutines.launch
46 
47 /**
48  * Controls folding to AOD animation: when AOD is enabled and foldable device is folded we play a
49  * special AOD animation on the outer screen
50  */
51 @SysUIUnfoldScope
52 class FoldAodAnimationController
53 @Inject
54 constructor(
55     @Main private val mainExecutor: DelayableExecutor,
56     private val context: Context,
57     private val deviceStateManager: DeviceStateManager,
58     private val wakefulnessLifecycle: WakefulnessLifecycle,
59     private val globalSettings: GlobalSettings,
60     private val latencyTracker: LatencyTracker,
61     private val keyguardInteractor: Lazy<KeyguardInteractor>,
62 ) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer {
63 
64     private lateinit var centralSurfaces: CentralSurfaces
65 
66     private var isFolded = false
67     private var isFoldHandled = true
68 
69     private var alwaysOnEnabled: Boolean = false
70     private var isDozing: Boolean = false
71     private var isScrimOpaque: Boolean = false
72     private var pendingScrimReadyCallback: Runnable? = null
73 
74     private var shouldPlayAnimation = false
75     private var isAnimationPlaying = false
76     private var cancelAnimation: Runnable? = null
77 
78     private val statusListeners = arrayListOf<FoldAodAnimationStatus>()
79     private val foldToAodLatencyTracker = FoldToAodLatencyTracker()
80 
81     private val startAnimationRunnable = Runnable {
82         centralSurfaces.notificationPanelViewController.startFoldToAodAnimation(
83             /* startAction= */ { foldToAodLatencyTracker.onAnimationStarted() },
84             /* endAction= */ { setAnimationState(playing = false) },
85             /* cancelAction= */ { setAnimationState(playing = false) },
86         )
87     }
88 
89     override fun initialize(centralSurfaces: CentralSurfaces, lightRevealScrim: LightRevealScrim) {
90         this.centralSurfaces = centralSurfaces
91 
92         deviceStateManager.registerCallback(mainExecutor, FoldListener())
93         wakefulnessLifecycle.addObserver(this)
94 
95         // TODO(b/254878364): remove this call to NPVC.getView()
96         centralSurfaces.notificationPanelViewController.view.repeatWhenAttached {
97             repeatOnLifecycle(Lifecycle.State.STARTED) { listenForDozing(this) }
98         }
99     }
100 
101     /** Returns true if we should run fold to AOD animation */
102     override fun shouldPlayAnimation(): Boolean = shouldPlayAnimation
103 
104     private fun shouldStartAnimation(): Boolean =
105         alwaysOnEnabled &&
106             wakefulnessLifecycle.lastSleepReason == PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD &&
107             globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0"
108 
109     override fun startAnimation(): Boolean =
110         if (shouldStartAnimation()) {
111             setAnimationState(playing = true)
112             centralSurfaces.notificationPanelViewController.prepareFoldToAodAnimation()
113             true
114         } else {
115             setAnimationState(playing = false)
116             false
117         }
118 
119     override fun onStartedWakingUp() {
120         if (isAnimationPlaying) {
121             foldToAodLatencyTracker.cancel()
122             cancelAnimation?.run()
123             centralSurfaces.notificationPanelViewController.cancelFoldToAodAnimation()
124         }
125 
126         setAnimationState(playing = false)
127     }
128 
129     private fun setAnimationState(playing: Boolean) {
130         shouldPlayAnimation = playing
131         isAnimationPlaying = playing
132         statusListeners.forEach(FoldAodAnimationStatus::onFoldToAodAnimationChanged)
133     }
134 
135     /**
136      * Called when screen starts turning on, the contents of the screen might not be visible yet.
137      * This method reports back that the animation is ready in [onReady] callback.
138      *
139      * @param onReady callback when the animation is ready
140      * @see [com.android.systemui.keyguard.KeyguardViewMediator]
141      */
142     @BinderThread
143     fun onScreenTurningOn(onReady: Runnable) = mainExecutor.execute {
144         if (shouldPlayAnimation) {
145             // The device was not dozing and going to sleep after folding, play the animation
146 
147             if (isScrimOpaque) {
148                 onReady.run()
149             } else {
150                 pendingScrimReadyCallback = onReady
151             }
152         } else if (isFolded && !isFoldHandled && alwaysOnEnabled && isDozing) {
153             // Screen turning on for the first time after folding and we are already dozing
154             // We should play the folding to AOD animation
155             isFoldHandled = true
156 
157             setAnimationState(playing = true)
158             centralSurfaces.notificationPanelViewController.prepareFoldToAodAnimation()
159 
160             // We don't need to wait for the scrim as it is already displayed
161             // but we should wait for the initial animation preparations to be drawn
162             // (setting initial alpha/translation)
163             // TODO(b/254878364): remove this call to NPVC.getView()
164             OneShotPreDrawListener.add(
165                 centralSurfaces.notificationPanelViewController.view,
166                 onReady
167             )
168         } else {
169             // No animation, call ready callback immediately
170             onReady.run()
171         }
172     }
173 
174     /** Called when keyguard scrim opaque changed */
175     override fun onScrimOpaqueChanged(isOpaque: Boolean) {
176         isScrimOpaque = isOpaque
177 
178         if (isOpaque) {
179             pendingScrimReadyCallback?.run()
180             pendingScrimReadyCallback = null
181         }
182     }
183 
184     @BinderThread
185     fun onScreenTurnedOn() = mainExecutor.execute {
186         if (shouldPlayAnimation) {
187             cancelAnimation?.run()
188 
189             // Post starting the animation to the next frame to avoid junk due to inset changes
190             cancelAnimation = mainExecutor.executeDelayed(
191                 startAnimationRunnable,
192                 /* delayMillis= */ 0
193             )
194             shouldPlayAnimation = false
195         }
196     }
197 
198     override fun isAnimationPlaying(): Boolean = isAnimationPlaying
199 
200     override fun isKeyguardHideDelayed(): Boolean = isAnimationPlaying()
201 
202     override fun shouldShowAodIconsWhenShade(): Boolean = shouldPlayAnimation()
203 
204     override fun shouldAnimateAodIcons(): Boolean = !shouldPlayAnimation()
205 
206     override fun shouldAnimateDozingChange(): Boolean = !shouldPlayAnimation()
207 
208     override fun shouldAnimateClockChange(): Boolean = !isAnimationPlaying()
209 
210     override fun shouldDelayDisplayDozeTransition(): Boolean = shouldPlayAnimation()
211 
212     /** Called when AOD status is changed */
213     override fun onAlwaysOnChanged(alwaysOn: Boolean) {
214         alwaysOnEnabled = alwaysOn
215     }
216 
217     override fun addCallback(listener: FoldAodAnimationStatus) {
218         statusListeners += listener
219     }
220 
221     override fun removeCallback(listener: FoldAodAnimationStatus) {
222         statusListeners.remove(listener)
223     }
224 
225     @VisibleForTesting
226     internal suspend fun listenForDozing(scope: CoroutineScope): Job {
227         return scope.launch { keyguardInteractor.get().isDozing.collect { isDozing = it } }
228     }
229 
230     interface FoldAodAnimationStatus {
231         fun onFoldToAodAnimationChanged()
232     }
233 
234     private inner class FoldListener :
235         DeviceStateManager.FoldStateListener(
236             context,
237             Consumer { isFolded ->
238                 if (!isFolded) {
239                     // We are unfolded now, reset the fold handle status
240                     isFoldHandled = false
241                 }
242                 this.isFolded = isFolded
243                 if (isFolded) {
244                     foldToAodLatencyTracker.onFolded()
245                 }
246             }
247         )
248 
249     /**
250      * Tracks the latency of fold to AOD using [LatencyTracker].
251      *
252      * Events that trigger start and end are:
253      *
254      * - Start: Once [DeviceStateManager] sends the folded signal [FoldToAodLatencyTracker.onFolded]
255      * is called and latency tracking starts.
256      * - End: Once the fold -> AOD animation starts, [FoldToAodLatencyTracker.onAnimationStarted] is
257      * called, and latency tracking stops.
258      */
259     private inner class FoldToAodLatencyTracker {
260 
261         /** Triggers the latency logging, if needed. */
262         fun onFolded() {
263             if (shouldStartAnimation()) {
264                 latencyTracker.onActionStart(LatencyTracker.ACTION_FOLD_TO_AOD)
265             }
266         }
267         /**
268          * Called once the Fold -> AOD animation is started.
269          *
270          * For latency tracking, this determines the end of the fold to aod action.
271          */
272         fun onAnimationStarted() {
273             latencyTracker.onActionEnd(LatencyTracker.ACTION_FOLD_TO_AOD)
274         }
275 
276         fun cancel() {
277             latencyTracker.onActionCancel(LatencyTracker.ACTION_FOLD_TO_AOD)
278         }
279     }
280 }
281