1 /* 2 * 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.car.wm.displayarea 18 19 import android.content.Context 20 import android.graphics.Rect 21 import android.graphics.Region 22 import android.gui.TrustedOverlay 23 import android.os.Binder 24 import android.util.AttributeSet 25 import android.util.Slog 26 import android.util.SparseArray 27 import android.view.InsetsSource 28 import android.view.SurfaceControl 29 import android.view.SurfaceHolder 30 import android.view.SurfaceView 31 import android.window.DisplayAreaInfo 32 import android.window.DisplayAreaOrganizer 33 import android.window.WindowContainerTransaction 34 import com.android.systemui.R 35 import com.android.systemui.car.Flags.daviewBasedWindowing 36 37 const val INVALID_DISPLAY_AREA_FEATURE_ID = -1 38 const val INVALID_DAVIEW_ID = -1L 39 40 /** 41 * A DaView is a SurfaceView which has surface of a display area as a child. It can either be used 42 * with a DAG (DisplayAreaGroup) or a TDA (TaskDisplayArea). When used with a DAG, the DAG must 43 * have only one TDA so that the atomic unit becomes (DAG -> TDA). 44 * 45 * <ul> 46 * <li> When used for a DAG, the displayAreaFeatureId should be the DAG and launchTaskDisplayAreaFeatureId should be 47 * the TDA inside the DAG</li> 48 * <li> When used for a TDA directly, displayAreaFeatureId and launchTaskDisplayAreaFeatureId can both point to TDA 49 * </li> 50 * </ul> 51 */ 52 class DaView : SurfaceView, SurfaceHolder.Callback { 53 companion object { 54 private val TAG = DaView::class.java.simpleName 55 } 56 57 /** 58 * A unique identifier composed of the [DaView.displayAreaFeatureId] and the display which 59 * this display area is in. 60 */ 61 val id: Long 62 val cornerRadius: Int 63 64 /** 65 * Directly maps to the [com.android.server.wm.DisplayArea.mFeatureId]. This is not a unique 66 * identifier though. Two display areas on different displays can have the same featureId. 67 */ 68 val displayAreaFeatureId: Int 69 val launchTaskDisplayAreaFeatureId: Int 70 71 internal lateinit var daInfo: DisplayAreaInfo 72 internal lateinit var daLeash: SurfaceControl 73 74 /** 75 * Governs if the surface change should instantly trigger a wm change without shell transitions 76 * for the corresponding DisplayAreaGroup. 77 * This can be helpful when using composable layouts for prototyping where the changes are need 78 * to take effect right away. But should ideally be disabled in the interest 79 * of using {@link DaViewTransitions} for all the WM updates. 80 * This doesn't apply to surfaceCreated callback. Surface creation leads to direct wm update 81 * as of today as a transition is usually not required when surface is created. 82 */ 83 var surfaceToWmSyncEnabled = true 84 85 private val tmpTransaction: SurfaceControl.Transaction = SurfaceControl.Transaction() 86 private val insetsOwner = Binder() 87 private val insets = SparseArray<Rect>() 88 private val touchableInsetsProvider = TouchableInsetsProvider(this) 89 private var obscuredTouchRegion: Region? = null 90 private var surfaceCreated = false 91 private lateinit var organizer: DisplayAreaOrganizer 92 93 constructor(context: Context) : super(context) { 94 if (!daviewBasedWindowing()) { 95 throw IllegalAccessException("DaView feature not available") 96 } 97 98 cornerRadius = 0 99 displayAreaFeatureId = INVALID_DISPLAY_AREA_FEATURE_ID 100 launchTaskDisplayAreaFeatureId = INVALID_DISPLAY_AREA_FEATURE_ID 101 id = INVALID_DAVIEW_ID 102 103 init() 104 } 105 106 constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { 107 val typedArray = context.obtainStyledAttributes(attrs, R.styleable.DisplayAreaView) 108 cornerRadius = typedArray.getInteger(R.styleable.DisplayAreaView_cornerRadius, 0) 109 displayAreaFeatureId = 110 typedArray.getInteger(R.styleable.DisplayAreaView_displayAreaFeatureId, -1) 111 launchTaskDisplayAreaFeatureId = 112 typedArray.getInteger(R.styleable.DisplayAreaView_launchTaskDisplayAreaFeatureId, -1) 113 id = (context.displayId.toLong() shl 32) or (displayAreaFeatureId.toLong() and 0xffffffffL) 114 115 typedArray.recycle() 116 117 init() 118 } 119 initnull120 private fun init() { 121 if (displayAreaFeatureId == INVALID_DISPLAY_AREA_FEATURE_ID) { 122 Slog.e(TAG, "Unknown feature ID for a DisplayAreaView") 123 return 124 } 125 126 organizer = object : DisplayAreaOrganizer(context.mainExecutor) { 127 override fun onDisplayAreaAppeared( 128 displayAreaInfo: DisplayAreaInfo, 129 leash: SurfaceControl 130 ) { 131 super.onDisplayAreaAppeared(displayAreaInfo, leash) 132 daInfo = displayAreaInfo 133 this@DaView.daLeash = leash 134 135 if (surfaceCreated) { 136 tmpTransaction.reparent(leash, surfaceControl) 137 // Sometimes when the systemui crashes and the leash is reattached to 138 // the new surface control, it could already have some dirty position 139 // set by WM or the container of DAView. So the child leash must be 140 // repositioned to 0,0 here. 141 .setPosition(leash, 0f, 0f) 142 .show(leash) 143 .apply() 144 } 145 } 146 147 override fun onDisplayAreaInfoChanged(displayAreaInfo: DisplayAreaInfo) { 148 super.onDisplayAreaInfoChanged(displayAreaInfo) 149 // This callback doesn't need to be handled as of now as the config changes will 150 // directly propagate to the children of DisplayArea. If in the future, the 151 // decors in the window owning the layout of screen are needed to be adjusted 152 // based on display area's config, DaView can expose APIs to listen to these 153 // changes. 154 } 155 }.apply { 156 val displayAreaInfos = registerOrganizer(displayAreaFeatureId) 157 displayAreaInfos.forEach { 158 if (it.displayAreaInfo.displayId == context.displayId) { 159 // There would be just one DisplayArea with a unique (displayId, 160 // displayAreaFeatureId) 161 daInfo = it.displayAreaInfo 162 daLeash = it.leash 163 } 164 } 165 } 166 holder.addCallback(this) 167 } 168 surfaceCreatednull169 override fun surfaceCreated(holder: SurfaceHolder) { 170 surfaceCreated = true 171 tmpTransaction.reparent(daLeash, surfaceControl) 172 // DaView is meant to contain app activities which shouldn't have trusted overlays 173 // flag set even when itself reparented in a window which is trusted. 174 .setTrustedOverlay(surfaceControl, TrustedOverlay.DISABLED) 175 .setCornerRadius(surfaceControl, cornerRadius.toFloat()) 176 .setPosition(daLeash, 0f, 0f) 177 .show(daLeash) 178 .apply() 179 syncBoundsToWm() 180 } 181 surfaceChangednull182 override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { 183 if (!surfaceToWmSyncEnabled) { 184 return 185 } 186 syncBoundsToWm() 187 } 188 syncBoundsToWmnull189 fun syncBoundsToWm() { 190 val wct = WindowContainerTransaction() 191 var rect = Rect() 192 getBoundsOnScreen(rect) 193 wct.setBounds(daInfo.token, rect) 194 DaViewTransitions.sInstance?.instantApplyViaShellTransit(wct) 195 } 196 resyncLeashToViewnull197 fun resyncLeashToView(tr: SurfaceControl.Transaction) { 198 if (!surfaceCreated) { 199 return 200 } 201 tr.reparent(daLeash, surfaceControl) 202 .setPosition(daLeash, 0f, 0f) 203 .show(daLeash) 204 } 205 surfaceDestroyednull206 override fun surfaceDestroyed(holder: SurfaceHolder) { 207 surfaceCreated = false 208 tmpTransaction.reparent(daLeash, null).apply() 209 } 210 onAttachedToWindownull211 public override fun onAttachedToWindow() { 212 super.onAttachedToWindow() 213 touchableInsetsProvider.addToViewTreeObserver() 214 DaViewTransitions.sInstance?.add(this) ?: run { 215 Slog.e(TAG, "Failed adding $this to DaViewTransitions") 216 } 217 } 218 onDetachedFromWindownull219 public override fun onDetachedFromWindow() { 220 super.onDetachedFromWindow() 221 touchableInsetsProvider.removeFromViewTreeObserver() 222 DaViewTransitions.sInstance?.remove(this) ?: run { 223 Slog.e(TAG, "Failed to remove $this from DaViewTransitions") 224 } 225 } 226 227 /** 228 * Indicates a region of the view that is not touchable. 229 * 230 * @param obscuredRect the obscured region of the view. 231 */ setObscuredTouchRectnull232 fun setObscuredTouchRect(obscuredRect: Rect) { 233 obscuredTouchRegion = Region(obscuredRect) 234 touchableInsetsProvider.setObscuredTouchRegion(obscuredTouchRegion) 235 } 236 237 /** 238 * Indicates a region of the view that is not touchable. 239 * 240 * @param obscuredRegion the obscured region of the view. 241 */ setObscuredTouchRegionnull242 fun setObscuredTouchRegion(obscuredRegion: Region) { 243 obscuredTouchRegion = obscuredRegion 244 touchableInsetsProvider.setObscuredTouchRegion(obscuredTouchRegion) 245 } 246 addInsetsnull247 fun addInsets(index: Int, type: Int, frame: Rect) { 248 insets.append(InsetsSource.createId(insetsOwner, index, type), frame) 249 val wct = WindowContainerTransaction() 250 val insetsFlags = 0 251 wct.addInsetsSource( 252 daInfo.token, 253 insetsOwner, 254 index, 255 type, 256 frame, 257 emptyArray<Rect>(), 258 insetsFlags 259 ) 260 DaViewTransitions.sInstance?.instantApplyViaTaskOrganizer(wct) 261 } 262 removeInsetsnull263 fun removeInsets(index: Int, type: Int) { 264 if (insets.size() == 0) { 265 Slog.w(TAG, "No insets set.") 266 return 267 } 268 val id = InsetsSource.createId(insetsOwner, index, type) 269 if (!insets.contains(id)) { 270 Slog.w( 271 TAG, 272 "Insets type: " + type + " can't be removed as it was not " + 273 "applied as part of the last addInsets()" 274 ) 275 return 276 } 277 insets.remove(id) 278 val wct = WindowContainerTransaction() 279 wct.removeInsetsSource( 280 daInfo.token, 281 insetsOwner, 282 index, 283 type 284 ) 285 DaViewTransitions.sInstance?.instantApplyViaTaskOrganizer(wct) 286 } 287 } 288