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 
18 package com.android.systemui.keyguard.ui.view.layout.sections
19 
20 import android.content.pm.PackageManager
21 import android.content.res.Resources
22 import android.view.View.GONE
23 import android.view.View.VISIBLE
24 import androidx.constraintlayout.widget.ConstraintSet
25 import androidx.test.ext.junit.runners.AndroidJUnit4
26 import androidx.test.filters.SmallTest
27 import com.android.systemui.SysuiTestCase
28 import com.android.systemui.coroutines.collectLastValue
29 import com.android.systemui.customization.R as customR
30 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
31 import com.android.systemui.keyguard.domain.interactor.keyguardBlueprintInteractor
32 import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
33 import com.android.systemui.keyguard.domain.interactor.keyguardSmartspaceInteractor
34 import com.android.systemui.keyguard.shared.model.ClockSize
35 import com.android.systemui.keyguard.ui.viewmodel.keyguardClockViewModel
36 import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
37 import com.android.systemui.keyguard.ui.viewmodel.keyguardSmartspaceViewModel
38 import com.android.systemui.kosmos.Kosmos
39 import com.android.systemui.kosmos.testScope
40 import com.android.systemui.res.R
41 import com.android.systemui.shade.data.repository.shadeRepository
42 import com.android.systemui.shade.shared.model.ShadeMode
43 import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
44 import com.android.systemui.statusbar.policy.fakeConfigurationController
45 import com.android.systemui.statusbar.ui.fakeSystemBarUtilsProxy
46 import com.android.systemui.testKosmos
47 import com.android.systemui.util.mockito.eq
48 import com.android.systemui.util.mockito.mock
49 import com.android.systemui.util.mockito.whenever
50 import com.google.common.truth.Truth.assertThat
51 import kotlinx.coroutines.test.advanceUntilIdle
52 import kotlinx.coroutines.test.runTest
53 import org.junit.Before
54 import org.junit.Test
55 import org.junit.runner.RunWith
56 import org.mockito.ArgumentMatchers.anyInt
57 import org.mockito.ArgumentMatchers.anyString
58 import org.mockito.MockitoAnnotations
59 
60 @RunWith(AndroidJUnit4::class)
61 @SmallTest
62 class ClockSectionTest : SysuiTestCase() {
63     private lateinit var underTest: ClockSection
64 
65     private val LARGE_CLOCK_TOP_WITHOUT_SMARTSPACE: Int
66         get() =
67             kosmos.fakeSystemBarUtilsProxy.getStatusBarHeight() +
68                 context.resources.getDimensionPixelSize(customR.dimen.small_clock_padding_top) +
69                 context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
70 
71     private val LARGE_CLOCK_TOP
72         get() =
73             LARGE_CLOCK_TOP_WITHOUT_SMARTSPACE +
74                 SMART_SPACE_DATE_WEATHER_HEIGHT +
75                 ENHANCED_SMART_SPACE_HEIGHT
76 
77     private val CLOCK_FADE_TRANSLATION_Y
78         get() = context.resources.getDimensionPixelSize(customR.dimen.small_clock_height)
79 
80     private var DIMENSION_BY_IDENTIFIER: List<Pair<String, Int>> = listOf()
81     private lateinit var kosmos: Kosmos
82 
83     @Before
84     fun setup() {
85         DIMENSION_BY_IDENTIFIER =
86             listOf(
87                 "date_weather_view_height" to SMART_SPACE_DATE_WEATHER_HEIGHT,
88                 "enhanced_smartspace_height" to ENHANCED_SMART_SPACE_HEIGHT,
89             )
90 
91         MockitoAnnotations.initMocks(this)
92         val remoteResources =
93             mock<Resources>().apply {
94                 whenever(getIdentifier(anyString(), eq("dimen"), anyString())).then { invocation ->
95                     val name = invocation.arguments[0] as String
96                     val index = DIMENSION_BY_IDENTIFIER.indexOfFirst { (key, _) -> key == name }
97                     // increment index so that the not-found sentinel value lines up w/ what is
98                     // returned by getIdentifier when a resource is not found
99                     index + 1
100                 }
101                 whenever(getDimensionPixelSize(anyInt())).then { invocation ->
102                     val id = invocation.arguments[0] as Int
103                     DIMENSION_BY_IDENTIFIER[id - 1].second
104                 }
105             }
106         mContext.setMockPackageManager(
107             mock<PackageManager>().apply {
108                 whenever(getResourcesForApplication(anyString())).thenReturn(remoteResources)
109             }
110         )
111 
112         kosmos = testKosmos()
113         with(kosmos) {
114             underTest =
115                 ClockSection(
116                     keyguardClockInteractor,
117                     keyguardClockViewModel,
118                     context,
119                     keyguardSmartspaceViewModel,
120                     { keyguardBlueprintInteractor },
121                     keyguardRootViewModel,
122                 )
123         }
124     }
125 
126     @Test
127     fun testApplyDefaultConstraints_LargeClock_SplitShade() =
128         kosmos.testScope.runTest {
129             with(kosmos) {
130                 shadeRepository.setShadeMode(ShadeMode.Split)
131                 keyguardClockInteractor.setClockSize(ClockSize.LARGE)
132                 advanceUntilIdle()
133             }
134 
135             val cs = ConstraintSet()
136             underTest.applyDefaultConstraints(cs)
137 
138             assertLargeClockTop(cs, LARGE_CLOCK_TOP)
139             assertSmallClockTop(cs)
140         }
141 
142     @Test
143     fun testApplyDefaultConstraints_LargeClock_NonSplitShade() =
144         kosmos.testScope.runTest {
145             with(kosmos) {
146                 val collectedShadeMode by collectLastValue(shadeRepository.shadeMode)
147                 val isLargeClockVisible by
148                     collectLastValue(keyguardClockViewModel.isLargeClockVisible)
149 
150                 shadeRepository.setShadeMode(ShadeMode.Single)
151                 keyguardClockInteractor.setClockSize(ClockSize.LARGE)
152                 fakeKeyguardRepository.setClockShouldBeCentered(true)
153                 notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
154                 keyguardSmartspaceInteractor.setBcSmartspaceVisibility(VISIBLE)
155                 fakeConfigurationController.notifyConfigurationChanged()
156                 advanceUntilIdle()
157 
158                 val cs = ConstraintSet()
159                 underTest.applyDefaultConstraints(cs)
160 
161                 assertLargeClockTop(cs, LARGE_CLOCK_TOP)
162                 assertSmallClockTop(cs)
163             }
164         }
165 
166     @Test
167     fun testApplyDefaultConstraints_LargeClock_MissingSmartspace_SplitShade() =
168         kosmos.testScope.runTest {
169             with(kosmos) {
170                 DIMENSION_BY_IDENTIFIER = listOf() // Remove Smartspace from mock
171                 val collectedShadeMode by collectLastValue(shadeRepository.shadeMode)
172                 val isLargeClockVisible by
173                     collectLastValue(keyguardClockViewModel.isLargeClockVisible)
174 
175                 shadeRepository.setShadeMode(ShadeMode.Split)
176                 keyguardClockInteractor.setClockSize(ClockSize.LARGE)
177                 fakeKeyguardRepository.setClockShouldBeCentered(true)
178                 notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
179                 keyguardSmartspaceInteractor.setBcSmartspaceVisibility(VISIBLE)
180                 fakeConfigurationController.notifyConfigurationChanged()
181                 advanceUntilIdle()
182 
183                 val cs = ConstraintSet()
184                 underTest.applyDefaultConstraints(cs)
185 
186                 assertLargeClockTop(cs, LARGE_CLOCK_TOP_WITHOUT_SMARTSPACE)
187                 assertSmallClockTop(cs)
188             }
189         }
190 
191     @Test
192     fun testApplyDefaultConstraints_LargeClock_MissingSmartspace_NonSplitShade() =
193         kosmos.testScope.runTest {
194             with(kosmos) {
195                 DIMENSION_BY_IDENTIFIER = listOf() // Remove Smartspace from mock
196                 val collectedShadeMode by collectLastValue(shadeRepository.shadeMode)
197                 val isLargeClockVisible by
198                     collectLastValue(keyguardClockViewModel.isLargeClockVisible)
199 
200                 shadeRepository.setShadeMode(ShadeMode.Single)
201                 keyguardClockInteractor.setClockSize(ClockSize.LARGE)
202                 fakeKeyguardRepository.setClockShouldBeCentered(true)
203                 notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
204                 keyguardSmartspaceInteractor.setBcSmartspaceVisibility(VISIBLE)
205                 fakeConfigurationController.notifyConfigurationChanged()
206                 advanceUntilIdle()
207 
208                 val cs = ConstraintSet()
209                 underTest.applyDefaultConstraints(cs)
210 
211                 assertLargeClockTop(cs, LARGE_CLOCK_TOP_WITHOUT_SMARTSPACE)
212                 assertSmallClockTop(cs)
213             }
214         }
215 
216     @Test
217     fun testApplyDefaultConstraints_SmallClock_SplitShade() =
218         kosmos.testScope.runTest {
219             with(kosmos) {
220                 val collectedShadeMode by collectLastValue(shadeRepository.shadeMode)
221                 val isLargeClockVisible by
222                     collectLastValue(keyguardClockViewModel.isLargeClockVisible)
223 
224                 shadeRepository.setShadeMode(ShadeMode.Split)
225                 keyguardClockInteractor.setClockSize(ClockSize.SMALL)
226                 fakeKeyguardRepository.setClockShouldBeCentered(true)
227                 notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
228                 keyguardSmartspaceInteractor.setBcSmartspaceVisibility(VISIBLE)
229                 fakeConfigurationController.notifyConfigurationChanged()
230                 advanceUntilIdle()
231 
232                 val cs = ConstraintSet()
233                 underTest.applyDefaultConstraints(cs)
234 
235                 assertLargeClockTop(cs, LARGE_CLOCK_TOP)
236                 assertSmallClockTop(cs)
237             }
238         }
239 
240     @Test
241     fun testApplyDefaultConstraints_SmallClock_NonSplitShade() =
242         kosmos.testScope.runTest {
243             with(kosmos) {
244                 val collectedShadeMode by collectLastValue(shadeRepository.shadeMode)
245                 val isLargeClockVisible by
246                     collectLastValue(keyguardClockViewModel.isLargeClockVisible)
247 
248                 shadeRepository.setShadeMode(ShadeMode.Single)
249                 keyguardClockInteractor.setClockSize(ClockSize.SMALL)
250                 fakeKeyguardRepository.setClockShouldBeCentered(true)
251                 notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
252                 keyguardSmartspaceInteractor.setBcSmartspaceVisibility(VISIBLE)
253                 fakeConfigurationController.notifyConfigurationChanged()
254                 advanceUntilIdle()
255 
256                 val cs = ConstraintSet()
257                 underTest.applyDefaultConstraints(cs)
258                 assertLargeClockTop(cs, LARGE_CLOCK_TOP)
259                 assertSmallClockTop(cs)
260             }
261         }
262 
263     @Test
264     fun testSmartspaceVisible_weatherClockDateAndIconsBarrierBottomBelowBCSmartspace() =
265         kosmos.testScope.runTest {
266             with(kosmos) {
267                 notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
268                 keyguardSmartspaceInteractor.setBcSmartspaceVisibility(VISIBLE)
269                 fakeConfigurationController.notifyConfigurationChanged()
270                 advanceUntilIdle()
271             }
272 
273             val cs = ConstraintSet()
274             underTest.applyDefaultConstraints(cs)
275             val referencedIds =
276                 cs.getReferencedIds(R.id.weather_clock_date_and_icons_barrier_bottom)
277             referencedIds.contentEquals(
278                 intArrayOf(com.android.systemui.shared.R.id.bc_smartspace_view)
279             )
280         }
281 
282     @Test
283     fun testSmartspaceGone_weatherClockDateAndIconsBarrierBottomBelowSmartspaceDateWeather() =
284         kosmos.testScope.runTest {
285             with(kosmos) {
286                 notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
287                 keyguardSmartspaceInteractor.setBcSmartspaceVisibility(GONE)
288                 fakeConfigurationController.notifyConfigurationChanged()
289                 advanceUntilIdle()
290             }
291 
292             val cs = ConstraintSet()
293             underTest.applyDefaultConstraints(cs)
294             val referencedIds =
295                 cs.getReferencedIds(R.id.weather_clock_date_and_icons_barrier_bottom)
296             referencedIds.contentEquals(intArrayOf(R.id.lockscreen_clock_view))
297         }
298 
299     @Test
300     fun testHasAodIcons_weatherClockDateAndIconsBarrierBottomBelowSmartspaceDateWeather() =
301         kosmos.testScope.runTest {
302             with(kosmos) {
303                 notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
304                 fakeConfigurationController.notifyConfigurationChanged()
305                 advanceUntilIdle()
306             }
307 
308             val cs = ConstraintSet()
309             underTest.applyDefaultConstraints(cs)
310             val referencedIds =
311                 cs.getReferencedIds(R.id.weather_clock_date_and_icons_barrier_bottom)
312             referencedIds.contentEquals(
313                 intArrayOf(
314                     com.android.systemui.shared.R.id.bc_smartspace_view,
315                     R.id.aod_notification_icon_container
316                 )
317             )
318         }
319 
320     private fun assertLargeClockTop(cs: ConstraintSet, expectedLargeClockTopMargin: Int) {
321         val largeClockConstraint = cs.getConstraint(R.id.lockscreen_clock_view_large)
322         assertThat(largeClockConstraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID)
323         assertThat(largeClockConstraint.layout.topMargin).isEqualTo(expectedLargeClockTopMargin)
324     }
325 
326     private fun assertSmallClockTop(cs: ConstraintSet) {
327         val smallClockGuidelineConstraint = cs.getConstraint(R.id.small_clock_guideline_top)
328         assertThat(smallClockGuidelineConstraint.layout.topToTop).isEqualTo(-1)
329 
330         val smallClockConstraint = cs.getConstraint(R.id.lockscreen_clock_view)
331         assertThat(smallClockConstraint.layout.topToBottom)
332             .isEqualTo(R.id.small_clock_guideline_top)
333         assertThat(smallClockConstraint.layout.topMargin).isEqualTo(0)
334     }
335 
336     companion object {
337         private val SMART_SPACE_DATE_WEATHER_HEIGHT = 10
338         private val ENHANCED_SMART_SPACE_HEIGHT = 11
339     }
340 }
341