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.testing.AndroidTestingRunner 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.LayoutParams.MATCH_PARENT 25 import android.view.accessibility.AccessibilityNodeInfo 26 import android.widget.FrameLayout 27 import android.widget.LinearLayout 28 import androidx.test.filters.SmallTest 29 import com.android.systemui.R 30 import com.android.systemui.SysuiTestCase 31 import com.android.systemui.plugins.qs.QSTile 32 import com.android.systemui.plugins.qs.QSTileView 33 import com.android.systemui.qs.QSPanelControllerBase.TileRecord 34 import com.android.systemui.qs.logging.QSLogger 35 import com.android.systemui.qs.tileimpl.QSIconViewImpl 36 import com.android.systemui.qs.tileimpl.QSTileViewImpl 37 import com.google.common.truth.Truth.assertThat 38 import org.junit.After 39 import org.junit.Before 40 import org.junit.Test 41 import org.junit.runner.RunWith 42 import org.mockito.Mock 43 import org.mockito.Mockito.mock 44 import org.mockito.Mockito.never 45 import org.mockito.Mockito.verify 46 import org.mockito.MockitoAnnotations 47 48 @RunWith(AndroidTestingRunner::class) 49 @RunWithLooper 50 @SmallTest 51 class QSPanelTest : SysuiTestCase() { 52 53 @Mock private lateinit var qsLogger: QSLogger 54 55 private lateinit var testableLooper: TestableLooper 56 private lateinit var qsPanel: QSPanel 57 58 private lateinit var footer: View 59 60 private val themedContext = TestableContext( 61 ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) 62 ) 63 64 @Before 65 @Throws(Exception::class) 66 fun setup() { 67 MockitoAnnotations.initMocks(this) 68 testableLooper = TestableLooper.get(this) 69 // Apply only the values of the theme that are not defined 70 71 testableLooper.runWithLooper { 72 qsPanel = QSPanel(themedContext, null) 73 qsPanel.mUsingMediaPlayer = true 74 75 qsPanel.initialize(qsLogger) 76 // QSPanel inflates a footer inside of it, mocking it here 77 footer = LinearLayout(themedContext).apply { id = R.id.qs_footer } 78 qsPanel.addView(footer, MATCH_PARENT, 100) 79 qsPanel.onFinishInflate() 80 // Provides a parent with non-zero size for QSPanel 81 ViewUtils.attachView(qsPanel) 82 } 83 } 84 85 @After 86 fun tearDown() { 87 ViewUtils.detachView(qsPanel) 88 } 89 90 @Test 91 fun testHasCollapseAccessibilityAction() { 92 val info = AccessibilityNodeInfo(qsPanel) 93 qsPanel.onInitializeAccessibilityNodeInfo(info) 94 95 assertThat(info.actions and AccessibilityNodeInfo.ACTION_COLLAPSE).isNotEqualTo(0) 96 assertThat(info.actions and AccessibilityNodeInfo.ACTION_EXPAND).isEqualTo(0) 97 } 98 99 @Test 100 fun testCollapseActionCallsRunnable() { 101 val mockRunnable = mock(Runnable::class.java) 102 qsPanel.setCollapseExpandAction(mockRunnable) 103 104 qsPanel.performAccessibilityAction(AccessibilityNodeInfo.ACTION_COLLAPSE, null) 105 verify(mockRunnable).run() 106 } 107 108 @Test 109 fun testTilesFooterVisibleRTLLandscapeMedia() { 110 qsPanel.layoutDirection = View.LAYOUT_DIRECTION_RTL 111 // We need at least a tile so the layout has a height 112 qsPanel.tileLayout?.addTile( 113 QSPanelControllerBase.TileRecord( 114 mock(QSTile::class.java), 115 QSTileViewImpl(themedContext, QSIconViewImpl(themedContext)) 116 ) 117 ) 118 119 val mediaView = FrameLayout(themedContext) 120 mediaView.addView(View(themedContext), MATCH_PARENT, 800) 121 122 qsPanel.setUsingHorizontalLayout(/* horizontal */ true, mediaView, /* force */ true) 123 qsPanel.measure( 124 /* width */ View.MeasureSpec.makeMeasureSpec(3000, View.MeasureSpec.EXACTLY), 125 /* height */ View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY) 126 ) 127 qsPanel.layout(0, 0, qsPanel.measuredWidth, qsPanel.measuredHeight) 128 129 val tiles = qsPanel.tileLayout as View 130 // Tiles are effectively to the right of media 131 assertThat(mediaView isLeftOf tiles) 132 assertThat(tiles.isVisibleToUser).isTrue() 133 134 assertThat(mediaView isLeftOf footer) 135 assertThat(footer.isVisibleToUser).isTrue() 136 } 137 138 @Test 139 fun testTilesFooterVisibleLandscapeMedia() { 140 qsPanel.layoutDirection = View.LAYOUT_DIRECTION_LTR 141 // We need at least a tile so the layout has a height 142 qsPanel.tileLayout?.addTile( 143 QSPanelControllerBase.TileRecord( 144 mock(QSTile::class.java), 145 QSTileViewImpl(themedContext, QSIconViewImpl(themedContext)) 146 ) 147 ) 148 149 val mediaView = FrameLayout(themedContext) 150 mediaView.addView(View(themedContext), MATCH_PARENT, 800) 151 152 qsPanel.setUsingHorizontalLayout(/* horizontal */ true, mediaView, /* force */ true) 153 qsPanel.measure( 154 /* width */ View.MeasureSpec.makeMeasureSpec(3000, View.MeasureSpec.EXACTLY), 155 /* height */ View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY) 156 ) 157 qsPanel.layout(0, 0, qsPanel.measuredWidth, qsPanel.measuredHeight) 158 159 val tiles = qsPanel.tileLayout as View 160 // Tiles are effectively to the left of media 161 assertThat(tiles isLeftOf mediaView) 162 assertThat(tiles.isVisibleToUser).isTrue() 163 164 assertThat(footer isLeftOf mediaView) 165 assertThat(footer.isVisibleToUser).isTrue() 166 } 167 168 @Test 169 fun testBottomPadding() { 170 val padding = 10 171 themedContext.orCreateTestableResources.addOverride( 172 R.dimen.qs_panel_padding_bottom, 173 padding 174 ) 175 qsPanel.updatePadding() 176 assertThat(qsPanel.paddingBottom).isEqualTo(padding) 177 } 178 179 @Test 180 fun testTopPadding() { 181 val padding = 10 182 val paddingCombined = 100 183 themedContext.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, padding) 184 themedContext.orCreateTestableResources.addOverride( 185 R.dimen.qs_panel_padding_top, 186 paddingCombined 187 ) 188 189 qsPanel.updatePadding() 190 assertThat(qsPanel.paddingTop).isEqualTo(paddingCombined) 191 } 192 193 @Test 194 fun testSetSquishinessFraction_noCrash() { 195 qsPanel.addView(qsPanel.mTileLayout as View, 0) 196 qsPanel.addView(FrameLayout(context)) 197 qsPanel.setSquishinessFraction(0.5f) 198 } 199 200 @Test 201 fun testSplitShade_CollapseAccessibilityActionNotAnnounced() { 202 qsPanel.setCanCollapse(false) 203 val accessibilityInfo = mock(AccessibilityNodeInfo::class.java) 204 qsPanel.onInitializeAccessibilityNodeInfo(accessibilityInfo) 205 206 val actionCollapse = AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE 207 verify(accessibilityInfo, never()).addAction(actionCollapse) 208 } 209 210 @Test 211 fun addTile_callbackAdded() { 212 val tile = mock(QSTile::class.java) 213 val tileView = mock(QSTileView::class.java) 214 215 val record = TileRecord(tile, tileView) 216 217 qsPanel.addTile(record) 218 219 verify(tile).addCallback(record.callback) 220 } 221 222 private infix fun View.isLeftOf(other: View): Boolean { 223 val rect = Rect() 224 getBoundsOnScreen(rect) 225 val thisRight = rect.right 226 227 other.getBoundsOnScreen(rect) 228 229 return thisRight <= rect.left 230 } 231 } 232