Merge "Add ConfigurationRepository and BurnInInteractor" into udc-dev
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -24,4 +24,8 @@ class BurnInHelperWrapper @Inject constructor() {
|
||||
fun burnInOffset(amplitude: Int, xAxis: Boolean): Int {
|
||||
return getBurnInOffset(amplitude, xAxis)
|
||||
}
|
||||
|
||||
fun burnInProgressOffset(): Float {
|
||||
return getBurnInProgressOffset()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user