diff --git a/app/src/main/java/org/mozilla/fenix/components/UseCases.kt b/app/src/main/java/org/mozilla/fenix/components/UseCases.kt index 281e5872ea73..1802e842051c 100644 --- a/app/src/main/java/org/mozilla/fenix/components/UseCases.kt +++ b/app/src/main/java/org/mozilla/fenix/components/UseCases.kt @@ -5,6 +5,7 @@ package org.mozilla.fenix.components import android.content.Context +import android.os.StrictMode import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.Engine import mozilla.components.concept.fetch.Client @@ -23,6 +24,7 @@ import mozilla.components.feature.tabs.CustomTabsUseCases import mozilla.components.feature.tabs.TabsUseCases import mozilla.components.feature.top.sites.TopSitesStorage import mozilla.components.feature.top.sites.TopSitesUseCases +import mozilla.components.support.locale.LocaleManager import mozilla.components.support.locale.LocaleUseCases import org.mozilla.fenix.components.bookmarks.BookmarksUseCase import org.mozilla.fenix.perf.StrictModeManager @@ -107,6 +109,13 @@ class UseCases( val bookmarksUseCases by lazyMonitored { BookmarksUseCase(bookmarksStorage, historyStorage) } val wallpaperUseCases by lazyMonitored { - WallpapersUseCases(context, appStore, client, strictMode) + // Required to even access context.filesDir property and to retrieve current locale + val (rootStorageDirectory, currentLocale) = strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { + val rootStorageDirectory = context.filesDir + val currentLocale = LocaleManager.getCurrentLocale(context)?.toLanguageTag() + ?: LocaleManager.getSystemDefault().toLanguageTag() + rootStorageDirectory to currentLocale + } + WallpapersUseCases(context, appStore, client, rootStorageDirectory, currentLocale) } } diff --git a/app/src/main/java/org/mozilla/fenix/components/appstate/AppAction.kt b/app/src/main/java/org/mozilla/fenix/components/appstate/AppAction.kt index 23653209d339..0ed6bdb5c371 100644 --- a/app/src/main/java/org/mozilla/fenix/components/appstate/AppAction.kt +++ b/app/src/main/java/org/mozilla/fenix/components/appstate/AppAction.kt @@ -161,5 +161,17 @@ sealed class AppAction : Action { * Indicates that the list of potential wallpapers has changed. */ data class UpdateAvailableWallpapers(val wallpapers: List) : WallpaperAction() + + /** + * Indicates a change in the download state of a wallpaper. Note that this is meant to be + * used for full size images, not thumbnails. + * + * @property wallpaper The wallpaper that is being updated. + * @property imageState The updated image state for the wallpaper. + */ + data class UpdateWallpaperDownloadState( + val wallpaper: Wallpaper, + val imageState: Wallpaper.ImageFileState + ) : WallpaperAction() } } diff --git a/app/src/main/java/org/mozilla/fenix/components/appstate/AppStoreReducer.kt b/app/src/main/java/org/mozilla/fenix/components/appstate/AppStoreReducer.kt index 95273fe43026..5e21bddb2033 100644 --- a/app/src/main/java/org/mozilla/fenix/components/appstate/AppStoreReducer.kt +++ b/app/src/main/java/org/mozilla/fenix/components/appstate/AppStoreReducer.kt @@ -199,6 +199,17 @@ internal object AppStoreReducer { state.copy( wallpaperState = state.wallpaperState.copy(availableWallpapers = action.wallpapers) ) + is AppAction.WallpaperAction.UpdateWallpaperDownloadState -> { + val wallpapers = state.wallpaperState.availableWallpapers.map { + if (it == action.wallpaper) { + it.copy(assetsFileState = action.imageState) + } else { + it + } + } + val wallpaperState = state.wallpaperState.copy(availableWallpapers = wallpapers) + state.copy(wallpaperState = wallpaperState) + } } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/wallpaper/WallpaperSettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/wallpaper/WallpaperSettingsFragment.kt index 6d7f23131f52..61c2d21ca29b 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/wallpaper/WallpaperSettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/wallpaper/WallpaperSettingsFragment.kt @@ -8,10 +8,12 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController +import kotlinx.coroutines.launch import mozilla.components.lib.state.ext.observeAsComposableState import mozilla.components.service.glean.private.NoExtras import org.mozilla.fenix.GleanMetrics.Wallpapers @@ -47,12 +49,18 @@ class WallpaperSettingsFragment : Fragment() { state.wallpaperState.currentWallpaper }.value ?: Wallpaper.Default + var coroutineScope = rememberCoroutineScope() + WallpaperSettings( wallpapers = wallpapers, defaultWallpaper = Wallpaper.Default, - loadWallpaperResource = { wallpaperUseCases.loadBitmap(it) }, selectedWallpaper = currentWallpaper, - onSelectWallpaper = { wallpaperUseCases.selectWallpaper(it) }, + loadWallpaperResource = { + wallpaperUseCases.loadThumbnail(it) + }, + onSelectWallpaper = { + coroutineScope.launch { wallpaperUseCases.selectWallpaper(it) } + }, onViewWallpaper = { findNavController().navigate(R.id.homeFragment) }, ) } 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 52efa7929408..79b76305c333 100644 --- a/app/src/main/java/org/mozilla/fenix/wallpapers/LegacyWallpaperFileManager.kt +++ b/app/src/main/java/org/mozilla/fenix/wallpapers/LegacyWallpaperFileManager.kt @@ -38,7 +38,8 @@ class LegacyWallpaperFileManager( collection = Wallpaper.DefaultCollection, textColor = null, cardColor = null, - thumbnailFileState = Wallpaper.ImageFileState.NotAvailable, + thumbnailFileState = Wallpaper.ImageFileState.Unavailable, + assetsFileState = Wallpaper.ImageFileState.Downloaded, ) } else null } 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 1f1db1e84fe8..81d92ea507b8 100644 --- a/app/src/main/java/org/mozilla/fenix/wallpapers/Wallpaper.kt +++ b/app/src/main/java/org/mozilla/fenix/wallpapers/Wallpaper.kt @@ -21,6 +21,7 @@ data class Wallpaper( val textColor: Long?, val cardColor: Long?, val thumbnailFileState: ImageFileState, + val assetsFileState: ImageFileState, ) { /** * Type that represents a collection that a [Wallpaper] belongs to. @@ -67,6 +68,7 @@ data class Wallpaper( textColor = null, cardColor = null, thumbnailFileState = ImageFileState.Downloaded, + assetsFileState = ImageFileState.Downloaded, ) /** @@ -106,7 +108,7 @@ data class Wallpaper( * Defines the download state of wallpaper asset. */ enum class ImageFileState { - NotAvailable, + Unavailable, Downloading, Downloaded, Error, 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 4f304626aa67..5041ae755f57 100644 --- a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperDownloader.kt +++ b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperDownloader.kt @@ -37,30 +37,39 @@ class WallpaperDownloader( * and will be stored in the local path: * wallpapers//.png */ - suspend fun downloadWallpaper(wallpaper: Wallpaper) = withContext(dispatcher) { - listOf(Wallpaper.ImageType.Portrait, Wallpaper.ImageType.Landscape).map { imageType -> - wallpaper.downloadAsset(imageType) + suspend fun downloadWallpaper(wallpaper: Wallpaper): Wallpaper.ImageFileState = withContext(dispatcher) { + val portraitResult = downloadAsset(wallpaper, Wallpaper.ImageType.Portrait) + val landscapeResult = downloadAsset(wallpaper, Wallpaper.ImageType.Landscape) + return@withContext if (portraitResult == Wallpaper.ImageFileState.Downloaded && + landscapeResult == Wallpaper.ImageFileState.Downloaded + ) { + Wallpaper.ImageFileState.Downloaded + } else { + Wallpaper.ImageFileState.Error } } /** * Downloads a thumbnail for a wallpaper from the network. This is expected to be found remotely * at: - * ///.png + * ///thumbnail.png * and stored locally at: - * wallpapers//.png + * wallpapers//thumbnail.png */ suspend fun downloadThumbnail(wallpaper: Wallpaper): Wallpaper.ImageFileState = withContext(dispatcher) { - wallpaper.downloadAsset(Wallpaper.ImageType.Thumbnail) + downloadAsset(wallpaper, Wallpaper.ImageType.Thumbnail) } - private suspend fun Wallpaper.downloadAsset( + private suspend fun downloadAsset( + wallpaper: Wallpaper, imageType: Wallpaper.ImageType ): Wallpaper.ImageFileState = withContext(dispatcher) { - val localFile = File(storageRootDirectory, getLocalPath(name, imageType)) - if (localFile.exists()) return@withContext Wallpaper.ImageFileState.Downloaded + val localFile = File(storageRootDirectory, getLocalPath(wallpaper.name, imageType)) + if (localFile.exists()) { + return@withContext Wallpaper.ImageFileState.Downloaded + } - val remotePath = "${collection.name}/${name}/${imageType.lowercase()}.png" + val remotePath = "${wallpaper.collection.name}/${wallpaper.name}/${imageType.lowercase()}.png" val request = Request( url = "$remoteHost/$remotePath", method = Request.Method.GET @@ -83,7 +92,7 @@ class WallpaperDownloader( localFile.delete() } } - Wallpaper.ImageFileState.Downloaded + Wallpaper.ImageFileState.Error } } } 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 9c7316645803..5d0e628889b9 100644 --- a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperFileManager.kt +++ b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperFileManager.kt @@ -31,20 +31,21 @@ class WallpaperFileManager( * 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(storageRootDirectory, it).exists() }) { + if (allAssetsExist(name)) { Wallpaper( name = name, collection = Wallpaper.DefaultCollection, textColor = null, cardColor = null, thumbnailFileState = Wallpaper.ImageFileState.Downloaded, + assetsFileState = Wallpaper.ImageFileState.Downloaded, ) } else null } - private fun getAllLocalWallpaperPaths(name: String): List = - Wallpaper.ImageType.values().map { orientation -> - getLocalPath(name, orientation) + private fun allAssetsExist(name: String): Boolean = + Wallpaper.ImageType.values().all { type -> + File(storageRootDirectory, getLocalPath(name, type)).exists() } /** @@ -60,4 +61,11 @@ class WallpaperFileManager( } } } + + /** + * Checks whether all the assets for a wallpaper exist on the file system. + */ + suspend fun wallpaperImagesExist(wallpaper: Wallpaper): Boolean = withContext(Dispatchers.IO) { + return@withContext allAssetsExist(wallpaper.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 cd6668ca0b2c..4f6139c7750d 100644 --- a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperMetadataFetcher.kt +++ b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperMetadataFetcher.kt @@ -83,7 +83,8 @@ class WallpaperMetadataFetcher( textColor = getArgbValueAsLong("text-color"), cardColor = getArgbValueAsLong("card-color"), collection = collection, - thumbnailFileState = Wallpaper.ImageFileState.NotAvailable, + thumbnailFileState = Wallpaper.ImageFileState.Unavailable, + assetsFileState = Wallpaper.ImageFileState.Unavailable, ) } } 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 dba38502b8e3..6bfb2ef08f09 100644 --- a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpapersUseCases.kt +++ b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpapersUseCases.kt @@ -8,19 +8,16 @@ import android.content.Context import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.BitmapFactory -import android.os.StrictMode import androidx.annotation.VisibleForTesting 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 import org.mozilla.fenix.components.appstate.AppAction import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.perf.StrictModeManager import org.mozilla.fenix.utils.Settings import java.io.File import java.util.Date @@ -31,7 +28,8 @@ import java.util.Date * @param context Used for various file and configuration checks. * @param store Will receive dispatches of metadata updates like the currently selected wallpaper. * @param client Handles downloading wallpapers and their metadata. - * @param strictMode Required for determining some device state like current locale and file paths. + * @param storageRootDirectory The top level app-local storage directory. + * @param currentLocale The locale currently being used on the device. * * @property initialize Usecase for initializing wallpaper feature. Should usually be called early * in the app's lifetime to ensure that any potential long-running tasks can complete quickly. @@ -42,20 +40,13 @@ class WallpapersUseCases( context: Context, store: AppStore, client: Client, - strictMode: StrictModeManager, - filesDir: File, + storageRootDirectory: File, + currentLocale: String, ) { + private val downloader = WallpaperDownloader(storageRootDirectory, client) + private val fileManager = WallpaperFileManager(storageRootDirectory) val initialize: InitializeWallpapersUseCase by lazy { 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, @@ -66,13 +57,7 @@ class WallpapersUseCases( 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 fileManager = LegacyWallpaperFileManager(storageRootDirectory) val downloader = LegacyWallpaperDownloader(context, client) LegacyInitializeWallpaperUseCase( store = store, @@ -92,12 +77,18 @@ class WallpapersUseCases( } val loadThumbnail: LoadThumbnailUseCase by lazy { if (FeatureFlags.wallpaperV2Enabled) { - DefaultLoadThumbnailUseCase(filesDir) + DefaultLoadThumbnailUseCase(storageRootDirectory) } else { LegacyLoadThumbnailUseCase(context) } } - val selectWallpaper: SelectWallpaperUseCase by lazy { DefaultSelectWallpaperUseCase(context.settings(), store) } + val selectWallpaper: SelectWallpaperUseCase by lazy { + if (FeatureFlags.wallpaperV2Enabled) { + DefaultSelectWallpaperUseCase(context.settings(), store, fileManager, downloader) + } else { + LegacySelectWallpaperUseCase(context.settings(), store) + } + } /** * Contract for usecases that initialize the wallpaper feature. @@ -183,21 +174,24 @@ class WallpapersUseCases( collection = firefoxClassicCollection, textColor = null, cardColor = null, - thumbnailFileState = Wallpaper.ImageFileState.NotAvailable, + thumbnailFileState = Wallpaper.ImageFileState.Unavailable, + assetsFileState = Wallpaper.ImageFileState.Downloaded, ), Wallpaper( name = Wallpaper.ceruleanName, collection = firefoxClassicCollection, textColor = null, cardColor = null, - thumbnailFileState = Wallpaper.ImageFileState.NotAvailable, + thumbnailFileState = Wallpaper.ImageFileState.Unavailable, + assetsFileState = Wallpaper.ImageFileState.Downloaded, ), Wallpaper( name = Wallpaper.sunriseName, collection = firefoxClassicCollection, textColor = null, cardColor = null, - thumbnailFileState = Wallpaper.ImageFileState.NotAvailable, + thumbnailFileState = Wallpaper.ImageFileState.Unavailable, + assetsFileState = Wallpaper.ImageFileState.Downloaded, ), ) private val remoteWallpapers: List = listOf( @@ -206,14 +200,16 @@ class WallpapersUseCases( collection = firefoxClassicCollection, textColor = null, cardColor = null, - thumbnailFileState = Wallpaper.ImageFileState.NotAvailable, + thumbnailFileState = Wallpaper.ImageFileState.Unavailable, + assetsFileState = Wallpaper.ImageFileState.Downloaded, ), Wallpaper( name = Wallpaper.beachVibeName, collection = firefoxClassicCollection, textColor = null, cardColor = null, - thumbnailFileState = Wallpaper.ImageFileState.NotAvailable, + thumbnailFileState = Wallpaper.ImageFileState.Unavailable, + assetsFileState = Wallpaper.ImageFileState.Downloaded, ), ) val allWallpapers = listOf(Wallpaper.Default) + localWallpapers + remoteWallpapers @@ -246,8 +242,6 @@ class WallpapersUseCases( possibleWallpapers ) - possibleWallpapers.forEach { downloader.downloadWallpaper(it) } - val wallpapersWithUpdatedThumbnailState = possibleWallpapers.map { wallpaper -> val result = downloader.downloadThumbnail(wallpaper) wallpaper.copy(thumbnailFileState = result) @@ -390,13 +384,13 @@ class WallpapersUseCases( } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - internal class LegacyLoadThumbnailUseCase(private val context: Context): LoadThumbnailUseCase { + internal class LegacyLoadThumbnailUseCase(private val context: Context) : LoadThumbnailUseCase { override suspend fun invoke(wallpaper: Wallpaper): Bitmap? = LegacyLoadBitmapUseCase(context).invoke(wallpaper) } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - internal class DefaultLoadThumbnailUseCase(private val filesDir: File): LoadThumbnailUseCase { + internal class DefaultLoadThumbnailUseCase(private val filesDir: File) : LoadThumbnailUseCase { override suspend fun invoke(wallpaper: Wallpaper): Bitmap? = withContext(Dispatchers.IO) { Result.runCatching { val path = Wallpaper.getLocalPath(wallpaper.name, Wallpaper.ImageType.Thumbnail) @@ -417,20 +411,58 @@ class WallpapersUseCases( * * @param wallpaper The selected wallpaper. */ - operator fun invoke(wallpaper: Wallpaper) + suspend operator fun invoke(wallpaper: Wallpaper) + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal class LegacySelectWallpaperUseCase( + private val settings: Settings, + private val store: AppStore, + ) : SelectWallpaperUseCase { + /** + * Select a new wallpaper. Storage and the store will be updated appropriately. + * + * @param wallpaper The selected wallpaper. + */ + override suspend fun invoke(wallpaper: Wallpaper) { + settings.currentWallpaperName = wallpaper.name + store.dispatch(AppAction.WallpaperAction.UpdateCurrentWallpaper(wallpaper)) + Wallpapers.wallpaperSelected.record( + Wallpapers.WallpaperSelectedExtra( + name = wallpaper.name, + themeCollection = wallpaper.collection.name + ) + ) + } } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) internal class DefaultSelectWallpaperUseCase( private val settings: Settings, private val store: AppStore, + private val fileManager: WallpaperFileManager, + private val downloader: WallpaperDownloader, ) : SelectWallpaperUseCase { /** * Select a new wallpaper. Storage and the store will be updated appropriately. * * @param wallpaper The selected wallpaper. */ - override fun invoke(wallpaper: Wallpaper) { + override suspend fun invoke(wallpaper: Wallpaper) { + if (fileManager.wallpaperImagesExist(wallpaper)) { + selectWallpaper(wallpaper) + dispatchDownloadState(wallpaper, Wallpaper.ImageFileState.Downloaded) + } else { + dispatchDownloadState(wallpaper, Wallpaper.ImageFileState.Downloading) + val result = downloader.downloadWallpaper(wallpaper) + dispatchDownloadState(wallpaper, result) + if (result == Wallpaper.ImageFileState.Downloaded) { + selectWallpaper(wallpaper) + } + } + } + + private fun selectWallpaper(wallpaper: Wallpaper) { settings.currentWallpaperName = wallpaper.name store.dispatch(AppAction.WallpaperAction.UpdateCurrentWallpaper(wallpaper)) Wallpapers.wallpaperSelected.record( @@ -440,5 +472,9 @@ class WallpapersUseCases( ) ) } + + private fun dispatchDownloadState(wallpaper: Wallpaper, downloadState: Wallpaper.ImageFileState) { + store.dispatch(AppAction.WallpaperAction.UpdateWallpaperDownloadState(wallpaper, downloadState)) + } } } diff --git a/app/src/test/java/org/mozilla/fenix/wallpapers/LegacyWallpaperFileManagerTest.kt b/app/src/test/java/org/mozilla/fenix/wallpapers/LegacyWallpaperFileManagerTest.kt index c490f9ff7092..890403a4a8a9 100644 --- a/app/src/test/java/org/mozilla/fenix/wallpapers/LegacyWallpaperFileManagerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/wallpapers/LegacyWallpaperFileManagerTest.kt @@ -98,7 +98,8 @@ class LegacyWallpaperFileManagerTest { name = name, textColor = null, cardColor = null, - thumbnailFileState = Wallpaper.ImageFileState.NotAvailable, + thumbnailFileState = Wallpaper.ImageFileState.Unavailable, + assetsFileState = Wallpaper.ImageFileState.Downloaded, collection = Wallpaper.Collection( name = Wallpaper.defaultName, heading = null, 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 f804c056b619..113e2490ebc2 100644 --- a/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperDownloaderTest.kt +++ b/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperDownloaderTest.kt @@ -74,10 +74,7 @@ class WallpaperDownloaderTest { @Test fun `GIVEN that thumbnail request is successful WHEN downloading THEN file is created in expected location`() = runTest { val wallpaper = generateWallpaper() - val thumbnailRequest = Request( - url = "$remoteHost/${wallpaper.collection.name}/${wallpaper.name}/thumbnail.png", - method = Request.Method.GET - ) + val thumbnailRequest = wallpaper.generateRequest("thumbnail") val mockThumbnailResponse = mockk() every { mockThumbnailResponse.status } returns 200 every { mockThumbnailResponse.body } returns Response.Body(wallpaperBytes.byteInputStream()) @@ -132,7 +129,8 @@ class WallpaperDownloaderTest { collection = wallpaperCollection, textColor = null, cardColor = null, - thumbnailFileState = Wallpaper.ImageFileState.NotAvailable + thumbnailFileState = Wallpaper.ImageFileState.Unavailable, + assetsFileState = Wallpaper.ImageFileState.Unavailable, ) private fun Wallpaper.generateRequest(type: String) = Request( 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 ecda1aee56be..51833a938726 100644 --- a/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperFileManagerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperFileManagerTest.kt @@ -3,6 +3,7 @@ package org.mozilla.fenix.wallpapers import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Rule @@ -109,6 +110,30 @@ class WallpaperFileManagerTest { assertTrue(getAllFiles(unavailableName).none { it.exists() }) } + @Test + fun `WHEN both wallpaper assets exist THEN the file lookup will succeed`() = runTest { + val wallpaper = generateWallpaper("name") + createAllFiles(wallpaper.name) + + val result = fileManager.wallpaperImagesExist(wallpaper) + + assertTrue(result) + } + + @Test + fun `WHEN at least one wallpaper asset does not exist THEN the file lookup will fail`() = runTest { + val wallpaper = generateWallpaper("name") + val allFiles = getAllFiles(wallpaper.name) + (0 until (allFiles.size - 1)).forEach { + allFiles[it].mkdirs() + allFiles[it].createNewFile() + } + + val result = fileManager.wallpaperImagesExist(wallpaper) + + assertFalse(result) + } + private fun createAllFiles(name: String) { for (file in getAllFiles(name)) { file.mkdirs() @@ -131,6 +156,7 @@ class WallpaperFileManagerTest { textColor = null, cardColor = null, thumbnailFileState = Wallpaper.ImageFileState.Downloaded, - collection = Wallpaper.DefaultCollection + assetsFileState = Wallpaper.ImageFileState.Downloaded, + collection = Wallpaper.DefaultCollection, ) } 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 dc162933280d..ad367c9f5244 100644 --- a/app/src/test/java/org/mozilla/fenix/wallpapers/WallpapersUseCasesTest.kt +++ b/app/src/test/java/org/mozilla/fenix/wallpapers/WallpapersUseCasesTest.kt @@ -21,6 +21,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mozilla.fenix.GleanMetrics.Wallpapers import org.mozilla.fenix.components.AppStore +import org.mozilla.fenix.components.appstate.AppAction import org.mozilla.fenix.utils.Settings import java.util.Calendar import java.util.Date @@ -313,11 +314,11 @@ class WallpapersUseCasesTest { } val expiredWallpaper = makeFakeRemoteWallpaper(TimeRelation.BEFORE, "expired") val allWallpapers = listOf(expiredWallpaper) + fakeRemoteWallpapers + every { mockSettings.currentWallpaperName } returns "expired" coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns expiredWallpaper coEvery { mockMetadataFetcher.downloadWallpaperList() } returns allWallpapers coEvery { mockDownloader.downloadThumbnail(any()) } returns Wallpaper.ImageFileState.Downloaded - WallpapersUseCases.DefaultInitializeWallpaperUseCase( appStore, mockDownloader, @@ -360,7 +361,7 @@ class WallpapersUseCasesTest { } @Test - fun `GIVEN available wallpapers WHEN invoking initialize use case THEN available wallpapers downloaded`() = runTest { + fun `GIVEN available wallpapers WHEN invoking initialize use case THEN available wallpaper thumbnails downloaded`() = runTest { val fakeRemoteWallpapers = listOf("first", "second", "third").map { name -> makeFakeRemoteWallpaper(TimeRelation.LATER, name) } @@ -379,12 +380,12 @@ class WallpapersUseCasesTest { ).invoke() for (fakeRemoteWallpaper in fakeRemoteWallpapers) { - coVerify { mockDownloader.downloadWallpaper(fakeRemoteWallpaper) } + coVerify { mockDownloader.downloadThumbnail(fakeRemoteWallpaper) } } } @Test - fun `GIVEN available wallpapers WHEN invoking initialize use case THEN thumbnails downloaded and store state reflects that`() = runTest { + fun `GIVEN available wallpapers WHEN invoking initialize use case THEN thumbnails downloaded and the store state is updated to reflect that`() = runTest { val fakeRemoteWallpapers = listOf("first", "second", "third").map { name -> makeFakeRemoteWallpaper(TimeRelation.LATER, name) } @@ -406,13 +407,15 @@ class WallpapersUseCasesTest { coVerify { mockDownloader.downloadThumbnail(fakeRemoteWallpaper) } } appStore.waitUntilIdle() - assertTrue(appStore.state.wallpaperState.availableWallpapers.all { - it.thumbnailFileState == Wallpaper.ImageFileState.Downloaded - }) + assertTrue( + appStore.state.wallpaperState.availableWallpapers.all { + it.thumbnailFileState == Wallpaper.ImageFileState.Downloaded + } + ) } @Test - fun `GIVEN thumbnail download fails WHEN invoking initialize use case THEN store state reflects that`() = runTest { + fun `GIVEN thumbnail download fails WHEN invoking initialize use case THEN the store state is updated to reflect that`() = runTest { val fakeRemoteWallpapers = listOf("first", "second", "third").map { name -> makeFakeRemoteWallpaper(TimeRelation.LATER, name) } @@ -490,14 +493,14 @@ class WallpapersUseCasesTest { } @Test - fun `WHEN selected wallpaper usecase invoked THEN storage updated and store receives dispatch`() { + fun `WHEN legacy selected wallpaper usecase invoked THEN storage updated and store receives dispatch`() = runTest { val selectedWallpaper = makeFakeRemoteWallpaper(TimeRelation.LATER, "selected") val slot = slot() coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null every { mockSettings.currentWallpaperName } returns "" every { mockSettings.currentWallpaperName = capture(slot) } just runs - WallpapersUseCases.DefaultSelectWallpaperUseCase( + WallpapersUseCases.LegacySelectWallpaperUseCase( mockSettings, appStore ).invoke(selectedWallpaper) @@ -508,6 +511,73 @@ class WallpapersUseCasesTest { assertEquals(selectedWallpaper.name, Wallpapers.wallpaperSelected.testGetValue()?.first()?.extra?.get("name")!!) } + @Test + fun `GIVEN wallpaper downloaded WHEN selecting a wallpaper THEN storage updated and store receives dispatch`() = runTest { + val selectedWallpaper = makeFakeRemoteWallpaper(TimeRelation.LATER, "selected") + val slot = slot() + coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null + every { mockSettings.currentWallpaperName } returns "" + every { mockSettings.currentWallpaperName = capture(slot) } just runs + coEvery { mockFileManager.wallpaperImagesExist(selectedWallpaper) } returns true + + WallpapersUseCases.DefaultSelectWallpaperUseCase( + mockSettings, + appStore, + mockFileManager, + mockDownloader, + ).invoke(selectedWallpaper) + + appStore.waitUntilIdle() + assertEquals(selectedWallpaper.name, slot.captured) + assertEquals(selectedWallpaper, appStore.state.wallpaperState.currentWallpaper) + assertEquals(selectedWallpaper.name, Wallpapers.wallpaperSelected.testGetValue()?.first()?.extra?.get("name")!!) + } + + @Test + fun `GIVEN wallpaper is not downloaded WHEN selecting a wallpaper and download succeeds THEN storage updated and store receives dispatch`() = runTest { + val selectedWallpaper = makeFakeRemoteWallpaper(TimeRelation.LATER, "selected") + val slot = slot() + val mockStore = mockk(relaxed = true) + coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null + every { mockSettings.currentWallpaperName } returns "" + every { mockSettings.currentWallpaperName = capture(slot) } just runs + coEvery { mockFileManager.wallpaperImagesExist(selectedWallpaper) } returns false + coEvery { mockDownloader.downloadWallpaper(selectedWallpaper) } returns Wallpaper.ImageFileState.Downloaded + + WallpapersUseCases.DefaultSelectWallpaperUseCase( + mockSettings, + mockStore, + mockFileManager, + mockDownloader, + ).invoke(selectedWallpaper) + + verify { mockStore.dispatch(AppAction.WallpaperAction.UpdateWallpaperDownloadState(selectedWallpaper, Wallpaper.ImageFileState.Downloading)) } + verify { mockStore.dispatch(AppAction.WallpaperAction.UpdateWallpaperDownloadState(selectedWallpaper, Wallpaper.ImageFileState.Downloaded)) } + verify { mockStore.dispatch(AppAction.WallpaperAction.UpdateCurrentWallpaper(selectedWallpaper)) } + } + + @Test + fun `GIVEN wallpaper is not downloaded WHEN selecting a wallpaper and any download fails THEN wallpaper not set and store receives dispatch`() = runTest { + val selectedWallpaper = makeFakeRemoteWallpaper(TimeRelation.LATER, "selected") + val slot = slot() + val mockStore = mockk(relaxed = true) + coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null + every { mockSettings.currentWallpaperName } returns "" + every { mockSettings.currentWallpaperName = capture(slot) } just runs + coEvery { mockFileManager.wallpaperImagesExist(selectedWallpaper) } returns false + coEvery { mockDownloader.downloadWallpaper(selectedWallpaper) } returns Wallpaper.ImageFileState.Error + + WallpapersUseCases.DefaultSelectWallpaperUseCase( + mockSettings, + mockStore, + mockFileManager, + mockDownloader, + ).invoke(selectedWallpaper) + + verify { mockStore.dispatch(AppAction.WallpaperAction.UpdateWallpaperDownloadState(selectedWallpaper, Wallpaper.ImageFileState.Downloading)) } + verify { mockStore.dispatch(AppAction.WallpaperAction.UpdateWallpaperDownloadState(selectedWallpaper, Wallpaper.ImageFileState.Error)) } + } + private enum class TimeRelation { BEFORE, NOW, @@ -543,7 +613,8 @@ class WallpapersUseCasesTest { ), textColor = null, cardColor = null, - thumbnailFileState = Wallpaper.ImageFileState.NotAvailable, + thumbnailFileState = Wallpaper.ImageFileState.Unavailable, + assetsFileState = Wallpaper.ImageFileState.Unavailable, ) } else { Wallpaper( @@ -559,7 +630,8 @@ class WallpapersUseCasesTest { ), textColor = null, cardColor = null, - thumbnailFileState = Wallpaper.ImageFileState.NotAvailable, + thumbnailFileState = Wallpaper.ImageFileState.Unavailable, + assetsFileState = Wallpaper.ImageFileState.Unavailable, ) } }