1 /*
2  * Copyright (C) 2022 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.settings.brightness
18 
19 import android.content.Intent
20 import android.graphics.Rect
21 import android.platform.test.flag.junit.FlagsParameterization
22 import android.testing.TestableLooper
23 import android.view.View
24 import android.view.ViewGroup
25 import android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY
26 import androidx.test.filters.FlakyTest
27 import androidx.test.filters.SmallTest
28 import androidx.test.rule.ActivityTestRule
29 import com.android.systemui.SysuiTestCase
30 import com.android.systemui.activity.SingleActivityFactory
31 import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
32 import com.android.systemui.brightness.ui.viewmodel.brightnessSliderViewModelFactory
33 import com.android.systemui.qs.flags.QSComposeFragment
34 import com.android.systemui.qs.flags.QsInCompose
35 import com.android.systemui.res.R
36 import com.android.systemui.shade.domain.interactor.ShadeInteractor
37 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
38 import com.android.systemui.testKosmos
39 import com.android.systemui.util.concurrency.DelayableExecutor
40 import com.android.systemui.util.concurrency.FakeExecutor
41 import com.android.systemui.util.mockito.any
42 import com.android.systemui.util.mockito.whenever
43 import com.android.systemui.util.time.FakeSystemClock
44 import com.google.common.truth.Truth.assertThat
45 import java.util.concurrent.CountDownLatch
46 import kotlin.time.Duration.Companion.milliseconds
47 import kotlinx.coroutines.FlowPreview
48 import kotlinx.coroutines.flow.MutableStateFlow
49 import kotlinx.coroutines.flow.takeWhile
50 import kotlinx.coroutines.flow.timeout
51 import kotlinx.coroutines.test.runTest
52 import org.junit.After
53 import org.junit.Before
54 import org.junit.Rule
55 import org.junit.Test
56 import org.junit.runner.RunWith
57 import org.mockito.Mock
58 import org.mockito.Mockito.anyInt
59 import org.mockito.Mockito.eq
60 import org.mockito.Mockito.`when`
61 import org.mockito.MockitoAnnotations
62 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
63 import platform.test.runner.parameterized.Parameters
64 
65 @SmallTest
66 @RunWith(ParameterizedAndroidJunit4::class)
67 @TestableLooper.RunWithLooper
68 class BrightnessDialogTest(val flags: FlagsParameterization) : SysuiTestCase() {
69 
70     init {
71         mSetFlagsRule.setFlagsParameterization(flags)
72     }
73 
<lambda>null74     private val viewId by lazy {
75         if (QsInCompose.isEnabled) {
76             R.id.brightness_dialog_slider
77         } else {
78             R.id.brightness_mirror_container
79         }
80     }
81 
82     @Mock private lateinit var brightnessSliderControllerFactory: BrightnessSliderController.Factory
83     @Mock private lateinit var brightnessSliderController: BrightnessSliderController
84     @Mock private lateinit var brightnessControllerFactory: BrightnessController.Factory
85     @Mock private lateinit var brightnessController: BrightnessController
86     @Mock private lateinit var accessibilityMgr: AccessibilityManagerWrapper
87     @Mock private lateinit var shadeInteractor: ShadeInteractor
88 
89     private val kosmos = testKosmos()
90 
91     private val clock = FakeSystemClock()
92     private val mainExecutor = FakeExecutor(clock)
93 
94     private val onDestroyLatch = CountDownLatch(1)
95 
96     @Rule(order = 200)
97     @JvmField
98     var activityRule =
99         ActivityTestRule(
<lambda>null100             /* activityFactory= */ SingleActivityFactory {
101                 TestDialog(
102                     brightnessSliderControllerFactory,
103                     brightnessControllerFactory,
104                     mainExecutor,
105                     accessibilityMgr,
106                     shadeInteractor,
107                     kosmos.brightnessSliderViewModelFactory,
108                     onDestroyLatch,
109                 )
110             },
111             /* initialTouchMode= */ false,
112             /* launchActivity= */ false,
113         )
114 
115     @Before
setUpnull116     fun setUp() {
117         MockitoAnnotations.initMocks(this)
118         `when`(brightnessSliderControllerFactory.create(any(), any()))
119             .thenReturn(brightnessSliderController)
120         `when`(brightnessSliderController.rootView).thenReturn(View(context))
121         `when`(brightnessControllerFactory.create(any())).thenReturn(brightnessController)
122         whenever(shadeInteractor.isQsExpanded).thenReturn(MutableStateFlow(false))
123     }
124 
125     @After
tearDownnull126     fun tearDown() {
127         activityRule.finishActivity()
128         onDestroyLatch.await()
129     }
130 
131     @Test
testGestureExclusionnull132     fun testGestureExclusion() {
133         activityRule.launchActivity(Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG))
134         val frame = activityRule.activity.requireViewById<View>(viewId)
135 
136         val lp = frame.layoutParams as ViewGroup.MarginLayoutParams
137         val horizontalMargin =
138             activityRule.activity.resources.getDimensionPixelSize(
139                 R.dimen.notification_side_paddings
140             )
141         assertThat(lp.leftMargin).isEqualTo(horizontalMargin)
142         assertThat(lp.rightMargin).isEqualTo(horizontalMargin)
143 
144         assertThat(frame.systemGestureExclusionRects.size).isEqualTo(1)
145         val exclusion = frame.systemGestureExclusionRects[0]
146         assertThat(exclusion)
147             .isEqualTo(Rect(-horizontalMargin, 0, frame.width + horizontalMargin, frame.height))
148     }
149 
150     @Test
testTimeoutnull151     fun testTimeout() {
152         `when`(
153                 accessibilityMgr.getRecommendedTimeoutMillis(
154                     eq(BrightnessDialog.DIALOG_TIMEOUT_MILLIS),
155                     anyInt(),
156                 )
157             )
158             .thenReturn(BrightnessDialog.DIALOG_TIMEOUT_MILLIS)
159         val intent = Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG)
160         intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true)
161         activityRule.launchActivity(intent)
162 
163         assertThat(activityRule.activity.isFinishing()).isFalse()
164 
165         clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong())
166         assertThat(activityRule.activity.isFinishing()).isTrue()
167     }
168 
169     @Test
testRestartTimeoutnull170     fun testRestartTimeout() {
171         `when`(
172                 accessibilityMgr.getRecommendedTimeoutMillis(
173                     eq(BrightnessDialog.DIALOG_TIMEOUT_MILLIS),
174                     anyInt(),
175                 )
176             )
177             .thenReturn(BrightnessDialog.DIALOG_TIMEOUT_MILLIS)
178         val intent = Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG)
179         intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true)
180         activityRule.launchActivity(intent)
181 
182         assertThat(activityRule.activity.isFinishing()).isFalse()
183 
184         clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong() / 2)
185         // Restart the timeout
186         activityRule.activity.onResume()
187 
188         clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong() / 2)
189         // The dialog should not have disappeared yet
190         assertThat(activityRule.activity.isFinishing()).isFalse()
191 
192         clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong() / 2)
193         assertThat(activityRule.activity.isFinishing()).isTrue()
194     }
195 
196     @Test
testNoTimeoutIfNotStartedByBrightnessKeynull197     fun testNoTimeoutIfNotStartedByBrightnessKey() {
198         `when`(
199                 accessibilityMgr.getRecommendedTimeoutMillis(
200                     eq(BrightnessDialog.DIALOG_TIMEOUT_MILLIS),
201                     anyInt(),
202                 )
203             )
204             .thenReturn(BrightnessDialog.DIALOG_TIMEOUT_MILLIS)
205         activityRule.launchActivity(Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG))
206 
207         assertThat(activityRule.activity.isFinishing()).isFalse()
208 
209         clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong())
210         assertThat(activityRule.activity.isFinishing()).isFalse()
211     }
212 
213     @OptIn(FlowPreview::class)
214     @FlakyTest(bugId = 326186573)
215     @Test
<lambda>null216     fun testFinishOnQSExpanded() = runTest {
217         val isQSExpanded = MutableStateFlow(false)
218         `when`(shadeInteractor.isQsExpanded).thenReturn(isQSExpanded)
219         activityRule.launchActivity(Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG))
220 
221         assertThat(activityRule.activity.isFinishing()).isFalse()
222 
223         isQSExpanded.value = true
224         // Observe the activity's state until is it finishing or the timeout is reached, whatever
225         // comes first. This fixes the flakiness seen when using advanceUntilIdle().
226         activityRule.activity.finishing.timeout(100.milliseconds).takeWhile { !it }.collect {}
227         assertThat(activityRule.activity.isFinishing()).isTrue()
228     }
229 
230     class TestDialog(
231         brightnessSliderControllerFactory: BrightnessSliderController.Factory,
232         brightnessControllerFactory: BrightnessController.Factory,
233         mainExecutor: DelayableExecutor,
234         accessibilityMgr: AccessibilityManagerWrapper,
235         shadeInteractor: ShadeInteractor,
236         brightnessSliderViewModelFactory: BrightnessSliderViewModel.Factory,
237         private val countdownLatch: CountDownLatch,
238     ) :
239         BrightnessDialog(
240             brightnessSliderControllerFactory,
241             brightnessControllerFactory,
242             mainExecutor,
243             accessibilityMgr,
244             shadeInteractor,
245             brightnessSliderViewModelFactory,
246         ) {
247         var finishing = MutableStateFlow(false)
248 
isFinishingnull249         override fun isFinishing(): Boolean {
250             return finishing.value
251         }
252 
requestFinishnull253         override fun requestFinish() {
254             finishing.value = true
255         }
256 
onDestroynull257         override fun onDestroy() {
258             super.onDestroy()
259             countdownLatch.countDown()
260         }
261     }
262 
263     companion object {
264         @JvmStatic
265         @Parameters(name = "{0}")
getParamsnull266         fun getParams(): List<FlagsParameterization> {
267             return FlagsParameterization.allCombinationsOf(QSComposeFragment.FLAG_NAME)
268         }
269     }
270 }
271