Merge "Creates an interface, implementation, and fake for UiModeManager" into main
diff --git a/src/com/android/customization/model/mode/DarkModeSectionController.java b/src/com/android/customization/model/mode/DarkModeSectionController.java
index 3da7ae9..3fd9bc4 100644
--- a/src/com/android/customization/model/mode/DarkModeSectionController.java
+++ b/src/com/android/customization/model/mode/DarkModeSectionController.java
@@ -20,7 +20,6 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.PowerManager.ACTION_POWER_SAVE_MODE_CHANGED;
-import android.app.UiModeManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -43,6 +42,7 @@
import com.android.customization.picker.mode.DarkModeSectionView;
import com.android.themepicker.R;
import com.android.wallpaper.model.CustomizationSectionController;
+import com.android.wallpaper.system.UiModeManagerWrapper;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -61,18 +61,21 @@
private Context mContext;
private DarkModeSectionView mDarkModeSectionView;
private final DarkModeSnapshotRestorer mSnapshotRestorer;
+ private final UiModeManagerWrapper mUiModeManager;
private final ThemesUserEventLogger mThemesUserEventLogger;
public DarkModeSectionController(
Context context,
Lifecycle lifecycle,
DarkModeSnapshotRestorer snapshotRestorer,
+ UiModeManagerWrapper uiModeManager,
ThemesUserEventLogger themesUserEventLogger) {
mContext = context;
mLifecycle = lifecycle;
mPowerManager = context.getSystemService(PowerManager.class);
mLifecycle.addObserver(this);
mSnapshotRestorer = snapshotRestorer;
+ mUiModeManager = uiModeManager;
mThemesUserEventLogger = themesUserEventLogger;
}
@@ -134,13 +137,12 @@
disableToast.show();
return;
}
- UiModeManager uiModeManager = context.getSystemService(UiModeManager.class);
int shortDelay = context.getResources().getInteger(android.R.integer.config_shortAnimTime);
new Handler(Looper.getMainLooper()).postDelayed(
() -> {
mDarkModeSectionView.announceForAccessibility(
context.getString(R.string.mode_changed));
- uiModeManager.setNightModeActivated(viewActivated);
+ mUiModeManager.setNightModeActivated(viewActivated);
mThemesUserEventLogger.logDarkThemeApplied(viewActivated);
mSnapshotRestorer.store(viewActivated);
},
diff --git a/src/com/android/customization/model/mode/DarkModeSnapshotRestorer.kt b/src/com/android/customization/model/mode/DarkModeSnapshotRestorer.kt
index 93bd0bf..69147bf 100644
--- a/src/com/android/customization/model/mode/DarkModeSnapshotRestorer.kt
+++ b/src/com/android/customization/model/mode/DarkModeSnapshotRestorer.kt
@@ -17,13 +17,13 @@
package com.android.customization.model.mode
-import android.app.UiModeManager
import android.content.Context
import android.content.res.Configuration
import androidx.annotation.VisibleForTesting
import com.android.wallpaper.picker.undo.domain.interactor.SnapshotRestorer
import com.android.wallpaper.picker.undo.domain.interactor.SnapshotStore
import com.android.wallpaper.picker.undo.shared.model.RestorableSnapshot
+import com.android.wallpaper.system.UiModeManagerWrapper
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
@@ -37,7 +37,7 @@
constructor(
context: Context,
- manager: UiModeManager,
+ manager: UiModeManagerWrapper,
backgroundDispatcher: CoroutineDispatcher,
) : this(
backgroundDispatcher = backgroundDispatcher,
diff --git a/src/com/android/customization/module/ThemePickerInjector.kt b/src/com/android/customization/module/ThemePickerInjector.kt
index 672c9c5..1491fcc 100644
--- a/src/com/android/customization/module/ThemePickerInjector.kt
+++ b/src/com/android/customization/module/ThemePickerInjector.kt
@@ -16,7 +16,6 @@
package com.android.customization.module
import android.app.Activity
-import android.app.UiModeManager
import android.app.WallpaperColors
import android.app.WallpaperManager
import android.content.Context
@@ -144,7 +143,7 @@
wallpaperColorsRepository = getWallpaperColorsRepository(),
),
getKeyguardQuickAffordancePickerViewModelFactory(appContext),
- getColorContrastSectionViewModelFactory(appContext),
+ getColorContrastSectionViewModelFactory(),
getNotificationSectionViewModelFactory(appContext),
getFlags(),
getClockCarouselViewModelFactory(
@@ -236,27 +235,22 @@
.also { wallpaperInteractor = it }
}
- private fun getColorContrastSectionInteractorImpl(
- context: Context
- ): ColorContrastSectionInteractor {
+ private fun getColorContrastSectionInteractorImpl(): ColorContrastSectionInteractor {
return ColorContrastSectionInteractor(
- ColorContrastSectionRepository(context, bgDispatcher),
+ ColorContrastSectionRepository(uiModeManager, bgDispatcher),
)
}
- fun getColorContrastSectionInteractor(context: Context): ColorContrastSectionInteractor {
+ fun getColorContrastSectionInteractor(): ColorContrastSectionInteractor {
return colorContrastSectionInteractor
- ?: getColorContrastSectionInteractorImpl(context).also {
- colorContrastSectionInteractor = it
- }
+ ?: getColorContrastSectionInteractorImpl().also { colorContrastSectionInteractor = it }
}
- fun getColorContrastSectionViewModelFactory(
- context: Context
- ): ColorContrastSectionViewModel.Factory {
+ fun getColorContrastSectionViewModelFactory(): ColorContrastSectionViewModel.Factory {
return colorContrastSectionViewModelFactory
- ?: ColorContrastSectionViewModel.Factory(getColorContrastSectionInteractor(context))
- .also { colorContrastSectionViewModelFactory = it }
+ ?: ColorContrastSectionViewModel.Factory(getColorContrastSectionInteractor()).also {
+ colorContrastSectionViewModelFactory = it
+ }
}
override fun getKeyguardQuickAffordancePickerInteractor(
@@ -495,7 +489,7 @@
return darkModeSnapshotRestorer
?: DarkModeSnapshotRestorer(
context = appContext,
- manager = appContext.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager,
+ manager = uiModeManager,
backgroundDispatcher = bgDispatcher,
)
.also { darkModeSnapshotRestorer = it }
diff --git a/src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt b/src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt
index d6c728e..bb0957c 100644
--- a/src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt
+++ b/src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt
@@ -204,6 +204,7 @@
context,
lifecycle,
injector.getDarkModeSnapshotRestorer(requireContext()),
+ injector.uiModeManager,
injector.getUserEventLogger(),
)
.createView(requireContext())
diff --git a/src/com/android/customization/picker/settings/data/repository/ColorContrastSectionRepository.kt b/src/com/android/customization/picker/settings/data/repository/ColorContrastSectionRepository.kt
index b44b052..1615486 100644
--- a/src/com/android/customization/picker/settings/data/repository/ColorContrastSectionRepository.kt
+++ b/src/com/android/customization/picker/settings/data/repository/ColorContrastSectionRepository.kt
@@ -17,8 +17,7 @@
package com.android.customization.picker.settings.data.repository
import android.app.UiModeManager
-import android.content.Context
-import android.content.Context.UI_MODE_SERVICE
+import com.android.wallpaper.system.UiModeManagerWrapper
import java.util.concurrent.Executor
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.asExecutor
@@ -26,13 +25,11 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
-open class ColorContrastSectionRepository(
- private val context: Context,
+class ColorContrastSectionRepository(
+ uiModeManager: UiModeManagerWrapper,
private val bgDispatcher: CoroutineDispatcher
) {
- var uiModeManager =
- context.applicationContext.getSystemService(UI_MODE_SERVICE) as UiModeManager?
- open var contrast: Flow<Float> = callbackFlow {
+ var contrast: Flow<Float> = callbackFlow {
val executor: Executor = bgDispatcher.asExecutor()
val listener =
UiModeManager.ContrastChangeListener { contrast ->
@@ -41,13 +38,13 @@
}
// Emit the current contrast value immediately
- uiModeManager?.contrast?.let { currentContrast -> trySend(currentContrast) }
+ uiModeManager.getContrast()?.let { currentContrast -> trySend(currentContrast) }
- uiModeManager?.addContrastChangeListener(executor, listener)
+ uiModeManager.addContrastChangeListener(executor, listener)
awaitClose {
// Unregister the listener when the flow collection is cancelled or no longer in use
- uiModeManager?.removeContrastChangeListener(listener)
+ uiModeManager.removeContrastChangeListener(listener)
}
}
}
diff --git a/src/com/android/customization/picker/settings/domain/interactor/ColorContrastSectionInteractor.kt b/src/com/android/customization/picker/settings/domain/interactor/ColorContrastSectionInteractor.kt
index 91c0fa5..ff5cd20 100644
--- a/src/com/android/customization/picker/settings/domain/interactor/ColorContrastSectionInteractor.kt
+++ b/src/com/android/customization/picker/settings/domain/interactor/ColorContrastSectionInteractor.kt
@@ -19,8 +19,8 @@
import com.android.customization.picker.settings.data.repository.ColorContrastSectionRepository
import kotlinx.coroutines.flow.Flow
-open class ColorContrastSectionInteractor(
- private val colorContrastSectionRepository: ColorContrastSectionRepository
+class ColorContrastSectionInteractor(
+ colorContrastSectionRepository: ColorContrastSectionRepository
) {
- open val contrast: Flow<Float> = colorContrastSectionRepository.contrast
+ val contrast: Flow<Float> = colorContrastSectionRepository.contrast
}
diff --git a/src_override/com/android/wallpaper/module/AppModule.kt b/src_override/com/android/wallpaper/module/AppModule.kt
index 8de80d6..3524151 100644
--- a/src_override/com/android/wallpaper/module/AppModule.kt
+++ b/src_override/com/android/wallpaper/module/AppModule.kt
@@ -26,6 +26,8 @@
import com.android.wallpaper.module.logging.UserEventLogger
import com.android.wallpaper.picker.preview.data.util.DefaultLiveWallpaperDownloader
import com.android.wallpaper.picker.preview.data.util.LiveWallpaperDownloader
+import com.android.wallpaper.system.UiModeManagerImpl
+import com.android.wallpaper.system.UiModeManagerWrapper
import com.android.wallpaper.util.converter.DefaultWallpaperModelFactory
import com.android.wallpaper.util.converter.WallpaperModelFactory
import dagger.Binds
@@ -61,6 +63,8 @@
impl: DefaultLiveWallpaperDownloader
): LiveWallpaperDownloader
+ @Binds @Singleton abstract fun bindUiModeManager(impl: UiModeManagerImpl): UiModeManagerWrapper
+
companion object {
@Provides
@Singleton
diff --git a/tests/module/src/com/android/customization/TestModule.kt b/tests/module/src/com/android/customization/TestModule.kt
index 4600434..d48671b 100644
--- a/tests/module/src/com/android/customization/TestModule.kt
+++ b/tests/module/src/com/android/customization/TestModule.kt
@@ -16,6 +16,8 @@
import com.android.wallpaper.module.logging.UserEventLogger
import com.android.wallpaper.picker.preview.data.util.DefaultLiveWallpaperDownloader
import com.android.wallpaper.picker.preview.data.util.LiveWallpaperDownloader
+import com.android.wallpaper.system.UiModeManagerWrapper
+import com.android.wallpaper.testing.FakeUiModeManager
import com.android.wallpaper.testing.TestInjector
import com.android.wallpaper.testing.TestWallpaperPreferences
import com.android.wallpaper.util.converter.DefaultWallpaperModelFactory
@@ -78,6 +80,8 @@
impl: DefaultLiveWallpaperDownloader
): LiveWallpaperDownloader
+ @Binds @Singleton abstract fun bindUiModeManager(impl: FakeUiModeManager): UiModeManagerWrapper
+
companion object {
@Provides
@Singleton
diff --git a/tests/robotests/module b/tests/robotests/module
new file mode 120000
index 0000000..ad3c990
--- /dev/null
+++ b/tests/robotests/module
@@ -0,0 +1 @@
+../module
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/customization/model/picker/settings/data/repository/ColorContrastSectionRepositoryTest.kt b/tests/robotests/src/com/android/customization/model/picker/settings/data/repository/ColorContrastSectionRepositoryTest.kt
index 01c0a62..f6a3a22 100644
--- a/tests/robotests/src/com/android/customization/model/picker/settings/data/repository/ColorContrastSectionRepositoryTest.kt
+++ b/tests/robotests/src/com/android/customization/model/picker/settings/data/repository/ColorContrastSectionRepositoryTest.kt
@@ -16,11 +16,9 @@
package com.android.customization.model.picker.settings.data.repository
-import android.app.UiModeManager
-import android.content.Context
-import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.SmallTest
import com.android.customization.picker.settings.data.repository.ColorContrastSectionRepository
+import com.android.wallpaper.testing.FakeUiModeManager
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
@@ -30,26 +28,20 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
-import org.mockito.kotlin.any
-import org.mockito.kotlin.argumentCaptor
import org.robolectric.RobolectricTestRunner
@SmallTest
@RunWith(RobolectricTestRunner::class)
class ColorContrastSectionRepositoryTest {
+ private val uiModeManager = FakeUiModeManager()
private lateinit var underTest: ColorContrastSectionRepository
- private lateinit var context: Context
private lateinit var bgDispatcher: TestCoroutineDispatcher
@Before
fun setUp() {
- context = ApplicationProvider.getApplicationContext<Context>()
bgDispatcher = TestCoroutineDispatcher()
- underTest = ColorContrastSectionRepository(context, bgDispatcher)
+ underTest = ColorContrastSectionRepository(uiModeManager, bgDispatcher)
}
@Test
@@ -60,35 +52,21 @@
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun contrastFlowEmitsValues() = runBlockingTest {
- val mockUiModeManager = mock(UiModeManager::class.java)
- val contrastValues = listOf(0.5f, 0.7f, 0.8f)
-
- // Stub the initial contrast value before the flow starts collecting
- `when`(mockUiModeManager.contrast).thenReturn(contrastValues[0])
-
- // Assign the mockUiModeManager to the repository's uiModeManager
- underTest.uiModeManager = mockUiModeManager
-
- // Create a collector for the flow
+ val nextContrastValues = listOf(0.5f, 0.7f, 0.8f)
+ // Set up a flow to collect all contrast values
val flowCollector = mutableListOf<Float>()
-
// Start collecting values from the flow
val job = launch { underTest.contrast.collect { flowCollector.add(it) } }
- // Capture the ContrastChangeListener
- val listenerCaptor = argumentCaptor<UiModeManager.ContrastChangeListener>()
- verify(mockUiModeManager).addContrastChangeListener(any(), listenerCaptor.capture())
+ nextContrastValues.forEach { uiModeManager.setContrast(it) }
- // Simulate contrast changes after the initial value has been emitted
- contrastValues.drop(1).forEach { newValue ->
- listenerCaptor.firstValue.onContrastChanged(newValue)
- }
-
- assertThat(flowCollector).containsExactlyElementsIn(contrastValues)
-
+ // Ignore the first contrast value from constructing the repository
+ val collectedValues = flowCollector.drop(1)
+ assertThat(collectedValues).containsExactlyElementsIn(nextContrastValues)
job.cancel()
}
+ @OptIn(ExperimentalCoroutinesApi::class)
@After
fun tearDown() {
bgDispatcher.cleanupTestCoroutines()
diff --git a/tests/robotests/src/com/android/customization/model/picker/settings/domain/interactor/ColorContrastSectionInteractorTest.kt b/tests/robotests/src/com/android/customization/model/picker/settings/domain/interactor/ColorContrastSectionInteractorTest.kt
index b274b63..9936928 100644
--- a/tests/robotests/src/com/android/customization/model/picker/settings/domain/interactor/ColorContrastSectionInteractorTest.kt
+++ b/tests/robotests/src/com/android/customization/model/picker/settings/domain/interactor/ColorContrastSectionInteractorTest.kt
@@ -19,27 +19,26 @@
import androidx.test.filters.SmallTest
import com.android.customization.picker.settings.data.repository.ColorContrastSectionRepository
import com.android.customization.picker.settings.domain.interactor.ColorContrastSectionInteractor
+import com.android.wallpaper.testing.FakeUiModeManager
import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.runBlockingTest
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.whenever
import org.robolectric.RobolectricTestRunner
@SmallTest
@RunWith(RobolectricTestRunner::class)
class ColorContrastSectionInteractorTest {
-
@Test
fun contrastEmitCorrectValuesFromRepository() = runBlockingTest {
- val mockRepository: ColorContrastSectionRepository = mock()
+ val bgDispatcher = TestCoroutineDispatcher()
+ val uiModeManager = FakeUiModeManager()
+ val repository = ColorContrastSectionRepository(uiModeManager, bgDispatcher)
val expectedContrast = 1.5f
- whenever(mockRepository.contrast).thenReturn(flowOf(expectedContrast))
- val interactor = ColorContrastSectionInteractor(mockRepository)
+ val interactor = ColorContrastSectionInteractor(repository)
+ uiModeManager.setContrast(expectedContrast)
val result = interactor.contrast.first()
diff --git a/tests/robotests/src/com/android/customization/model/picker/settings/ui/viewmodel/ColorContrastSectionViewModelTest.kt b/tests/robotests/src/com/android/customization/model/picker/settings/ui/viewmodel/ColorContrastSectionViewModelTest.kt
index b77c017..0910697 100644
--- a/tests/robotests/src/com/android/customization/model/picker/settings/ui/viewmodel/ColorContrastSectionViewModelTest.kt
+++ b/tests/robotests/src/com/android/customization/model/picker/settings/ui/viewmodel/ColorContrastSectionViewModelTest.kt
@@ -16,49 +16,42 @@
package com.android.customization.model.picker.settings.ui.viewmodel
-import android.content.Context
-import androidx.test.core.app.ApplicationProvider
+import com.android.customization.picker.settings.data.repository.ColorContrastSectionRepository
import com.android.customization.picker.settings.domain.interactor.ColorContrastSectionInteractor
import com.android.customization.picker.settings.ui.viewmodel.ColorContrastSectionDataViewModel
import com.android.customization.picker.settings.ui.viewmodel.ColorContrastSectionViewModel
import com.android.themepicker.R
import com.android.wallpaper.picker.common.icon.ui.viewmodel.Icon
import com.android.wallpaper.picker.common.text.ui.viewmodel.Text
+import com.android.wallpaper.testing.FakeUiModeManager
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.runBlockingTest
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.mock
import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class ColorContrastSectionViewModelTest {
- private lateinit var bgDispatcher: TestCoroutineDispatcher
- private lateinit var context: Context
+ private val uiModeManager = FakeUiModeManager()
+ private val bgDispatcher = TestCoroutineDispatcher()
+
private lateinit var viewModel: ColorContrastSectionViewModel
- private lateinit var interactor: ColorContrastSectionInteractor
@Before
fun setUp() {
- context = ApplicationProvider.getApplicationContext<Context>()
- bgDispatcher = TestCoroutineDispatcher()
+ val repository = ColorContrastSectionRepository(uiModeManager, bgDispatcher)
+ val factory =
+ ColorContrastSectionViewModel.Factory(ColorContrastSectionInteractor(repository))
+ viewModel = factory.create(ColorContrastSectionViewModel::class.java)
}
@Test
fun summaryEmitsCorrectDataValueForStandard() = runBlockingTest {
- interactor = mock {
- on { contrast } doReturn
- flowOf(ColorContrastSectionViewModel.ContrastValue.STANDARD.value)
- }
- val factory = ColorContrastSectionViewModel.Factory(interactor)
- viewModel = factory.create(ColorContrastSectionViewModel::class.java)
-
+ uiModeManager.setContrast(ColorContrastSectionViewModel.ContrastValue.STANDARD.value)
val expected =
ColorContrastSectionDataViewModel(
Text.Resource(R.string.color_contrast_default_title),
@@ -66,18 +59,13 @@
)
val result = viewModel.summary.first()
+
assertEquals(expected, result)
}
@Test
fun summaryEmitsCorrectDataValueForMedium() = runBlockingTest {
- interactor = mock {
- on { contrast } doReturn
- flowOf(ColorContrastSectionViewModel.ContrastValue.MEDIUM.value)
- }
- val factory = ColorContrastSectionViewModel.Factory(interactor)
- viewModel = factory.create(ColorContrastSectionViewModel::class.java)
-
+ uiModeManager.setContrast(ColorContrastSectionViewModel.ContrastValue.MEDIUM.value)
val expected =
ColorContrastSectionDataViewModel(
Text.Resource(R.string.color_contrast_medium_title),
@@ -85,17 +73,13 @@
)
val result = viewModel.summary.first()
+
assertEquals(expected, result)
}
@Test
fun summaryEmitsCorrectDataValueForHigh() = runBlockingTest {
- interactor = mock {
- on { contrast } doReturn flowOf(ColorContrastSectionViewModel.ContrastValue.HIGH.value)
- }
- val factory = ColorContrastSectionViewModel.Factory(interactor)
- viewModel = factory.create(ColorContrastSectionViewModel::class.java)
-
+ uiModeManager.setContrast(ColorContrastSectionViewModel.ContrastValue.HIGH.value)
val expected =
ColorContrastSectionDataViewModel(
Text.Resource(R.string.color_contrast_high_title),
@@ -103,14 +87,14 @@
)
val result = viewModel.summary.first()
+
assertEquals(expected, result)
}
@Test(expected = IllegalArgumentException::class)
fun summaryThrowsIllegalArgumentExceptionForInvalidValue() = runBlockingTest {
- interactor = mock { on { contrast } doReturn flowOf(999f) }
- val factory = ColorContrastSectionViewModel.Factory(interactor)
- viewModel = factory.create(ColorContrastSectionViewModel::class.java)
+ uiModeManager.setContrast(999f)
+
viewModel.summary.collect() // This should throw an IllegalArgumentException
}
}