From 299f88795353ac4710cfa00a4b5f73309007ac11 Mon Sep 17 00:00:00 2001 From: MatthewTighe Date: Thu, 18 Aug 2022 11:03:34 -0700 Subject: [PATCH] Add updated wallpaper use cases and activate them with feature flag. --- .../mozilla/fenix/ui/SettingsHomepageTest.kt | 2 + .../java/org/mozilla/fenix/utils/Settings.kt | 2 +- .../wallpapers/LegacyWallpaperFileManager.kt | 7 + .../org/mozilla/fenix/wallpapers/Wallpaper.kt | 8 +- .../fenix/wallpapers/WallpaperDownloader.kt | 19 +- .../fenix/wallpapers/WallpaperFileManager.kt | 19 +- .../fenix/wallpapers/WallpaperManager.kt | 2 +- .../wallpapers/WallpaperMetadataFetcher.kt | 15 +- .../fenix/wallpapers/WallpapersUseCases.kt | 143 ++++++++-- .../wallpapers/WallpaperDownloaderTest.kt | 44 +-- .../wallpapers/WallpaperFileManagerTest.kt | 16 +- .../fenix/wallpapers/WallpaperManagerTest.kt | 6 +- .../WallpaperMetadataFetcherTest.kt | 41 +++ .../wallpapers/WallpapersUseCasesTest.kt | 261 +++++++++++++++--- 14 files changed, 472 insertions(+), 113 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsHomepageTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsHomepageTest.kt index fa49824db2..b02a32c0f6 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsHomepageTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsHomepageTest.kt @@ -7,6 +7,7 @@ package org.mozilla.fenix.ui import okhttp3.mockwebserver.MockWebServer import org.junit.After import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.mozilla.fenix.customannotations.SmokeTest @@ -143,6 +144,7 @@ class SettingsHomepageTest { @SmokeTest @Test + @Ignore("Intermittent test: https://github.com/mozilla-mobile/fenix/issues/26559") fun setWallpaperTest() { val wallpapers = listOf( "Wallpaper Item: amethyst", diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt index e56e197ee5..2d117a9a7b 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -185,7 +185,7 @@ class Settings(private val appContext: Context) : PreferencesHolder { default = "" ) - var currentWallpaper by stringPreference( + var currentWallpaperName by stringPreference( appContext.getPreferenceKey(R.string.pref_key_current_wallpaper), default = Wallpaper.Default.name ) diff --git a/app/src/main/java/org/mozilla/fenix/wallpapers/LegacyWallpaperFileManager.kt b/app/src/main/java/org/mozilla/fenix/wallpapers/LegacyWallpaperFileManager.kt index 2546e73621..71126eee4e 100644 --- a/app/src/main/java/org/mozilla/fenix/wallpapers/LegacyWallpaperFileManager.kt +++ b/app/src/main/java/org/mozilla/fenix/wallpapers/LegacyWallpaperFileManager.kt @@ -11,6 +11,13 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File +/** + * Manages various functions related to the locally-stored wallpaper assets. + * + * @property rootDirectory The top level app-local storage directory. + * @param coroutineDispatcher Dispatcher used to execute suspending functions. Default parameter + * should be likely be used except for when under test. + */ class LegacyWallpaperFileManager( private val rootDirectory: File, coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO diff --git a/app/src/main/java/org/mozilla/fenix/wallpapers/Wallpaper.kt b/app/src/main/java/org/mozilla/fenix/wallpapers/Wallpaper.kt index b4b5ce3391..6cc8eae0ad 100644 --- a/app/src/main/java/org/mozilla/fenix/wallpapers/Wallpaper.kt +++ b/app/src/main/java/org/mozilla/fenix/wallpapers/Wallpaper.kt @@ -83,14 +83,20 @@ data class Wallpaper( * @param type The type of image that should be retrieved. * @param name The name of the wallpaper. */ - fun getLocalPath(type: ImageType, name: String) = "wallpapers/$name/${type.lowercase()}.png" + fun getLocalPath(name: String, type: ImageType) = "wallpapers/$name/${type.lowercase()}.png" } + /** + * Defines various image asset types that can be downloaded for each wallpaper. + */ enum class ImageType { Portrait, Landscape, Thumbnail; + /** + * Get a lowercase string representation of the [ImageType.name] for use in path segments. + */ fun lowercase(): String = this.name.lowercase() } } diff --git a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperDownloader.kt b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperDownloader.kt index 2a06fa6b72..c16c20afeb 100644 --- a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperDownloader.kt +++ b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperDownloader.kt @@ -10,7 +10,6 @@ import kotlinx.coroutines.withContext import mozilla.components.concept.fetch.Client import mozilla.components.concept.fetch.Request import mozilla.components.concept.fetch.isSuccess -import mozilla.components.support.base.log.logger.Logger import org.mozilla.fenix.BuildConfig import org.mozilla.fenix.wallpapers.Wallpaper.Companion.getLocalPath import java.io.File @@ -18,11 +17,13 @@ import java.io.File /** * Can download wallpapers from a remote host. * - * @param filesDir The top level app-local storage directory. + * @param storageRootDirectory The top level app-local storage directory. * @param client Required for fetching files from network. + * @param dispatcher Dispatcher used to execute suspending functions. Default parameter + * should be likely be used except for when under test. */ class WallpaperDownloader( - private val filesDir: File, + private val storageRootDirectory: File, private val client: Client, private val dispatcher: CoroutineDispatcher = Dispatchers.IO ) { @@ -37,7 +38,7 @@ class WallpaperDownloader( */ suspend fun downloadWallpaper(wallpaper: Wallpaper) = withContext(dispatcher) { for (metadata in wallpaper.toMetadata()) { - val localFile = File(filesDir.absolutePath, metadata.localPath) + val localFile = File(storageRootDirectory.absolutePath, metadata.localPath) // Don't overwrite an asset if it exists if (localFile.exists()) continue val request = Request( @@ -54,7 +55,7 @@ class WallpaperDownloader( input.copyTo(localFile.outputStream()) } }.onFailure { - // This should clean up any partial downloads. + // This should clean up any partial downloads Result.runCatching { if (localFile.exists()) { localFile.delete() @@ -68,8 +69,8 @@ class WallpaperDownloader( private fun Wallpaper.toMetadata(): List = listOf(Wallpaper.ImageType.Portrait, Wallpaper.ImageType.Landscape).map { orientation -> - val localPath = getLocalPath(orientation, this.name) - val remotePath = "${collection.name}/${this.name}/${orientation.lowercase()}.png" - WallpaperMetadata(remotePath, localPath) - } + val localPath = getLocalPath(this.name, orientation) + val remotePath = "${collection.name}/${this.name}/${orientation.lowercase()}.png" + WallpaperMetadata(remotePath, localPath) + } } diff --git a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperFileManager.kt b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperFileManager.kt index 7062a94588..69ca7f8cea 100644 --- a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperFileManager.kt +++ b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperFileManager.kt @@ -12,19 +12,26 @@ import kotlinx.coroutines.withContext import org.mozilla.fenix.wallpapers.Wallpaper.Companion.getLocalPath import java.io.File +/** + * Manages various functions related to the locally-stored wallpaper assets. + * + * @property storageRootDirectory The top level app-local storage directory. + * @param coroutineDispatcher Dispatcher used to execute suspending functions. Default parameter + * should be likely be used except for when under test. + */ class WallpaperFileManager( - private val rootDirectory: File, + private val storageRootDirectory: File, coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO ) { private val scope = CoroutineScope(coroutineDispatcher) - private val wallpapersDirectory = File(rootDirectory, "wallpapers") + private val wallpapersDirectory = File(storageRootDirectory, "wallpapers") /** * Lookup all the files for a wallpaper name. This lookup will fail if there are not * files for each of a portrait and landscape orientation as well as a thumbnail. */ suspend fun lookupExpiredWallpaper(name: String): Wallpaper? = withContext(Dispatchers.IO) { - if (getAllLocalWallpaperPaths(name).all { File(rootDirectory, it).exists() }) { + if (getAllLocalWallpaperPaths(name).all { File(storageRootDirectory, it).exists() }) { Wallpaper( name = name, collection = Wallpaper.DefaultCollection, @@ -36,16 +43,16 @@ class WallpaperFileManager( private fun getAllLocalWallpaperPaths(name: String): List = Wallpaper.ImageType.values().map { orientation -> - getLocalPath(orientation, name) + getLocalPath(name, orientation) } /** * Remove all wallpapers that are not the [currentWallpaper] or in [availableWallpapers]. */ - fun clean(currentWallpaper: Wallpaper, availableWallpapers: List) { + suspend fun clean(currentWallpaper: Wallpaper, availableWallpapers: List) = withContext(Dispatchers.IO) { scope.launch { val wallpapersToKeep = (listOf(currentWallpaper) + availableWallpapers).map { it.name } - for (file in wallpapersDirectory.listFiles()?.toList() ?: listOf()) { + wallpapersDirectory.listFiles()?.forEach { file -> if (file.isDirectory && !wallpapersToKeep.contains(file.name)) { file.deleteRecursively() } diff --git a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperManager.kt b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperManager.kt index 8edf4bf706..5d6bef29be 100644 --- a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperManager.kt +++ b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperManager.kt @@ -24,7 +24,7 @@ class WallpaperManager( /** * Get whether the default wallpaper should be used. */ - fun isDefaultTheCurrentWallpaper(settings: Settings): Boolean = with(settings.currentWallpaper) { + fun isDefaultTheCurrentWallpaper(settings: Settings): Boolean = with(settings.currentWallpaperName) { return isEmpty() || equals(defaultWallpaper.name) } diff --git a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperMetadataFetcher.kt b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperMetadataFetcher.kt index 0aec787236..118f2e0c8d 100644 --- a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperMetadataFetcher.kt +++ b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperMetadataFetcher.kt @@ -48,11 +48,11 @@ class WallpaperMetadataFetcher( private fun JSONObject.toCollectionOfWallpapers(): List { val collectionId = getString("id") - val heading = optString("heading") - val description = optString("description") + val heading = optStringOrNull("heading") + val description = optStringOrNull("description") val availableLocales = optJSONArray("available-locales")?.getAvailableLocales() val availabilityRange = optJSONObject("availability-range")?.getAvailabilityRange() - val learnMoreUrl = optString("learn-more-url") + val learnMoreUrl = optStringOrNull("learn-more-url") val collection = Wallpaper.Collection( name = collectionId, heading = heading, @@ -87,6 +87,15 @@ class WallpaperMetadataFetcher( } } + /** + * Normally, if a field is specified in json as null, then optString will return it as "null". If + * a field is missing completely, optString will return "". This will correctly return null in + * both those cases so that optional properties are marked as missing. + */ + private fun JSONObject.optStringOrNull(propName: String) = optString(propName).takeIf { + it != "null" && it.isNotEmpty() + } + /** * The wallpaper metadata has 6 digit hex color codes for compatibility with iOS. Since Android * expects 8 digit ARBG values, we prepend FF for the "fully visible" version of the color diff --git a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpapersUseCases.kt b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpapersUseCases.kt index a5cbb3392a..75e17823f3 100644 --- a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpapersUseCases.kt +++ b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpapersUseCases.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import mozilla.components.concept.fetch.Client import mozilla.components.support.locale.LocaleManager +import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.GleanMetrics.Wallpapers import org.mozilla.fenix.R import org.mozilla.fenix.components.AppStore @@ -41,26 +42,53 @@ class WallpapersUseCases( context: Context, store: AppStore, client: Client, - strictMode: StrictModeManager + strictMode: StrictModeManager, ) { val initialize: InitializeWallpapersUseCase by lazy { - // Required to even access context.filesDir property and to retrieve current locale - val (fileManager, currentLocale) = strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { - val fileManager = LegacyWallpaperFileManager(context.filesDir) - val currentLocale = LocaleManager.getCurrentLocale(context)?.toLanguageTag() - ?: LocaleManager.getSystemDefault().toLanguageTag() - fileManager to currentLocale + if (FeatureFlags.wallpaperV2Enabled) { + // Required to even access context.filesDir property and to retrieve current locale + val (storageRootDirectory, currentLocale) = strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { + val storageRootDirectory = context.filesDir + val currentLocale = LocaleManager.getCurrentLocale(context)?.toLanguageTag() + ?: LocaleManager.getSystemDefault().toLanguageTag() + storageRootDirectory to currentLocale + } + val downloader = WallpaperDownloader(storageRootDirectory, client) + val fileManager = WallpaperFileManager(storageRootDirectory) + val metadataFetcher = WallpaperMetadataFetcher(client) + DefaultInitializeWallpaperUseCase( + store = store, + downloader = downloader, + fileManager = fileManager, + metadataFetcher = metadataFetcher, + settings = context.settings(), + currentLocale = currentLocale + ) + } else { + // Required to even access context.filesDir property and to retrieve current locale + val (fileManager, currentLocale) = strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { + val fileManager = LegacyWallpaperFileManager(context.filesDir) + val currentLocale = LocaleManager.getCurrentLocale(context)?.toLanguageTag() + ?: LocaleManager.getSystemDefault().toLanguageTag() + fileManager to currentLocale + } + val downloader = LegacyWallpaperDownloader(context, client) + LegacyInitializeWallpaperUseCase( + store = store, + downloader = downloader, + fileManager = fileManager, + settings = context.settings(), + currentLocale = currentLocale + ) + } + } + val loadBitmap: LoadBitmapUseCase by lazy { + if (FeatureFlags.wallpaperV2Enabled) { + DefaultLoadBitmapUseCase(context) + } else { + LegacyLoadBitmapUseCase(context) } - val downloader = LegacyWallpaperDownloader(context, client) - LegacyInitializeWallpaperUseCase( - store = store, - downloader = downloader, - fileManager = fileManager, - settings = context.settings(), - currentLocale = currentLocale - ) } - val loadBitmap: LoadBitmapUseCase by lazy { LegacyLoadBitmapUseCase(context) } val selectWallpaper: SelectWallpaperUseCase by lazy { DefaultSelectWallpaperUseCase(context.settings(), store) } /** @@ -97,7 +125,7 @@ class WallpapersUseCases( // and download utilities. withContext(Dispatchers.IO) { val availableWallpapers = possibleWallpapers.getAvailableWallpapers() - val currentWallpaperName = settings.currentWallpaper + val currentWallpaperName = settings.currentWallpaperName val currentWallpaper = possibleWallpapers.find { it.name == currentWallpaperName } ?: fileManager.lookupExpiredWallpaper(currentWallpaperName) ?: Wallpaper.Default @@ -125,7 +153,7 @@ class WallpapersUseCases( private fun Wallpaper.isExpired(): Boolean { val expired = this.collection.endDate?.let { Date().after(it) } ?: false - return expired && this.name != settings.currentWallpaper + return expired && this.name != settings.currentWallpaperName } private fun Wallpaper.isAvailableInLocale(): Boolean = @@ -179,6 +207,49 @@ class WallpapersUseCases( } } + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal class DefaultInitializeWallpaperUseCase( + private val store: AppStore, + private val downloader: WallpaperDownloader, + private val fileManager: WallpaperFileManager, + private val metadataFetcher: WallpaperMetadataFetcher, + private val settings: Settings, + private val currentLocale: String, + ) : InitializeWallpapersUseCase { + override suspend fun invoke() { + val currentWallpaperName = withContext(Dispatchers.IO) { settings.currentWallpaperName } + val possibleWallpapers = metadataFetcher.downloadWallpaperList().filter { + !it.isExpired() && it.isAvailableInLocale() + } + val currentWallpaper = possibleWallpapers.find { it.name == currentWallpaperName } + ?: fileManager.lookupExpiredWallpaper(currentWallpaperName) + ?: Wallpaper.Default + + // Dispatching this early will make it accessible to the home screen ASAP + store.dispatch(AppAction.WallpaperAction.UpdateCurrentWallpaper(currentWallpaper)) + + fileManager.clean( + currentWallpaper, + possibleWallpapers + ) + + possibleWallpapers.forEach { downloader.downloadWallpaper(it) } + + val defaultIncluded = listOf(Wallpaper.Default) + possibleWallpapers + store.dispatch(AppAction.WallpaperAction.UpdateAvailableWallpapers(defaultIncluded)) + } + + private fun Wallpaper.isExpired(): Boolean = when (this) { + Wallpaper.Default -> false + else -> { + val expired = this.collection.endDate?.let { Date().after(it) } ?: false + expired && this.name != settings.currentWallpaperName + } + } + + private fun Wallpaper.isAvailableInLocale(): Boolean = + this.collection.availableLocales?.contains(currentLocale) ?: true + } /** * Contract for usecase for loading bitmaps related to a specific wallpaper. */ @@ -254,6 +325,40 @@ class WallpapersUseCases( } } + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal class DefaultLoadBitmapUseCase(private val context: Context) : LoadBitmapUseCase { + override suspend fun invoke(wallpaper: Wallpaper): Bitmap? = + loadWallpaperFromDisk(context, wallpaper) + + private suspend fun loadWallpaperFromDisk( + context: Context, + wallpaper: Wallpaper + ): Bitmap? = Result.runCatching { + val path = wallpaper.getLocalPathFromContext(context) + withContext(Dispatchers.IO) { + val file = File(context.filesDir, path) + BitmapFactory.decodeStream(file.inputStream()) + } + }.getOrNull() + + /** + * Get the expected local path on disk for a wallpaper. This will differ depending + * on orientation and app theme. + */ + private fun Wallpaper.getLocalPathFromContext(context: Context): String { + val orientation = if (context.isLandscape()) { + Wallpaper.ImageType.Landscape + } else { + Wallpaper.ImageType.Portrait + } + return Wallpaper.getLocalPath(name, orientation) + } + + private fun Context.isLandscape(): Boolean { + return resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE + } + } + /** * Contract for usecase of selecting a new wallpaper. */ @@ -277,7 +382,7 @@ class WallpapersUseCases( * @param wallpaper The selected wallpaper. */ override fun invoke(wallpaper: Wallpaper) { - settings.currentWallpaper = wallpaper.name + settings.currentWallpaperName = wallpaper.name store.dispatch(AppAction.WallpaperAction.UpdateCurrentWallpaper(wallpaper)) Wallpapers.wallpaperSelected.record( Wallpapers.WallpaperSelectedExtra( diff --git a/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperDownloaderTest.kt b/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperDownloaderTest.kt index 8c978425a0..c9ea5cb79e 100644 --- a/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperDownloaderTest.kt +++ b/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperDownloaderTest.kt @@ -2,16 +2,13 @@ package org.mozilla.fenix.wallpapers import io.mockk.every import io.mockk.mockk -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestDispatcher -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import mozilla.components.concept.fetch.Client import mozilla.components.concept.fetch.Request import mozilla.components.concept.fetch.Response -import mozilla.components.concept.fetch.isSuccess -import org.junit.Assert.* +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Rule import org.junit.Test @@ -56,14 +53,8 @@ class WallpaperDownloaderTest { @Test fun `GIVEN that request is successful WHEN downloading THEN file is created in expected location`() = runTest { val wallpaper = generateWallpaper() - val portraitRequest = Request( - url = "$remoteHost/${wallpaper.collection.name}/${wallpaper.name}/portrait.png", - method = Request.Method.GET - ) - val landscapeRequest = Request( - url = "$remoteHost/${wallpaper.collection.name}/${wallpaper.name}/landscape.png", - method = Request.Method.GET - ) + val portraitRequest = wallpaper.generateRequest("portrait") + val landscapeRequest = wallpaper.generateRequest("landscape") every { mockPortraitResponse.status } returns 200 every { mockLandscapeResponse.status } returns 200 every { mockPortraitResponse.body } returns portraitResponseBodySuccess @@ -82,14 +73,8 @@ class WallpaperDownloaderTest { @Test fun `GIVEN that request fails WHEN downloading THEN file is not created`() = runTest { val wallpaper = generateWallpaper() - val portraitRequest = Request( - url = "$remoteHost/${wallpaper.collection.name}/${wallpaper.name}/portrait.png", - method = Request.Method.GET - ) - val landscapeRequest = Request( - url = "$remoteHost/${wallpaper.collection.name}/${wallpaper.name}/landscape.png", - method = Request.Method.GET - ) + val portraitRequest = wallpaper.generateRequest("portrait") + val landscapeRequest = wallpaper.generateRequest("landscape") every { mockPortraitResponse.status } returns 400 every { mockLandscapeResponse.status } returns 400 every { mockClient.fetch(portraitRequest) } returns mockPortraitResponse @@ -106,14 +91,8 @@ class WallpaperDownloaderTest { @Test fun `GIVEN that copying the file fails WHEN downloading THEN file is not created`() = runTest { val wallpaper = generateWallpaper() - val portraitRequest = Request( - url = "$remoteHost/${wallpaper.collection.name}/${wallpaper.name}/portrait.png", - method = Request.Method.GET - ) - val landscapeRequest = Request( - url = "$remoteHost/${wallpaper.collection.name}/${wallpaper.name}/landscape.png", - method = Request.Method.GET - ) + val portraitRequest = wallpaper.generateRequest("portrait") + val landscapeRequest = wallpaper.generateRequest("landscape") every { mockPortraitResponse.status } returns 200 every { mockLandscapeResponse.status } returns 200 every { mockPortraitResponse.body } throws IllegalStateException() @@ -134,4 +113,9 @@ class WallpaperDownloaderTest { textColor = null, cardColor = null ) -} \ No newline at end of file + + private fun Wallpaper.generateRequest(type: String) = Request( + url = "$remoteHost/${collection.name}/$name/$type.png", + method = Request.Method.GET + ) +} diff --git a/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperFileManagerTest.kt b/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperFileManagerTest.kt index bf80a967f4..e7874fac1f 100644 --- a/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperFileManagerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperFileManagerTest.kt @@ -2,7 +2,7 @@ package org.mozilla.fenix.wallpapers import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest -import org.junit.Assert +import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Rule @@ -24,7 +24,7 @@ class WallpaperFileManagerTest { fun setup() { wallpapersFolder = File(tempFolder.root, "wallpapers") fileManager = WallpaperFileManager( - rootDirectory = tempFolder.root, + storageRootDirectory = tempFolder.root, coroutineDispatcher = dispatcher, ) } @@ -37,7 +37,7 @@ class WallpaperFileManagerTest { val result = fileManager.lookupExpiredWallpaper(wallpaperName) val expected = generateWallpaper(name = wallpaperName) - Assert.assertEquals(expected, result) + assertEquals(expected, result) } @Test @@ -54,7 +54,7 @@ class WallpaperFileManagerTest { val result = fileManager.lookupExpiredWallpaper(wallpaperName) - Assert.assertEquals(null, result) + assertEquals(null, result) } @Test @@ -71,7 +71,7 @@ class WallpaperFileManagerTest { val result = fileManager.lookupExpiredWallpaper(wallpaperName) - Assert.assertEquals(null, result) + assertEquals(null, result) } @Test @@ -88,11 +88,11 @@ class WallpaperFileManagerTest { val result = fileManager.lookupExpiredWallpaper(wallpaperName) - Assert.assertEquals(null, result) + assertEquals(null, result) } @Test - fun `WHEN cleaned THEN current wallpaper and available wallpapers kept`() { + fun `WHEN cleaned THEN current wallpaper and available wallpapers kept`() = runTest { val currentName = "current" val currentWallpaper = generateWallpaper(name = currentName) val availableName = "available" @@ -132,4 +132,4 @@ class WallpaperFileManagerTest { cardColor = null, collection = Wallpaper.DefaultCollection ) -} \ No newline at end of file +} diff --git a/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperManagerTest.kt b/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperManagerTest.kt index 942b38ca82..311ebbf76e 100644 --- a/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperManagerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperManagerTest.kt @@ -13,7 +13,7 @@ class WallpaperManagerTest { @Test fun `GIVEN no custom wallpaper set WHEN checking whether the current wallpaper should be default THEN return true`() { - every { mockSettings.currentWallpaper } returns "" + every { mockSettings.currentWallpaperName } returns "" val result = WallpaperManager.isDefaultTheCurrentWallpaper(mockSettings) @@ -22,7 +22,7 @@ class WallpaperManagerTest { @Test fun `GIVEN the default wallpaper is set to be shown WHEN checking whether the current wallpaper should be default THEN return true`() { - every { mockSettings.currentWallpaper } returns WallpaperManager.defaultWallpaper.name + every { mockSettings.currentWallpaperName } returns WallpaperManager.defaultWallpaper.name val result = WallpaperManager.isDefaultTheCurrentWallpaper(mockSettings) @@ -31,7 +31,7 @@ class WallpaperManagerTest { @Test fun `GIVEN a custom wallpaper is set to be shown WHEN checking whether the current wallpaper should be default THEN return false`() { - every { mockSettings.currentWallpaper } returns "test" + every { mockSettings.currentWallpaperName } returns "test" val result = WallpaperManager.isDefaultTheCurrentWallpaper(mockSettings) diff --git a/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperMetadataFetcherTest.kt b/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperMetadataFetcherTest.kt index d440dfdd14..946dc6d886 100644 --- a/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperMetadataFetcherTest.kt +++ b/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperMetadataFetcherTest.kt @@ -346,4 +346,45 @@ class WallpaperMetadataFetcherTest { } ) } + + @Test + fun `GIVEN string fields with null values WHEN parsed THEN fields are correctly null`() = runTest { + val json = """ + { + "last-updated-date": "2022-01-01", + "collections": [ + { + "id": "classic-firefox", + "heading": null, + "description": null, + "available-locales": null, + "availability-range": null, + "learn-more-url": null, + "wallpapers": [ + { + "id": "beach-vibes", + "text-color": "FBFBFE", + "card-color": "15141A" + }, + { + "id": "sunrise", + "text-color": "15141A", + "card-color": "FBFBFE" + } + ] + } + ] + } + """.trimIndent() + every { mockResponse.body } returns Response.Body(json.byteInputStream()) + + val wallpapers = metadataFetcher.downloadWallpaperList() + + assertTrue(wallpapers.isNotEmpty()) + assertTrue( + wallpapers.all { + it.collection.heading == null && it.collection.description == null + } + ) + } } diff --git a/app/src/test/java/org/mozilla/fenix/wallpapers/WallpapersUseCasesTest.kt b/app/src/test/java/org/mozilla/fenix/wallpapers/WallpapersUseCasesTest.kt index b8bf24b25f..2f53a93847 100644 --- a/app/src/test/java/org/mozilla/fenix/wallpapers/WallpapersUseCasesTest.kt +++ b/app/src/test/java/org/mozilla/fenix/wallpapers/WallpapersUseCasesTest.kt @@ -37,26 +37,214 @@ class WallpapersUseCasesTest { private val appStore = AppStore() private val mockSettings = mockk() - private val mockDownloader = mockk(relaxed = true) - private val mockFileManager = mockk { + private val mockLegacyDownloader = mockk(relaxed = true) + private val mockLegacyFileManager = mockk { every { clean(any(), any()) } just runs } + private val mockMetadataFetcher = mockk() + private val mockDownloader = mockk(relaxed = true) + private val mockFileManager = mockk { + coEvery { clean(any(), any()) } returns mockk() + } + + @Test + fun `GIVEN legacy use case WHEN initializing THEN the default wallpaper is not downloaded`() = runTest { + val fakeRemoteWallpapers = listOf("first", "second", "third").map { name -> + makeFakeRemoteWallpaper(TimeRelation.LATER, name) + } + every { mockSettings.currentWallpaperName } returns "" + coEvery { mockLegacyFileManager.lookupExpiredWallpaper(any()) } returns null + + WallpapersUseCases.LegacyInitializeWallpaperUseCase( + appStore, + mockLegacyDownloader, + mockLegacyFileManager, + mockSettings, + "en-US", + possibleWallpapers = listOf(Wallpaper.Default) + fakeRemoteWallpapers + ).invoke() + + appStore.waitUntilIdle() + coVerify(exactly = 0) { mockLegacyDownloader.downloadWallpaper(Wallpaper.Default) } + } + + @Test + fun `GIVEN legacy use case WHEN initializing THEN default wallpaper is included in available wallpapers`() = runTest { + val fakeRemoteWallpapers = listOf("first", "second", "third").map { name -> + makeFakeRemoteWallpaper(TimeRelation.LATER, name) + } + every { mockSettings.currentWallpaperName } returns "" + coEvery { mockLegacyFileManager.lookupExpiredWallpaper(any()) } returns null + + WallpapersUseCases.LegacyInitializeWallpaperUseCase( + appStore, + mockLegacyDownloader, + mockLegacyFileManager, + mockSettings, + "en-US", + possibleWallpapers = listOf(Wallpaper.Default) + fakeRemoteWallpapers + ).invoke() + + appStore.waitUntilIdle() + assertTrue(appStore.state.wallpaperState.availableWallpapers.contains(Wallpaper.Default)) + } + + @Test + fun `GIVEN legacy use case and wallpapers that expired WHEN invoking initialize use case THEN expired wallpapers are filtered out and cleaned up`() = runTest { + val fakeRemoteWallpapers = listOf("first", "second", "third").map { name -> + makeFakeRemoteWallpaper(TimeRelation.LATER, name) + } + val fakeExpiredRemoteWallpapers = listOf("expired").map { name -> + makeFakeRemoteWallpaper(TimeRelation.BEFORE, name) + } + val possibleWallpapers = fakeRemoteWallpapers + fakeExpiredRemoteWallpapers + every { mockSettings.currentWallpaperName } returns "" + coEvery { mockLegacyFileManager.lookupExpiredWallpaper(any()) } returns null + + WallpapersUseCases.LegacyInitializeWallpaperUseCase( + appStore, + mockLegacyDownloader, + mockLegacyFileManager, + mockSettings, + "en-US", + possibleWallpapers = possibleWallpapers + ).invoke() + + val expectedFilteredWallpaper = fakeExpiredRemoteWallpapers[0] + appStore.waitUntilIdle() + assertFalse(appStore.state.wallpaperState.availableWallpapers.contains(expectedFilteredWallpaper)) + verify { mockLegacyFileManager.clean(Wallpaper.Default, possibleWallpapers) } + } + + @Test + fun `GIVEN leagacy use case and wallpapers that expired and an expired one is selected WHEN invoking initialize use case THEN selected wallpaper is not filtered out`() = runTest { + val fakeRemoteWallpapers = listOf("first", "second", "third").map { name -> + makeFakeRemoteWallpaper(TimeRelation.LATER, name) + } + val expiredWallpaper = makeFakeRemoteWallpaper(TimeRelation.BEFORE, "expired") + every { mockSettings.currentWallpaperName } returns expiredWallpaper.name + coEvery { mockLegacyFileManager.lookupExpiredWallpaper(any()) } returns null + + WallpapersUseCases.LegacyInitializeWallpaperUseCase( + appStore, + mockLegacyDownloader, + mockLegacyFileManager, + mockSettings, + "en-US", + possibleWallpapers = fakeRemoteWallpapers + listOf(expiredWallpaper) + ).invoke() + + appStore.waitUntilIdle() + assertTrue(appStore.state.wallpaperState.availableWallpapers.contains(expiredWallpaper)) + assertEquals(expiredWallpaper, appStore.state.wallpaperState.currentWallpaper) + } + + @Test + fun `GIVEN legacy use case and wallpapers that are in promotions outside of locale WHEN invoking initialize use case THEN promotional wallpapers are filtered out`() = runTest { + val fakeRemoteWallpapers = listOf("first", "second", "third").map { name -> + makeFakeRemoteWallpaper(TimeRelation.LATER, name) + } + val locale = "en-CA" + every { mockSettings.currentWallpaperName } returns "" + coEvery { mockLegacyFileManager.lookupExpiredWallpaper(any()) } returns null + + WallpapersUseCases.LegacyInitializeWallpaperUseCase( + appStore, + mockLegacyDownloader, + mockLegacyFileManager, + mockSettings, + locale, + possibleWallpapers = fakeRemoteWallpapers + ).invoke() + + appStore.waitUntilIdle() + assertTrue(appStore.state.wallpaperState.availableWallpapers.isEmpty()) + } + + @Test + fun `GIVEN legacy use case and available wallpapers WHEN invoking initialize use case THEN available wallpapers downloaded`() = runTest { + val fakeRemoteWallpapers = listOf("first", "second", "third").map { name -> + makeFakeRemoteWallpaper(TimeRelation.LATER, name) + } + every { mockSettings.currentWallpaperName } returns "" + coEvery { mockLegacyFileManager.lookupExpiredWallpaper(any()) } returns null + + WallpapersUseCases.LegacyInitializeWallpaperUseCase( + appStore, + mockLegacyDownloader, + mockLegacyFileManager, + mockSettings, + "en-US", + possibleWallpapers = fakeRemoteWallpapers + ).invoke() + + for (fakeRemoteWallpaper in fakeRemoteWallpapers) { + coVerify { mockLegacyDownloader.downloadWallpaper(fakeRemoteWallpaper) } + } + } + + @Test + fun `GIVEN legacy use case and a wallpaper has not been selected WHEN invoking initialize use case THEN store contains default`() = runTest { + val fakeRemoteWallpapers = listOf("first", "second", "third").map { name -> + makeFakeRemoteWallpaper(TimeRelation.LATER, name) + } + every { mockSettings.currentWallpaperName } returns "" + coEvery { mockLegacyFileManager.lookupExpiredWallpaper(any()) } returns null + + WallpapersUseCases.LegacyInitializeWallpaperUseCase( + appStore, + mockLegacyDownloader, + mockLegacyFileManager, + mockSettings, + "en-US", + possibleWallpapers = fakeRemoteWallpapers + ).invoke() + + appStore.waitUntilIdle() + assertTrue(appStore.state.wallpaperState.currentWallpaper == Wallpaper.Default) + } + + @Test + fun `GIVEN legacy use case a wallpaper is selected and there are available wallpapers WHEN invoking initialize use case THEN these are dispatched to the store`() = runTest { + val selectedWallpaper = makeFakeRemoteWallpaper(TimeRelation.LATER, "selected") + val fakeRemoteWallpapers = listOf("first", "second", "third").map { name -> + makeFakeRemoteWallpaper(TimeRelation.LATER, name) + } + val possibleWallpapers = listOf(selectedWallpaper) + fakeRemoteWallpapers + every { mockSettings.currentWallpaperName } returns selectedWallpaper.name + coEvery { mockLegacyFileManager.lookupExpiredWallpaper(any()) } returns null + + WallpapersUseCases.LegacyInitializeWallpaperUseCase( + appStore, + mockLegacyDownloader, + mockLegacyFileManager, + mockSettings, + "en-US", + possibleWallpapers = possibleWallpapers + ).invoke() + + appStore.waitUntilIdle() + assertEquals(selectedWallpaper, appStore.state.wallpaperState.currentWallpaper) + assertEquals(possibleWallpapers, appStore.state.wallpaperState.availableWallpapers) + } + @Test fun `WHEN initializing THEN the default wallpaper is not downloaded`() = runTest { val fakeRemoteWallpapers = listOf("first", "second", "third").map { name -> makeFakeRemoteWallpaper(TimeRelation.LATER, name) } - every { mockSettings.currentWallpaper } returns "" + every { mockSettings.currentWallpaperName } returns "" coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null + coEvery { mockMetadataFetcher.downloadWallpaperList() } returns fakeRemoteWallpapers - WallpapersUseCases.LegacyInitializeWallpaperUseCase( + WallpapersUseCases.DefaultInitializeWallpaperUseCase( appStore, mockDownloader, mockFileManager, + mockMetadataFetcher, mockSettings, "en-US", - possibleWallpapers = listOf(Wallpaper.Default) + fakeRemoteWallpapers ).invoke() appStore.waitUntilIdle() @@ -68,16 +256,17 @@ class WallpapersUseCasesTest { val fakeRemoteWallpapers = listOf("first", "second", "third").map { name -> makeFakeRemoteWallpaper(TimeRelation.LATER, name) } - every { mockSettings.currentWallpaper } returns "" + every { mockSettings.currentWallpaperName } returns "" coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null + coEvery { mockMetadataFetcher.downloadWallpaperList() } returns fakeRemoteWallpapers - WallpapersUseCases.LegacyInitializeWallpaperUseCase( + WallpapersUseCases.DefaultInitializeWallpaperUseCase( appStore, mockDownloader, mockFileManager, + mockMetadataFetcher, mockSettings, "en-US", - possibleWallpapers = listOf(Wallpaper.Default) + fakeRemoteWallpapers ).invoke() appStore.waitUntilIdle() @@ -93,22 +282,23 @@ class WallpapersUseCasesTest { makeFakeRemoteWallpaper(TimeRelation.BEFORE, name) } val possibleWallpapers = fakeRemoteWallpapers + fakeExpiredRemoteWallpapers - every { mockSettings.currentWallpaper } returns "" + every { mockSettings.currentWallpaperName } returns "" coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null + coEvery { mockMetadataFetcher.downloadWallpaperList() } returns possibleWallpapers - WallpapersUseCases.LegacyInitializeWallpaperUseCase( + WallpapersUseCases.DefaultInitializeWallpaperUseCase( appStore, mockDownloader, mockFileManager, + mockMetadataFetcher, mockSettings, "en-US", - possibleWallpapers = possibleWallpapers ).invoke() val expectedFilteredWallpaper = fakeExpiredRemoteWallpapers[0] appStore.waitUntilIdle() assertFalse(appStore.state.wallpaperState.availableWallpapers.contains(expectedFilteredWallpaper)) - verify { mockFileManager.clean(Wallpaper.Default, possibleWallpapers) } + coVerify { mockFileManager.clean(Wallpaper.Default, fakeRemoteWallpapers) } } @Test @@ -117,16 +307,17 @@ class WallpapersUseCasesTest { makeFakeRemoteWallpaper(TimeRelation.LATER, name) } val expiredWallpaper = makeFakeRemoteWallpaper(TimeRelation.BEFORE, "expired") - every { mockSettings.currentWallpaper } returns expiredWallpaper.name - coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null + every { mockSettings.currentWallpaperName } returns expiredWallpaper.name + coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns expiredWallpaper + coEvery { mockMetadataFetcher.downloadWallpaperList() } returns listOf(expiredWallpaper) + fakeRemoteWallpapers - WallpapersUseCases.LegacyInitializeWallpaperUseCase( + WallpapersUseCases.DefaultInitializeWallpaperUseCase( appStore, mockDownloader, mockFileManager, + mockMetadataFetcher, mockSettings, "en-US", - possibleWallpapers = fakeRemoteWallpapers + listOf(expiredWallpaper) ).invoke() appStore.waitUntilIdle() @@ -140,20 +331,22 @@ class WallpapersUseCasesTest { makeFakeRemoteWallpaper(TimeRelation.LATER, name) } val locale = "en-CA" - every { mockSettings.currentWallpaper } returns "" + every { mockSettings.currentWallpaperName } returns "" coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null + coEvery { mockMetadataFetcher.downloadWallpaperList() } returns fakeRemoteWallpapers - WallpapersUseCases.LegacyInitializeWallpaperUseCase( + WallpapersUseCases.DefaultInitializeWallpaperUseCase( appStore, mockDownloader, mockFileManager, + mockMetadataFetcher, mockSettings, locale, - possibleWallpapers = fakeRemoteWallpapers ).invoke() appStore.waitUntilIdle() - assertTrue(appStore.state.wallpaperState.availableWallpapers.isEmpty()) + assertEquals(1, appStore.state.wallpaperState.availableWallpapers.size) + assertEquals(Wallpaper.Default, appStore.state.wallpaperState.availableWallpapers[0]) } @Test @@ -161,16 +354,17 @@ class WallpapersUseCasesTest { val fakeRemoteWallpapers = listOf("first", "second", "third").map { name -> makeFakeRemoteWallpaper(TimeRelation.LATER, name) } - every { mockSettings.currentWallpaper } returns "" + every { mockSettings.currentWallpaperName } returns "" coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null + coEvery { mockMetadataFetcher.downloadWallpaperList() } returns fakeRemoteWallpapers - WallpapersUseCases.LegacyInitializeWallpaperUseCase( + WallpapersUseCases.DefaultInitializeWallpaperUseCase( appStore, mockDownloader, mockFileManager, + mockMetadataFetcher, mockSettings, "en-US", - possibleWallpapers = fakeRemoteWallpapers ).invoke() for (fakeRemoteWallpaper in fakeRemoteWallpapers) { @@ -183,16 +377,17 @@ class WallpapersUseCasesTest { val fakeRemoteWallpapers = listOf("first", "second", "third").map { name -> makeFakeRemoteWallpaper(TimeRelation.LATER, name) } - every { mockSettings.currentWallpaper } returns "" + every { mockSettings.currentWallpaperName } returns "" coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null + coEvery { mockMetadataFetcher.downloadWallpaperList() } returns fakeRemoteWallpapers - WallpapersUseCases.LegacyInitializeWallpaperUseCase( + WallpapersUseCases.DefaultInitializeWallpaperUseCase( appStore, mockDownloader, mockFileManager, + mockMetadataFetcher, mockSettings, "en-US", - possibleWallpapers = fakeRemoteWallpapers ).invoke() appStore.waitUntilIdle() @@ -206,21 +401,23 @@ class WallpapersUseCasesTest { makeFakeRemoteWallpaper(TimeRelation.LATER, name) } val possibleWallpapers = listOf(selectedWallpaper) + fakeRemoteWallpapers - every { mockSettings.currentWallpaper } returns selectedWallpaper.name + val allWallpapers = listOf(Wallpaper.Default) + possibleWallpapers + every { mockSettings.currentWallpaperName } returns selectedWallpaper.name coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null + coEvery { mockMetadataFetcher.downloadWallpaperList() } returns possibleWallpapers - WallpapersUseCases.LegacyInitializeWallpaperUseCase( + WallpapersUseCases.DefaultInitializeWallpaperUseCase( appStore, mockDownloader, mockFileManager, + mockMetadataFetcher, mockSettings, "en-US", - possibleWallpapers = possibleWallpapers ).invoke() appStore.waitUntilIdle() assertEquals(selectedWallpaper, appStore.state.wallpaperState.currentWallpaper) - assertEquals(possibleWallpapers, appStore.state.wallpaperState.availableWallpapers) + assertEquals(allWallpapers, appStore.state.wallpaperState.availableWallpapers) } @Test @@ -228,8 +425,8 @@ class WallpapersUseCasesTest { val selectedWallpaper = makeFakeRemoteWallpaper(TimeRelation.LATER, "selected") val slot = slot() coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null - every { mockSettings.currentWallpaper } returns "" - every { mockSettings.currentWallpaper = capture(slot) } just runs + every { mockSettings.currentWallpaperName } returns "" + every { mockSettings.currentWallpaperName = capture(slot) } just runs WallpapersUseCases.DefaultSelectWallpaperUseCase( mockSettings,