1 /*
<lambda>null2  * 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.bouncer.ui.helper
18 
19 import androidx.test.filters.SmallTest
20 import com.android.systemui.SysuiTestCase
21 import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.BELOW_USER_SWITCHER
22 import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.BESIDE_USER_SWITCHER
23 import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SPLIT_BOUNCER
24 import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STANDARD_BOUNCER
25 import com.google.common.truth.Truth.assertThat
26 import java.util.Locale
27 import org.junit.Test
28 import org.junit.runner.RunWith
29 import platform.test.runner.parameterized.Parameter
30 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
31 import platform.test.runner.parameterized.Parameters
32 
33 @SmallTest
34 @RunWith(ParameterizedAndroidJunit4::class)
35 class BouncerSceneLayoutTest : SysuiTestCase() {
36 
37     data object Phone :
38         Device(
39             name = "phone",
40             width = SizeClass.COMPACT,
41             height = SizeClass.EXPANDED,
42             naturallyHeld = Vertically,
43         )
44 
45     data object Tablet :
46         Device(
47             name = "tablet",
48             width = SizeClass.EXPANDED,
49             height = SizeClass.MEDIUM,
50             naturallyHeld = Horizontally,
51         )
52 
53     data object Folded :
54         Device(
55             name = "folded",
56             width = SizeClass.COMPACT,
57             height = SizeClass.MEDIUM,
58             naturallyHeld = Vertically,
59         )
60 
61     data object Unfolded :
62         Device(
63             name = "unfolded",
64             width = SizeClass.EXPANDED,
65             height = SizeClass.MEDIUM,
66             naturallyHeld = Vertically,
67             widthWhenUnnaturallyHeld = SizeClass.MEDIUM,
68             heightWhenUnnaturallyHeld = SizeClass.MEDIUM,
69         )
70 
71     data object TallerFolded :
72         Device(
73             name = "taller folded",
74             width = SizeClass.COMPACT,
75             height = SizeClass.EXPANDED,
76             naturallyHeld = Vertically,
77         )
78 
79     data object TallerUnfolded :
80         Device(
81             name = "taller unfolded",
82             width = SizeClass.EXPANDED,
83             height = SizeClass.EXPANDED,
84             naturallyHeld = Vertically,
85         )
86 
87     companion object {
88         @JvmStatic
89         @Parameters(name = "{0}")
90         fun testCases() =
91             listOf(
92                     Phone to
93                         Expected(
94                             whenNaturallyHeld = STANDARD_BOUNCER,
95                             whenUnnaturallyHeld = SPLIT_BOUNCER,
96                         ),
97                     Tablet to
98                         Expected(
99                             whenNaturallyHeld = BESIDE_USER_SWITCHER,
100                             whenUnnaturallyHeld = BELOW_USER_SWITCHER,
101                         ),
102                     Folded to
103                         Expected(
104                             whenNaturallyHeld = STANDARD_BOUNCER,
105                             whenUnnaturallyHeld = SPLIT_BOUNCER,
106                         ),
107                     Unfolded to
108                         Expected(
109                             whenNaturallyHeld = BESIDE_USER_SWITCHER,
110                             whenUnnaturallyHeld = STANDARD_BOUNCER,
111                         ),
112                     TallerFolded to
113                         Expected(
114                             whenNaturallyHeld = STANDARD_BOUNCER,
115                             whenUnnaturallyHeld = SPLIT_BOUNCER,
116                         ),
117                     TallerUnfolded to
118                         Expected(
119                             whenNaturallyHeld = BESIDE_USER_SWITCHER,
120                             whenUnnaturallyHeld = BESIDE_USER_SWITCHER,
121                         ),
122                 )
123                 .flatMap { (device, expected) ->
124                     buildList {
125                         // Holding the device in its natural orientation (vertical or horizontal):
126                         add(
127                             TestCase(
128                                 device = device,
129                                 held = device.naturallyHeld,
130                                 expected = expected.layout(heldNaturally = true),
131                             )
132                         )
133 
134                         if (expected.whenNaturallyHeld == BESIDE_USER_SWITCHER) {
135                             add(
136                                 TestCase(
137                                     device = device,
138                                     held = device.naturallyHeld,
139                                     isOneHandedModeSupported = false,
140                                     expected = STANDARD_BOUNCER,
141                                 )
142                             )
143                         }
144 
145                         // Holding the device the other way:
146                         add(
147                             TestCase(
148                                 device = device,
149                                 held = device.naturallyHeld.flip(),
150                                 expected = expected.layout(heldNaturally = false),
151                             )
152                         )
153 
154                         if (expected.whenUnnaturallyHeld == BESIDE_USER_SWITCHER) {
155                             add(
156                                 TestCase(
157                                     device = device,
158                                     held = device.naturallyHeld.flip(),
159                                     isOneHandedModeSupported = false,
160                                     expected = STANDARD_BOUNCER,
161                                 )
162                             )
163                         }
164                     }
165                 }
166     }
167 
168     @Parameter @JvmField var testCase: TestCase? = null
169 
170     @Test
171     fun calculateLayout() {
172         testCase?.let { nonNullTestCase ->
173             with(nonNullTestCase) {
174                 assertThat(
175                         calculateLayoutInternal(
176                             width = device.width(whenHeld = held),
177                             height = device.height(whenHeld = held),
178                             isOneHandedModeSupported = isOneHandedModeSupported,
179                         )
180                     )
181                     .isEqualTo(expected)
182             }
183         }
184     }
185 
186     data class TestCase(
187         val device: Device,
188         val held: Held,
189         val expected: BouncerSceneLayout,
190         val isOneHandedModeSupported: Boolean = true,
191     ) {
192         override fun toString(): String {
193             return buildString {
194                 append(device.name)
195                 append(" width: ${device.width(held).name.lowercase(Locale.US)}")
196                 append(" height: ${device.height(held).name.lowercase(Locale.US)}")
197                 append(" when held $held")
198                 if (!isOneHandedModeSupported) {
199                     append(" (one-handed-mode not supported)")
200                 }
201             }
202         }
203     }
204 
205     data class Expected(
206         val whenNaturallyHeld: BouncerSceneLayout,
207         val whenUnnaturallyHeld: BouncerSceneLayout,
208     ) {
209         fun layout(heldNaturally: Boolean): BouncerSceneLayout {
210             return if (heldNaturally) {
211                 whenNaturallyHeld
212             } else {
213                 whenUnnaturallyHeld
214             }
215         }
216     }
217 
218     sealed class Device(
219         val name: String,
220         private val width: SizeClass,
221         private val height: SizeClass,
222         val naturallyHeld: Held,
223         private val widthWhenUnnaturallyHeld: SizeClass = height,
224         private val heightWhenUnnaturallyHeld: SizeClass = width,
225     ) {
226         fun width(whenHeld: Held): SizeClass {
227             return if (isHeldNaturally(whenHeld)) {
228                 width
229             } else {
230                 widthWhenUnnaturallyHeld
231             }
232         }
233 
234         fun height(whenHeld: Held): SizeClass {
235             return if (isHeldNaturally(whenHeld)) {
236                 height
237             } else {
238                 heightWhenUnnaturallyHeld
239             }
240         }
241 
242         private fun isHeldNaturally(whenHeld: Held): Boolean {
243             return whenHeld == naturallyHeld
244         }
245     }
246 
247     sealed class Held {
248         abstract fun flip(): Held
249     }
250 
251     data object Vertically : Held() {
252         override fun flip(): Held {
253             return Horizontally
254         }
255     }
256 
257     data object Horizontally : Held() {
258         override fun flip(): Held {
259             return Vertically
260         }
261     }
262 }
263