• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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