1 /*
2  * 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.common.ui.domain.interactor
18 
19 import android.content.res.Configuration
20 import android.graphics.Rect
21 import android.util.LayoutDirection
22 import android.view.Surface
23 import androidx.test.ext.junit.runners.AndroidJUnit4
24 import androidx.test.filters.SmallTest
25 import com.android.systemui.SysuiTestCase
26 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
27 import com.android.systemui.coroutines.collectLastValue
28 import com.android.systemui.coroutines.collectValues
29 import com.google.common.truth.Truth.assertThat
30 import kotlinx.coroutines.ExperimentalCoroutinesApi
31 import kotlinx.coroutines.test.TestScope
32 import kotlinx.coroutines.test.runCurrent
33 import kotlinx.coroutines.test.runTest
34 import org.junit.Before
35 import org.junit.Test
36 import org.junit.runner.RunWith
37 import org.mockito.MockitoAnnotations
38 import org.mockito.kotlin.mock
39 import org.mockito.kotlin.whenever
40 
41 @ExperimentalCoroutinesApi
42 @SmallTest
43 @RunWith(AndroidJUnit4::class)
44 class ConfigurationInteractorTest : SysuiTestCase() {
45     private lateinit var testScope: TestScope
46     private lateinit var configuration: Configuration
47     private lateinit var underTest: ConfigurationInteractor
48     private lateinit var configurationRepository: FakeConfigurationRepository
49 
50     @Before
setUpnull51     fun setUp() {
52         MockitoAnnotations.initMocks(this)
53         configuration = context.resources.configuration
54         val testableResources = context.getOrCreateTestableResources()
55         testableResources.overrideConfiguration(configuration)
56         configurationRepository = FakeConfigurationRepository()
57         testScope = TestScope()
58         underTest = ConfigurationInteractorImpl(configurationRepository)
59     }
60 
61     @Test
dimensionPixelSizenull62     fun dimensionPixelSize() =
63         testScope.runTest {
64             val resourceId = 1001
65             val pixelSize = 501
66             configurationRepository.setDimensionPixelSize(resourceId, pixelSize)
67 
68             val dimensionPixelSize by collectLastValue(underTest.dimensionPixelSize(resourceId))
69 
70             configurationRepository.onAnyConfigurationChange()
71 
72             assertThat(dimensionPixelSize).isEqualTo(pixelSize)
73         }
74 
75     @Test
directionalDimensionPixelSizenull76     fun directionalDimensionPixelSize() =
77         testScope.runTest {
78             val resourceId = 1001
79             val pixelSize = 501
80             configurationRepository.setDimensionPixelSize(resourceId, pixelSize)
81 
82             val config: Configuration = mock()
83             val dimensionPixelSize by
84                 collectLastValue(
85                     underTest.directionalDimensionPixelSize(LayoutDirection.LTR, resourceId)
86                 )
87 
88             whenever(config.layoutDirection).thenReturn(LayoutDirection.LTR)
89             configurationRepository.onConfigurationChange(config)
90             assertThat(dimensionPixelSize).isEqualTo(pixelSize)
91 
92             whenever(config.layoutDirection).thenReturn(LayoutDirection.RTL)
93             configurationRepository.onConfigurationChange(config)
94             assertThat(dimensionPixelSize).isEqualTo(-pixelSize)
95         }
96 
97     @Test
dimensionPixelSizesnull98     fun dimensionPixelSizes() =
99         testScope.runTest {
100             val resourceId1 = 1001
101             val pixelSize1 = 501
102             val resourceId2 = 1002
103             val pixelSize2 = 502
104             configurationRepository.setDimensionPixelSize(resourceId1, pixelSize1)
105             configurationRepository.setDimensionPixelSize(resourceId2, pixelSize2)
106 
107             val dimensionPixelSizes by
108                 collectLastValue(underTest.dimensionPixelSize(setOf(resourceId1, resourceId2)))
109 
110             configurationRepository.onAnyConfigurationChange()
111 
112             assertThat(dimensionPixelSizes!![resourceId1]).isEqualTo(pixelSize1)
113             assertThat(dimensionPixelSizes!![resourceId2]).isEqualTo(pixelSize2)
114         }
115 
116     @Test
maxBoundsChange_emitsMaxBoundsChangenull117     fun maxBoundsChange_emitsMaxBoundsChange() =
118         testScope.runTest {
119             val values by collectValues(underTest.maxBounds)
120 
121             updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
122             runCurrent()
123             updateDisplay(width = DISPLAY_WIDTH * 2, height = DISPLAY_HEIGHT * 3)
124             runCurrent()
125 
126             assertThat(values)
127                 .containsExactly(
128                     Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT),
129                     Rect(0, 0, DISPLAY_WIDTH * 2, DISPLAY_HEIGHT * 3),
130                 )
131                 .inOrder()
132         }
133 
134     @Test
maxBoundsSameOnConfigChange_doesNotEmitMaxBoundsChangenull135     fun maxBoundsSameOnConfigChange_doesNotEmitMaxBoundsChange() =
136         testScope.runTest {
137             val values by collectValues(underTest.maxBounds)
138 
139             updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
140             runCurrent()
141             updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
142             runCurrent()
143 
144             assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
145         }
146 
147     @Test
firstMaxBoundsChange_emitsMaxBoundsChangenull148     fun firstMaxBoundsChange_emitsMaxBoundsChange() =
149         testScope.runTest {
150             val values by collectValues(underTest.maxBounds)
151 
152             updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
153             runCurrent()
154 
155             assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
156         }
157 
158     @Test
maxBoundsChange_emitsNaturalMaxBoundsChangenull159     fun maxBoundsChange_emitsNaturalMaxBoundsChange() =
160         testScope.runTest {
161             val values by collectValues(underTest.naturalMaxBounds)
162 
163             updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
164             runCurrent()
165             updateDisplay(width = DISPLAY_WIDTH * 2, height = DISPLAY_HEIGHT * 3)
166             runCurrent()
167 
168             assertThat(values)
169                 .containsExactly(
170                     Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT),
171                     Rect(0, 0, DISPLAY_WIDTH * 2, DISPLAY_HEIGHT * 3),
172                 )
173                 .inOrder()
174         }
175 
176     @Test
maxBoundsSameOnConfigChange_doesNotEmitNaturalMaxBoundsChangenull177     fun maxBoundsSameOnConfigChange_doesNotEmitNaturalMaxBoundsChange() =
178         testScope.runTest {
179             val values by collectValues(underTest.naturalMaxBounds)
180 
181             updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
182             runCurrent()
183             updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
184             runCurrent()
185 
186             assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
187         }
188 
189     @Test
firstMaxBoundsChange_emitsNaturalMaxBoundsChangenull190     fun firstMaxBoundsChange_emitsNaturalMaxBoundsChange() =
191         testScope.runTest {
192             val values by collectValues(underTest.naturalMaxBounds)
193 
194             updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
195             runCurrent()
196 
197             assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
198         }
199 
200     @Test
displayRotatedButMaxBoundsTheSame_doesNotEmitNewNaturalMaxBoundsChangenull201     fun displayRotatedButMaxBoundsTheSame_doesNotEmitNewNaturalMaxBoundsChange() =
202         testScope.runTest {
203             val values by collectValues(underTest.naturalMaxBounds)
204 
205             updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
206             runCurrent()
207             updateDisplay(
208                 width = DISPLAY_HEIGHT,
209                 height = DISPLAY_WIDTH,
210                 rotation = Surface.ROTATION_90,
211             )
212             runCurrent()
213 
214             assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
215         }
216 
updateDisplaynull217     private fun updateDisplay(
218         width: Int = DISPLAY_WIDTH,
219         height: Int = DISPLAY_HEIGHT,
220         @Surface.Rotation rotation: Int = Surface.ROTATION_0,
221     ) {
222         configuration.windowConfiguration.maxBounds.set(Rect(0, 0, width, height))
223         configuration.windowConfiguration.displayRotation = rotation
224 
225         configurationRepository.onConfigurationChange(configuration)
226     }
227 
228     private companion object {
229         private const val DISPLAY_WIDTH = 100
230         private const val DISPLAY_HEIGHT = 200
231     }
232 }
233