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