• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.KeyEvent
25 import android.view.View
26 import android.view.WindowInsets
27 import android.widget.FrameLayout
28 import androidx.core.view.updateMargins
29 import com.android.systemui.Flags
30 import com.android.systemui.compose.ComposeInitializer
31 import com.android.systemui.res.R
32 
33 /** A view that can serve as the root of the main SysUI window. */
34 open class WindowRootView(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {
35 
36     private lateinit var layoutInsetsController: LayoutInsetsController
37     private var leftInset = 0
38     private var rightInset = 0
39 
40     private var previousInsets: WindowInsets? = null
41     private lateinit var windowRootViewKeyEventHandler: WindowRootViewKeyEventHandler
42 
setWindowRootViewKeyEventHandlernull43     fun setWindowRootViewKeyEventHandler(wrvkeh: WindowRootViewKeyEventHandler) {
44         windowRootViewKeyEventHandler = wrvkeh
45     }
46 
onAttachedToWindownull47     override fun onAttachedToWindow() {
48         super.onAttachedToWindow()
49 
50         if (isRoot()) {
51             ComposeInitializer.onAttachedToWindow(this)
52         }
53     }
54 
onDetachedFromWindownull55     override fun onDetachedFromWindow() {
56         super.onDetachedFromWindow()
57 
58         if (isRoot()) {
59             ComposeInitializer.onDetachedFromWindow(this)
60         }
61     }
62 
generateLayoutParamsnull63     override fun generateLayoutParams(attrs: AttributeSet?): FrameLayout.LayoutParams? {
64         return LayoutParams(context, attrs)
65     }
66 
generateDefaultLayoutParamsnull67     override fun generateDefaultLayoutParams(): FrameLayout.LayoutParams? {
68         return LayoutParams(
69             FrameLayout.LayoutParams.MATCH_PARENT,
70             FrameLayout.LayoutParams.MATCH_PARENT,
71         )
72     }
73 
onApplyWindowInsetsnull74     override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets? {
75         if (windowInsets == previousInsets) {
76             return windowInsets
77         }
78         val insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars())
79         if (fitsSystemWindows) {
80             val paddingChanged = insets.top != paddingTop || insets.bottom != paddingBottom
81 
82             // Drop top inset, and pass through bottom inset.
83             if (paddingChanged) {
84                 setPadding(0, 0, 0, 0)
85             }
86         } else {
87             val changed =
88                 paddingLeft != 0 || paddingRight != 0 || paddingTop != 0 || paddingBottom != 0
89             if (changed) {
90                 setPadding(0, 0, 0, 0)
91             }
92         }
93         leftInset = 0
94         rightInset = 0
95 
96         val displayCutout = rootWindowInsets.displayCutout
97         val pairInsets: Pair<Int, Int> =
98             layoutInsetsController.getinsets(windowInsets, displayCutout)
99         leftInset = pairInsets.first
100         rightInset = pairInsets.second
101         applyMargins()
102         return windowInsets.also { previousInsets = WindowInsets(it) }
103     }
104 
setLayoutInsetsControllernull105     fun setLayoutInsetsController(layoutInsetsController: LayoutInsetsController) {
106         this.layoutInsetsController = layoutInsetsController
107     }
108 
applyMarginsnull109     private fun applyMargins() {
110         val count = childCount
111         val hasFlagsEnabled = Flags.checkLockscreenGoneTransition()
112         var hasChildMarginUpdated = false
113         for (i in 0 until count) {
114             val child = getChildAt(i)
115             if (child.layoutParams is LayoutParams) {
116                 val layoutParams = child.layoutParams as LayoutParams
117                 if (
118                     !layoutParams.ignoreRightInset &&
119                         (layoutParams.rightMargin != rightInset ||
120                             layoutParams.leftMargin != leftInset)
121                 ) {
122                     layoutParams.updateMargins(left = leftInset, right = rightInset)
123                     hasChildMarginUpdated = true
124                     if (!hasFlagsEnabled) {
125                         child.requestLayout()
126                     }
127                 }
128             }
129         }
130         if (hasFlagsEnabled && hasChildMarginUpdated) {
131             // Request layout at once after all children's margins has updated
132             requestLayout()
133         }
134     }
135 
136     /**
137      * Returns `true` if this view is the true root of the view-hierarchy; `false` otherwise.
138      *
139      * Please see the class-level documentation to understand why this is possible.
140      */
isRootnull141     private fun isRoot(): Boolean {
142         // TODO(b/283300105): remove this check once there's only one subclass of WindowRootView.
143         return parent.let { it !is View || it.id == android.R.id.content }
144     }
145 
dispatchKeyEventnull146     override fun dispatchKeyEvent(event: KeyEvent): Boolean {
147         windowRootViewKeyEventHandler.collectKeyEvent(event)
148 
149         if (windowRootViewKeyEventHandler.interceptMediaKey(event)) {
150             return true
151         }
152 
153         if (super.dispatchKeyEvent(event)) {
154             return true
155         }
156 
157         return windowRootViewKeyEventHandler.dispatchKeyEvent(event)
158     }
159 
dispatchKeyEventPreImenull160     override fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
161         return windowRootViewKeyEventHandler.dispatchKeyEventPreIme(event) ?: false
162     }
163 
164     /** Controller responsible for calculating insets for the shade window. */
165     interface LayoutInsetsController {
166 
167         /** Update the insets and calculate them accordingly. */
getinsetsnull168         fun getinsets(windowInsets: WindowInsets?, displayCutout: DisplayCutout?): Pair<Int, Int>
169     }
170 
171     private class LayoutParams : FrameLayout.LayoutParams {
172         var ignoreRightInset = false
173 
174         constructor(width: Int, height: Int) : super(width, height)
175 
176         @SuppressLint("CustomViewStyleable")
177         constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
178             val obtainedAttributes =
179                 context.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout)
180             ignoreRightInset =
181                 obtainedAttributes.getBoolean(
182                     R.styleable.StatusBarWindowView_Layout_ignoreRightInset,
183                     false,
184                 )
185             obtainedAttributes.recycle()
186         }
187     }
188 }
189