• 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 kotlinx.coroutines.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 overlayContainerName: 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     private var scrimView: LightRevealScrim? = null
85 
86     private val rotationWatcher = RotationWatcher()
87     private val internalDisplayInfos: List<DisplayInfo> =
88         displayManager
89             .getDisplays(DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
<lambda>null90             .map { DisplayInfo().apply { it.getDisplayInfo(this) } }
<lambda>null91             .filter { it.type == Display.TYPE_INTERNAL }
92 
93     var isTouchBlocked: Boolean = false
94         set(value) {
95             if (value != field) {
<lambda>null96                 traceSection("$TAG#relayoutToUpdateTouch") { root?.relayout(getLayoutParams()) }
97                 field = value
98             }
99         }
100 
initnull101     fun init() {
102         bgExecutor = threadFactory.buildDelayableExecutorOnHandler(bgHandler)
103         rotationChangeProvider.addCallback(rotationWatcher)
104 
105         buildSurface { builder ->
106             applicationScope.launch(executor.asCoroutineDispatcher()) {
107                 val overlayContainer = builder.build()
108 
109                 SurfaceControl.Transaction()
110                     .setLayer(overlayContainer, OVERLAY_LAYER_Z_INDEX)
111                     .show(overlayContainer)
112                     .apply()
113 
114                 wwm =
115                     WindowlessWindowManager(context.resources.configuration, overlayContainer, null)
116             }
117         }
118     }
119 
addOverlaynull120     fun addOverlay(
121         initialAlpha: Float,
122         onOverlayReady: Runnable? = null,
123     ) {
124         if (!::wwm.isInitialized) {
125             // Surface overlay is not created yet on the first SysUI launch
126             onOverlayReady?.run()
127             return
128         }
129         ensureInBackground()
130         ensureOverlayRemoved()
131         prepareOverlay(onOverlayReady, wwm, bgExecutor, initialAlpha)
132     }
133 
ensureOverlayRemovednull134     fun ensureOverlayRemoved() {
135         ensureInBackground()
136 
137         traceSection("ensureOverlayRemoved") {
138             root?.release()
139             root = null
140             scrimView = null
141         }
142     }
143 
isOverlayVisiblenull144     fun isOverlayVisible(): Boolean {
145         return scrimView == null
146     }
147 
updateRevealAmountnull148     fun updateRevealAmount(revealAmount: Float) {
149         scrimView?.revealAmount = revealAmount
150     }
151 
buildSurfacenull152     private fun buildSurface(onUpdated: Consumer<SurfaceControl.Builder>) {
153         val containerBuilder =
154             SurfaceControl.Builder(SurfaceSession())
155                 .setContainerLayer()
156                 .setName(overlayContainerName)
157 
158         displayAreaHelper
159             .get()
160             .attachToRootDisplayArea(displayTracker.defaultDisplayId, containerBuilder, onUpdated)
161     }
162 
prepareOverlaynull163     private fun prepareOverlay(
164         onOverlayReady: Runnable? = null,
165         wwm: WindowlessWindowManager,
166         bgExecutor: Executor,
167         initialAlpha: Float,
168     ) {
169         val newRoot = SurfaceControlViewHost(context, context.display, wwm, javaClass.simpleName)
170 
171         val params = getLayoutParams()
172         val newView =
173             LightRevealScrim(
174                     context,
175                     attrs = null,
176                     initialWidth = params.width,
177                     initialHeight = params.height
178                 )
179                 .apply {
180                     revealEffect = lightRevealEffectFactory(currentRotation)
181                     revealAmount = initialAlpha
182                 }
183 
184         newRoot.setView(newView, params)
185 
186         if (onOverlayReady != null) {
187             Trace.beginAsyncSection("$TAG#relayout", 0)
188 
189             newRoot.relayout(params) { transaction ->
190                 val vsyncId = Choreographer.getSfInstance().vsyncId
191                 transaction.setFrameTimelineVsync(vsyncId).apply()
192 
193                 transaction
194                     .setFrameTimelineVsync(vsyncId + 1)
195                     .addTransactionCommittedListener(bgExecutor) {
196                         Trace.endAsyncSection("$TAG#relayout", 0)
197                         onOverlayReady.run()
198                     }
199                     .apply()
200             }
201         }
202         root = newRoot
203         scrimView = newView
204     }
205 
ensureInBackgroundnull206     private fun ensureInBackground() {
207         check(Looper.myLooper() == bgHandler.looper) { "Not being executed in the background!" }
208     }
209 
getLayoutParamsnull210     private fun getLayoutParams(): WindowManager.LayoutParams {
211         val displayInfo =
212             internalDisplayInfos.displaySelector()
213                 ?: throw IllegalArgumentException("No internal displays found!")
214         return WindowManager.LayoutParams().apply {
215             if (currentRotation.isVerticalRotation()) {
216                 height = displayInfo.naturalHeight
217                 width = displayInfo.naturalWidth
218             } else {
219                 height = displayInfo.naturalWidth
220                 width = displayInfo.naturalHeight
221             }
222             format = PixelFormat.TRANSLUCENT
223             type = WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY
224             title = javaClass.simpleName
225             layoutInDisplayCutoutMode =
226                 WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
227             fitInsetsTypes = 0
228 
229             flags =
230                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
231                     WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
232             setTrustedOverlay()
233 
234             packageName = context.opPackageName
235         }
236     }
237 
238     private inner class RotationWatcher : RotationChangeProvider.RotationListener {
239         @WorkerThread
onRotationChangednull240         override fun onRotationChanged(newRotation: Int) {
241             traceSection("$TAG#onRotationChanged") {
242                 ensureInBackground()
243                 if (currentRotation != newRotation) {
244                     currentRotation = newRotation
245                     scrimView?.revealEffect = lightRevealEffectFactory(currentRotation)
246                     root?.relayout(getLayoutParams())
247                 }
248             }
249         }
250     }
251 
252     @AssistedFactory
253     interface Factory {
createnull254         fun create(
255             displaySelector: List<DisplayInfo>.() -> DisplayInfo?,
256             effectFactory: (rotation: Int) -> LightRevealEffect,
257             overlayContainerName: String
258         ): FullscreenLightRevealAnimationController
259     }
260 
261     companion object {
262         private const val TAG = "FullscreenLightRevealAnimation"
263         private const val ROTATION_ANIMATION_OVERLAY_Z_INDEX = Integer.MAX_VALUE
264         private const val OVERLAY_LAYER_Z_INDEX = ROTATION_ANIMATION_OVERLAY_Z_INDEX - 1
265         const val ALPHA_TRANSPARENT = 1f
266         const val ALPHA_OPAQUE = 0f
267 
268         fun @receiver:Rotation Int.isVerticalRotation(): Boolean =
269             this == Surface.ROTATION_0 || this == Surface.ROTATION_180
270     }
271 }
272