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.testing.AndroidTestingRunner 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.res.R 32 import com.android.systemui.shade.domain.interactor.ShadeInteractor 33 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper 34 import com.android.systemui.util.concurrency.DelayableExecutor 35 import com.android.systemui.util.concurrency.FakeExecutor 36 import com.android.systemui.util.mockito.any 37 import com.android.systemui.util.mockito.whenever 38 import com.android.systemui.util.time.FakeSystemClock 39 import com.google.common.truth.Truth.assertThat 40 import kotlin.time.Duration.Companion.milliseconds 41 import kotlinx.coroutines.FlowPreview 42 import kotlinx.coroutines.flow.MutableStateFlow 43 import kotlinx.coroutines.flow.takeWhile 44 import kotlinx.coroutines.flow.timeout 45 import kotlinx.coroutines.test.runTest 46 import org.junit.After 47 import org.junit.Before 48 import org.junit.Rule 49 import org.junit.Test 50 import org.junit.runner.RunWith 51 import org.mockito.Mock 52 import org.mockito.Mockito.anyInt 53 import org.mockito.Mockito.eq 54 import org.mockito.Mockito.`when` 55 import org.mockito.MockitoAnnotations 56 57 @SmallTest 58 @RunWith(AndroidTestingRunner::class) 59 @TestableLooper.RunWithLooper 60 class BrightnessDialogTest : SysuiTestCase() { 61 62 @Mock private lateinit var brightnessSliderControllerFactory: BrightnessSliderController.Factory 63 @Mock private lateinit var brightnessSliderController: BrightnessSliderController 64 @Mock private lateinit var brightnessControllerFactory: BrightnessController.Factory 65 @Mock private lateinit var brightnessController: BrightnessController 66 @Mock private lateinit var accessibilityMgr: AccessibilityManagerWrapper 67 @Mock private lateinit var shadeInteractor: ShadeInteractor 68 69 private val clock = FakeSystemClock() 70 private val mainExecutor = FakeExecutor(clock) 71 72 @Rule 73 @JvmField 74 var activityRule = 75 ActivityTestRule( <lambda>null76 /* activityFactory= */ SingleActivityFactory { 77 TestDialog( 78 brightnessSliderControllerFactory, 79 brightnessControllerFactory, 80 mainExecutor, 81 accessibilityMgr, 82 shadeInteractor 83 ) 84 }, 85 /* initialTouchMode= */ false, 86 /* launchActivity= */ false, 87 ) 88 89 @Before setUpnull90 fun setUp() { 91 MockitoAnnotations.initMocks(this) 92 `when`(brightnessSliderControllerFactory.create(any(), any())) 93 .thenReturn(brightnessSliderController) 94 `when`(brightnessSliderController.rootView).thenReturn(View(context)) 95 `when`(brightnessControllerFactory.create(any())).thenReturn(brightnessController) 96 whenever(shadeInteractor.isQsExpanded).thenReturn(MutableStateFlow(false)) 97 } 98 99 @After tearDownnull100 fun tearDown() { 101 activityRule.finishActivity() 102 } 103 104 @Test testGestureExclusionnull105 fun testGestureExclusion() { 106 activityRule.launchActivity(Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG)) 107 val frame = activityRule.activity.requireViewById<View>(R.id.brightness_mirror_container) 108 109 val lp = frame.layoutParams as ViewGroup.MarginLayoutParams 110 val horizontalMargin = 111 activityRule.activity.resources.getDimensionPixelSize( 112 R.dimen.notification_side_paddings 113 ) 114 assertThat(lp.leftMargin).isEqualTo(horizontalMargin) 115 assertThat(lp.rightMargin).isEqualTo(horizontalMargin) 116 117 assertThat(frame.systemGestureExclusionRects.size).isEqualTo(1) 118 val exclusion = frame.systemGestureExclusionRects[0] 119 assertThat(exclusion) 120 .isEqualTo(Rect(-horizontalMargin, 0, frame.width + horizontalMargin, frame.height)) 121 } 122 123 @Test testTimeoutnull124 fun testTimeout() { 125 `when`( 126 accessibilityMgr.getRecommendedTimeoutMillis( 127 eq(BrightnessDialog.DIALOG_TIMEOUT_MILLIS), 128 anyInt() 129 ) 130 ) 131 .thenReturn(BrightnessDialog.DIALOG_TIMEOUT_MILLIS) 132 val intent = Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG) 133 intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true) 134 activityRule.launchActivity(intent) 135 136 assertThat(activityRule.activity.isFinishing()).isFalse() 137 138 clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong()) 139 assertThat(activityRule.activity.isFinishing()).isTrue() 140 } 141 142 @Test testRestartTimeoutnull143 fun testRestartTimeout() { 144 `when`( 145 accessibilityMgr.getRecommendedTimeoutMillis( 146 eq(BrightnessDialog.DIALOG_TIMEOUT_MILLIS), 147 anyInt() 148 ) 149 ) 150 .thenReturn(BrightnessDialog.DIALOG_TIMEOUT_MILLIS) 151 val intent = Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG) 152 intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true) 153 activityRule.launchActivity(intent) 154 155 assertThat(activityRule.activity.isFinishing()).isFalse() 156 157 clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong() / 2) 158 // Restart the timeout 159 activityRule.activity.onResume() 160 161 clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong() / 2) 162 // The dialog should not have disappeared yet 163 assertThat(activityRule.activity.isFinishing()).isFalse() 164 165 clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong() / 2) 166 assertThat(activityRule.activity.isFinishing()).isTrue() 167 } 168 169 @Test testNoTimeoutIfNotStartedByBrightnessKeynull170 fun testNoTimeoutIfNotStartedByBrightnessKey() { 171 `when`( 172 accessibilityMgr.getRecommendedTimeoutMillis( 173 eq(BrightnessDialog.DIALOG_TIMEOUT_MILLIS), 174 anyInt() 175 ) 176 ) 177 .thenReturn(BrightnessDialog.DIALOG_TIMEOUT_MILLIS) 178 activityRule.launchActivity(Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG)) 179 180 assertThat(activityRule.activity.isFinishing()).isFalse() 181 182 clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong()) 183 assertThat(activityRule.activity.isFinishing()).isFalse() 184 } 185 186 @OptIn(FlowPreview::class) 187 @FlakyTest(bugId = 326186573) 188 @Test <lambda>null189 fun testFinishOnQSExpanded() = runTest { 190 val isQSExpanded = MutableStateFlow(false) 191 `when`(shadeInteractor.isQsExpanded).thenReturn(isQSExpanded) 192 activityRule.launchActivity(Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG)) 193 194 assertThat(activityRule.activity.isFinishing()).isFalse() 195 196 isQSExpanded.value = true 197 // Observe the activity's state until is it finishing or the timeout is reached, whatever 198 // comes first. This fixes the flakiness seen when using advanceUntilIdle(). 199 activityRule.activity.finishing.timeout(100.milliseconds).takeWhile { !it }.collect {} 200 assertThat(activityRule.activity.isFinishing()).isTrue() 201 } 202 203 class TestDialog( 204 brightnessSliderControllerFactory: BrightnessSliderController.Factory, 205 brightnessControllerFactory: BrightnessController.Factory, 206 mainExecutor: DelayableExecutor, 207 accessibilityMgr: AccessibilityManagerWrapper, 208 shadeInteractor: ShadeInteractor 209 ) : 210 BrightnessDialog( 211 brightnessSliderControllerFactory, 212 brightnessControllerFactory, 213 mainExecutor, 214 accessibilityMgr, 215 shadeInteractor 216 ) { 217 var finishing = MutableStateFlow(false) 218 isFinishingnull219 override fun isFinishing(): Boolean { 220 return finishing.value 221 } 222 requestFinishnull223 override fun requestFinish() { 224 finishing.value = true 225 } 226 } 227 } 228