1 /*
2  * Copyright (C) 2024 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.qs.tiles.impl.night.ui
18 
19 import android.graphics.drawable.TestStubDrawable
20 import android.service.quicksettings.Tile
21 import android.text.TextUtils
22 import android.widget.Switch
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.shared.model.Icon
27 import com.android.systemui.kosmos.Kosmos
28 import com.android.systemui.qs.tiles.base.logging.QSTileLogger
29 import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
30 import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
31 import com.android.systemui.qs.tiles.impl.night.qsNightDisplayTileConfig
32 import com.android.systemui.qs.tiles.viewmodel.QSTileState
33 import com.android.systemui.res.R
34 import com.android.systemui.util.mockito.mock
35 import java.time.LocalTime
36 import java.time.format.DateTimeFormatter
37 import org.junit.Before
38 import org.junit.Test
39 import org.junit.runner.RunWith
40 
41 @SmallTest
42 @RunWith(AndroidJUnit4::class)
43 class NightDisplayTileMapperTest : SysuiTestCase() {
44     private val kosmos = Kosmos()
45     private val config = kosmos.qsNightDisplayTileConfig
46 
47     private val testStartTime = LocalTime.MIDNIGHT
48     private val testEndTime = LocalTime.NOON
49 
50     private lateinit var mapper: NightDisplayTileMapper
51 
52     @Before
setupnull53     fun setup() {
54         mapper =
55             NightDisplayTileMapper(
56                 context.orCreateTestableResources
57                     .apply {
58                         addOverride(R.drawable.qs_nightlight_icon_on, TestStubDrawable())
59                         addOverride(R.drawable.qs_nightlight_icon_off, TestStubDrawable())
60                     }
61                     .resources,
62                 context.theme,
63                 mock<QSTileLogger>(),
64             )
65     }
66 
67     @Test
disabledModel_whenAutoModeOffnull68     fun disabledModel_whenAutoModeOff() {
69         val inputModel = NightDisplayTileModel.AutoModeOff(false, false)
70 
71         val outputState = mapper.map(config, inputModel)
72 
73         val expectedState =
74             createNightDisplayTileState(
75                 QSTileState.ActivationState.INACTIVE,
76                 context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE],
77             )
78         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
79     }
80 
81     /** Force enable does not change the mode by itself. */
82     @Test
disabledModel_whenAutoModeOff_whenForceEnablenull83     fun disabledModel_whenAutoModeOff_whenForceEnable() {
84         val inputModel = NightDisplayTileModel.AutoModeOff(false, true)
85 
86         val outputState = mapper.map(config, inputModel)
87 
88         val expectedState =
89             createNightDisplayTileState(
90                 QSTileState.ActivationState.INACTIVE,
91                 context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE],
92             )
93         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
94     }
95 
96     @Test
enabledModel_whenAutoModeOffnull97     fun enabledModel_whenAutoModeOff() {
98         val inputModel = NightDisplayTileModel.AutoModeOff(true, false)
99 
100         val outputState = mapper.map(config, inputModel)
101 
102         val expectedState =
103             createNightDisplayTileState(
104                 QSTileState.ActivationState.ACTIVE,
105                 context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE],
106             )
107         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
108     }
109 
110     @Test
enabledModel_forceAutoMode_whenAutoModeOffnull111     fun enabledModel_forceAutoMode_whenAutoModeOff() {
112         val inputModel = NightDisplayTileModel.AutoModeOff(true, true)
113 
114         val outputState = mapper.map(config, inputModel)
115 
116         val expectedState =
117             createNightDisplayTileState(
118                 QSTileState.ActivationState.ACTIVE,
119                 context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE],
120             )
121         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
122     }
123 
124     @Test
enabledModel_autoModeTwilight_locationOffnull125     fun enabledModel_autoModeTwilight_locationOff() {
126         val inputModel = NightDisplayTileModel.AutoModeTwilight(true, false, false)
127 
128         val outputState = mapper.map(config, inputModel)
129 
130         val expectedState = createNightDisplayTileState(QSTileState.ActivationState.ACTIVE, null)
131         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
132     }
133 
134     @Test
enabledModel_autoModeTwilight_locationOnnull135     fun enabledModel_autoModeTwilight_locationOn() {
136         val inputModel = NightDisplayTileModel.AutoModeTwilight(true, false, true)
137 
138         val outputState = mapper.map(config, inputModel)
139 
140         val expectedState =
141             createNightDisplayTileState(
142                 QSTileState.ActivationState.ACTIVE,
143                 context.getString(R.string.quick_settings_night_secondary_label_until_sunrise),
144             )
145         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
146     }
147 
148     @Test
disabledModel_autoModeTwilight_locationOnnull149     fun disabledModel_autoModeTwilight_locationOn() {
150         val inputModel = NightDisplayTileModel.AutoModeTwilight(false, false, true)
151 
152         val outputState = mapper.map(config, inputModel)
153 
154         val expectedState =
155             createNightDisplayTileState(
156                 QSTileState.ActivationState.INACTIVE,
157                 context.getString(R.string.quick_settings_night_secondary_label_on_at_sunset),
158             )
159         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
160     }
161 
162     @Test
disabledModel_autoModeTwilight_locationOffnull163     fun disabledModel_autoModeTwilight_locationOff() {
164         val inputModel = NightDisplayTileModel.AutoModeTwilight(false, false, false)
165 
166         val outputState = mapper.map(config, inputModel)
167 
168         val expectedState = createNightDisplayTileState(QSTileState.ActivationState.INACTIVE, null)
169         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
170     }
171 
172     @Test
disabledModel_autoModeCustom_24Hournull173     fun disabledModel_autoModeCustom_24Hour() {
174         val inputModel =
175             NightDisplayTileModel.AutoModeCustom(false, false, testStartTime, null, true)
176 
177         val outputState = mapper.map(config, inputModel)
178 
179         val expectedState =
180             createNightDisplayTileState(
181                 QSTileState.ActivationState.INACTIVE,
182                 context.getString(
183                     R.string.quick_settings_night_secondary_label_on_at,
184                     formatter24Hour.format(testStartTime),
185                 ),
186             )
187         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
188     }
189 
190     @Test
disabledModel_autoModeCustom_12Hournull191     fun disabledModel_autoModeCustom_12Hour() {
192         val inputModel =
193             NightDisplayTileModel.AutoModeCustom(false, false, testStartTime, null, false)
194 
195         val outputState = mapper.map(config, inputModel)
196 
197         val expectedState =
198             createNightDisplayTileState(
199                 QSTileState.ActivationState.INACTIVE,
200                 context.getString(
201                     R.string.quick_settings_night_secondary_label_on_at,
202                     formatter12Hour.format(testStartTime),
203                 ),
204             )
205         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
206     }
207 
208     /** Should have the same outcome as [disabledModel_autoModeCustom_12Hour] */
209     @Test
disabledModel_autoModeCustom_12Hour_isEnrolledForcedAutoModenull210     fun disabledModel_autoModeCustom_12Hour_isEnrolledForcedAutoMode() {
211         val inputModel =
212             NightDisplayTileModel.AutoModeCustom(false, true, testStartTime, null, false)
213 
214         val outputState = mapper.map(config, inputModel)
215 
216         val expectedState =
217             createNightDisplayTileState(
218                 QSTileState.ActivationState.INACTIVE,
219                 context.getString(
220                     R.string.quick_settings_night_secondary_label_on_at,
221                     formatter12Hour.format(testStartTime),
222                 ),
223             )
224         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
225     }
226 
227     @Test
enabledModel_autoModeCustom_24Hournull228     fun enabledModel_autoModeCustom_24Hour() {
229         val inputModel = NightDisplayTileModel.AutoModeCustom(true, false, null, testEndTime, true)
230 
231         val outputState = mapper.map(config, inputModel)
232 
233         val expectedState =
234             createNightDisplayTileState(
235                 QSTileState.ActivationState.ACTIVE,
236                 context.getString(
237                     R.string.quick_settings_secondary_label_until,
238                     formatter24Hour.format(testEndTime),
239                 ),
240             )
241         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
242     }
243 
244     @Test
enabledModel_autoModeCustom_12Hournull245     fun enabledModel_autoModeCustom_12Hour() {
246         val inputModel = NightDisplayTileModel.AutoModeCustom(true, false, null, testEndTime, false)
247 
248         val outputState = mapper.map(config, inputModel)
249 
250         val expectedState =
251             createNightDisplayTileState(
252                 QSTileState.ActivationState.ACTIVE,
253                 context.getString(
254                     R.string.quick_settings_secondary_label_until,
255                     formatter12Hour.format(testEndTime),
256                 ),
257             )
258         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
259     }
260 
261     /** Should have the same state as [enabledModel_autoModeCustom_24Hour] */
262     @Test
enabledModel_autoModeCustom_24Hour_forceEnablednull263     fun enabledModel_autoModeCustom_24Hour_forceEnabled() {
264         val inputModel = NightDisplayTileModel.AutoModeCustom(true, true, null, testEndTime, true)
265 
266         val outputState = mapper.map(config, inputModel)
267 
268         val expectedState =
269             createNightDisplayTileState(
270                 QSTileState.ActivationState.ACTIVE,
271                 context.getString(
272                     R.string.quick_settings_secondary_label_until,
273                     formatter24Hour.format(testEndTime),
274                 ),
275             )
276         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
277     }
278 
createNightDisplayTileStatenull279     private fun createNightDisplayTileState(
280         activationState: QSTileState.ActivationState,
281         secondaryLabel: String?,
282     ): QSTileState {
283         val label = context.getString(R.string.quick_settings_night_display_label)
284         val iconRes =
285             if (activationState == QSTileState.ActivationState.ACTIVE)
286                 R.drawable.qs_nightlight_icon_on
287             else R.drawable.qs_nightlight_icon_off
288         val contentDescription =
289             if (TextUtils.isEmpty(secondaryLabel)) label
290             else TextUtils.concat(label, ", ", secondaryLabel)
291         return QSTileState(
292             Icon.Loaded(context.getDrawable(iconRes)!!, null),
293             iconRes,
294             label,
295             activationState,
296             secondaryLabel,
297             setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
298             contentDescription,
299             null,
300             QSTileState.SideViewIcon.None,
301             QSTileState.EnabledState.ENABLED,
302             Switch::class.qualifiedName,
303         )
304     }
305 
306     private companion object {
307         val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a")
308         val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")
309     }
310 }
311