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 b566b352a..190ef76ef 100644
--- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
+++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
@@ -207,6 +207,14 @@ class Settings(private val appContext: Context) : PreferencesHolder {
default = 0,
)
+ /**
+ * Indicates if the current legacy wallpaper should be migrated.
+ */
+ var shouldMigrateLegacyWallpaper by booleanPreference(
+ key = appContext.getPreferenceKey(R.string.pref_key_should_migrate_wallpaper),
+ default = true,
+ )
+
/**
* Indicates if the wallpaper onboarding dialog should be shown.
*/
diff --git a/app/src/main/java/org/mozilla/fenix/wallpapers/LegacyWallpaperMigration.kt b/app/src/main/java/org/mozilla/fenix/wallpapers/LegacyWallpaperMigration.kt
new file mode 100644
index 000000000..993e26b72
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/wallpapers/LegacyWallpaperMigration.kt
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.wallpapers
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import mozilla.components.support.base.log.logger.Logger
+import java.io.File
+import java.io.IOException
+
+/**
+ * Manages the migration of legacy wallpapers to the new paths
+ *
+ * @property storageRootDirectory The top level app-local storage directory.
+ */
+class LegacyWallpaperMigration(
+ private val storageRootDirectory: File,
+) {
+ /**
+ * Migrate the legacy wallpaper to the new path and delete the remaining legacy files.
+ *
+ * @param wallpaperName Name of the wallpaper to be migrated.
+ */
+ suspend fun migrateLegacyWallpaper(
+ wallpaperName: String,
+ ) = withContext(Dispatchers.IO) {
+ val legacyPortraitFile =
+ File(storageRootDirectory, "wallpapers/portrait/light/$wallpaperName.png")
+ val legacyLandscapeFile =
+ File(storageRootDirectory, "wallpapers/landscape/light/$wallpaperName.png")
+ // If any of portrait or landscape files of the wallpaper are missing, then we shouldn't
+ // migrate it
+ if (!legacyLandscapeFile.exists() || !legacyPortraitFile.exists()) {
+ return@withContext
+ }
+ // Directory where the legacy wallpaper files should be migrated
+ val targetDirectory = "wallpapers/${wallpaperName.lowercase()}"
+
+ try {
+ // Use the portrait file as thumbnail
+ legacyPortraitFile.copyTo(
+ File(
+ storageRootDirectory,
+ "$targetDirectory/thumbnail.png",
+ ),
+ )
+ // Copy the portrait file
+ legacyPortraitFile.copyTo(
+ File(
+ storageRootDirectory,
+ "$targetDirectory/portrait.png",
+ ),
+ )
+ // Copy the landscape file
+ legacyLandscapeFile.copyTo(
+ File(
+ storageRootDirectory,
+ "$targetDirectory/landscape.png",
+ ),
+ )
+ } catch (e: IOException) {
+ Logger.error("Failed to migrate legacy wallpaper", e)
+ }
+
+ // Delete the remaining legacy files
+ File(storageRootDirectory, "wallpapers/portrait").deleteRecursively()
+ File(storageRootDirectory, "wallpapers/landscape").deleteRecursively()
+ }
+}
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 6e5cd7ef4..74751736c 100644
--- a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpapersUseCases.kt
+++ b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpapersUseCases.kt
@@ -47,11 +47,13 @@ class WallpapersUseCases(
val initialize: InitializeWallpapersUseCase by lazy {
if (FeatureFlags.wallpaperV2Enabled) {
val metadataFetcher = WallpaperMetadataFetcher(client)
+ val migrationHelper = LegacyWallpaperMigration(storageRootDirectory)
DefaultInitializeWallpaperUseCase(
store = store,
downloader = downloader,
fileManager = fileManager,
metadataFetcher = metadataFetcher,
+ migrationHelper = migrationHelper,
settings = context.settings(),
currentLocale = currentLocale,
)
@@ -222,12 +224,14 @@ class WallpapersUseCases(
}
}
+ @Suppress("LongParameterList")
@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 migrationHelper: LegacyWallpaperMigration,
private val settings: Settings,
private val currentLocale: String,
) : InitializeWallpapersUseCase {
@@ -236,6 +240,10 @@ class WallpapersUseCases(
store.dispatch(AppAction.WallpaperAction.UpdateCurrentWallpaper(it))
}
val currentWallpaperName = withContext(Dispatchers.IO) { settings.currentWallpaperName }
+ if (settings.shouldMigrateLegacyWallpaper) {
+ migrationHelper.migrateLegacyWallpaper(currentWallpaperName)
+ settings.shouldMigrateLegacyWallpaper = false
+ }
val possibleWallpapers = metadataFetcher.downloadWallpaperList().filter {
!it.isExpired() && it.isAvailableInLocale()
}
diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml
index 09d692a9e..cc96aead2 100644
--- a/app/src/main/res/values/preference_keys.xml
+++ b/app/src/main/res/values/preference_keys.xml
@@ -207,6 +207,7 @@
pref_key_current_wallpaper_text_color
pref_key_current_wallpaper_card_color
pref_key_wallpapers_onboarding
+ pref_key_should_migrate_wallpaper
pref_key_encryption_key_generated
diff --git a/app/src/test/java/org/mozilla/fenix/wallpapers/LegacyWallpaperMigrationTest.kt b/app/src/test/java/org/mozilla/fenix/wallpapers/LegacyWallpaperMigrationTest.kt
new file mode 100644
index 000000000..8d7dab271
--- /dev/null
+++ b/app/src/test/java/org/mozilla/fenix/wallpapers/LegacyWallpaperMigrationTest.kt
@@ -0,0 +1,117 @@
+package org.mozilla.fenix.wallpapers
+
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import java.io.File
+
+class LegacyWallpaperMigrationTest {
+ @Rule
+ @JvmField
+ val tempFolder = TemporaryFolder()
+ private lateinit var wallpapersFolder: File
+ private lateinit var migrationHelper: LegacyWallpaperMigration
+ private lateinit var portraitLightFolder: File
+ private lateinit var portraitDarkFolder: File
+ private lateinit var landscapeLightFolder: File
+ private lateinit var landscapeDarkFolder: File
+
+ @Before
+ fun setup() {
+ wallpapersFolder = File(tempFolder.root, "wallpapers")
+ migrationHelper = LegacyWallpaperMigration(
+ storageRootDirectory = tempFolder.root,
+ )
+ }
+
+ @Test
+ fun `WHEN the legacy wallpaper is migrated THEN the legacy wallpapers are deleted`() = runTest {
+ val wallpaperName = "wallpaper1"
+
+ createAllLegacyFiles(wallpaperName)
+
+ migrationHelper.migrateLegacyWallpaper(wallpaperName)
+
+ assertTrue(getAllFiles(wallpaperName).all { it.exists() })
+ assertFalse(File(portraitLightFolder, "$wallpaperName.png").exists())
+ assertFalse(File(portraitDarkFolder, "$wallpaperName.png").exists())
+ assertFalse(File(landscapeLightFolder, "$wallpaperName.png").exists())
+ assertFalse(File(landscapeDarkFolder, "$wallpaperName.png").exists())
+ }
+
+ @Test
+ fun `GIVEN landscape legacy wallpaper is missing WHEN the wallpapers are migrated THEN the wallpaper is not migrated`() =
+ runTest {
+ val portraitOnlyWallpaperName = "portraitOnly"
+ val completeWallpaperName = "legacy"
+ createAllLegacyFiles(completeWallpaperName)
+ File(landscapeLightFolder, "$portraitOnlyWallpaperName.png").apply {
+ createNewFile()
+ }
+ File(landscapeDarkFolder, "$portraitOnlyWallpaperName.png").apply {
+ createNewFile()
+ }
+
+ migrationHelper.migrateLegacyWallpaper(portraitOnlyWallpaperName)
+ migrationHelper.migrateLegacyWallpaper(completeWallpaperName)
+
+ assertTrue(getAllFiles(completeWallpaperName).all { it.exists() })
+ assertFalse(getAllFiles(portraitOnlyWallpaperName).any { it.exists() })
+ }
+
+ @Test
+ fun `GIVEN portrait legacy wallpaper is missing WHEN the wallpapers are migrated THEN the wallpaper is not migrated`() =
+ runTest {
+ val landscapeOnlyWallpaperName = "portraitOnly"
+ val completeWallpaperName = "legacy"
+ createAllLegacyFiles(completeWallpaperName)
+ File(portraitLightFolder, "$landscapeOnlyWallpaperName.png").apply {
+ createNewFile()
+ }
+ File(portraitDarkFolder, "$landscapeOnlyWallpaperName.png").apply {
+ createNewFile()
+ }
+
+ migrationHelper.migrateLegacyWallpaper(landscapeOnlyWallpaperName)
+ migrationHelper.migrateLegacyWallpaper(completeWallpaperName)
+
+ assertTrue(getAllFiles(completeWallpaperName).all { it.exists() })
+ assertFalse(getAllFiles(landscapeOnlyWallpaperName).any { it.exists() })
+ }
+
+ private fun createAllLegacyFiles(name: String) {
+ if (!this::portraitLightFolder.isInitialized) {
+ portraitLightFolder = tempFolder.newFolder("wallpapers", "portrait", "light")
+ portraitDarkFolder = tempFolder.newFolder("wallpapers", "portrait", "dark")
+ landscapeLightFolder = tempFolder.newFolder("wallpapers", "landscape", "light")
+ landscapeDarkFolder = tempFolder.newFolder("wallpapers", "landscape", "dark")
+ }
+
+ File(portraitLightFolder, "$name.png").apply {
+ createNewFile()
+ }
+ File(landscapeLightFolder, "$name.png").apply {
+ createNewFile()
+ }
+ File(portraitDarkFolder, "$name.png").apply {
+ createNewFile()
+ }
+ File(landscapeDarkFolder, "$name.png").apply {
+ createNewFile()
+ }
+ }
+
+ private fun getAllFiles(name: String): List {
+ val folder = File(wallpapersFolder, name)
+ return listOf(
+ folder,
+ File(folder, "portrait.png"),
+ File(folder, "landscape.png"),
+ File(folder, "thumbnail.png"),
+ )
+ }
+}
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 7308ca0f8..e9ef700f3 100644
--- a/app/src/test/java/org/mozilla/fenix/wallpapers/WallpapersUseCasesTest.kt
+++ b/app/src/test/java/org/mozilla/fenix/wallpapers/WallpapersUseCasesTest.kt
@@ -42,11 +42,16 @@ class WallpapersUseCasesTest {
every { currentWallpaperTextColor = any() } just Runs
every { currentWallpaperCardColor } returns 0L
every { currentWallpaperCardColor = any() } just Runs
+ every { shouldMigrateLegacyWallpaper } returns false
+ every { shouldMigrateLegacyWallpaper = any() } just Runs
}
private val mockLegacyDownloader = mockk(relaxed = true)
private val mockLegacyFileManager = mockk {
every { clean(any(), any()) } just runs
}
+ private val mockMigrationHelper = mockk {
+ coEvery { migrateLegacyWallpaper(any()) } just runs
+ }
private val mockMetadataFetcher = mockk()
private val mockDownloader = mockk {
@@ -252,6 +257,7 @@ class WallpapersUseCasesTest {
mockDownloader,
mockFileManager,
mockMetadataFetcher,
+ mockMigrationHelper,
mockSettings,
"en-US",
).invoke()
@@ -275,6 +281,7 @@ class WallpapersUseCasesTest {
mockDownloader,
mockFileManager,
mockMetadataFetcher,
+ mockMigrationHelper,
mockSettings,
"en-US",
).invoke()
@@ -302,6 +309,7 @@ class WallpapersUseCasesTest {
mockDownloader,
mockFileManager,
mockMetadataFetcher,
+ mockMigrationHelper,
mockSettings,
"en-US",
).invoke()
@@ -329,6 +337,7 @@ class WallpapersUseCasesTest {
mockDownloader,
mockFileManager,
mockMetadataFetcher,
+ mockMigrationHelper,
mockSettings,
"en-US",
).invoke()
@@ -356,6 +365,7 @@ class WallpapersUseCasesTest {
mockDownloader,
mockFileManager,
mockMetadataFetcher,
+ mockMigrationHelper,
mockSettings,
locale,
).invoke()
@@ -380,6 +390,7 @@ class WallpapersUseCasesTest {
mockDownloader,
mockFileManager,
mockMetadataFetcher,
+ mockMigrationHelper,
mockSettings,
"en-US",
).invoke()
@@ -404,6 +415,7 @@ class WallpapersUseCasesTest {
mockDownloader,
mockFileManager,
mockMetadataFetcher,
+ mockMigrationHelper,
mockSettings,
"en-US",
).invoke()
@@ -436,6 +448,7 @@ class WallpapersUseCasesTest {
mockDownloader,
mockFileManager,
mockMetadataFetcher,
+ mockMigrationHelper,
mockSettings,
"en-US",
).invoke()
@@ -460,6 +473,7 @@ class WallpapersUseCasesTest {
mockDownloader,
mockFileManager,
mockMetadataFetcher,
+ mockMigrationHelper,
mockSettings,
"en-US",
).invoke()
@@ -485,6 +499,7 @@ class WallpapersUseCasesTest {
mockDownloader,
mockFileManager,
mockMetadataFetcher,
+ mockMigrationHelper,
mockSettings,
"en-US",
).invoke()