• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 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.content.Context
20 import android.graphics.PixelFormat
21 import android.hardware.display.DisplayManager
22 import android.os.Handler
23 import android.os.Looper
24 import android.os.Trace
25 import android.view.Choreographer
26 import android.view.Display
27 import android.view.DisplayInfo
28 import android.view.Surface
29 import android.view.Surface.Rotation
30 import android.view.SurfaceControl
31 import android.view.SurfaceControlViewHost
32 import android.view.SurfaceSession
33 import android.view.WindowManager
34 import android.view.WindowlessWindowManager
35 import androidx.annotation.WorkerThread
36 import com.android.app.tracing.traceSection
37 import com.android.systemui.dagger.qualifiers.Background
38 import com.android.systemui.dagger.qualifiers.Main
39 import com.android.systemui.settings.DisplayTracker
40 import com.android.systemui.statusbar.LightRevealEffect
41 import com.android.systemui.statusbar.LightRevealScrim
42 import com.android.systemui.unfold.dagger.UnfoldBg
43 import com.android.systemui.unfold.updates.RotationChangeProvider
44 import com.android.systemui.util.concurrency.ThreadFactory
45 import com.android.wm.shell.displayareahelper.DisplayAreaHelper
46 import dagger.assisted.Assisted
47 import dagger.assisted.AssistedFactory
48 import dagger.assisted.AssistedInject
49 import java.util.Optional
50 import java.util.concurrent.Executor
51 import java.util.function.Consumer
52 import kotlinx.coroutines.CoroutineScope
53 import kotlinx.coroutines.asCoroutineDispatcher
54 import com.android.app.tracing.coroutines.launchTraced as launch
55 
56 interface FullscreenLightRevealAnimation {
57     fun init()
58 
59     fun onScreenTurningOn(onOverlayReady: Runnable)
60 }
61 
62 class FullscreenLightRevealAnimationController
63 @AssistedInject
64 constructor(
65     private val context: Context,
66     private val displayManager: DisplayManager,
67     private val threadFactory: ThreadFactory,
68     @UnfoldBg private val bgHandler: Handler,
69     @UnfoldBg private val rotationChangeProvider: RotationChangeProvider,
70     private val displayAreaHelper: Optional<DisplayAreaHelper>,
71     private val displayTracker: DisplayTracker,
72     @Background private val applicationScope: CoroutineScope,
73     @Main private val executor: Executor,
74     @Assisted private val displaySelector: List<DisplayInfo>.() -> DisplayInfo?,
75     @Assisted private val lightRevealEffectFactory: (rotation: Int) -> LightRevealEffect,
76     @Assisted private val overlayTitle: String
77 ) {
78 
79     private lateinit var bgExecutor: Executor
80     private lateinit var wwm: WindowlessWindowManager
81 
82     private var currentRotation: Int = context.display.rotation
83     private var root: SurfaceControlViewHost? = null
84 
85     /** The scrim view that is used to reveal the screen. */
86     var scrimView: LightRevealScrim? = null
87         private set
88 
89     private val rotationWatcher = RotationWatcher()
90     private val internalDisplayInfos: List<DisplayInfo> =
91         displayManager
92             .getDisplays(DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
<lambda>null93             .map { DisplayInfo().apply { it.getDisplayInfo(this) } }
<lambda>null94             .filter { it.type == Display.TYPE_INTERNAL }
95 
96     var isTouchBlocked: Boolean = false
97         set(value) {
98             if (value != field) {
<lambda>null99                 traceSection("$TAG#relayoutToUpdateTouch") { root?.relayout(getLayoutParams()) }
100                 field = value
101             }
102         }
103 
initnull104     fun init() {
105         bgExecutor = threadFactory.buildDelayableExecutorOnHandler(bgHandler)
106         rotationChangeProvider.addCallback(rotationWatcher)
107 
108         buildSurface { builder ->
109             applicationScope.launch(context = executor.asCoroutineDispatcher()) {
110                 val overlayContainer = builder.build()
111 
112                 SurfaceControl.Transaction()
113                     .setLayer(overlayContainer, OVERLAY_LAYER_Z_INDEX)
114                     .show(overlayContainer)
115                     .apply()
116 
117                 wwm =
118                     WindowlessWindowManager(context.resources.configuration, overlayContainer, null)
119             }
120         }
121     }
122 
addOverlaynull123     fun addOverlay(
124         initialAlpha: Float,
125         onOverlayReady: Runnable? = null,
126     ) {
127         if (!::wwm.isInitialized) {
128             // Surface overlay is not created yet on the first SysUI launch
129             onOverlayReady?.run()
130             return
131         }
132         ensureInBackground()
133         ensureOverlayRemoved()
134         prepareOverlay(onOverlayReady, wwm, bgExecutor, initialAlpha)
135     }
136 
ensureOverlayRemovednull137     fun ensureOverlayRemoved() {
138         ensureInBackground()
139 
140         traceSection("ensureOverlayRemoved") {
141             root?.release()
142             root = null
143             scrimView = null
144         }
145     }
146 
isOverlayVisiblenull147     fun isOverlayVisible(): Boolean {
148         return scrimView == null
149     }
150 
updateRevealAmountnull151     fun updateRevealAmount(revealAmount: Float) {
152         scrimView?.revealAmount = revealAmount
153     }
154 
buildSurfacenull155     private fun buildSurface(onUpdated: Consumer<SurfaceControl.Builder>) {
156         val containerBuilder =
157             SurfaceControl.Builder(SurfaceSession())
158                 .setContainerLayer()
159                 .setName("FoldUnfoldAnimationContainer")
160 
161         displayAreaHelper
162             .get()
163             .attachToRootDisplayArea(displayTracker.defaultDisplayId, containerBuilder, onUpdated)
164     }
165 
prepareOverlaynull166     private fun prepareOverlay(
167         onOverlayReady: Runnable? = null,
168         wwm: WindowlessWindowManager,
169         bgExecutor: Executor,
170         initialAlpha: Float,
171     ) {
172         val newRoot = SurfaceControlViewHost(context, context.display, wwm, javaClass.simpleName)
173 
174         val params = getLayoutParams()
175         val newView =
176             LightRevealScrim(
177                     context,
178                     attrs = null,
179                     initialWidth = params.width,
180                     initialHeight = params.height
181                 )
182                 .apply {
183                     revealEffect = lightRevealEffectFactory(currentRotation)
184                     revealAmount = initialAlpha
185                 }
186 
187         newRoot.setView(newView, params)
188 
189         if (onOverlayReady != null) {
190             Trace.beginAsyncSection("$TAG#relayout", 0)
191 
192             newRoot.relayout(params) { transaction ->
193                 val vsyncId = Choreographer.getSfInstance().vsyncId
194                 transaction.setFrameTimelineVsync(vsyncId).apply()
195 
196                 transaction
197                     .setFrameTimelineVsync(vsyncId + 1)
198                     .addTransactionCommittedListener(bgExecutor) {
199                         Trace.endAsyncSection("$TAG#relayout", 0)
200                         onOverlayReady.run()
201                     }
202                     .apply()
203             }
204         }
205         root = newRoot
206         scrimView = newView
207     }
208 
ensureInBackgroundnull209     private fun ensureInBackground() {
210         check(Looper.myLooper() == bgHandler.looper) { "Not being executed in the background!" }
211     }
212 
getLayoutParamsnull213     private fun getLayoutParams(): WindowManager.LayoutParams {
214         val displayInfo =
215             internalDisplayInfos.displaySelector()
216                 ?: throw IllegalArgumentException("No internal displays found!")
217         return WindowManager.LayoutParams().apply {
218             if (currentRotation.isVerticalRotation()) {
219                 height = displayInfo.naturalHeight
220                 width = displayInfo.naturalWidth
221             } else {
222                 height = displayInfo.naturalWidth
223                 width = displayInfo.naturalHeight
224             }
225             format = PixelFormat.TRANSLUCENT
226             type = WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY
227             title = overlayTitle
228             layoutInDisplayCutoutMode =
229                 WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
230             fitInsetsTypes = 0
231 
232             flags =
233                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
234                     WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
235             setTrustedOverlay()
236 
237             packageName = context.opPackageName
238         }
239     }
240 
241     private inner class RotationWatcher : RotationChangeProvider.RotationListener {
242         @WorkerThread
onRotationChangednull243         override fun onRotationChanged(newRotation: Int) {
244             traceSection("$TAG#onRotationChanged") {
245                 ensureInBackground()
246                 if (currentRotation != newRotation) {
247                     currentRotation = newRotation
248                     scrimView?.revealEffect = lightRevealEffectFactory(currentRotation)
249                     root?.relayout(getLayoutParams())
250                 }
251             }
252         }
253     }
254 
255     @AssistedFactory
256     interface Factory {
createnull257         fun create(
258             displaySelector: List<DisplayInfo>.() -> DisplayInfo?,
259             effectFactory: (rotation: Int) -> LightRevealEffect,
260             overlayContainerName: String
261         ): FullscreenLightRevealAnimationController
262     }
263 
264     companion object {
265         private const val TAG = "FullscreenLightRevealAnimation"
266         private const val ROTATION_ANIMATION_OVERLAY_Z_INDEX = Integer.MAX_VALUE
267         private const val OVERLAY_LAYER_Z_INDEX = ROTATION_ANIMATION_OVERLAY_Z_INDEX - 1
268         const val ALPHA_TRANSPARENT = 1f
269         const val ALPHA_OPAQUE = 0f
270 
271         fun @receiver:Rotation Int.isVerticalRotation(): Boolean =
272             this == Surface.ROTATION_0 || this == Surface.ROTATION_180
273     }
274 }
275