1 /*
2  * Copyright (C) 2023 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.scene.ui.view
18 
19 import android.annotation.SuppressLint
20 import android.content.Context
21 import android.util.AttributeSet
22 import android.util.Pair
23 import android.view.DisplayCutout
24 import android.view.View
25 import android.view.WindowInsets
26 import android.widget.FrameLayout
27 import androidx.core.view.updateMargins
28 import com.android.systemui.compose.ComposeInitializer
29 import com.android.systemui.res.R
30 
31 /** A view that can serve as the root of the main SysUI window. */
32 open class WindowRootView(
33     context: Context,
34     attrs: AttributeSet?,
35 ) :
36     FrameLayout(
37         context,
38         attrs,
39     ) {
40 
41     private lateinit var layoutInsetsController: LayoutInsetsController
42     private var leftInset = 0
43     private var rightInset = 0
44 
onAttachedToWindownull45     override fun onAttachedToWindow() {
46         super.onAttachedToWindow()
47 
48         if (isRoot()) {
49             ComposeInitializer.onAttachedToWindow(this)
50         }
51     }
52 
onDetachedFromWindownull53     override fun onDetachedFromWindow() {
54         super.onDetachedFromWindow()
55 
56         if (isRoot()) {
57             ComposeInitializer.onDetachedFromWindow(this)
58         }
59     }
60 
generateLayoutParamsnull61     override fun generateLayoutParams(attrs: AttributeSet?): FrameLayout.LayoutParams? {
62         return LayoutParams(context, attrs)
63     }
64 
generateDefaultLayoutParamsnull65     override fun generateDefaultLayoutParams(): FrameLayout.LayoutParams? {
66         return LayoutParams(
67             FrameLayout.LayoutParams.MATCH_PARENT,
68             FrameLayout.LayoutParams.MATCH_PARENT
69         )
70     }
71 
onApplyWindowInsetsnull72     override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets? {
73         val insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars())
74         if (fitsSystemWindows) {
75             val paddingChanged = insets.top != paddingTop || insets.bottom != paddingBottom
76 
77             // Drop top inset, and pass through bottom inset.
78             if (paddingChanged) {
79                 setPadding(0, 0, 0, 0)
80             }
81         } else {
82             val changed =
83                 paddingLeft != 0 || paddingRight != 0 || paddingTop != 0 || paddingBottom != 0
84             if (changed) {
85                 setPadding(0, 0, 0, 0)
86             }
87         }
88         leftInset = 0
89         rightInset = 0
90 
91         val displayCutout = rootWindowInsets.displayCutout
92         val pairInsets: Pair<Int, Int> =
93             layoutInsetsController.getinsets(windowInsets, displayCutout)
94         leftInset = pairInsets.first
95         rightInset = pairInsets.second
96         applyMargins()
97         return windowInsets
98     }
99 
setLayoutInsetsControllernull100     fun setLayoutInsetsController(layoutInsetsController: LayoutInsetsController) {
101         this.layoutInsetsController = layoutInsetsController
102     }
103 
applyMarginsnull104     private fun applyMargins() {
105         val count = childCount
106         for (i in 0 until count) {
107             val child = getChildAt(i)
108             if (child.layoutParams is LayoutParams) {
109                 val layoutParams = child.layoutParams as LayoutParams
110                 if (
111                     !layoutParams.ignoreRightInset &&
112                         (layoutParams.rightMargin != rightInset ||
113                             layoutParams.leftMargin != leftInset)
114                 ) {
115                     layoutParams.updateMargins(left = leftInset, right = rightInset)
116                     child.requestLayout()
117                 }
118             }
119         }
120     }
121 
122     /**
123      * Returns `true` if this view is the true root of the view-hierarchy; `false` otherwise.
124      *
125      * Please see the class-level documentation to understand why this is possible.
126      */
isRootnull127     private fun isRoot(): Boolean {
128         // TODO(b/283300105): remove this check once there's only one subclass of WindowRootView.
129         return parent.let { it !is View || it.id == android.R.id.content }
130     }
131 
132     /** Controller responsible for calculating insets for the shade window. */
133     interface LayoutInsetsController {
134 
135         /** Update the insets and calculate them accordingly. */
getinsetsnull136         fun getinsets(
137             windowInsets: WindowInsets?,
138             displayCutout: DisplayCutout?,
139         ): Pair<Int, Int>
140     }
141 
142     private class LayoutParams : FrameLayout.LayoutParams {
143         var ignoreRightInset = false
144 
145         constructor(
146             width: Int,
147             height: Int,
148         ) : super(
149             width,
150             height,
151         )
152 
153         @SuppressLint("CustomViewStyleable")
154         constructor(
155             context: Context,
156             attrs: AttributeSet?,
157         ) : super(
158             context,
159             attrs,
160         ) {
161             val obtainedAttributes =
162                 context.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout)
163             ignoreRightInset =
164                 obtainedAttributes.getBoolean(
165                     R.styleable.StatusBarWindowView_Layout_ignoreRightInset,
166                     false
167                 )
168             obtainedAttributes.recycle()
169         }
170     }
171 }
172