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