• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 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.animation.Animator
20 import android.animation.AnimatorListenerAdapter
21 import android.animation.ValueAnimator
22 import android.annotation.BinderThread
23 import android.os.SystemProperties
24 import android.util.Log
25 import android.view.View
26 import android.view.animation.DecelerateInterpolator
27 import com.android.app.tracing.TraceUtils.traceAsync
28 import com.android.internal.foldables.FoldLockSettingAvailabilityProvider
29 import com.android.internal.jank.Cuj.CUJ_FOLD_ANIM
30 import com.android.internal.jank.InteractionJankMonitor
31 import com.android.systemui.dagger.qualifiers.Background
32 import com.android.systemui.display.data.repository.DeviceStateRepository
33 import com.android.systemui.power.domain.interactor.PowerInteractor
34 import com.android.systemui.power.shared.model.ScreenPowerState
35 import com.android.systemui.statusbar.LinearSideLightRevealEffect
36 import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.ALPHA_OPAQUE
37 import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.ALPHA_TRANSPARENT
38 import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.isVerticalRotation
39 import com.android.systemui.unfold.dagger.UnfoldBg
40 import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
41 import com.android.systemui.util.kotlin.race
42 import javax.inject.Inject
43 import kotlin.coroutines.resume
44 import kotlinx.coroutines.CompletableDeferred
45 import kotlinx.coroutines.CoroutineDispatcher
46 import kotlinx.coroutines.CoroutineScope
47 import kotlinx.coroutines.TimeoutCancellationException
48 import kotlinx.coroutines.flow.Flow
49 import kotlinx.coroutines.flow.catch
50 import kotlinx.coroutines.flow.distinctUntilChanged
51 import kotlinx.coroutines.flow.filter
52 import kotlinx.coroutines.flow.first
53 import kotlinx.coroutines.flow.flatMapLatest
54 import kotlinx.coroutines.flow.flow
55 import kotlinx.coroutines.flow.map
56 import kotlinx.coroutines.flow.onCompletion
57 import com.android.app.tracing.coroutines.launchTraced as launch
58 import kotlinx.coroutines.suspendCancellableCoroutine
59 import kotlinx.coroutines.withTimeout
60 
61 class FoldLightRevealOverlayAnimation
62 @Inject
63 constructor(
64     @UnfoldBg private val bgDispatcher: CoroutineDispatcher,
65     private val deviceStateRepository: DeviceStateRepository,
66     private val powerInteractor: PowerInteractor,
67     @Background private val applicationScope: CoroutineScope,
68     private val animationStatusRepository: AnimationStatusRepository,
69     private val controllerFactory: FullscreenLightRevealAnimationController.Factory,
70     private val foldLockSettingAvailabilityProvider: FoldLockSettingAvailabilityProvider,
71     private val interactionJankMonitor: InteractionJankMonitor
72 ) : FullscreenLightRevealAnimation {
73 
74     private val revealProgressValueAnimator: ValueAnimator =
75         ValueAnimator.ofFloat(ALPHA_OPAQUE, ALPHA_TRANSPARENT)
76     private val areAnimationEnabled: Flow<Boolean>
77         get() = animationStatusRepository.areAnimationsEnabled()
78 
79     private lateinit var controller: FullscreenLightRevealAnimationController
80     @Volatile private var readyCallback: CompletableDeferred<Runnable>? = null
81 
82     override fun init() {
83         // This method will be called only on devices where this animation is enabled,
84         // so normally this thread won't be created
85         if (!foldLockSettingAvailabilityProvider.isFoldLockBehaviorAvailable) {
86             return
87         }
88 
89         controller =
90             controllerFactory.create(
91                 displaySelector = { minByOrNull { it.naturalWidth } },
92                 effectFactory = { LinearSideLightRevealEffect(it.isVerticalRotation()) },
93                 overlayContainerName = OVERLAY_TITLE
94             )
95         controller.init()
96 
97         applicationScope.launch(context = bgDispatcher) {
98             powerInteractor.screenPowerState.collect {
99                 if (it == ScreenPowerState.SCREEN_ON) {
100                     readyCallback = null
101                 }
102             }
103         }
104 
105         applicationScope.launch(context = bgDispatcher) {
106             deviceStateRepository.state
107                 .map { it == DeviceStateRepository.DeviceState.FOLDED }
108                 .distinctUntilChanged()
109                 .flatMapLatest { isFolded ->
110                     flow<Nothing> {
111                             if (!areAnimationEnabled.first() || !isFolded) {
112                                 return@flow
113                             }
114                             race(
115                                 {
116                                     traceAsync(TAG, "prepareAndPlayFoldAnimation()") {
117                                         withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) {
118                                             readyCallback = CompletableDeferred()
119                                             val onReady = readyCallback?.await()
120                                             readyCallback = null
121                                             controller.addOverlay(ALPHA_OPAQUE, onReady)
122                                             waitForScreenTurnedOn()
123                                         }
124                                         playFoldLightRevealOverlayAnimation()
125                                     }
126                                 },
127                                 { waitForGoToSleep() }
128                             )
129                         }
130                         .catchTimeoutAndLog()
131                         .onCompletion {
132                             controller.ensureOverlayRemoved()
133                             val onReady = readyCallback?.takeIf { it.isCompleted }?.getCompleted()
134                             onReady?.run()
135                             readyCallback = null
136                         }
137                 }
138                 .collect {}
139         }
140     }
141 
142     @BinderThread
143     override fun onScreenTurningOn(onOverlayReady: Runnable) {
144         readyCallback?.complete(onOverlayReady) ?: onOverlayReady.run()
145     }
146 
147     private suspend fun waitForScreenTurnedOn() =
148         traceAsync(TAG, "waitForScreenTurnedOn()") {
149             powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
150         }
151 
152     private suspend fun waitForGoToSleep() =
153         traceAsync(TAG, "waitForGoToSleep()") { powerInteractor.isAsleep.filter { it }.first() }
154 
155     private suspend fun playFoldLightRevealOverlayAnimation() =
156         trackCuj(CUJ_FOLD_ANIM, controller.scrimView) {
157             revealProgressValueAnimator.duration = ANIMATION_DURATION
158             revealProgressValueAnimator.interpolator = DecelerateInterpolator()
159             revealProgressValueAnimator.addUpdateListener { animation ->
160                 controller.updateRevealAmount(animation.animatedFraction)
161             }
162             revealProgressValueAnimator.startAndAwaitCompletion()
163         }
164 
165     private suspend fun trackCuj(cuj: Int, view: View?, block: suspend () -> Unit) {
166         view?.let { interactionJankMonitor.begin(it, cuj) }
167         try {
168             block()
169         } finally {
170             if (view != null) interactionJankMonitor.end(cuj)
171         }
172     }
173 
174     private suspend fun ValueAnimator.startAndAwaitCompletion(): Unit =
175         suspendCancellableCoroutine { continuation ->
176             val listener =
177                 object : AnimatorListenerAdapter() {
178                     override fun onAnimationEnd(animation: Animator) {
179                         continuation.resume(Unit)
180                         removeListener(this)
181                     }
182                 }
183             addListener(listener)
184             continuation.invokeOnCancellation { removeListener(listener) }
185             start()
186         }
187 
188     private fun <T> Flow<T>.catchTimeoutAndLog() = catch { exception ->
189         when (exception) {
190             is TimeoutCancellationException -> Log.e(TAG, "Fold light reveal animation timed out")
191             else -> throw exception
192         }
193     }
194 
195     private companion object {
196         const val TAG = "FoldLightRevealOverlayAnimation"
197         const val WAIT_FOR_ANIMATION_TIMEOUT_MS = 2000L
198         const val OVERLAY_TITLE = "fold-animation-overlay"
199         val ANIMATION_DURATION: Long
200             get() = SystemProperties.getLong("persist.fold_animation_duration", 200L)
201     }
202 }
203