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