• 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 com.android.internal.util.LatencyTracker
25 import com.android.systemui.dagger.qualifiers.Main
26 import com.android.systemui.keyguard.WakefulnessLifecycle
27 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
28 import com.android.systemui.keyguard.domain.interactor.ToAodFoldTransitionInteractor
29 import com.android.systemui.shade.ShadeFoldAnimator
30 import com.android.systemui.shade.ShadeViewController
31 import com.android.systemui.statusbar.LightRevealScrim
32 import com.android.systemui.statusbar.phone.CentralSurfaces
33 import com.android.systemui.statusbar.phone.ScreenOffAnimation
34 import com.android.systemui.statusbar.policy.CallbackController
35 import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus
36 import com.android.systemui.util.concurrency.DelayableExecutor
37 import com.android.systemui.util.settings.GlobalSettings
38 import dagger.Lazy
39 import java.util.function.Consumer
40 import javax.inject.Inject
41 
42 /**
43  * Controls folding to AOD animation: when AOD is enabled and foldable device is folded we play a
44  * special AOD animation on the outer screen
45  */
46 @SysUIUnfoldScope
47 class FoldAodAnimationController
48 @Inject
49 constructor(
50     @Main private val mainExecutor: DelayableExecutor,
51     private val context: Context,
52     private val deviceStateManager: DeviceStateManager,
53     private val wakefulnessLifecycle: WakefulnessLifecycle,
54     private val globalSettings: GlobalSettings,
55     private val latencyTracker: LatencyTracker,
56     private val keyguardInteractor: Lazy<KeyguardInteractor>,
57     private val foldTransitionInteractor: Lazy<ToAodFoldTransitionInteractor>,
58 ) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer {
59 
60     private lateinit var shadeViewController: ShadeViewController
61 
62     private var isFolded = false
63     private var isFoldHandled = true
64 
65     private var alwaysOnEnabled: Boolean = false
66     private var isScrimOpaque: Boolean = false
67     private var pendingScrimReadyCallback: Runnable? = null
68 
69     private var shouldPlayAnimation = false
70     private var isAnimationPlaying = false
71     private var cancelAnimation: Runnable? = null
72 
73     private val statusListeners = arrayListOf<FoldAodAnimationStatus>()
74     private val foldToAodLatencyTracker = FoldToAodLatencyTracker()
75 
76     private val startAnimationRunnable = Runnable {
77         shadeFoldAnimator.startFoldToAodAnimation(
78             /* startAction= */ { foldToAodLatencyTracker.onAnimationStarted() },
79             /* endAction= */ { setAnimationState(playing = false) },
80             /* cancelAction= */ { setAnimationState(playing = false) },
81         )
82     }
83 
84     override fun initialize(
85         centralSurfaces: CentralSurfaces,
86         shadeViewController: ShadeViewController,
87         lightRevealScrim: LightRevealScrim,
88     ) {
89         this.shadeViewController = shadeViewController
90         foldTransitionInteractor.get().initialize(shadeViewController.shadeFoldAnimator)
91 
92         deviceStateManager.registerCallback(mainExecutor, FoldListener())
93         wakefulnessLifecycle.addObserver(this)
94     }
95 
96     /** Returns true if we should run fold to AOD animation */
97     override fun shouldPlayAnimation(): Boolean = shouldPlayAnimation
98 
99     private fun shouldStartAnimation(): Boolean =
100         alwaysOnEnabled &&
101             wakefulnessLifecycle.lastSleepReason == PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD &&
102             globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0"
103 
104     override fun startAnimation(): Boolean =
105         if (shouldStartAnimation()) {
106             setAnimationState(playing = true)
107             shadeFoldAnimator.prepareFoldToAodAnimation()
108             true
109         } else {
110             setAnimationState(playing = false)
111             false
112         }
113 
114     override fun onStartedWakingUp() {
115         if (isAnimationPlaying) {
116             foldToAodLatencyTracker.cancel()
117             cancelAnimation?.run()
118             shadeFoldAnimator.cancelFoldToAodAnimation()
119         }
120 
121         setAnimationState(playing = false)
122     }
123 
124     private val shadeFoldAnimator: ShadeFoldAnimator
125         get() {
126             return foldTransitionInteractor.get().foldAnimator
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) =
144         mainExecutor.execute {
145             if (shouldPlayAnimation) {
146                 // The device was not dozing and going to sleep after folding, play the animation
147                 if (isScrimOpaque) {
148                     onReady.run()
149                 } else {
150                     pendingScrimReadyCallback = onReady
151                 }
152             } else if (
153                 isFolded &&
154                     !isFoldHandled &&
155                     alwaysOnEnabled &&
156                     keyguardInteractor.get().isDozing.value
157             ) {
158                 setAnimationState(playing = true)
159                 shadeFoldAnimator.prepareFoldToAodAnimation()
160 
161                 onReady.run()
162             } else {
163                 // No animation, call ready callback immediately
164                 onReady.run()
165             }
166 
167             if (isFolded) {
168                 // Any time the screen turns on, this state needs to be reset if the device has been
169                 // folded. Reaching this line implies AOD has been shown in one way or another,
170                 // if enabled
171                 isFoldHandled = true
172             }
173         }
174 
175     /** Called when keyguard scrim opaque changed */
176     override fun onScrimOpaqueChanged(isOpaque: Boolean) {
177         isScrimOpaque = isOpaque
178 
179         if (isOpaque) {
180             pendingScrimReadyCallback?.run()
181             pendingScrimReadyCallback = null
182         }
183     }
184 
185     @BinderThread
186     fun onScreenTurnedOn() =
187         mainExecutor.execute {
188             if (shouldPlayAnimation) {
189                 cancelAnimation?.run()
190 
191                 // Post starting the animation to the next frame to avoid junk due to inset changes
192                 cancelAnimation =
193                     mainExecutor.executeDelayed(startAnimationRunnable, /* delayMillis= */ 0)
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     interface FoldAodAnimationStatus {
226         fun onFoldToAodAnimationChanged()
227     }
228 
229     private inner class FoldListener :
230         DeviceStateManager.FoldStateListener(
231             context,
232             Consumer { isFolded ->
233                 if (!isFolded) {
234                     // We are unfolded now, reset the fold handle status
235                     isFoldHandled = false
236                 }
237                 this.isFolded = isFolded
238                 if (isFolded) {
239                     foldToAodLatencyTracker.onFolded()
240                 }
241             },
242         )
243 
244     /**
245      * Tracks the latency of fold to AOD using [LatencyTracker].
246      *
247      * Events that trigger start and end are:
248      * - Start: Once [DeviceStateManager] sends the folded signal [FoldToAodLatencyTracker.onFolded]
249      *   is called and latency tracking starts.
250      * - End: Once the fold -> AOD animation starts, [FoldToAodLatencyTracker.onAnimationStarted] is
251      *   called, and latency tracking stops.
252      */
253     private inner class FoldToAodLatencyTracker {
254 
255         /** Triggers the latency logging, if needed. */
256         fun onFolded() {
257             if (shouldStartAnimation()) {
258                 latencyTracker.onActionStart(LatencyTracker.ACTION_FOLD_TO_AOD)
259             }
260         }
261 
262         /**
263          * Called once the Fold -> AOD animation is started.
264          *
265          * For latency tracking, this determines the end of the fold to aod action.
266          */
267         fun onAnimationStarted() {
268             latencyTracker.onActionEnd(LatencyTracker.ACTION_FOLD_TO_AOD)
269         }
270 
271         fun cancel() {
272             latencyTracker.onActionCancel(LatencyTracker.ACTION_FOLD_TO_AOD)
273         }
274     }
275 }
276