1 /* 2 * 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.animation.ValueAnimator 19 import android.content.Context 20 import android.graphics.PixelFormat 21 import android.hardware.devicestate.DeviceStateManager 22 import android.hardware.devicestate.DeviceStateManager.FoldStateListener 23 import android.hardware.display.DisplayManager 24 import android.os.Handler 25 import android.os.Trace 26 import android.view.Choreographer 27 import android.view.Display 28 import android.view.DisplayInfo 29 import android.view.Surface 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 com.android.systemui.dagger.qualifiers.Main 36 import com.android.systemui.dagger.qualifiers.UiBackground 37 import com.android.systemui.statusbar.LightRevealEffect 38 import com.android.systemui.statusbar.LightRevealScrim 39 import com.android.systemui.statusbar.LinearLightRevealEffect 40 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener 41 import com.android.wm.shell.displayareahelper.DisplayAreaHelper 42 import java.util.Optional 43 import java.util.concurrent.Executor 44 import java.util.function.Consumer 45 import javax.inject.Inject 46 47 @SysUIUnfoldScope 48 class UnfoldLightRevealOverlayAnimation @Inject constructor( 49 private val context: Context, 50 private val deviceStateManager: DeviceStateManager, 51 private val displayManager: DisplayManager, 52 private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider, 53 private val displayAreaHelper: Optional<DisplayAreaHelper>, 54 @Main private val executor: Executor, 55 @Main private val handler: Handler, 56 @UiBackground private val backgroundExecutor: Executor 57 ) { 58 59 private val transitionListener = TransitionListener() 60 private val displayListener = DisplayChangeListener() 61 62 private lateinit var wwm: WindowlessWindowManager 63 private lateinit var unfoldedDisplayInfo: DisplayInfo 64 private lateinit var overlayContainer: SurfaceControl 65 66 private var root: SurfaceControlViewHost? = null 67 private var scrimView: LightRevealScrim? = null 68 private var isFolded: Boolean = false 69 private var isUnfoldHandled: Boolean = true 70 71 private var currentRotation: Int = context.display!!.rotation 72 73 fun init() { 74 deviceStateManager.registerCallback(executor, FoldListener()) 75 unfoldTransitionProgressProvider.addCallback(transitionListener) 76 77 val containerBuilder = SurfaceControl.Builder(SurfaceSession()) 78 .setContainerLayer() 79 .setName("unfold-overlay-container") 80 81 displayAreaHelper.get().attachToRootDisplayArea(Display.DEFAULT_DISPLAY, 82 containerBuilder) { builder -> 83 executor.execute { 84 overlayContainer = builder.build() 85 86 SurfaceControl.Transaction() 87 .setLayer(overlayContainer, Integer.MAX_VALUE) 88 .show(overlayContainer) 89 .apply() 90 91 wwm = WindowlessWindowManager(context.resources.configuration, 92 overlayContainer, null) 93 } 94 } 95 96 displayManager.registerDisplayListener(displayListener, handler, 97 DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) 98 99 // Get unfolded display size immediately as 'current display info' might be 100 // not up-to-date during unfolding 101 unfoldedDisplayInfo = getUnfoldedDisplayInfo() 102 } 103 104 /** 105 * Called when screen starts turning on, the contents of the screen might not be visible yet. 106 * This method reports back that the overlay is ready in [onOverlayReady] callback. 107 * 108 * @param onOverlayReady callback when the overlay is drawn and visible on the screen 109 * @see [com.android.systemui.keyguard.KeyguardViewMediator] 110 */ 111 fun onScreenTurningOn(onOverlayReady: Runnable) { 112 Trace.beginSection("UnfoldLightRevealOverlayAnimation#onScreenTurningOn") 113 try { 114 // Add the view only if we are unfolding and this is the first screen on 115 if (!isFolded && !isUnfoldHandled && ValueAnimator.areAnimatorsEnabled()) { 116 addView(onOverlayReady) 117 isUnfoldHandled = true 118 } else { 119 // No unfold transition, immediately report that overlay is ready 120 ensureOverlayRemoved() 121 onOverlayReady.run() 122 } 123 } finally { 124 Trace.endSection() 125 } 126 } 127 128 private fun addView(onOverlayReady: Runnable? = null) { 129 if (!::wwm.isInitialized) { 130 // Surface overlay is not created yet on the first SysUI launch 131 onOverlayReady?.run() 132 return 133 } 134 135 ensureOverlayRemoved() 136 137 val newRoot = SurfaceControlViewHost(context, context.display!!, wwm, false) 138 val newView = LightRevealScrim(context, null) 139 .apply { 140 revealEffect = createLightRevealEffect() 141 isScrimOpaqueChangedListener = Consumer {} 142 revealAmount = 0f 143 } 144 145 val params = getLayoutParams() 146 newRoot.setView(newView, params) 147 148 onOverlayReady?.let { callback -> 149 Trace.beginAsyncSection( 150 "UnfoldLightRevealOverlayAnimation#relayout", 0) 151 152 newRoot.relayout(params) { transaction -> 153 val vsyncId = Choreographer.getSfInstance().vsyncId 154 155 backgroundExecutor.execute { 156 // Apply the transaction that contains the first frame of the overlay 157 // synchronously and apply another empty transaction with 158 // 'vsyncId + 1' to make sure that it is actually displayed on 159 // the screen. The second transaction is necessary to remove the screen blocker 160 // (turn on the brightness) only when the content is actually visible as it 161 // might be presented only in the next frame. 162 // See b/197538198 163 transaction.setFrameTimelineVsync(vsyncId) 164 .apply(/* sync */true) 165 166 transaction 167 .setFrameTimelineVsync(vsyncId + 1) 168 .apply(/* sync */ true) 169 170 Trace.endAsyncSection( 171 "UnfoldLightRevealOverlayAnimation#relayout", 0) 172 callback.run() 173 } 174 } 175 } 176 177 scrimView = newView 178 root = newRoot 179 } 180 181 private fun getLayoutParams(): WindowManager.LayoutParams { 182 val params: WindowManager.LayoutParams = WindowManager.LayoutParams() 183 184 val rotation = context.display!!.rotation 185 val isNatural = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 186 187 params.height = if (isNatural) 188 unfoldedDisplayInfo.naturalHeight else unfoldedDisplayInfo.naturalWidth 189 params.width = if (isNatural) 190 unfoldedDisplayInfo.naturalWidth else unfoldedDisplayInfo.naturalHeight 191 192 params.format = PixelFormat.TRANSLUCENT 193 params.type = WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY 194 params.title = "Unfold Light Reveal Animation" 195 params.layoutInDisplayCutoutMode = 196 WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS 197 params.fitInsetsTypes = 0 198 params.flags = (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 199 or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) 200 params.setTrustedOverlay() 201 202 val packageName: String = context.opPackageName 203 params.packageName = packageName 204 205 return params 206 } 207 208 private fun createLightRevealEffect(): LightRevealEffect { 209 val isVerticalFold = currentRotation == Surface.ROTATION_0 || 210 currentRotation == Surface.ROTATION_180 211 return LinearLightRevealEffect(isVertical = isVerticalFold) 212 } 213 214 private fun ensureOverlayRemoved() { 215 root?.release() 216 root = null 217 scrimView = null 218 } 219 220 private fun getUnfoldedDisplayInfo(): DisplayInfo = 221 displayManager.displays 222 .asSequence() 223 .map { DisplayInfo().apply { it.getDisplayInfo(this) } } 224 .filter { it.type == Display.TYPE_INTERNAL } 225 .maxByOrNull { it.naturalWidth }!! 226 227 private inner class TransitionListener : TransitionProgressListener { 228 229 override fun onTransitionProgress(progress: Float) { 230 scrimView?.revealAmount = progress 231 } 232 233 override fun onTransitionFinished() { 234 ensureOverlayRemoved() 235 } 236 237 override fun onTransitionStarted() { 238 // Add view for folding case (when unfolding the view is added earlier) 239 if (scrimView == null) { 240 addView() 241 } 242 } 243 } 244 245 private inner class DisplayChangeListener : DisplayManager.DisplayListener { 246 247 override fun onDisplayChanged(displayId: Int) { 248 val newRotation: Int = context.display!!.rotation 249 if (currentRotation != newRotation) { 250 currentRotation = newRotation 251 scrimView?.revealEffect = createLightRevealEffect() 252 root?.relayout(getLayoutParams()) 253 } 254 } 255 256 override fun onDisplayAdded(displayId: Int) { 257 } 258 259 override fun onDisplayRemoved(displayId: Int) { 260 } 261 } 262 263 private inner class FoldListener : FoldStateListener(context, Consumer { isFolded -> 264 if (isFolded) { 265 ensureOverlayRemoved() 266 isUnfoldHandled = false 267 } 268 this.isFolded = isFolded 269 }) 270 } 271