1 package com.android.systemui.scene.ui.view 2 3 import android.annotation.SuppressLint 4 import android.content.Context 5 import android.util.AttributeSet 6 import android.util.Pair 7 import android.view.DisplayCutout 8 import android.view.View 9 import android.view.WindowInsets 10 import android.widget.FrameLayout 11 import androidx.core.view.updateMargins 12 import com.android.systemui.R 13 import com.android.systemui.compose.ComposeFacade 14 15 /** 16 * A view that can serve as the root of the main SysUI window (but might not, see below for more 17 * information regarding this confusing comment). 18 * 19 * Naturally, only one view may ever be at the root of a view hierarchy tree. Under certain 20 * conditions, the view hierarchy tree in the scene-containing window view may actually have one 21 * [WindowRootView] acting as the true root view and another [WindowRootView] which doesn't and is, 22 * instead, a child of the true root view. To discern which one is which, please use the [isRoot] 23 * method. 24 */ 25 open class WindowRootView( 26 context: Context, 27 attrs: AttributeSet?, 28 ) : 29 FrameLayout( 30 context, 31 attrs, 32 ) { 33 34 private lateinit var layoutInsetsController: LayoutInsetsController 35 private var leftInset = 0 36 private var rightInset = 0 37 38 override fun onAttachedToWindow() { 39 super.onAttachedToWindow() 40 41 if (ComposeFacade.isComposeAvailable() && isRoot()) { 42 ComposeFacade.composeInitializer().onAttachedToWindow(this) 43 } 44 } 45 46 override fun onDetachedFromWindow() { 47 super.onDetachedFromWindow() 48 49 if (ComposeFacade.isComposeAvailable() && isRoot()) { 50 ComposeFacade.composeInitializer().onDetachedFromWindow(this) 51 } 52 } 53 54 override fun generateLayoutParams(attrs: AttributeSet?): FrameLayout.LayoutParams? { 55 return LayoutParams(context, attrs) 56 } 57 58 override fun generateDefaultLayoutParams(): FrameLayout.LayoutParams? { 59 return LayoutParams( 60 FrameLayout.LayoutParams.MATCH_PARENT, 61 FrameLayout.LayoutParams.MATCH_PARENT 62 ) 63 } 64 65 override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets? { 66 val insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()) 67 if (fitsSystemWindows) { 68 val paddingChanged = insets.top != paddingTop || insets.bottom != paddingBottom 69 70 // Drop top inset, and pass through bottom inset. 71 if (paddingChanged) { 72 setPadding(0, 0, 0, 0) 73 } 74 } else { 75 val changed = 76 paddingLeft != 0 || paddingRight != 0 || paddingTop != 0 || paddingBottom != 0 77 if (changed) { 78 setPadding(0, 0, 0, 0) 79 } 80 } 81 leftInset = 0 82 rightInset = 0 83 84 val displayCutout = rootWindowInsets.displayCutout 85 val pairInsets: Pair<Int, Int> = 86 layoutInsetsController.getinsets(windowInsets, displayCutout) 87 leftInset = pairInsets.first 88 rightInset = pairInsets.second 89 applyMargins() 90 return windowInsets 91 } 92 93 fun setLayoutInsetsController(layoutInsetsController: LayoutInsetsController) { 94 this.layoutInsetsController = layoutInsetsController 95 } 96 97 private fun applyMargins() { 98 val count = childCount 99 for (i in 0 until count) { 100 val child = getChildAt(i) 101 if (child.layoutParams is LayoutParams) { 102 val layoutParams = child.layoutParams as LayoutParams 103 if ( 104 !layoutParams.ignoreRightInset && 105 (layoutParams.rightMargin != rightInset || 106 layoutParams.leftMargin != leftInset) 107 ) { 108 layoutParams.updateMargins(left = leftInset, right = rightInset) 109 child.requestLayout() 110 } 111 } 112 } 113 } 114 115 /** 116 * Returns `true` if this view is the true root of the view-hierarchy; `false` otherwise. 117 * 118 * Please see the class-level documentation to understand why this is possible. 119 */ 120 private fun isRoot(): Boolean { 121 // TODO(b/283300105): remove this check once there's only one subclass of WindowRootView. 122 return parent.let { it !is View || it.id == android.R.id.content } 123 } 124 125 /** Controller responsible for calculating insets for the shade window. */ 126 interface LayoutInsetsController { 127 128 /** Update the insets and calculate them accordingly. */ 129 fun getinsets( 130 windowInsets: WindowInsets?, 131 displayCutout: DisplayCutout?, 132 ): Pair<Int, Int> 133 } 134 135 private class LayoutParams : FrameLayout.LayoutParams { 136 var ignoreRightInset = false 137 138 constructor( 139 width: Int, 140 height: Int, 141 ) : super( 142 width, 143 height, 144 ) 145 146 @SuppressLint("CustomViewStyleable") 147 constructor( 148 context: Context, 149 attrs: AttributeSet?, 150 ) : super( 151 context, 152 attrs, 153 ) { 154 val obtainedAttributes = 155 context.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout) 156 ignoreRightInset = 157 obtainedAttributes.getBoolean( 158 R.styleable.StatusBarWindowView_Layout_ignoreRightInset, 159 false 160 ) 161 obtainedAttributes.recycle() 162 } 163 } 164 } 165