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