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