1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 package com.android.systemui.qs 15 16 import android.graphics.Rect 17 import android.platform.test.flag.junit.FlagsParameterization 18 import android.testing.TestableContext 19 import android.testing.TestableLooper 20 import android.testing.TestableLooper.RunWithLooper 21 import android.testing.ViewUtils 22 import android.view.ContextThemeWrapper 23 import android.view.View 24 import android.view.ViewGroup 25 import android.view.ViewGroup.LayoutParams.MATCH_PARENT 26 import android.view.accessibility.AccessibilityNodeInfo 27 import android.widget.FrameLayout 28 import android.widget.LinearLayout 29 import androidx.test.filters.SmallTest 30 import com.android.systemui.SysuiTestCase 31 import com.android.systemui.flags.DisableSceneContainer 32 import com.android.systemui.flags.parameterizeSceneContainerFlag 33 import com.android.systemui.plugins.qs.QSTile 34 import com.android.systemui.plugins.qs.QSTileView 35 import com.android.systemui.qs.QSPanelControllerBase.TileRecord 36 import com.android.systemui.qs.logging.QSLogger 37 import com.android.systemui.qs.tileimpl.QSTileViewImpl 38 import com.android.systemui.res.R 39 import com.google.common.truth.Truth.assertThat 40 import org.junit.After 41 import org.junit.Before 42 import org.junit.Test 43 import org.junit.runner.RunWith 44 import org.mockito.Mock 45 import org.mockito.Mockito.mock 46 import org.mockito.Mockito.never 47 import org.mockito.Mockito.verify 48 import org.mockito.MockitoAnnotations 49 import platform.test.runner.parameterized.ParameterizedAndroidJunit4 50 import platform.test.runner.parameterized.Parameters 51 52 @RunWith(ParameterizedAndroidJunit4::class) 53 @RunWithLooper 54 @SmallTest 55 class QSPanelTest(flags: FlagsParameterization) : SysuiTestCase() { 56 57 init { 58 mSetFlagsRule.setFlagsParameterization(flags) 59 } 60 61 @Mock private lateinit var qsLogger: QSLogger 62 63 private lateinit var testableLooper: TestableLooper 64 private lateinit var qsPanel: QSPanel 65 66 private lateinit var footer: View 67 68 private val themedContext = 69 TestableContext(ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)) 70 71 @Before 72 @Throws(Exception::class) setupnull73 fun setup() { 74 MockitoAnnotations.initMocks(this) 75 testableLooper = TestableLooper.get(this) 76 // Apply only the values of the theme that are not defined 77 78 testableLooper.runWithLooper { 79 qsPanel = QSPanel(themedContext, null) 80 qsPanel.mUsingMediaPlayer = true 81 82 qsPanel.initialize(qsLogger, true) 83 // QSPanel inflates a footer inside of it, mocking it here 84 footer = LinearLayout(themedContext).apply { id = R.id.qs_footer } 85 qsPanel.addView(footer, MATCH_PARENT, 100) 86 qsPanel.onFinishInflate() 87 // Provides a parent with non-zero size for QSPanel 88 ViewUtils.attachView(qsPanel) 89 } 90 } 91 92 @After tearDownnull93 fun tearDown() { 94 ViewUtils.detachView(qsPanel) 95 } 96 97 @Test testHasCollapseAccessibilityActionnull98 fun testHasCollapseAccessibilityAction() { 99 val info = AccessibilityNodeInfo(qsPanel) 100 qsPanel.onInitializeAccessibilityNodeInfo(info) 101 102 assertThat(info.actions and AccessibilityNodeInfo.ACTION_COLLAPSE).isNotEqualTo(0) 103 assertThat(info.actions and AccessibilityNodeInfo.ACTION_EXPAND).isEqualTo(0) 104 } 105 106 @Test testCollapseActionCallsRunnablenull107 fun testCollapseActionCallsRunnable() { 108 val mockRunnable = mock(Runnable::class.java) 109 qsPanel.setCollapseExpandAction(mockRunnable) 110 111 qsPanel.performAccessibilityAction(AccessibilityNodeInfo.ACTION_COLLAPSE, null) 112 verify(mockRunnable).run() 113 } 114 115 @Test 116 @DisableSceneContainer testTilesFooterVisibleLandscapeMedianull117 fun testTilesFooterVisibleLandscapeMedia() { 118 // We need at least a tile so the layout has a height 119 qsPanel.tileLayout?.addTile( 120 QSPanelControllerBase.TileRecord( 121 mock(QSTile::class.java), 122 QSTileViewImpl(themedContext) 123 ) 124 ) 125 126 val mediaView = FrameLayout(themedContext) 127 mediaView.addView(View(themedContext), MATCH_PARENT, 800) 128 129 qsPanel.setUsingHorizontalLayout(/* horizontal */ true, mediaView, /* force */ true) 130 qsPanel.measure( 131 /* width */ View.MeasureSpec.makeMeasureSpec(3000, View.MeasureSpec.EXACTLY), 132 /* height */ View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY) 133 ) 134 qsPanel.layout(0, 0, qsPanel.measuredWidth, qsPanel.measuredHeight) 135 136 val tiles = qsPanel.tileLayout as View 137 // Tiles are effectively to the left of media 138 assertThat(tiles isLeftOf mediaView).isTrue() 139 assertThat(tiles.isVisibleToUser).isTrue() 140 141 assertThat(footer isLeftOf mediaView).isTrue() 142 assertThat(footer.isVisibleToUser).isTrue() 143 } 144 145 @Test testBottomPaddingnull146 fun testBottomPadding() { 147 val padding = 10 148 themedContext.orCreateTestableResources.addOverride( 149 R.dimen.qs_panel_padding_bottom, 150 padding 151 ) 152 qsPanel.updatePadding() 153 assertThat(qsPanel.paddingBottom).isEqualTo(padding) 154 } 155 156 @Test testTopPaddingnull157 fun testTopPadding() { 158 val padding = 10 159 val paddingCombined = 100 160 themedContext.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, padding) 161 themedContext.orCreateTestableResources.addOverride( 162 R.dimen.qs_panel_padding_top, 163 paddingCombined 164 ) 165 166 qsPanel.updatePadding() 167 assertThat(qsPanel.paddingTop).isEqualTo(paddingCombined) 168 } 169 170 @Test testSetSquishinessFraction_noCrashnull171 fun testSetSquishinessFraction_noCrash() { 172 qsPanel.addView(qsPanel.mTileLayout as View, 0) 173 qsPanel.addView(FrameLayout(context)) 174 qsPanel.setSquishinessFraction(0.5f) 175 } 176 177 @Test testSplitShade_CollapseAccessibilityActionNotAnnouncednull178 fun testSplitShade_CollapseAccessibilityActionNotAnnounced() { 179 qsPanel.setCanCollapse(false) 180 val accessibilityInfo = mock(AccessibilityNodeInfo::class.java) 181 qsPanel.onInitializeAccessibilityNodeInfo(accessibilityInfo) 182 183 val actionCollapse = AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE 184 verify(accessibilityInfo, never()).addAction(actionCollapse) 185 } 186 187 @Test addTile_callbackAddednull188 fun addTile_callbackAdded() { 189 val tile = mock(QSTile::class.java) 190 val tileView = mock(QSTileView::class.java) 191 192 val record = TileRecord(tile, tileView) 193 194 qsPanel.addTile(record) 195 196 verify(tile).addCallback(record.callback) 197 } 198 199 @Test 200 @DisableSceneContainer initializedWithNoMedia_sceneContainerDisabled_tileLayoutParentIsAlwaysQsPanelnull201 fun initializedWithNoMedia_sceneContainerDisabled_tileLayoutParentIsAlwaysQsPanel() { 202 lateinit var panel: QSPanel 203 lateinit var tileLayout: View 204 testableLooper.runWithLooper { 205 panel = QSPanel(themedContext, null) 206 panel.mUsingMediaPlayer = true 207 208 panel.initialize(qsLogger, /* usingMediaPlayer= */ false) 209 tileLayout = panel.orCreateTileLayout as View 210 // QSPanel inflates a footer inside of it, mocking it here 211 footer = LinearLayout(themedContext).apply { id = R.id.qs_footer } 212 panel.addView(footer, MATCH_PARENT, 100) 213 panel.onFinishInflate() 214 // Provides a parent with non-zero size for QSPanel 215 ViewUtils.attachView(panel) 216 } 217 val mockMediaHost = mock(ViewGroup::class.java) 218 219 panel.setUsingHorizontalLayout(false, mockMediaHost, true) 220 221 assertThat(tileLayout.parent).isSameInstanceAs(panel) 222 223 panel.setUsingHorizontalLayout(true, mockMediaHost, true) 224 assertThat(tileLayout.parent).isSameInstanceAs(panel) 225 226 ViewUtils.detachView(panel) 227 } 228 229 @Test 230 @DisableSceneContainer initializeWithNoMedia_mediaNeverAttachednull231 fun initializeWithNoMedia_mediaNeverAttached() { 232 lateinit var panel: QSPanel 233 testableLooper.runWithLooper { 234 panel = QSPanel(themedContext, null) 235 panel.mUsingMediaPlayer = true 236 237 panel.initialize(qsLogger, /* usingMediaPlayer= */ false) 238 panel.orCreateTileLayout as View 239 // QSPanel inflates a footer inside of it, mocking it here 240 footer = LinearLayout(themedContext).apply { id = R.id.qs_footer } 241 panel.addView(footer, MATCH_PARENT, 100) 242 panel.onFinishInflate() 243 // Provides a parent with non-zero size for QSPanel 244 ViewUtils.attachView(panel) 245 } 246 val mockMediaHost = FrameLayout(themedContext) 247 248 panel.setUsingHorizontalLayout(false, mockMediaHost, true) 249 assertThat(mockMediaHost.parent).isNull() 250 251 panel.setUsingHorizontalLayout(true, mockMediaHost, true) 252 assertThat(mockMediaHost.parent).isNull() 253 254 ViewUtils.detachView(panel) 255 } 256 257 @Test setRowColumnLayoutnull258 fun setRowColumnLayout() { 259 qsPanel.setColumnRowLayout(/* withMedia= */ false) 260 261 assertThat(qsPanel.tileLayout!!.minRows).isEqualTo(1) 262 assertThat(qsPanel.tileLayout!!.maxColumns).isEqualTo(4) 263 264 qsPanel.setColumnRowLayout(/* withMedia= */ true) 265 266 assertThat(qsPanel.tileLayout!!.minRows).isEqualTo(2) 267 assertThat(qsPanel.tileLayout!!.maxColumns).isEqualTo(2) 268 } 269 270 companion object { getParamsnull271 @Parameters(name = "{0}") @JvmStatic fun getParams() = parameterizeSceneContainerFlag() 272 } 273 274 private infix fun View.isLeftOf(other: View): Boolean { 275 val rect = Rect() 276 getBoundsOnScreen(rect) 277 val thisRight = rect.right 278 279 other.getBoundsOnScreen(rect) 280 281 return thisRight <= rect.left 282 } 283 } 284