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.annotation.VisibleForTesting 25 import androidx.core.view.OneShotPreDrawListener 26 import androidx.lifecycle.Lifecycle 27 import androidx.lifecycle.repeatOnLifecycle 28 import com.android.internal.util.LatencyTracker 29 import com.android.systemui.dagger.qualifiers.Main 30 import com.android.systemui.keyguard.WakefulnessLifecycle 31 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor 32 import com.android.systemui.lifecycle.repeatWhenAttached 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 import kotlinx.coroutines.CoroutineScope 44 import kotlinx.coroutines.Job 45 import kotlinx.coroutines.launch 46 47 /** 48 * Controls folding to AOD animation: when AOD is enabled and foldable device is folded we play a 49 * special AOD animation on the outer screen 50 */ 51 @SysUIUnfoldScope 52 class FoldAodAnimationController 53 @Inject 54 constructor( 55 @Main private val mainExecutor: DelayableExecutor, 56 private val context: Context, 57 private val deviceStateManager: DeviceStateManager, 58 private val wakefulnessLifecycle: WakefulnessLifecycle, 59 private val globalSettings: GlobalSettings, 60 private val latencyTracker: LatencyTracker, 61 private val keyguardInteractor: Lazy<KeyguardInteractor>, 62 ) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer { 63 64 private lateinit var centralSurfaces: CentralSurfaces 65 66 private var isFolded = false 67 private var isFoldHandled = true 68 69 private var alwaysOnEnabled: Boolean = false 70 private var isDozing: Boolean = false 71 private var isScrimOpaque: Boolean = false 72 private var pendingScrimReadyCallback: Runnable? = null 73 74 private var shouldPlayAnimation = false 75 private var isAnimationPlaying = false 76 private var cancelAnimation: Runnable? = null 77 78 private val statusListeners = arrayListOf<FoldAodAnimationStatus>() 79 private val foldToAodLatencyTracker = FoldToAodLatencyTracker() 80 81 private val startAnimationRunnable = Runnable { 82 centralSurfaces.notificationPanelViewController.startFoldToAodAnimation( 83 /* startAction= */ { foldToAodLatencyTracker.onAnimationStarted() }, 84 /* endAction= */ { setAnimationState(playing = false) }, 85 /* cancelAction= */ { setAnimationState(playing = false) }, 86 ) 87 } 88 89 override fun initialize(centralSurfaces: CentralSurfaces, lightRevealScrim: LightRevealScrim) { 90 this.centralSurfaces = centralSurfaces 91 92 deviceStateManager.registerCallback(mainExecutor, FoldListener()) 93 wakefulnessLifecycle.addObserver(this) 94 95 // TODO(b/254878364): remove this call to NPVC.getView() 96 centralSurfaces.notificationPanelViewController.view.repeatWhenAttached { 97 repeatOnLifecycle(Lifecycle.State.STARTED) { listenForDozing(this) } 98 } 99 } 100 101 /** Returns true if we should run fold to AOD animation */ 102 override fun shouldPlayAnimation(): Boolean = shouldPlayAnimation 103 104 private fun shouldStartAnimation(): Boolean = 105 alwaysOnEnabled && 106 wakefulnessLifecycle.lastSleepReason == PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD && 107 globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0" 108 109 override fun startAnimation(): Boolean = 110 if (shouldStartAnimation()) { 111 setAnimationState(playing = true) 112 centralSurfaces.notificationPanelViewController.prepareFoldToAodAnimation() 113 true 114 } else { 115 setAnimationState(playing = false) 116 false 117 } 118 119 override fun onStartedWakingUp() { 120 if (isAnimationPlaying) { 121 foldToAodLatencyTracker.cancel() 122 cancelAnimation?.run() 123 centralSurfaces.notificationPanelViewController.cancelFoldToAodAnimation() 124 } 125 126 setAnimationState(playing = false) 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) = mainExecutor.execute { 144 if (shouldPlayAnimation) { 145 // The device was not dozing and going to sleep after folding, play the animation 146 147 if (isScrimOpaque) { 148 onReady.run() 149 } else { 150 pendingScrimReadyCallback = onReady 151 } 152 } else if (isFolded && !isFoldHandled && alwaysOnEnabled && isDozing) { 153 // Screen turning on for the first time after folding and we are already dozing 154 // We should play the folding to AOD animation 155 isFoldHandled = true 156 157 setAnimationState(playing = true) 158 centralSurfaces.notificationPanelViewController.prepareFoldToAodAnimation() 159 160 // We don't need to wait for the scrim as it is already displayed 161 // but we should wait for the initial animation preparations to be drawn 162 // (setting initial alpha/translation) 163 // TODO(b/254878364): remove this call to NPVC.getView() 164 OneShotPreDrawListener.add( 165 centralSurfaces.notificationPanelViewController.view, 166 onReady 167 ) 168 } else { 169 // No animation, call ready callback immediately 170 onReady.run() 171 } 172 } 173 174 /** Called when keyguard scrim opaque changed */ 175 override fun onScrimOpaqueChanged(isOpaque: Boolean) { 176 isScrimOpaque = isOpaque 177 178 if (isOpaque) { 179 pendingScrimReadyCallback?.run() 180 pendingScrimReadyCallback = null 181 } 182 } 183 184 @BinderThread 185 fun onScreenTurnedOn() = mainExecutor.execute { 186 if (shouldPlayAnimation) { 187 cancelAnimation?.run() 188 189 // Post starting the animation to the next frame to avoid junk due to inset changes 190 cancelAnimation = mainExecutor.executeDelayed( 191 startAnimationRunnable, 192 /* delayMillis= */ 0 193 ) 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 @VisibleForTesting 226 internal suspend fun listenForDozing(scope: CoroutineScope): Job { 227 return scope.launch { keyguardInteractor.get().isDozing.collect { isDozing = it } } 228 } 229 230 interface FoldAodAnimationStatus { 231 fun onFoldToAodAnimationChanged() 232 } 233 234 private inner class FoldListener : 235 DeviceStateManager.FoldStateListener( 236 context, 237 Consumer { isFolded -> 238 if (!isFolded) { 239 // We are unfolded now, reset the fold handle status 240 isFoldHandled = false 241 } 242 this.isFolded = isFolded 243 if (isFolded) { 244 foldToAodLatencyTracker.onFolded() 245 } 246 } 247 ) 248 249 /** 250 * Tracks the latency of fold to AOD using [LatencyTracker]. 251 * 252 * Events that trigger start and end are: 253 * 254 * - Start: Once [DeviceStateManager] sends the folded signal [FoldToAodLatencyTracker.onFolded] 255 * is called and latency tracking starts. 256 * - End: Once the fold -> AOD animation starts, [FoldToAodLatencyTracker.onAnimationStarted] is 257 * called, and latency tracking stops. 258 */ 259 private inner class FoldToAodLatencyTracker { 260 261 /** Triggers the latency logging, if needed. */ 262 fun onFolded() { 263 if (shouldStartAnimation()) { 264 latencyTracker.onActionStart(LatencyTracker.ACTION_FOLD_TO_AOD) 265 } 266 } 267 /** 268 * Called once the Fold -> AOD animation is started. 269 * 270 * For latency tracking, this determines the end of the fold to aod action. 271 */ 272 fun onAnimationStarted() { 273 latencyTracker.onActionEnd(LatencyTracker.ACTION_FOLD_TO_AOD) 274 } 275 276 fun cancel() { 277 latencyTracker.onActionCancel(LatencyTracker.ACTION_FOLD_TO_AOD) 278 } 279 } 280 } 281