Merge "Add ConfigurationRepository and BurnInInteractor" into udc-dev

This commit is contained in:
Beverly Tai
2023-05-01 12:49:17 +00:00
committed by Android (Google) Code Review
11 changed files with 714 additions and 3 deletions

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.common.ui.data.repository
import dagger.Binds
import dagger.Module
@Module
interface CommonRepositoryModule {
@Binds fun bindRepository(impl: ConfigurationRepositoryImpl): ConfigurationRepository
}

View File

@@ -0,0 +1,118 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.systemui.common.ui.data.repository
import android.content.Context
import android.content.res.Configuration
import android.view.DisplayInfo
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.wrapper.DisplayUtilsWrapper
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
interface ConfigurationRepository {
/** Called whenever ui mode, theme or configuration has changed. */
val onAnyConfigurationChange: Flow<Unit>
val scaleForResolution: Flow<Float>
fun getResolutionScale(): Float
}
@ExperimentalCoroutinesApi
@SysUISingleton
class ConfigurationRepositoryImpl
@Inject
constructor(
private val configurationController: ConfigurationController,
private val context: Context,
@Application private val scope: CoroutineScope,
private val displayUtils: DisplayUtilsWrapper,
) : ConfigurationRepository {
private val displayInfo = MutableStateFlow(DisplayInfo())
override val onAnyConfigurationChange: Flow<Unit> =
ConflatedCallbackFlow.conflatedCallbackFlow {
val callback =
object : ConfigurationController.ConfigurationListener {
override fun onUiModeChanged() {
sendUpdate("ConfigurationRepository#onUiModeChanged")
}
override fun onThemeChanged() {
sendUpdate("ConfigurationRepository#onThemeChanged")
}
override fun onConfigChanged(newConfig: Configuration) {
sendUpdate("ConfigurationRepository#onConfigChanged")
}
fun sendUpdate(reason: String) {
trySendWithFailureLogging(Unit, reason)
}
}
configurationController.addCallback(callback)
awaitClose { configurationController.removeCallback(callback) }
}
private val configurationChange: Flow<Unit> =
ConflatedCallbackFlow.conflatedCallbackFlow {
val callback =
object : ConfigurationController.ConfigurationListener {
override fun onConfigChanged(newConfig: Configuration) {
trySendWithFailureLogging(Unit, "ConfigurationRepository#onConfigChanged")
}
}
configurationController.addCallback(callback)
awaitClose { configurationController.removeCallback(callback) }
}
override val scaleForResolution: StateFlow<Float> =
configurationChange
.mapLatest { getResolutionScale() }
.distinctUntilChanged()
.stateIn(scope, SharingStarted.WhileSubscribed(), getResolutionScale())
override fun getResolutionScale(): Float {
context.display.getDisplayInfo(displayInfo.value)
val maxDisplayMode =
displayUtils.getMaximumResolutionDisplayMode(displayInfo.value.supportedModes)
maxDisplayMode?.let {
val scaleFactor =
displayUtils.getPhysicalPixelDisplaySizeRatio(
maxDisplayMode.physicalWidth,
maxDisplayMode.physicalHeight,
displayInfo.value.naturalWidth,
displayInfo.value.naturalHeight
)
return if (scaleFactor == Float.POSITIVE_INFINITY) 1f else scaleFactor
}
return 1f
}
}

View File

@@ -39,6 +39,7 @@ import com.android.systemui.biometrics.dagger.BiometricsModule;
import com.android.systemui.biometrics.dagger.UdfpsModule;
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
import com.android.systemui.common.ui.data.repository.CommonRepositoryModule;
import com.android.systemui.complication.dagger.ComplicationComponent;
import com.android.systemui.controls.dagger.ControlsModule;
import com.android.systemui.dagger.qualifiers.Main;
@@ -155,6 +156,7 @@ import javax.inject.Named;
ClipboardOverlayModule.class,
ClockInfoModule.class,
ClockRegistryModule.class,
CommonRepositoryModule.class,
ConnectivityModule.class,
CoroutinesModule.class,
DreamModule.class,

View File

@@ -24,4 +24,8 @@ class BurnInHelperWrapper @Inject constructor() {
fun burnInOffset(amplitude: Int, xAxis: Boolean): Int {
return getBurnInOffset(amplitude, xAxis)
}
fun burnInProgressOffset(): Float {
return getBurnInProgressOffset()
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.android.systemui.keyguard.domain.interactor
import android.content.Context
import androidx.annotation.DimenRes
import com.android.systemui.R
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
/** Encapsulates business-logic related to Ambient Display burn-in offsets. */
@ExperimentalCoroutinesApi
@SysUISingleton
class BurnInInteractor
@Inject
constructor(
private val context: Context,
private val burnInHelperWrapper: BurnInHelperWrapper,
@Application private val scope: CoroutineScope,
private val configurationRepository: ConfigurationRepository,
private val systemClock: SystemClock,
) {
private val _dozeTimeTick = MutableStateFlow<Long>(0)
val dozeTimeTick: StateFlow<Long> = _dozeTimeTick.asStateFlow()
val udfpsBurnInXOffset: StateFlow<Int> =
burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_x, isXAxis = true)
val udfpsBurnInYOffset: StateFlow<Int> =
burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_y, isXAxis = false)
val udfpsBurnInProgress: StateFlow<Float> =
dozeTimeTick
.mapLatest { burnInHelperWrapper.burnInProgressOffset() }
.stateIn(scope, SharingStarted.Lazily, burnInHelperWrapper.burnInProgressOffset())
fun dozeTimeTick() {
_dozeTimeTick.value = systemClock.uptimeMillis()
}
/**
* Use for max burn-in offsets that are NOT specified in pixels. This flow will recalculate the
* max burn-in offset on any configuration changes. If the max burn-in offset is specified in
* pixels, use [burnInOffsetDefinedInPixels].
*/
private fun burnInOffset(
@DimenRes maxBurnInOffsetResourceId: Int,
isXAxis: Boolean,
): StateFlow<Int> {
return configurationRepository.onAnyConfigurationChange
.flatMapLatest {
val maxBurnInOffsetPixels =
context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
dozeTimeTick.mapLatest { calculateOffset(maxBurnInOffsetPixels, isXAxis) }
}
.stateIn(
scope,
SharingStarted.Lazily,
calculateOffset(
context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId),
isXAxis,
)
)
}
/**
* Use for max burn-in offBurn-in offsets that ARE specified in pixels. This flow will apply the
* a scale for any resolution changes. If the max burn-in offset is specified in dp, use
* [burnInOffset].
*/
private fun burnInOffsetDefinedInPixels(
@DimenRes maxBurnInOffsetResourceId: Int,
isXAxis: Boolean,
): StateFlow<Int> {
return configurationRepository.scaleForResolution
.flatMapLatest { scale ->
val maxBurnInOffsetPixels =
context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
dozeTimeTick.mapLatest { calculateOffset(maxBurnInOffsetPixels, isXAxis, scale) }
}
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
calculateOffset(
context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId),
isXAxis,
configurationRepository.getResolutionScale(),
)
)
}
private fun calculateOffset(
maxBurnInOffsetPixels: Int,
isXAxis: Boolean,
scale: Float = 1f
): Int {
return (burnInHelperWrapper.burnInOffset(maxBurnInOffsetPixels, isXAxis) * scale).toInt()
}
}

View File

@@ -37,6 +37,7 @@ import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor;
import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -56,10 +57,12 @@ import java.util.ArrayList;
import javax.inject.Inject;
import kotlinx.coroutines.ExperimentalCoroutinesApi;
/**
* Implementation of DozeHost for SystemUI.
*/
@SysUISingleton
@ExperimentalCoroutinesApi @SysUISingleton
public final class DozeServiceHost implements DozeHost {
private static final String TAG = "DozeServiceHost";
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
@@ -89,6 +92,7 @@ public final class DozeServiceHost implements DozeHost {
private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
private final AuthController mAuthController;
private final NotificationIconAreaController mNotificationIconAreaController;
private final BurnInInteractor mBurnInInteractor;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private ShadeViewController mNotificationPanel;
private View mAmbientIndicationContainer;
@@ -110,7 +114,8 @@ public final class DozeServiceHost implements DozeHost {
NotificationShadeWindowController notificationShadeWindowController,
NotificationWakeUpCoordinator notificationWakeUpCoordinator,
AuthController authController,
NotificationIconAreaController notificationIconAreaController) {
NotificationIconAreaController notificationIconAreaController,
BurnInInteractor burnInInteractor) {
super();
mDozeLog = dozeLog;
mPowerManager = powerManager;
@@ -129,6 +134,7 @@ public final class DozeServiceHost implements DozeHost {
mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
mAuthController = authController;
mNotificationIconAreaController = notificationIconAreaController;
mBurnInInteractor = burnInInteractor;
mHeadsUpManagerPhone.addListener(mOnHeadsUpChangedListener);
}
@@ -304,6 +310,7 @@ public final class DozeServiceHost implements DozeHost {
if (mAmbientIndicationContainer instanceof DozeReceiver) {
((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick();
}
mBurnInInteractor.dozeTimeTick();
}
@Override

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.util.wrapper
import android.util.DisplayUtils
import android.view.Display
import javax.inject.Inject
/** Injectable wrapper around `DisplayUtils` functions */
class DisplayUtilsWrapper @Inject constructor() {
fun getPhysicalPixelDisplaySizeRatio(
physicalWidth: Int,
physicalHeight: Int,
currentWidth: Int,
currentHeight: Int
): Float {
return DisplayUtils.getPhysicalPixelDisplaySizeRatio(
physicalWidth,
physicalHeight,
currentWidth,
currentHeight
)
}
fun getMaximumResolutionDisplayMode(modes: Array<Display.Mode>?): Display.Mode? {
return DisplayUtils.getMaximumResolutionDisplayMode(modes)
}
}

View File

@@ -0,0 +1,196 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.systemui.common.ui.data.repository
import android.content.res.Configuration
import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.wrapper.DisplayUtilsWrapper
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@ExperimentalCoroutinesApi
@SmallTest
@RunWith(AndroidJUnit4::class)
class ConfigurationRepositoryImplTest : SysuiTestCase() {
private var displaySizeRatio = 0f
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var displayUtils: DisplayUtilsWrapper
private lateinit var testScope: TestScope
private lateinit var underTest: ConfigurationRepositoryImpl
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
setPhysicalPixelDisplaySizeRatio(displaySizeRatio)
testScope = TestScope()
underTest =
ConfigurationRepositoryImpl(
configurationController,
context,
testScope.backgroundScope,
displayUtils,
)
}
@Test
fun onAnyConfigurationChange_updatesOnUiModeChanged() =
testScope.runTest {
val lastAnyConfigurationChange by collectLastValue(underTest.onAnyConfigurationChange)
assertThat(lastAnyConfigurationChange).isNull()
val configurationCallback = withArgCaptor {
verify(configurationController).addCallback(capture())
}
configurationCallback.onUiModeChanged()
runCurrent()
assertThat(lastAnyConfigurationChange).isNotNull()
}
@Test
fun onAnyConfigurationChange_updatesOnThemeChanged() =
testScope.runTest {
val lastAnyConfigurationChange by collectLastValue(underTest.onAnyConfigurationChange)
assertThat(lastAnyConfigurationChange).isNull()
val configurationCallback = withArgCaptor {
verify(configurationController).addCallback(capture())
}
configurationCallback.onThemeChanged()
runCurrent()
assertThat(lastAnyConfigurationChange).isNotNull()
}
@Test
fun onAnyConfigurationChange_updatesOnConfigChanged() =
testScope.runTest {
val lastAnyConfigurationChange by collectLastValue(underTest.onAnyConfigurationChange)
assertThat(lastAnyConfigurationChange).isNull()
val configurationCallback = withArgCaptor {
verify(configurationController).addCallback(capture())
}
configurationCallback.onConfigChanged(mock(Configuration::class.java))
runCurrent()
assertThat(lastAnyConfigurationChange).isNotNull()
}
@Test
fun onResolutionScale_updatesOnConfigurationChange() =
testScope.runTest {
val scaleForResolution by collectLastValue(underTest.scaleForResolution)
assertThat(scaleForResolution).isEqualTo(displaySizeRatio)
val configurationCallback = withArgCaptor {
verify(configurationController).addCallback(capture())
}
setPhysicalPixelDisplaySizeRatio(2f)
configurationCallback.onConfigChanged(mock(Configuration::class.java))
assertThat(scaleForResolution).isEqualTo(displaySizeRatio)
setPhysicalPixelDisplaySizeRatio(.21f)
configurationCallback.onConfigChanged(mock(Configuration::class.java))
assertThat(scaleForResolution).isEqualTo(displaySizeRatio)
}
@Test
fun onResolutionScale_nullMaxResolution() =
testScope.runTest {
val scaleForResolution by collectLastValue(underTest.scaleForResolution)
runCurrent()
givenNullMaxResolutionDisplayMode()
val configurationCallback = withArgCaptor {
verify(configurationController).addCallback(capture())
}
configurationCallback.onConfigChanged(mock(Configuration::class.java))
assertThat(scaleForResolution).isEqualTo(1f)
}
@Test
fun getResolutionScale_nullMaxResolutionDisplayMode() {
givenNullMaxResolutionDisplayMode()
assertThat(underTest.getResolutionScale()).isEqualTo(1f)
}
@Test
fun getResolutionScale_infiniteDisplayRatios() {
setPhysicalPixelDisplaySizeRatio(Float.POSITIVE_INFINITY)
assertThat(underTest.getResolutionScale()).isEqualTo(1f)
}
@Test
fun getResolutionScale_differentDisplayRatios() {
setPhysicalPixelDisplaySizeRatio(.5f)
assertThat(underTest.getResolutionScale()).isEqualTo(displaySizeRatio)
setPhysicalPixelDisplaySizeRatio(.283f)
assertThat(underTest.getResolutionScale()).isEqualTo(displaySizeRatio)
setPhysicalPixelDisplaySizeRatio(3.58f)
assertThat(underTest.getResolutionScale()).isEqualTo(displaySizeRatio)
setPhysicalPixelDisplaySizeRatio(0f)
assertThat(underTest.getResolutionScale()).isEqualTo(displaySizeRatio)
setPhysicalPixelDisplaySizeRatio(1f)
assertThat(underTest.getResolutionScale()).isEqualTo(displaySizeRatio)
}
private fun givenNullMaxResolutionDisplayMode() {
whenever(displayUtils.getMaximumResolutionDisplayMode(any())).thenReturn(null)
}
private fun setPhysicalPixelDisplaySizeRatio(ratio: Float) {
displaySizeRatio = ratio
whenever(displayUtils.getMaximumResolutionDisplayMode(any()))
.thenReturn(Display.Mode(0, 0, 0, 90f))
whenever(
displayUtils.getPhysicalPixelDisplaySizeRatio(
anyInt(),
anyInt(),
anyInt(),
anyInt()
)
)
.thenReturn(ratio)
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.systemui.common.ui.data.repository
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
class FakeConfigurationRepository : ConfigurationRepository {
private val onAnyConfigurationChangeChannel = Channel<Unit>()
override val onAnyConfigurationChange: Flow<Unit> =
onAnyConfigurationChangeChannel.receiveAsFlow()
private val _scaleForResolution = MutableStateFlow(1f)
override val scaleForResolution: Flow<Float> = _scaleForResolution.asStateFlow()
suspend fun onAnyConfigurationChange() {
onAnyConfigurationChangeChannel.send(Unit)
}
fun setScaleForResolution(scale: Float) {
_scaleForResolution.value = scale
}
override fun getResolutionScale(): Float {
return _scaleForResolution.value
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.android.systemui.keyguard.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@ExperimentalCoroutinesApi
@SmallTest
@RunWith(AndroidJUnit4::class)
class BurnInInteractorTest : SysuiTestCase() {
private val burnInOffset = 7
private var burnInProgress = 0f
@Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
private lateinit var configurationRepository: FakeConfigurationRepository
private lateinit var systemClock: FakeSystemClock
private lateinit var testScope: TestScope
private lateinit var underTest: BurnInInteractor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
configurationRepository = FakeConfigurationRepository()
systemClock = FakeSystemClock()
whenever(burnInHelperWrapper.burnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset)
setBurnInProgress(.65f)
testScope = TestScope()
underTest =
BurnInInteractor(
context,
burnInHelperWrapper,
testScope.backgroundScope,
configurationRepository,
systemClock,
)
}
@Test
fun dozeTimeTick_updatesOnDozeTimeTick() =
testScope.runTest {
// Initial state set to 0
val lastDozeTimeTick by collectLastValue(underTest.dozeTimeTick)
assertEquals(0L, lastDozeTimeTick)
// WHEN dozeTimeTick updated
incrementUptimeMillis()
underTest.dozeTimeTick()
// THEN listeners were updated to the latest uptime millis
assertThat(systemClock.uptimeMillis()).isEqualTo(lastDozeTimeTick)
}
@Test
fun udfpsBurnInOffset_updatesOnResolutionScaleChange() =
testScope.runTest {
val udfpsBurnInOffsetX by collectLastValue(underTest.udfpsBurnInXOffset)
val udfpsBurnInOffsetY by collectLastValue(underTest.udfpsBurnInYOffset)
assertThat(udfpsBurnInOffsetX).isEqualTo(burnInOffset)
assertThat(udfpsBurnInOffsetY).isEqualTo(burnInOffset)
configurationRepository.setScaleForResolution(3f)
assertThat(udfpsBurnInOffsetX).isEqualTo(burnInOffset * 3)
assertThat(udfpsBurnInOffsetY).isEqualTo(burnInOffset * 3)
configurationRepository.setScaleForResolution(.5f)
assertThat(udfpsBurnInOffsetX).isEqualTo(burnInOffset / 2)
assertThat(udfpsBurnInOffsetY).isEqualTo(burnInOffset / 2)
}
@Test
fun udfpsBurnInProgress_updatesOnDozeTimeTick() =
testScope.runTest {
val udfpsBurnInProgress by collectLastValue(underTest.udfpsBurnInProgress)
assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress)
setBurnInProgress(.88f)
incrementUptimeMillis()
underTest.dozeTimeTick()
assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress)
setBurnInProgress(.92f)
incrementUptimeMillis()
underTest.dozeTimeTick()
assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress)
setBurnInProgress(.32f)
incrementUptimeMillis()
underTest.dozeTimeTick()
assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress)
}
private fun incrementUptimeMillis() {
systemClock.setUptimeMillis(systemClock.uptimeMillis() + 5)
}
private fun setBurnInProgress(progress: Float) {
burnInProgress = progress
whenever(burnInHelperWrapper.burnInProgressOffset()).thenReturn(burnInProgress)
}
}

View File

@@ -41,6 +41,7 @@ import com.android.systemui.biometrics.AuthController;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor;
import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -92,6 +93,7 @@ public class DozeServiceHostTest extends SysuiTestCase {
@Mock private BiometricUnlockController mBiometricUnlockController;
@Mock private AuthController mAuthController;
@Mock private DozeHost.Callback mCallback;
@Mock private BurnInInteractor mBurnInInteractor;
@Before
public void setup() {
@@ -102,7 +104,8 @@ public class DozeServiceHostTest extends SysuiTestCase {
() -> mAssistManager, mDozeScrimController,
mKeyguardUpdateMonitor, mPulseExpansionHandler,
mNotificationShadeWindowController, mNotificationWakeUpCoordinator,
mAuthController, mNotificationIconAreaController);
mAuthController, mNotificationIconAreaController,
mBurnInInteractor);
mDozeServiceHost.initialize(
mCentralSurfaces,
@@ -213,4 +216,12 @@ public class DozeServiceHostTest extends SysuiTestCase {
assertFalse(mDozeServiceHost.isPulsePending());
verify(mDozeScrimController).pulseOutNow();
}
@Test
public void dozeTimeTickSentTBurnInInteractor() {
// WHEN dozeTimeTick
mDozeServiceHost.dozeTimeTick();
// THEN burnInInteractor's dozeTimeTick is updated
verify(mBurnInInteractor).dozeTimeTick();
}
}