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