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