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.launcher3.responsive
18 
19 import android.content.Context
20 import androidx.test.ext.junit.runners.AndroidJUnit4
21 import androidx.test.filters.SmallTest
22 import androidx.test.platform.app.InstrumentationRegistry
23 import com.android.launcher3.AbstractDeviceProfileTest
24 import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
25 import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
26 import com.android.launcher3.util.TestResourceHelper
27 import com.google.common.truth.Truth.assertThat
28 import org.junit.Before
29 import org.junit.Test
30 import org.junit.runner.RunWith
31 
32 @SmallTest
33 @RunWith(AndroidJUnit4::class)
34 class ResponsiveSpecsProviderTest : AbstractDeviceProfileTest() {
35     override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
36     val deviceSpec = deviceSpecs["tablet"]!!
37     var aspectRatio = deviceSpec.naturalSize.first.toFloat() / deviceSpec.naturalSize.second
38 
39     @Before
40     fun setup() {
41         initializeVarsForPhone(deviceSpec)
42     }
43 
44     @Test
45     fun parseValidFile() {
46         val resourceHelper = TestResourceHelper(context, "valid_responsive_spec_unsorted".xmlToId())
47         val provider = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
48 
49         // Validate Portrait
50         val aspectRatioPortrait = 1.0f
51         val portraitSpecs = provider.getSpecsByAspectRatio(aspectRatioPortrait)
52 
53         val (expectedPortWidthSpecs, expectedPortHeightSpecs) = expectedPortraitSpecs()
54         validateSpecs(
55             portraitSpecs,
56             aspectRatioPortrait,
57             expectedPortWidthSpecs,
58             expectedPortHeightSpecs
59         )
60 
61         // Validate Landscape
62         val aspectRatioLandscape = 1.051f
63         val landscapeSpecs = provider.getSpecsByAspectRatio(aspectRatioLandscape)
64 
65         val (expectedLandWidthSpecs, expectedLandHeightSpecs) = expectedLandscapeSpecs()
66         validateSpecs(
67             landscapeSpecs,
68             aspectRatioLandscape,
69             expectedLandWidthSpecs,
70             expectedLandHeightSpecs
71         )
72 
73         // Validate Extra Spec
74         val aspectRatioExtra = 10.1f
75         val extraSpecs = provider.getSpecsByAspectRatio(aspectRatioExtra)
76 
77         val expectedOtherWidthSpecs =
78             listOf(
79                 ResponsiveSpec(
80                     maxAvailableSize = 9999.dpToPx(),
81                     dimensionType = DimensionType.WIDTH,
82                     specType = ResponsiveSpecType.Workspace,
83                     startPadding = SizeSpec(1f.dpToPx()),
84                     endPadding = SizeSpec(1f.dpToPx()),
85                     gutter = SizeSpec(8f.dpToPx()),
86                     cellSize = SizeSpec(ofRemainderSpace = 1f)
87                 )
88             )
89 
90         val expectedOtherHeightSpecs =
91             listOf(
92                 ResponsiveSpec(
93                     maxAvailableSize = 9999.dpToPx(),
94                     dimensionType = DimensionType.HEIGHT,
95                     specType = ResponsiveSpecType.Workspace,
96                     startPadding = SizeSpec(2f.dpToPx()),
97                     endPadding = SizeSpec(2f.dpToPx()),
98                     gutter = SizeSpec(8f.dpToPx()),
99                     cellSize = SizeSpec(ofRemainderSpace = 1f)
100                 ),
101             )
102 
103         validateSpecs(
104             extraSpecs,
105             aspectRatioExtra,
106             expectedOtherWidthSpecs,
107             expectedOtherHeightSpecs
108         )
109     }
110 
111     @Test(expected = IllegalStateException::class)
112     fun parseValidFile_invalidAspectRatio_throwsError() {
113         val resourceHelper = TestResourceHelper(context, "valid_responsive_spec_unsorted".xmlToId())
114         val provider = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
115         provider.getSpecsByAspectRatio(0f)
116     }
117 
118     @Test(expected = InvalidResponsiveGridSpec::class)
119     fun parseInvalidFile_missingGroups_throwsError() {
120         val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_1".xmlToId())
121         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
122     }
123 
124     @Test(expected = InvalidResponsiveGridSpec::class)
125     fun parseInvalidFile_partialGroups_throwsError() {
126         val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_2".xmlToId())
127         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
128     }
129 
130     @Test(expected = IllegalStateException::class)
131     fun parseInvalidFile_invalidAspectRatio_throwsError() {
132         val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_3".xmlToId())
133         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
134     }
135 
136     @Test(expected = IllegalStateException::class)
137     fun parseInvalidFile_invalidRemainderSpace_throwsError() {
138         val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_4".xmlToId())
139         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
140     }
141 
142     @Test(expected = IllegalStateException::class)
143     fun parseInvalidFile_invalidAvailableSpace_throwsError() {
144         val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_5".xmlToId())
145         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
146     }
147 
148     @Test(expected = IllegalStateException::class)
149     fun parseInvalidFile_invalidFixedSize_throwsError() {
150         val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_6".xmlToId())
151         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
152     }
153 
154     private fun validateSpecs(
155         specs: ResponsiveSpecGroup<ResponsiveSpec>,
156         expectedAspectRatio: Float,
157         expectedWidthSpecs: List<ResponsiveSpec>,
158         expectedHeightSpecs: List<ResponsiveSpec>
159     ) {
160         assertThat(specs.aspectRatio).isAtLeast(expectedAspectRatio)
161 
162         assertThat(specs.widthSpecs.size).isEqualTo(expectedWidthSpecs.size)
163         specs.widthSpecs.forEachIndexed { index, responsiveSpec ->
164             assertThat(responsiveSpec).isEqualTo(expectedWidthSpecs[index])
165         }
166 
167         assertThat(specs.heightSpecs.size).isEqualTo(expectedHeightSpecs.size)
168         specs.heightSpecs.forEachIndexed { index, responsiveSpec ->
169             assertThat(responsiveSpec).isEqualTo(expectedHeightSpecs[index])
170         }
171     }
172 
173     private fun expectedPortraitSpecs(): Pair<List<ResponsiveSpec>, List<ResponsiveSpec>> {
174         val sizeSpec16 = SizeSpec(16f.dpToPx())
175         val expectedWidthSpecs =
176             listOf(
177                 ResponsiveSpec(
178                     maxAvailableSize = 9999.dpToPx(),
179                     dimensionType = DimensionType.WIDTH,
180                     specType = ResponsiveSpecType.Workspace,
181                     startPadding = SizeSpec(22f.dpToPx()),
182                     endPadding = SizeSpec(22f.dpToPx()),
183                     gutter = sizeSpec16,
184                     cellSize = SizeSpec(ofRemainderSpace = 1f)
185                 )
186             )
187 
188         val expectedHeightSpecs =
189             listOf(
190                 ResponsiveSpec(
191                     maxAvailableSize = 584.dpToPx(),
192                     dimensionType = DimensionType.HEIGHT,
193                     specType = ResponsiveSpecType.Workspace,
194                     startPadding = SizeSpec(0f),
195                     endPadding = SizeSpec(32f.dpToPx()),
196                     gutter = sizeSpec16,
197                     cellSize = SizeSpec(ofAvailableSpace = .15808f)
198                 ),
199                 ResponsiveSpec(
200                     maxAvailableSize = 612.dpToPx(),
201                     dimensionType = DimensionType.HEIGHT,
202                     specType = ResponsiveSpecType.Workspace,
203                     startPadding = SizeSpec(0f),
204                     endPadding = SizeSpec(ofRemainderSpace = 1f),
205                     gutter = sizeSpec16,
206                     cellSize = SizeSpec(104f.dpToPx())
207                 ),
208                 ResponsiveSpec(
209                     maxAvailableSize = 9999.dpToPx(),
210                     dimensionType = DimensionType.HEIGHT,
211                     specType = ResponsiveSpecType.Workspace,
212                     startPadding = SizeSpec(8f.dpToPx()),
213                     endPadding = SizeSpec(ofRemainderSpace = 1f),
214                     gutter = sizeSpec16,
215                     cellSize = SizeSpec(104f.dpToPx())
216                 ),
217             )
218 
219         return Pair(expectedWidthSpecs, expectedHeightSpecs)
220     }
221 
222     private fun expectedLandscapeSpecs(): Pair<List<ResponsiveSpec>, List<ResponsiveSpec>> {
223         val sizeSpec12 = SizeSpec(12f.dpToPx())
224         val expectedWidthSpecs =
225             listOf(
226                 ResponsiveSpec(
227                     maxAvailableSize = 602.dpToPx(),
228                     dimensionType = DimensionType.WIDTH,
229                     specType = ResponsiveSpecType.Workspace,
230                     startPadding = SizeSpec(0f.dpToPx()),
231                     endPadding = SizeSpec(36f.dpToPx()),
232                     gutter = sizeSpec12,
233                     cellSize = SizeSpec(ofRemainderSpace = 1f)
234                 ),
235                 ResponsiveSpec(
236                     maxAvailableSize = 716.dpToPx(),
237                     dimensionType = DimensionType.WIDTH,
238                     specType = ResponsiveSpecType.Workspace,
239                     startPadding = SizeSpec(16f.dpToPx()),
240                     endPadding = SizeSpec(64f.dpToPx()),
241                     gutter = sizeSpec12,
242                     cellSize = SizeSpec(ofRemainderSpace = 1f)
243                 ),
244                 ResponsiveSpec(
245                     maxAvailableSize = 9999.dpToPx(),
246                     dimensionType = DimensionType.WIDTH,
247                     specType = ResponsiveSpecType.Workspace,
248                     startPadding = SizeSpec(36f.dpToPx()),
249                     endPadding = SizeSpec(80f.dpToPx()),
250                     gutter = sizeSpec12,
251                     cellSize = SizeSpec(ofRemainderSpace = 1f)
252                 )
253             )
254 
255         val expectedHeightSpecs =
256             listOf(
257                 ResponsiveSpec(
258                     maxAvailableSize = 371.dpToPx(),
259                     dimensionType = DimensionType.HEIGHT,
260                     specType = ResponsiveSpecType.Workspace,
261                     startPadding = SizeSpec(0f),
262                     endPadding = SizeSpec(24f.dpToPx()),
263                     gutter = sizeSpec12,
264                     cellSize = SizeSpec(ofRemainderSpace = 1f)
265                 ),
266                 ResponsiveSpec(
267                     maxAvailableSize = 9999.dpToPx(),
268                     dimensionType = DimensionType.HEIGHT,
269                     specType = ResponsiveSpecType.Workspace,
270                     startPadding = SizeSpec(0f),
271                     endPadding = SizeSpec(34f.dpToPx()),
272                     gutter = sizeSpec12,
273                     cellSize = SizeSpec(ofRemainderSpace = 1f)
274                 ),
275             )
276 
277         return Pair(expectedWidthSpecs, expectedHeightSpecs)
278     }
279 }
280