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