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 package com.android.systemui.unfold 17 18 import android.annotation.BinderThread 19 import android.content.ContentResolver 20 import android.content.Context 21 import android.hardware.devicestate.DeviceStateManager 22 import android.hardware.input.InputManagerGlobal 23 import android.os.Handler 24 import android.os.Trace 25 import com.android.systemui.Flags.unfoldAnimationBackgroundProgress 26 import com.android.systemui.flags.FeatureFlagsClassic 27 import com.android.systemui.flags.Flags 28 import com.android.systemui.statusbar.LinearLightRevealEffect 29 import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.ALPHA_OPAQUE 30 import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.ALPHA_TRANSPARENT 31 import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.isVerticalRotation 32 import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.FOLD 33 import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.UNFOLD 34 import com.android.systemui.unfold.dagger.UnfoldBg 35 import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled 36 import com.android.systemui.util.concurrency.ThreadFactory 37 import java.util.concurrent.Executor 38 import java.util.function.Consumer 39 import javax.inject.Inject 40 import javax.inject.Provider 41 42 @SysUIUnfoldScope 43 class UnfoldLightRevealOverlayAnimation 44 @Inject 45 constructor( 46 private val context: Context, 47 private val featureFlags: FeatureFlagsClassic, 48 private val contentResolver: ContentResolver, 49 @UnfoldBg private val unfoldProgressHandler: Handler, 50 @UnfoldBg 51 private val unfoldTransitionBgProgressProvider: Provider<UnfoldTransitionProgressProvider>, 52 private val unfoldTransitionProgressProvider: Provider<UnfoldTransitionProgressProvider>, 53 private val deviceStateManager: DeviceStateManager, 54 private val threadFactory: ThreadFactory, 55 private val fullscreenLightRevealAnimationControllerFactory: 56 FullscreenLightRevealAnimationController.Factory 57 ) : FullscreenLightRevealAnimation { 58 59 private val transitionListener = TransitionListener() 60 private var isFolded: Boolean = false 61 private var isUnfoldHandled: Boolean = true 62 private var overlayAddReason: AddOverlayReason = UNFOLD 63 private lateinit var controller: FullscreenLightRevealAnimationController 64 private lateinit var bgExecutor: Executor 65 66 override fun init() { 67 // This method will be called only on devices where this animation is enabled, 68 // so normally this thread won't be created 69 70 controller = 71 fullscreenLightRevealAnimationControllerFactory.create( 72 displaySelector = { maxByOrNull { it.naturalWidth } }, 73 effectFactory = { LinearLightRevealEffect(it.isVerticalRotation()) }, 74 overlayContainerName = SURFACE_CONTAINER_NAME, 75 ) 76 controller.init() 77 bgExecutor = threadFactory.buildDelayableExecutorOnHandler(unfoldProgressHandler) 78 deviceStateManager.registerCallback(bgExecutor, FoldListener()) 79 if (unfoldAnimationBackgroundProgress()) { 80 unfoldTransitionBgProgressProvider.get().addCallback(transitionListener) 81 } else { 82 unfoldTransitionProgressProvider.get().addCallback(transitionListener) 83 } 84 } 85 86 /** 87 * Called when screen starts turning on, the contents of the screen might not be visible yet. 88 * This method reports back that the overlay is ready in [onOverlayReady] callback. 89 * 90 * @param onOverlayReady callback when the overlay is drawn and visible on the screen 91 * @see [com.android.systemui.keyguard.KeyguardViewMediator] 92 */ 93 @BinderThread 94 override fun onScreenTurningOn(onOverlayReady: Runnable) { 95 executeInBackground { 96 Trace.beginSection("$TAG#onScreenTurningOn") 97 try { 98 // Add the view only if we are unfolding and this is the first screen on 99 if (!isFolded && !isUnfoldHandled && contentResolver.areAnimationsEnabled()) { 100 overlayAddReason = UNFOLD 101 controller.addOverlay(calculateRevealAmount(), onOverlayReady) 102 isUnfoldHandled = true 103 } else { 104 // No unfold transition, immediately report that overlay is ready 105 controller.ensureOverlayRemoved() 106 onOverlayReady.run() 107 } 108 } finally { 109 Trace.endSection() 110 } 111 } 112 } 113 114 private fun calculateRevealAmount(animationProgress: Float? = null): Float { 115 val overlayAddReason = overlayAddReason 116 117 if (animationProgress == null) { 118 // Animation progress unknown, calculate the initial value based on the overlay 119 // add reason 120 return when (overlayAddReason) { 121 FOLD -> ALPHA_TRANSPARENT 122 UNFOLD -> ALPHA_OPAQUE 123 } 124 } 125 126 val showVignetteWhenFolding = 127 featureFlags.isEnabled(Flags.ENABLE_DARK_VIGNETTE_WHEN_FOLDING) 128 129 return if (!showVignetteWhenFolding && overlayAddReason == FOLD) { 130 // Do not darken the content when SHOW_VIGNETTE_WHEN_FOLDING flag is off 131 // and we are folding the device. We still add the overlay to block touches 132 // while the animation is running but the overlay is transparent. 133 ALPHA_TRANSPARENT 134 } else { 135 animationProgress 136 } 137 } 138 139 private inner class TransitionListener : 140 UnfoldTransitionProgressProvider.TransitionProgressListener { 141 142 override fun onTransitionProgress(progress: Float) = executeInBackground { 143 controller.updateRevealAmount(calculateRevealAmount(progress)) 144 // When unfolding unblock touches a bit earlier than the animation end as the 145 // interpolation has a long tail of very slight movement at the end which should not 146 // affect much the usage of the device 147 controller.isTouchBlocked = 148 overlayAddReason == FOLD || progress < UNFOLD_BLOCK_TOUCHES_UNTIL_PROGRESS 149 } 150 151 override fun onTransitionFinished() = executeInBackground { 152 controller.ensureOverlayRemoved() 153 } 154 155 override fun onTransitionStarted() { 156 // Add view for folding case (when unfolding the view is added earlier) 157 if (controller.isOverlayVisible()) { 158 executeInBackground { 159 overlayAddReason = FOLD 160 controller.addOverlay(calculateRevealAmount()) 161 } 162 } 163 // Disable input dispatching during transition. 164 InputManagerGlobal.getInstance().cancelCurrentTouch() 165 } 166 } 167 168 private fun executeInBackground(f: () -> Unit) { 169 // This is needed to allow progresses to be received both from the main thread (that will 170 // schedule a runnable on the bg thread), and from the bg thread directly (no reposting). 171 if (unfoldProgressHandler.looper.isCurrentThread) { 172 f() 173 } else { 174 unfoldProgressHandler.post(f) 175 } 176 } 177 178 private inner class FoldListener : 179 DeviceStateManager.FoldStateListener( 180 context, 181 Consumer { isFolded -> 182 if (isFolded) { 183 controller.ensureOverlayRemoved() 184 isUnfoldHandled = false 185 } 186 this.isFolded = isFolded 187 } 188 ) 189 190 private enum class AddOverlayReason { 191 FOLD, 192 UNFOLD 193 } 194 195 private companion object { 196 const val TAG = "UnfoldLightRevealOverlayAnimation" 197 const val SURFACE_CONTAINER_NAME = "unfold-overlay-container" 198 const val UNFOLD_BLOCK_TOUCHES_UNTIL_PROGRESS = 0.8f 199 } 200 } 201