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