xref: /aosp_15_r20/frameworks/base/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
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.content.res.Configuration
17 import android.graphics.Rect
18 import android.platform.test.flag.junit.FlagsParameterization
19 import android.testing.TestableContext
20 import android.testing.TestableLooper
21 import android.testing.TestableLooper.RunWithLooper
22 import android.testing.ViewUtils
23 import android.view.ContextThemeWrapper
24 import android.view.View
25 import android.view.ViewGroup
26 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
27 import android.view.accessibility.AccessibilityNodeInfo
28 import android.widget.FrameLayout
29 import android.widget.LinearLayout
30 import androidx.test.filters.SmallTest
31 import com.android.systemui.SysuiTestCase
32 import com.android.systemui.flags.DisableSceneContainer
33 import com.android.systemui.flags.parameterizeSceneContainerFlag
34 import com.android.systemui.plugins.qs.QSTile
35 import com.android.systemui.plugins.qs.QSTileView
36 import com.android.systemui.qs.QSPanelControllerBase.TileRecord
37 import com.android.systemui.qs.logging.QSLogger
38 import com.android.systemui.qs.tileimpl.QSTileViewImpl
39 import com.android.systemui.res.R
40 import com.google.common.truth.Truth.assertThat
41 import org.junit.After
42 import org.junit.Before
43 import org.junit.Test
44 import org.junit.runner.RunWith
45 import org.mockito.Mock
46 import org.mockito.Mockito.mock
47 import org.mockito.Mockito.never
48 import org.mockito.Mockito.verify
49 import org.mockito.MockitoAnnotations
50 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
51 import platform.test.runner.parameterized.Parameters
52 
53 @RunWith(ParameterizedAndroidJunit4::class)
54 @RunWithLooper
55 @SmallTest
56 class QSPanelTest(flags: FlagsParameterization) : SysuiTestCase() {
57 
58     init {
59         mSetFlagsRule.setFlagsParameterization(flags)
60     }
61 
62     @Mock private lateinit var qsLogger: QSLogger
63 
64     private lateinit var testableLooper: TestableLooper
65     private lateinit var qsPanel: QSPanel
66 
67     private lateinit var footer: View
68 
69     private val themedContext =
70         TestableContext(ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings))
71 
72     @Before
73     @Throws(Exception::class)
setupnull74     fun setup() {
75         MockitoAnnotations.initMocks(this)
76         testableLooper = TestableLooper.get(this)
77         // Apply only the values of the theme that are not defined
78 
79         testableLooper.runWithLooper {
80             qsPanel = QSPanel(themedContext, null)
81             qsPanel.mUsingMediaPlayer = true
82 
83             qsPanel.initialize(qsLogger, true)
84             // QSPanel inflates a footer inside of it, mocking it here
85             footer = LinearLayout(themedContext).apply { id = R.id.qs_footer }
86             qsPanel.addView(footer, MATCH_PARENT, 100)
87             qsPanel.onFinishInflate()
88             // Provides a parent with non-zero size for QSPanel
89             ViewUtils.attachView(qsPanel)
90         }
91     }
92 
93     @After
tearDownnull94     fun tearDown() {
95         if (qsPanel.isAttachedToWindow) {
96             ViewUtils.detachView(qsPanel)
97         }
98     }
99 
100     @Test
testHasCollapseAccessibilityActionnull101     fun testHasCollapseAccessibilityAction() {
102         val info = AccessibilityNodeInfo(qsPanel)
103         qsPanel.onInitializeAccessibilityNodeInfo(info)
104 
105         assertThat(info.actions and AccessibilityNodeInfo.ACTION_COLLAPSE).isNotEqualTo(0)
106         assertThat(info.actions and AccessibilityNodeInfo.ACTION_EXPAND).isEqualTo(0)
107     }
108 
109     @Test
testCollapseActionCallsRunnablenull110     fun testCollapseActionCallsRunnable() {
111         val mockRunnable = mock(Runnable::class.java)
112         qsPanel.setCollapseExpandAction(mockRunnable)
113 
114         qsPanel.performAccessibilityAction(AccessibilityNodeInfo.ACTION_COLLAPSE, null)
115         verify(mockRunnable).run()
116     }
117 
118     @Test
119     @DisableSceneContainer
testTilesFooterVisibleLandscapeMedianull120     fun testTilesFooterVisibleLandscapeMedia() {
121         // We need at least a tile so the layout has a height
122         qsPanel.tileLayout?.addTile(
123             QSPanelControllerBase.TileRecord(
124                 mock(QSTile::class.java),
125                 QSTileViewImpl(themedContext),
126             )
127         )
128 
129         val mediaView = FrameLayout(themedContext)
130         mediaView.addView(View(themedContext), MATCH_PARENT, 800)
131 
132         qsPanel.setUsingHorizontalLayout(/* horizontal */ true, mediaView, /* force */ true)
133         qsPanel.measure(
134             /* width */ View.MeasureSpec.makeMeasureSpec(3000, View.MeasureSpec.EXACTLY),
135             /* height */ View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY),
136         )
137         qsPanel.layout(0, 0, qsPanel.measuredWidth, qsPanel.measuredHeight)
138 
139         val tiles = qsPanel.tileLayout as View
140         // Tiles are effectively to the left of media
141         assertThat(tiles isLeftOf mediaView).isTrue()
142         assertThat(tiles.isVisibleToUser).isTrue()
143 
144         assertThat(footer isLeftOf mediaView).isTrue()
145         assertThat(footer.isVisibleToUser).isTrue()
146     }
147 
148     @Test
testBottomPaddingnull149     fun testBottomPadding() {
150         val padding = 10
151         themedContext.orCreateTestableResources.addOverride(
152             R.dimen.qs_panel_padding_bottom,
153             padding,
154         )
155         qsPanel.updatePadding()
156         assertThat(qsPanel.paddingBottom).isEqualTo(padding)
157     }
158 
159     @Test
testTopPaddingnull160     fun testTopPadding() {
161         val padding = 10
162         val paddingCombined = 100
163         themedContext.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, padding)
164         themedContext.orCreateTestableResources.addOverride(
165             R.dimen.qs_panel_padding_top,
166             paddingCombined,
167         )
168 
169         qsPanel.updatePadding()
170         assertThat(qsPanel.paddingTop).isEqualTo(paddingCombined)
171     }
172 
173     @Test
testSetSquishinessFraction_noCrashnull174     fun testSetSquishinessFraction_noCrash() {
175         qsPanel.addView(qsPanel.mTileLayout as View, 0)
176         qsPanel.addView(FrameLayout(context))
177         qsPanel.setSquishinessFraction(0.5f)
178     }
179 
180     @Test
testSplitShade_CollapseAccessibilityActionNotAnnouncednull181     fun testSplitShade_CollapseAccessibilityActionNotAnnounced() {
182         qsPanel.setCanCollapse(false)
183         val accessibilityInfo = mock(AccessibilityNodeInfo::class.java)
184         qsPanel.onInitializeAccessibilityNodeInfo(accessibilityInfo)
185 
186         val actionCollapse = AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE
187         verify(accessibilityInfo, never()).addAction(actionCollapse)
188     }
189 
190     @Test
addTile_callbackAddednull191     fun addTile_callbackAdded() {
192         val tile = mock(QSTile::class.java)
193         val tileView = mock(QSTileView::class.java)
194 
195         val record = TileRecord(tile, tileView)
196 
197         qsPanel.addTile(record)
198 
199         verify(tile).addCallback(record.callback)
200     }
201 
202     @Test
203     @DisableSceneContainer
initializedWithNoMedia_sceneContainerDisabled_tileLayoutParentIsAlwaysQsPanelnull204     fun initializedWithNoMedia_sceneContainerDisabled_tileLayoutParentIsAlwaysQsPanel() {
205         lateinit var panel: QSPanel
206         lateinit var tileLayout: View
207         testableLooper.runWithLooper {
208             panel = QSPanel(themedContext, null)
209             panel.mUsingMediaPlayer = true
210 
211             panel.initialize(qsLogger, /* usingMediaPlayer= */ false)
212             tileLayout = panel.orCreateTileLayout as View
213             // QSPanel inflates a footer inside of it, mocking it here
214             footer = LinearLayout(themedContext).apply { id = R.id.qs_footer }
215             panel.addView(footer, MATCH_PARENT, 100)
216             panel.onFinishInflate()
217             // Provides a parent with non-zero size for QSPanel
218             ViewUtils.attachView(panel)
219         }
220         val mockMediaHost = mock(ViewGroup::class.java)
221 
222         panel.setUsingHorizontalLayout(false, mockMediaHost, true)
223 
224         assertThat(tileLayout.parent).isSameInstanceAs(panel)
225 
226         panel.setUsingHorizontalLayout(true, mockMediaHost, true)
227         assertThat(tileLayout.parent).isSameInstanceAs(panel)
228 
229         ViewUtils.detachView(panel)
230     }
231 
232     @Test
233     @DisableSceneContainer
initializeWithNoMedia_mediaNeverAttachednull234     fun initializeWithNoMedia_mediaNeverAttached() {
235         lateinit var panel: QSPanel
236         testableLooper.runWithLooper {
237             panel = QSPanel(themedContext, null)
238             panel.mUsingMediaPlayer = true
239 
240             panel.initialize(qsLogger, /* usingMediaPlayer= */ false)
241             panel.orCreateTileLayout as View
242             // QSPanel inflates a footer inside of it, mocking it here
243             footer = LinearLayout(themedContext).apply { id = R.id.qs_footer }
244             panel.addView(footer, MATCH_PARENT, 100)
245             panel.onFinishInflate()
246             // Provides a parent with non-zero size for QSPanel
247             ViewUtils.attachView(panel)
248         }
249         val mockMediaHost = FrameLayout(themedContext)
250 
251         panel.setUsingHorizontalLayout(false, mockMediaHost, true)
252         assertThat(mockMediaHost.parent).isNull()
253 
254         panel.setUsingHorizontalLayout(true, mockMediaHost, true)
255         assertThat(mockMediaHost.parent).isNull()
256 
257         ViewUtils.detachView(panel)
258     }
259 
260     @Test
setRowColumnLayoutnull261     fun setRowColumnLayout() {
262         qsPanel.setColumnRowLayout(/* withMedia= */ false)
263 
264         assertThat(qsPanel.tileLayout!!.minRows).isEqualTo(1)
265         assertThat(qsPanel.tileLayout!!.maxColumns).isEqualTo(4)
266 
267         qsPanel.setColumnRowLayout(/* withMedia= */ true)
268 
269         assertThat(qsPanel.tileLayout!!.minRows).isEqualTo(2)
270         assertThat(qsPanel.tileLayout!!.maxColumns).isEqualTo(2)
271     }
272 
273     @Test
noPendingConfigChangesAtBeginningnull274     fun noPendingConfigChangesAtBeginning() {
275         assertThat(qsPanel.hadConfigurationChangeWhileDetached()).isFalse()
276     }
277 
278     @Test
configChangesWhileDetached_pendingConfigChangesnull279     fun configChangesWhileDetached_pendingConfigChanges() {
280         ViewUtils.detachView(qsPanel)
281 
282         qsPanel.onConfigurationChanged(Configuration())
283 
284         assertThat(qsPanel.hadConfigurationChangeWhileDetached()).isTrue()
285     }
286 
287     @Test
configChangesWhileDetached_reattach_pendingConfigChangesnull288     fun configChangesWhileDetached_reattach_pendingConfigChanges() {
289         ViewUtils.detachView(qsPanel)
290 
291         qsPanel.onConfigurationChanged(Configuration())
292         testableLooper.runWithLooper { ViewUtils.attachView(qsPanel) }
293 
294         assertThat(qsPanel.hadConfigurationChangeWhileDetached()).isTrue()
295     }
296 
297     @Test
configChangesWhileDetached_reattach_detach_pendingConfigChanges_resetnull298     fun configChangesWhileDetached_reattach_detach_pendingConfigChanges_reset() {
299         ViewUtils.detachView(qsPanel)
300 
301         qsPanel.onConfigurationChanged(Configuration())
302 
303         testableLooper.runWithLooper { ViewUtils.attachView(qsPanel) }
304         ViewUtils.detachView(qsPanel)
305 
306         assertThat(qsPanel.hadConfigurationChangeWhileDetached()).isFalse()
307     }
308 
309     @Test
configChangeWhileAttached_noPendingConfigChangesnull310     fun configChangeWhileAttached_noPendingConfigChanges() {
311         qsPanel.onConfigurationChanged(Configuration())
312 
313         assertThat(qsPanel.hadConfigurationChangeWhileDetached()).isFalse()
314     }
315 
316     @Test
configChangeWhileAttachedWithPending_doesntResetPendingnull317     fun configChangeWhileAttachedWithPending_doesntResetPending() {
318         ViewUtils.detachView(qsPanel)
319 
320         qsPanel.onConfigurationChanged(Configuration())
321 
322         testableLooper.runWithLooper { ViewUtils.attachView(qsPanel) }
323 
324         qsPanel.onConfigurationChanged(Configuration())
325 
326         assertThat(qsPanel.hadConfigurationChangeWhileDetached()).isTrue()
327     }
328 
329     companion object {
getParamsnull330         @Parameters(name = "{0}") @JvmStatic fun getParams() = parameterizeSceneContainerFlag()
331     }
332 
333     private infix fun View.isLeftOf(other: View): Boolean {
334         val rect = Rect()
335         getBoundsOnScreen(rect)
336         val thisRight = rect.right
337 
338         other.getBoundsOnScreen(rect)
339 
340         return thisRight <= rect.left
341     }
342 }
343