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