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