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