Fixes #26245: refactor the WallpaperManager as several WallpaperUseCases
This commit is contained in:
parent
d0c21c06aa
commit
72959901d8
|
@ -35,7 +35,7 @@ import org.mozilla.fenix.helpers.HomeActivityTestRule
|
|||
*
|
||||
* Say no to main thread IO! 🙅
|
||||
*/
|
||||
private const val EXPECTED_SUPPRESSION_COUNT = 19
|
||||
private const val EXPECTED_SUPPRESSION_COUNT = 18
|
||||
|
||||
/**
|
||||
* The number of times we call the `runBlocking` coroutine method on the main thread during this
|
||||
|
|
|
@ -850,7 +850,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
|
|||
@OptIn(DelicateCoroutinesApi::class)
|
||||
open fun downloadWallpapers() {
|
||||
GlobalScope.launch {
|
||||
components.wallpaperManager.downloadAllRemoteWallpapers()
|
||||
components.useCases.wallpaperUseCases.initialize()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ import mozilla.components.feature.addons.update.DefaultAddonUpdater
|
|||
import mozilla.components.feature.autofill.AutofillConfiguration
|
||||
import mozilla.components.lib.publicsuffixlist.PublicSuffixList
|
||||
import mozilla.components.support.base.worker.Frequency
|
||||
import mozilla.components.support.locale.LocaleManager
|
||||
import org.mozilla.fenix.BuildConfig
|
||||
import org.mozilla.fenix.Config
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
|
@ -45,8 +44,6 @@ import org.mozilla.fenix.perf.StrictModeManager
|
|||
import org.mozilla.fenix.perf.lazyMonitored
|
||||
import org.mozilla.fenix.utils.ClipboardHandler
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
import org.mozilla.fenix.wallpapers.WallpaperDownloader
|
||||
import org.mozilla.fenix.wallpapers.WallpaperFileManager
|
||||
import org.mozilla.fenix.wallpapers.WallpaperManager
|
||||
import org.mozilla.fenix.wifi.WifiConnectionMonitor
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
@ -85,7 +82,10 @@ class Components(private val context: Context) {
|
|||
core.webAppShortcutManager,
|
||||
core.topSitesStorage,
|
||||
core.bookmarksStorage,
|
||||
core.historyStorage
|
||||
core.historyStorage,
|
||||
appStore,
|
||||
core.client,
|
||||
strictMode,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -161,17 +161,11 @@ class Components(private val context: Context) {
|
|||
val strictMode by lazyMonitored { StrictModeManager(Config, this) }
|
||||
|
||||
val wallpaperManager by lazyMonitored {
|
||||
val currentLocale = strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
|
||||
LocaleManager.getCurrentLocale(context)?.toLanguageTag()
|
||||
?: LocaleManager.getSystemDefault().toLanguageTag()
|
||||
}
|
||||
strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
|
||||
WallpaperManager(
|
||||
settings,
|
||||
appStore,
|
||||
WallpaperDownloader(context, core.client),
|
||||
WallpaperFileManager(context.filesDir),
|
||||
currentLocale
|
||||
useCases.wallpaperUseCases.selectWallpaper,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.mozilla.fenix.components
|
|||
import android.content.Context
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.concept.engine.Engine
|
||||
import mozilla.components.concept.fetch.Client
|
||||
import mozilla.components.concept.storage.BookmarksStorage
|
||||
import mozilla.components.concept.storage.HistoryStorage
|
||||
import mozilla.components.feature.app.links.AppLinksUseCases
|
||||
|
@ -24,7 +25,9 @@ import mozilla.components.feature.top.sites.TopSitesStorage
|
|||
import mozilla.components.feature.top.sites.TopSitesUseCases
|
||||
import mozilla.components.support.locale.LocaleUseCases
|
||||
import org.mozilla.fenix.components.bookmarks.BookmarksUseCase
|
||||
import org.mozilla.fenix.perf.StrictModeManager
|
||||
import org.mozilla.fenix.perf.lazyMonitored
|
||||
import org.mozilla.fenix.wallpapers.WallpapersUseCases
|
||||
|
||||
/**
|
||||
* Component group for all use cases. Use cases are provided by feature
|
||||
|
@ -38,7 +41,10 @@ class UseCases(
|
|||
private val shortcutManager: WebAppShortcutManager,
|
||||
private val topSitesStorage: TopSitesStorage,
|
||||
private val bookmarksStorage: BookmarksStorage,
|
||||
private val historyStorage: HistoryStorage
|
||||
private val historyStorage: HistoryStorage,
|
||||
appStore: AppStore,
|
||||
client: Client,
|
||||
strictMode: StrictModeManager,
|
||||
) {
|
||||
/**
|
||||
* Use cases that provide engine interactions for a given browser session.
|
||||
|
@ -99,4 +105,8 @@ class UseCases(
|
|||
* Use cases that provide bookmark management.
|
||||
*/
|
||||
val bookmarksUseCases by lazyMonitored { BookmarksUseCase(bookmarksStorage, historyStorage) }
|
||||
|
||||
val wallpaperUseCases by lazyMonitored {
|
||||
WallpapersUseCases(context, appStore, client, strictMode)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package org.mozilla.fenix.ext
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Matrix
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
|
||||
/**
|
||||
* This will scale the received [Bitmap] to the size of the [view]. It retains the bitmap's
|
||||
* original aspect ratio, but will shrink or enlarge it to fit the viewport. If bitmap does not
|
||||
* correctly fit the aspect ratio of the view, it will be shifted to prioritize the bottom-left
|
||||
* of the bitmap.
|
||||
*/
|
||||
fun Bitmap.scaleToBottomOfView(view: ImageView) {
|
||||
val bitmap = this
|
||||
view.setImageBitmap(bitmap)
|
||||
view.scaleType = ImageView.ScaleType.MATRIX
|
||||
val matrix = Matrix()
|
||||
view.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
|
||||
override fun onLayoutChange(
|
||||
v: View?,
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int,
|
||||
oldLeft: Int,
|
||||
oldTop: Int,
|
||||
oldRight: Int,
|
||||
oldBottom: Int
|
||||
) {
|
||||
val viewWidth: Float = view.width.toFloat()
|
||||
val viewHeight: Float = view.height.toFloat()
|
||||
val bitmapWidth = bitmap.width
|
||||
val bitmapHeight = bitmap.height
|
||||
val widthScale = viewWidth / bitmapWidth
|
||||
val heightScale = viewHeight / bitmapHeight
|
||||
val scale = widthScale.coerceAtLeast(heightScale)
|
||||
matrix.postScale(scale, scale)
|
||||
// The image is translated to its bottom such that any pertinent information is
|
||||
// guaranteed to be shown.
|
||||
// Majority of this math borrowed from // https://medium.com/@tokudu/how-to-whitelist-strictmode-violations-on-android-based-on-stacktrace-eb0018e909aa
|
||||
// except that there is no need to translate horizontally in our case.
|
||||
matrix.postTranslate(0f, (viewHeight - bitmapHeight * scale))
|
||||
view.imageMatrix = matrix
|
||||
view.removeOnLayoutChangeListener(this)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -94,6 +94,7 @@ import org.mozilla.fenix.ext.hideToolbar
|
|||
import org.mozilla.fenix.ext.nav
|
||||
import org.mozilla.fenix.ext.requireComponents
|
||||
import org.mozilla.fenix.ext.runIfFragmentIsAttached
|
||||
import org.mozilla.fenix.ext.scaleToBottomOfView
|
||||
import org.mozilla.fenix.ext.settings
|
||||
import org.mozilla.fenix.gleanplumb.DefaultMessageController
|
||||
import org.mozilla.fenix.gleanplumb.MessagingFeature
|
||||
|
@ -968,11 +969,11 @@ class HomeFragment : Fragment() {
|
|||
binding.wallpaperImageView.visibility = View.GONE
|
||||
}
|
||||
else -> {
|
||||
with(requireComponents.wallpaperManager) {
|
||||
val bitmap = currentWallpaper.load(requireContext()) ?: return@onEach
|
||||
bitmap.scaleBitmapToBottomOfView(binding.wallpaperImageView)
|
||||
val bitmap = requireComponents.useCases.wallpaperUseCases.loadBitmap(currentWallpaper)
|
||||
bitmap?.let {
|
||||
it.scaleToBottomOfView(binding.wallpaperImageView)
|
||||
binding.wallpaperImageView.visibility = View.VISIBLE
|
||||
}
|
||||
binding.wallpaperImageView.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ import androidx.compose.material.SwitchDefaults
|
|||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.rememberScaffoldState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
|
@ -43,7 +43,6 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
|
@ -53,11 +52,9 @@ import androidx.compose.ui.unit.sp
|
|||
import kotlinx.coroutines.launch
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.compose.button.TextButton
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.theme.FirefoxTheme
|
||||
import org.mozilla.fenix.theme.Theme
|
||||
import org.mozilla.fenix.wallpapers.Wallpaper
|
||||
import org.mozilla.fenix.wallpapers.WallpaperManager
|
||||
|
||||
/**
|
||||
* The screen for controlling settings around Wallpapers. When a new wallpaper is selected,
|
||||
|
@ -77,7 +74,7 @@ import org.mozilla.fenix.wallpapers.WallpaperManager
|
|||
fun WallpaperSettings(
|
||||
wallpapers: List<Wallpaper>,
|
||||
defaultWallpaper: Wallpaper,
|
||||
loadWallpaperResource: (Wallpaper) -> Bitmap?,
|
||||
loadWallpaperResource: suspend (Wallpaper) -> Bitmap?,
|
||||
selectedWallpaper: Wallpaper,
|
||||
onSelectWallpaper: (Wallpaper) -> Unit,
|
||||
onViewWallpaper: () -> Unit,
|
||||
|
@ -164,7 +161,7 @@ private fun WallpaperSnackbar(
|
|||
private fun WallpaperThumbnails(
|
||||
wallpapers: List<Wallpaper>,
|
||||
defaultWallpaper: Wallpaper,
|
||||
loadWallpaperResource: (Wallpaper) -> Bitmap?,
|
||||
loadWallpaperResource: suspend (Wallpaper) -> Bitmap?,
|
||||
selectedWallpaper: Wallpaper,
|
||||
numColumns: Int = 3,
|
||||
onSelectWallpaper: (Wallpaper) -> Unit,
|
||||
|
@ -211,14 +208,14 @@ private fun WallpaperThumbnails(
|
|||
private fun WallpaperThumbnailItem(
|
||||
wallpaper: Wallpaper,
|
||||
defaultWallpaper: Wallpaper,
|
||||
loadWallpaperResource: (Wallpaper) -> Bitmap?,
|
||||
loadWallpaperResource: suspend (Wallpaper) -> Bitmap?,
|
||||
isSelected: Boolean,
|
||||
aspectRatio: Float = 1.1f,
|
||||
onSelect: (Wallpaper) -> Unit
|
||||
) {
|
||||
var bitmap by remember { mutableStateOf(loadWallpaperResource(wallpaper)) }
|
||||
DisposableEffect(LocalConfiguration.current.orientation) {
|
||||
onDispose { bitmap = loadWallpaperResource(wallpaper) }
|
||||
var bitmap: Bitmap? by remember { mutableStateOf(null) }
|
||||
LaunchedEffect(LocalConfiguration.current.orientation) {
|
||||
bitmap = loadWallpaperResource(wallpaper)
|
||||
}
|
||||
val thumbnailShape = RoundedCornerShape(8.dp)
|
||||
val border = if (isSelected) {
|
||||
|
@ -292,16 +289,12 @@ private fun WallpaperLogoSwitch(
|
|||
@Composable
|
||||
private fun WallpaperThumbnailsPreview() {
|
||||
FirefoxTheme(theme = Theme.getTheme()) {
|
||||
val context = LocalContext.current
|
||||
val wallpaperManager = context.components.wallpaperManager
|
||||
|
||||
WallpaperSettings(
|
||||
defaultWallpaper = WallpaperManager.defaultWallpaper,
|
||||
loadWallpaperResource = { wallpaper ->
|
||||
with(wallpaperManager) { wallpaper.load(context) }
|
||||
},
|
||||
wallpapers = wallpaperManager.wallpapers,
|
||||
selectedWallpaper = wallpaperManager.currentWallpaper,
|
||||
defaultWallpaper = Wallpaper.Default,
|
||||
loadWallpaperResource = { null },
|
||||
wallpapers = listOf(Wallpaper.Default),
|
||||
selectedWallpaper = Wallpaper.Default,
|
||||
onSelectWallpaper = {},
|
||||
onViewWallpaper = {},
|
||||
tapLogoSwitchChecked = false,
|
||||
|
|
|
@ -16,6 +16,7 @@ import androidx.compose.ui.platform.ComposeView
|
|||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import mozilla.components.lib.state.ext.observeAsComposableState
|
||||
import mozilla.components.service.glean.private.NoExtras
|
||||
import org.mozilla.fenix.GleanMetrics.Wallpapers
|
||||
import org.mozilla.fenix.R
|
||||
|
@ -23,11 +24,14 @@ import org.mozilla.fenix.ext.requireComponents
|
|||
import org.mozilla.fenix.ext.showToolbar
|
||||
import org.mozilla.fenix.theme.FirefoxTheme
|
||||
import org.mozilla.fenix.wallpapers.Wallpaper
|
||||
import org.mozilla.fenix.wallpapers.WallpaperManager
|
||||
|
||||
class WallpaperSettingsFragment : Fragment() {
|
||||
private val wallpaperManager by lazy {
|
||||
requireComponents.wallpaperManager
|
||||
private val appStore by lazy {
|
||||
requireComponents.appStore
|
||||
}
|
||||
|
||||
private val wallpaperUseCases by lazy {
|
||||
requireComponents.useCases.wallpaperUseCases
|
||||
}
|
||||
|
||||
private val settings by lazy {
|
||||
|
@ -44,25 +48,20 @@ class WallpaperSettingsFragment : Fragment() {
|
|||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setContent {
|
||||
FirefoxTheme {
|
||||
var currentWallpaper by remember { mutableStateOf(wallpaperManager.currentWallpaper) }
|
||||
val wallpapers = appStore.observeAsComposableState { state ->
|
||||
state.wallpaperState.availableWallpapers
|
||||
}.value ?: listOf()
|
||||
val currentWallpaper = appStore.observeAsComposableState { state ->
|
||||
state.wallpaperState.currentWallpaper
|
||||
}.value ?: Wallpaper.Default
|
||||
var wallpapersSwitchedByLogo by remember { mutableStateOf(settings.wallpapersSwitchedByLogoTap) }
|
||||
|
||||
WallpaperSettings(
|
||||
wallpapers = wallpaperManager.wallpapers,
|
||||
defaultWallpaper = WallpaperManager.defaultWallpaper,
|
||||
loadWallpaperResource = { wallpaper ->
|
||||
with(wallpaperManager) { wallpaper.load(context) }
|
||||
},
|
||||
wallpapers = wallpapers,
|
||||
defaultWallpaper = Wallpaper.Default,
|
||||
loadWallpaperResource = { wallpaperUseCases.loadBitmap(it) },
|
||||
selectedWallpaper = currentWallpaper,
|
||||
onSelectWallpaper = { selectedWallpaper: Wallpaper ->
|
||||
currentWallpaper = selectedWallpaper
|
||||
wallpaperManager.currentWallpaper = selectedWallpaper
|
||||
Wallpapers.wallpaperSelected.record(
|
||||
Wallpapers.WallpaperSelectedExtra(
|
||||
name = selectedWallpaper.name,
|
||||
themeCollection = selectedWallpaper::class.simpleName
|
||||
)
|
||||
)
|
||||
},
|
||||
onSelectWallpaper = { wallpaperUseCases.selectWallpaper(it) },
|
||||
onViewWallpaper = { findNavController().navigate(R.id.homeFragment) },
|
||||
tapLogoSwitchChecked = wallpapersSwitchedByLogo,
|
||||
onTapLogoSwitchCheckedChange = {
|
||||
|
|
|
@ -51,7 +51,7 @@ import org.mozilla.fenix.settings.logins.SortingStrategy
|
|||
import org.mozilla.fenix.settings.registerOnSharedPreferenceChangeListener
|
||||
import org.mozilla.fenix.settings.sitepermissions.AUTOPLAY_BLOCK_ALL
|
||||
import org.mozilla.fenix.settings.sitepermissions.AUTOPLAY_BLOCK_AUDIBLE
|
||||
import org.mozilla.fenix.wallpapers.WallpaperManager
|
||||
import org.mozilla.fenix.wallpapers.Wallpaper
|
||||
import java.security.InvalidParameterException
|
||||
import java.util.UUID
|
||||
|
||||
|
@ -187,7 +187,7 @@ class Settings(private val appContext: Context) : PreferencesHolder {
|
|||
|
||||
var currentWallpaper by stringPreference(
|
||||
appContext.getPreferenceKey(R.string.pref_key_current_wallpaper),
|
||||
default = WallpaperManager.defaultWallpaper.name
|
||||
default = Wallpaper.Default.name
|
||||
)
|
||||
|
||||
var wallpapersSwitchedByLogoTap by booleanPreference(
|
||||
|
|
|
@ -8,6 +8,7 @@ import kotlinx.coroutines.CoroutineDispatcher
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
class WallpaperFileManager(
|
||||
|
@ -23,8 +24,8 @@ class WallpaperFileManager(
|
|||
* files for each of the following orientation and theme combinations:
|
||||
* light/portrait - light/landscape - dark/portrait - dark/landscape
|
||||
*/
|
||||
fun lookupExpiredWallpaper(name: String): Wallpaper.Expired? {
|
||||
return if (getAllLocalWallpaperPaths(name).all { File(rootDirectory, it).exists() }) {
|
||||
suspend fun lookupExpiredWallpaper(name: String): Wallpaper.Expired? = withContext(Dispatchers.IO) {
|
||||
if (getAllLocalWallpaperPaths(name).all { File(rootDirectory, it).exists() }) {
|
||||
Wallpaper.Expired(name)
|
||||
} else null
|
||||
}
|
||||
|
|
|
@ -6,25 +6,12 @@ package org.mozilla.fenix.wallpapers
|
|||
|
||||
import android.animation.AnimatorSet
|
||||
import android.animation.ObjectAnimator
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Matrix
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import mozilla.components.support.base.log.logger.Logger
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.components.AppStore
|
||||
import org.mozilla.fenix.components.appstate.AppAction
|
||||
import org.mozilla.fenix.perf.runBlockingIncrement
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
import java.io.File
|
||||
import java.util.Date
|
||||
|
||||
/**
|
||||
* Provides access to available wallpapers and manages their states.
|
||||
|
@ -33,39 +20,12 @@ import java.util.Date
|
|||
class WallpaperManager(
|
||||
private val settings: Settings,
|
||||
private val appStore: AppStore,
|
||||
private val downloader: WallpaperDownloader,
|
||||
private val fileManager: WallpaperFileManager,
|
||||
private val currentLocale: String,
|
||||
allWallpapers: List<Wallpaper> = availableWallpapers
|
||||
private val selectWallpaperUseCase: WallpapersUseCases.SelectWallpaperUseCase,
|
||||
) {
|
||||
val logger = Logger("WallpaperManager")
|
||||
|
||||
val wallpapers = allWallpapers
|
||||
.filter(::filterExpiredRemoteWallpapers)
|
||||
.filter(::filterPromotionalWallpapers)
|
||||
.also {
|
||||
appStore.dispatch(AppAction.WallpaperAction.UpdateAvailableWallpapers(it))
|
||||
}
|
||||
|
||||
var currentWallpaper: Wallpaper = getCurrentWallpaperFromSettings()
|
||||
set(value) {
|
||||
settings.currentWallpaper = value.name
|
||||
appStore.dispatch(AppAction.WallpaperAction.UpdateCurrentWallpaper(value))
|
||||
field = value
|
||||
}
|
||||
|
||||
init {
|
||||
fileManager.clean(currentWallpaper, wallpapers.filterIsInstance<Wallpaper.Remote>())
|
||||
}
|
||||
|
||||
/**
|
||||
* Download all known remote wallpapers.
|
||||
*/
|
||||
suspend fun downloadAllRemoteWallpapers() {
|
||||
for (wallpaper in wallpapers.filterIsInstance<Wallpaper.Remote>()) {
|
||||
downloader.downloadWallpaper(wallpaper)
|
||||
}
|
||||
}
|
||||
val wallpapers get() = appStore.state.wallpaperState.availableWallpapers
|
||||
val currentWallpaper: Wallpaper get() = appStore.state.wallpaperState.currentWallpaper
|
||||
|
||||
/**
|
||||
* Returns the next available [Wallpaper], the [currentWallpaper] is the last one then
|
||||
|
@ -80,126 +40,10 @@ class WallpaperManager(
|
|||
} else {
|
||||
values[index]
|
||||
}.also {
|
||||
currentWallpaper = it
|
||||
selectWallpaperUseCase(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun filterExpiredRemoteWallpapers(wallpaper: Wallpaper): Boolean = when (wallpaper) {
|
||||
is Wallpaper.Remote -> {
|
||||
val notExpired = wallpaper.expirationDate?.let { Date().before(it) } ?: true
|
||||
notExpired || wallpaper.name == settings.currentWallpaper
|
||||
}
|
||||
else -> true
|
||||
}
|
||||
|
||||
private fun filterPromotionalWallpapers(wallpaper: Wallpaper): Boolean =
|
||||
if (wallpaper is Wallpaper.Promotional) {
|
||||
wallpaper.isAvailableInLocale(currentLocale)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
private fun getCurrentWallpaperFromSettings(): Wallpaper {
|
||||
return if (isDefaultTheCurrentWallpaper(settings)) {
|
||||
defaultWallpaper
|
||||
} else {
|
||||
val currentWallpaper = settings.currentWallpaper
|
||||
wallpapers.find { it.name == currentWallpaper }
|
||||
?: fileManager.lookupExpiredWallpaper(currentWallpaper)
|
||||
?: defaultWallpaper
|
||||
}.also {
|
||||
appStore.dispatch(AppAction.WallpaperAction.UpdateCurrentWallpaper(it))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a wallpaper that is saved locally.
|
||||
*/
|
||||
fun Wallpaper.load(context: Context): Bitmap? =
|
||||
when (this) {
|
||||
is Wallpaper.Local -> loadWallpaperFromDrawables(context, this)
|
||||
is Wallpaper.Remote -> loadWallpaperFromDisk(context, this)
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun loadWallpaperFromDrawables(context: Context, wallpaper: Wallpaper.Local): Bitmap? = Result.runCatching {
|
||||
BitmapFactory.decodeResource(context.resources, wallpaper.drawableId)
|
||||
}.getOrNull()
|
||||
|
||||
/**
|
||||
* Load a wallpaper from app-specific storage.
|
||||
*/
|
||||
private fun loadWallpaperFromDisk(context: Context, wallpaper: Wallpaper.Remote): Bitmap? = Result.runCatching {
|
||||
val path = wallpaper.getLocalPathFromContext(context)
|
||||
runBlockingIncrement {
|
||||
withContext(Dispatchers.IO) {
|
||||
val file = File(context.filesDir, path)
|
||||
BitmapFactory.decodeStream(file.inputStream())
|
||||
}
|
||||
}
|
||||
}.getOrNull()
|
||||
|
||||
/**
|
||||
* This will scale the received [Bitmap] to the size of the [view]. It retains the bitmap's
|
||||
* original aspect ratio, but will shrink or enlarge it to fit the viewport. If bitmap does not
|
||||
* correctly fit the aspect ratio of the view, it will be shifted to prioritize the bottom-left
|
||||
* of the bitmap.
|
||||
*/
|
||||
fun Bitmap.scaleBitmapToBottomOfView(view: ImageView) {
|
||||
val bitmap = this
|
||||
view.setImageBitmap(bitmap)
|
||||
view.scaleType = ImageView.ScaleType.MATRIX
|
||||
val matrix = Matrix()
|
||||
view.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
|
||||
override fun onLayoutChange(
|
||||
v: View?,
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int,
|
||||
oldLeft: Int,
|
||||
oldTop: Int,
|
||||
oldRight: Int,
|
||||
oldBottom: Int
|
||||
) {
|
||||
val viewWidth: Float = view.width.toFloat()
|
||||
val viewHeight: Float = view.height.toFloat()
|
||||
val bitmapWidth = bitmap.width
|
||||
val bitmapHeight = bitmap.height
|
||||
val widthScale = viewWidth / bitmapWidth
|
||||
val heightScale = viewHeight / bitmapHeight
|
||||
val scale = widthScale.coerceAtLeast(heightScale)
|
||||
matrix.postScale(scale, scale)
|
||||
// The image is translated to its bottom such that any pertinent information is
|
||||
// guaranteed to be shown.
|
||||
// Majority of this math borrowed from // https://medium.com/@tokudu/how-to-whitelist-strictmode-violations-on-android-based-on-stacktrace-eb0018e909aa
|
||||
// except that there is no need to translate horizontally in our case.
|
||||
matrix.postTranslate(0f, (viewHeight - bitmapHeight * scale))
|
||||
view.imageMatrix = matrix
|
||||
view.removeOnLayoutChangeListener(this)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the expected local path on disk for a wallpaper. This will differ depending
|
||||
* on orientation and app theme.
|
||||
*/
|
||||
private fun Wallpaper.Remote.getLocalPathFromContext(context: Context): String {
|
||||
val orientation = if (context.isLandscape()) "landscape" else "portrait"
|
||||
val theme = if (context.isDark()) "dark" else "light"
|
||||
return Wallpaper.getBaseLocalPath(orientation, theme, name)
|
||||
}
|
||||
|
||||
private fun Context.isLandscape(): Boolean {
|
||||
return resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
}
|
||||
|
||||
private fun Context.isDark(): Boolean {
|
||||
return resources.configuration.uiMode and
|
||||
Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the Firefox logo, if it hasn't been animated before, otherwise nothing will happen.
|
||||
* After animating the first time, the [Settings.shouldAnimateFirefoxLogo] setting
|
||||
|
@ -242,20 +86,6 @@ class WallpaperManager(
|
|||
}
|
||||
|
||||
val defaultWallpaper = Wallpaper.Default
|
||||
private val localWallpapers: List<Wallpaper.Local> = listOf(
|
||||
Wallpaper.Local.Firefox("amethyst", R.drawable.amethyst),
|
||||
Wallpaper.Local.Firefox("cerulean", R.drawable.cerulean),
|
||||
Wallpaper.Local.Firefox("sunrise", R.drawable.sunrise),
|
||||
)
|
||||
private val remoteWallpapers: List<Wallpaper.Remote> = listOf(
|
||||
Wallpaper.Remote.Firefox(
|
||||
"twilight-hills"
|
||||
),
|
||||
Wallpaper.Remote.Firefox(
|
||||
"beach-vibe"
|
||||
),
|
||||
)
|
||||
private val availableWallpapers = listOf(defaultWallpaper) + localWallpapers + remoteWallpapers
|
||||
private const val ANIMATION_DELAY_MS = 1500L
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,255 @@
|
|||
/* 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 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.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
|
||||
|
||||
/**
|
||||
* Contains use cases related to the wallpaper feature.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @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.
|
||||
* @property loadBitmap Usecase for loading specific wallpaper bitmaps.
|
||||
* @property selectWallpaper Usecase for selecting a new wallpaper.
|
||||
*/
|
||||
class WallpapersUseCases(
|
||||
context: Context,
|
||||
store: AppStore,
|
||||
client: Client,
|
||||
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 = WallpaperFileManager(context.filesDir)
|
||||
val currentLocale = LocaleManager.getCurrentLocale(context)?.toLanguageTag()
|
||||
?: LocaleManager.getSystemDefault().toLanguageTag()
|
||||
fileManager to currentLocale
|
||||
}
|
||||
val downloader = WallpaperDownloader(context, client)
|
||||
DefaultInitializeWallpaperUseCase(
|
||||
store = store,
|
||||
downloader = downloader,
|
||||
fileManager = fileManager,
|
||||
settings = context.settings(),
|
||||
currentLocale = currentLocale
|
||||
)
|
||||
}
|
||||
val loadBitmap: LoadBitmapUseCase by lazy { DefaultLoadBitmapUseCase(context) }
|
||||
val selectWallpaper: SelectWallpaperUseCase by lazy { DefaultSelectWallpaperUseCase(context.settings(), store) }
|
||||
|
||||
/**
|
||||
* Contract for usecases that initialize the wallpaper feature.
|
||||
*/
|
||||
interface InitializeWallpapersUseCase {
|
||||
/**
|
||||
* Start operations that should be down during initialization, like remote metadata
|
||||
* retrieval and determining the currently selected wallpaper.
|
||||
*/
|
||||
suspend operator fun invoke()
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal class DefaultInitializeWallpaperUseCase(
|
||||
private val store: AppStore,
|
||||
private val downloader: WallpaperDownloader,
|
||||
private val fileManager: WallpaperFileManager,
|
||||
private val settings: Settings,
|
||||
private val currentLocale: String,
|
||||
private val possibleWallpapers: List<Wallpaper> = allWallpapers,
|
||||
) : InitializeWallpapersUseCase {
|
||||
|
||||
/**
|
||||
* Downloads the currently available wallpaper metadata from a remote source.
|
||||
* Updates the [store] with that metadata and with the selected wallpaper found in storage.
|
||||
* Removes any unused promotional or time-limited assets from local storage.
|
||||
* Should usually be called early the app's lifetime to ensure that metadata and thumbnails
|
||||
* are available as soon as they are needed.
|
||||
*/
|
||||
override suspend operator fun invoke() {
|
||||
// Quite a bit of code needs to be executed off the main thread in some of this setup.
|
||||
// This should be cleaned up as improvements are made to the storage, file management,
|
||||
// and download utilities.
|
||||
withContext(Dispatchers.IO) {
|
||||
val availableWallpapers = getAvailableWallpapers()
|
||||
val currentWallpaperName = settings.currentWallpaper
|
||||
val currentWallpaper = possibleWallpapers.find { it.name == currentWallpaperName }
|
||||
?: fileManager.lookupExpiredWallpaper(currentWallpaperName)
|
||||
?: Wallpaper.Default
|
||||
|
||||
fileManager.clean(
|
||||
currentWallpaper,
|
||||
possibleWallpapers.filterIsInstance<Wallpaper.Remote>()
|
||||
)
|
||||
downloadAllRemoteWallpapers(availableWallpapers)
|
||||
store.dispatch(AppAction.WallpaperAction.UpdateAvailableWallpapers(availableWallpapers))
|
||||
store.dispatch(AppAction.WallpaperAction.UpdateCurrentWallpaper(currentWallpaper))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAvailableWallpapers() = possibleWallpapers
|
||||
.filter { !it.isExpired() && it.isAvailableInLocale() }
|
||||
|
||||
private suspend fun downloadAllRemoteWallpapers(availableWallpapers: List<Wallpaper>) {
|
||||
for (wallpaper in availableWallpapers.filterIsInstance<Wallpaper.Remote>()) {
|
||||
downloader.downloadWallpaper(wallpaper)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Wallpaper.isExpired(): Boolean = when (this) {
|
||||
is Wallpaper.Remote -> {
|
||||
val expired = this.expirationDate?.let { Date().after(it) } ?: false
|
||||
expired && this.name != settings.currentWallpaper
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
|
||||
private fun Wallpaper.isAvailableInLocale(): Boolean =
|
||||
if (this is Wallpaper.Promotional) {
|
||||
this.isAvailableInLocale(currentLocale)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val localWallpapers: List<Wallpaper.Local> = listOf(
|
||||
Wallpaper.Local.Firefox("amethyst", R.drawable.amethyst),
|
||||
Wallpaper.Local.Firefox("cerulean", R.drawable.cerulean),
|
||||
Wallpaper.Local.Firefox("sunrise", R.drawable.sunrise),
|
||||
)
|
||||
private val remoteWallpapers: List<Wallpaper.Remote> = listOf(
|
||||
Wallpaper.Remote.Firefox(
|
||||
"twilight-hills"
|
||||
),
|
||||
Wallpaper.Remote.Firefox(
|
||||
"beach-vibe"
|
||||
),
|
||||
)
|
||||
val allWallpapers = listOf(Wallpaper.Default) + localWallpapers + remoteWallpapers
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contract for usecase for loading bitmaps related to a specific wallpaper.
|
||||
*/
|
||||
interface LoadBitmapUseCase {
|
||||
/**
|
||||
* Load the bitmap for a [wallpaper], if available.
|
||||
*
|
||||
* @param wallpaper The wallpaper to load a bitmap for.
|
||||
*/
|
||||
suspend operator fun invoke(wallpaper: Wallpaper): Bitmap?
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal class DefaultLoadBitmapUseCase(private val context: Context) : LoadBitmapUseCase {
|
||||
/**
|
||||
* Load the bitmap for a [wallpaper], if available.
|
||||
*
|
||||
* @param wallpaper The wallpaper to load a bitmap for.
|
||||
*/
|
||||
override suspend operator fun invoke(wallpaper: Wallpaper): Bitmap? = when (wallpaper) {
|
||||
is Wallpaper.Local -> loadWallpaperFromDrawable(context, wallpaper)
|
||||
is Wallpaper.Remote -> loadWallpaperFromDisk(context, wallpaper)
|
||||
else -> null
|
||||
}
|
||||
|
||||
private suspend fun loadWallpaperFromDrawable(
|
||||
context: Context,
|
||||
wallpaper: Wallpaper.Local
|
||||
): Bitmap? = Result.runCatching {
|
||||
withContext(Dispatchers.IO) {
|
||||
BitmapFactory.decodeResource(context.resources, wallpaper.drawableId)
|
||||
}
|
||||
}.getOrNull()
|
||||
|
||||
private suspend fun loadWallpaperFromDisk(
|
||||
context: Context,
|
||||
wallpaper: Wallpaper.Remote
|
||||
): 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.Remote.getLocalPathFromContext(context: Context): String {
|
||||
val orientation = if (context.isLandscape()) "landscape" else "portrait"
|
||||
val theme = if (context.isDark()) "dark" else "light"
|
||||
return Wallpaper.getBaseLocalPath(orientation, theme, name)
|
||||
}
|
||||
|
||||
private fun Context.isLandscape(): Boolean {
|
||||
return resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
}
|
||||
|
||||
private fun Context.isDark(): Boolean {
|
||||
return resources.configuration.uiMode and
|
||||
Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contract for usecase of selecting a new wallpaper.
|
||||
*/
|
||||
interface SelectWallpaperUseCase {
|
||||
/**
|
||||
* Select a new wallpaper.
|
||||
*
|
||||
* @param wallpaper The selected wallpaper.
|
||||
*/
|
||||
operator fun invoke(wallpaper: Wallpaper)
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal class DefaultSelectWallpaperUseCase(
|
||||
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 fun invoke(wallpaper: Wallpaper) {
|
||||
settings.currentWallpaper = wallpaper.name
|
||||
store.dispatch(AppAction.WallpaperAction.UpdateCurrentWallpaper(wallpaper))
|
||||
Wallpapers.wallpaperSelected.record(
|
||||
Wallpapers.WallpaperSelectedExtra(
|
||||
name = wallpaper.name,
|
||||
themeCollection = wallpaper::class.simpleName
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
package org.mozilla.fenix.perf
|
||||
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.every
|
||||
import io.mockk.impl.annotations.MockK
|
||||
|
@ -22,7 +23,6 @@ import org.junit.Test
|
|||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.GleanMetrics.PerfStartup
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
import org.mozilla.fenix.perf.StartupPathProvider.StartupPath
|
||||
import org.mozilla.fenix.perf.StartupStateProvider.StartupState
|
||||
|
||||
|
@ -35,7 +35,7 @@ private val validTelemetryLabels = run {
|
|||
|
||||
private val activityClass = HomeActivity::class.java
|
||||
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class StartupTypeTelemetryTest {
|
||||
|
||||
@get:Rule
|
||||
|
|
|
@ -5,6 +5,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.assertTrue
|
||||
import org.junit.Before
|
||||
|
@ -39,21 +40,25 @@ class WallpaperFileManagerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN files exist in all directories WHEN expired wallpaper looked up THEN expired wallpaper returned`() {
|
||||
fun `GIVEN files exist in all directories WHEN expired wallpaper looked up THEN expired wallpaper returned`() = runTest {
|
||||
val wallpaperName = "name"
|
||||
createAllFiles(wallpaperName)
|
||||
|
||||
val result = fileManager.lookupExpiredWallpaper(wallpaperName)
|
||||
|
||||
val expected = Wallpaper.Expired(name = wallpaperName)
|
||||
assertEquals(expected, fileManager.lookupExpiredWallpaper(wallpaperName))
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN any missing file in directories WHEN expired wallpaper looked up THEN null returned`() {
|
||||
fun `GIVEN any missing file in directories WHEN expired wallpaper looked up THEN null returned`() = runTest {
|
||||
val wallpaperName = "name"
|
||||
File(landscapeLightFolder, "$wallpaperName.png").createNewFile()
|
||||
File(landscapeDarkFolder, "$wallpaperName.png").createNewFile()
|
||||
|
||||
assertEquals(null, fileManager.lookupExpiredWallpaper(wallpaperName))
|
||||
val result = fileManager.lookupExpiredWallpaper(wallpaperName)
|
||||
|
||||
assertEquals(null, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -1,182 +1,15 @@
|
|||
package org.mozilla.fenix.wallpapers
|
||||
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.slot
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import mozilla.components.support.test.libstate.ext.waitUntilIdle
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.components.AppStore
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
|
||||
class WallpaperManagerTest {
|
||||
|
||||
// initialize this once, so it can be shared throughout tests
|
||||
private val baseFakeDate = Date()
|
||||
private val fakeCalendar = Calendar.getInstance()
|
||||
|
||||
private val mockSettings: Settings = mockk()
|
||||
private val mockDownloader: WallpaperDownloader = mockk {
|
||||
coEvery { downloadWallpaper(any()) } just runs
|
||||
}
|
||||
private val mockFileManager: WallpaperFileManager = mockk {
|
||||
every { clean(any(), any()) } just runs
|
||||
}
|
||||
|
||||
private val appStore = AppStore()
|
||||
|
||||
@Test
|
||||
fun `WHEN wallpaper set THEN current wallpaper updated in settings and dispatched to store`() {
|
||||
val currentCaptureSlot = slot<String>()
|
||||
every { mockSettings.currentWallpaper } returns ""
|
||||
every { mockSettings.currentWallpaper = capture(currentCaptureSlot) } just runs
|
||||
|
||||
val updatedName = "new name"
|
||||
val updatedWallpaper = Wallpaper.Local.Firefox(updatedName, drawableId = 0)
|
||||
val wallpaperManager = WallpaperManager(mockSettings, appStore, mockk(), mockFileManager, "en-US", listOf())
|
||||
wallpaperManager.currentWallpaper = updatedWallpaper
|
||||
|
||||
assertEquals(updatedWallpaper.name, currentCaptureSlot.captured)
|
||||
appStore.waitUntilIdle()
|
||||
assertEquals(updatedWallpaper, appStore.state.wallpaperState.currentWallpaper)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN no remote wallpapers expired and locale in promo WHEN downloading remote wallpapers THEN all downloaded`() = runTest {
|
||||
every { mockSettings.currentWallpaper } returns ""
|
||||
val fakeRemoteWallpapers = listOf("first", "second", "third").map { name ->
|
||||
makeFakeRemoteWallpaper(TimeRelation.LATER, name)
|
||||
}
|
||||
val wallpaperManager = WallpaperManager(
|
||||
mockSettings,
|
||||
appStore,
|
||||
mockDownloader,
|
||||
mockFileManager,
|
||||
"en-US",
|
||||
allWallpapers = fakeRemoteWallpapers
|
||||
)
|
||||
wallpaperManager.downloadAllRemoteWallpapers()
|
||||
|
||||
for (fakeRemoteWallpaper in fakeRemoteWallpapers) {
|
||||
coVerify { mockDownloader.downloadWallpaper(fakeRemoteWallpaper) }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN no remote wallpapers expired and locale not in promo WHEN downloading remote wallpapers THEN none downloaded`() = runTest {
|
||||
every { mockSettings.currentWallpaper } returns ""
|
||||
val fakeRemoteWallpapers = listOf("first", "second", "third").map { name ->
|
||||
makeFakeRemoteWallpaper(TimeRelation.LATER, name)
|
||||
}
|
||||
val wallpaperManager = WallpaperManager(
|
||||
mockSettings,
|
||||
appStore,
|
||||
mockDownloader,
|
||||
mockFileManager,
|
||||
"en-CA",
|
||||
allWallpapers = fakeRemoteWallpapers
|
||||
)
|
||||
wallpaperManager.downloadAllRemoteWallpapers()
|
||||
|
||||
for (fakeRemoteWallpaper in fakeRemoteWallpapers) {
|
||||
coVerify(exactly = 0) { mockDownloader.downloadWallpaper(fakeRemoteWallpaper) }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN no remote wallpapers expired and locale not in promo WHEN downloading remote wallpapers THEN non-promo wallpapers downloaded`() = runTest {
|
||||
every { mockSettings.currentWallpaper } returns ""
|
||||
val fakePromoWallpapers = listOf("first", "second", "third").map { name ->
|
||||
makeFakeRemoteWallpaper(TimeRelation.LATER, name)
|
||||
}
|
||||
val fakeNonPromoWallpapers = listOf(makeFakeRemoteWallpaper(TimeRelation.LATER, "fourth", false))
|
||||
val fakeRemoteWallpapers = fakePromoWallpapers + fakeNonPromoWallpapers
|
||||
val wallpaperManager = WallpaperManager(
|
||||
mockSettings,
|
||||
appStore,
|
||||
mockDownloader,
|
||||
mockFileManager,
|
||||
"en-CA",
|
||||
allWallpapers = fakeRemoteWallpapers
|
||||
)
|
||||
wallpaperManager.downloadAllRemoteWallpapers()
|
||||
|
||||
for (wallpaper in fakePromoWallpapers) {
|
||||
coVerify(exactly = 0) { mockDownloader.downloadWallpaper(wallpaper) }
|
||||
}
|
||||
for (wallpaper in fakeNonPromoWallpapers) {
|
||||
coVerify { mockDownloader.downloadWallpaper(wallpaper) }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN some expired wallpapers WHEN initialized THEN wallpapers are not available`() {
|
||||
every { mockSettings.currentWallpaper } returns ""
|
||||
val expiredRemoteWallpaper = makeFakeRemoteWallpaper(TimeRelation.BEFORE, "expired")
|
||||
val activeRemoteWallpaper = makeFakeRemoteWallpaper(TimeRelation.LATER, "expired")
|
||||
val wallpaperManager = WallpaperManager(
|
||||
mockSettings,
|
||||
appStore,
|
||||
mockDownloader,
|
||||
mockFileManager,
|
||||
"en-US",
|
||||
allWallpapers = listOf(expiredRemoteWallpaper, activeRemoteWallpaper)
|
||||
)
|
||||
|
||||
assertFalse(wallpaperManager.wallpapers.contains(expiredRemoteWallpaper))
|
||||
assertTrue(wallpaperManager.wallpapers.contains(activeRemoteWallpaper))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN current wallpaper is expired THEN it is available as expired even when others are filtered`() {
|
||||
val currentWallpaperName = "named"
|
||||
val currentExpiredWallpaper = makeFakeRemoteWallpaper(TimeRelation.BEFORE, name = currentWallpaperName)
|
||||
every { mockSettings.currentWallpaper } returns currentWallpaperName
|
||||
val expiredRemoteWallpaper = makeFakeRemoteWallpaper(TimeRelation.BEFORE, "expired")
|
||||
val expected = Wallpaper.Expired(currentWallpaperName)
|
||||
every { mockFileManager.lookupExpiredWallpaper(currentWallpaperName) } returns expected
|
||||
|
||||
val wallpaperManager = WallpaperManager(
|
||||
mockSettings,
|
||||
appStore,
|
||||
mockDownloader,
|
||||
mockFileManager,
|
||||
"en-US",
|
||||
allWallpapers = listOf(expiredRemoteWallpaper)
|
||||
)
|
||||
|
||||
assertFalse(wallpaperManager.wallpapers.contains(expiredRemoteWallpaper))
|
||||
assertFalse(wallpaperManager.wallpapers.contains(currentExpiredWallpaper))
|
||||
assertEquals(expected, wallpaperManager.currentWallpaper)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN current wallpaper is expired THEN it is available even if not listed in initial parameter`() {
|
||||
val currentWallpaperName = "named"
|
||||
every { mockSettings.currentWallpaper } returns currentWallpaperName
|
||||
val expected = Wallpaper.Expired(currentWallpaperName)
|
||||
every { mockFileManager.lookupExpiredWallpaper(currentWallpaperName) } returns expected
|
||||
|
||||
val wallpaperManager = WallpaperManager(
|
||||
mockSettings,
|
||||
appStore,
|
||||
mockDownloader,
|
||||
mockFileManager,
|
||||
"en-US",
|
||||
allWallpapers = listOf()
|
||||
)
|
||||
|
||||
assertEquals(expected, wallpaperManager.currentWallpaper)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN no custom wallpaper set WHEN checking whether the current wallpaper should be default THEN return true`() {
|
||||
|
@ -204,47 +37,4 @@ class WallpaperManagerTest {
|
|||
|
||||
assertFalse(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN manager initialized THEN available wallpapers are dispatched to store`() {
|
||||
every { mockSettings.currentWallpaper } returns WallpaperManager.defaultWallpaper.name
|
||||
val wallpaperManager = WallpaperManager(
|
||||
mockSettings,
|
||||
appStore,
|
||||
mockDownloader,
|
||||
mockFileManager,
|
||||
"en-US",
|
||||
)
|
||||
|
||||
appStore.waitUntilIdle()
|
||||
assertEquals(wallpaperManager.wallpapers, appStore.state.wallpaperState.availableWallpapers)
|
||||
}
|
||||
|
||||
private enum class TimeRelation {
|
||||
BEFORE,
|
||||
NOW,
|
||||
LATER,
|
||||
}
|
||||
|
||||
/**
|
||||
* [timeRelation] should specify a time relative to the time the tests are run
|
||||
*/
|
||||
private fun makeFakeRemoteWallpaper(
|
||||
timeRelation: TimeRelation,
|
||||
name: String = "name",
|
||||
isInPromo: Boolean = true
|
||||
): Wallpaper.Remote {
|
||||
fakeCalendar.time = baseFakeDate
|
||||
when (timeRelation) {
|
||||
TimeRelation.BEFORE -> fakeCalendar.add(Calendar.DATE, -5)
|
||||
TimeRelation.NOW -> Unit
|
||||
TimeRelation.LATER -> fakeCalendar.add(Calendar.DATE, 5)
|
||||
}
|
||||
val relativeTime = fakeCalendar.time
|
||||
return if (isInPromo) {
|
||||
Wallpaper.Remote.House(name = name, expirationDate = relativeTime)
|
||||
} else {
|
||||
Wallpaper.Remote.Firefox(name = name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,230 @@
|
|||
package org.mozilla.fenix.wallpapers
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.slot
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import mozilla.components.service.glean.testing.GleanTestRule
|
||||
import mozilla.components.support.test.libstate.ext.waitUntilIdle
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
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.utils.Settings
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class WallpapersUseCasesTest {
|
||||
|
||||
@get:Rule
|
||||
val gleanTestRule = GleanTestRule(testContext)
|
||||
|
||||
// initialize this once, so it can be shared throughout tests
|
||||
private val baseFakeDate = Date()
|
||||
private val fakeCalendar = Calendar.getInstance()
|
||||
|
||||
private val appStore = AppStore()
|
||||
private val mockSettings = mockk<Settings>()
|
||||
private val mockDownloader = mockk<WallpaperDownloader>(relaxed = true)
|
||||
private val mockFileManager = mockk<WallpaperFileManager> {
|
||||
every { clean(any(), any()) } just runs
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN 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.currentWallpaper } returns ""
|
||||
coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null
|
||||
|
||||
WallpapersUseCases.DefaultInitializeWallpaperUseCase(
|
||||
appStore,
|
||||
mockDownloader,
|
||||
mockFileManager,
|
||||
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) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN 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.currentWallpaper } returns expiredWallpaper.name
|
||||
coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null
|
||||
|
||||
WallpapersUseCases.DefaultInitializeWallpaperUseCase(
|
||||
appStore,
|
||||
mockDownloader,
|
||||
mockFileManager,
|
||||
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 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.currentWallpaper } returns ""
|
||||
coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null
|
||||
|
||||
WallpapersUseCases.DefaultInitializeWallpaperUseCase(
|
||||
appStore,
|
||||
mockDownloader,
|
||||
mockFileManager,
|
||||
mockSettings,
|
||||
locale,
|
||||
possibleWallpapers = fakeRemoteWallpapers
|
||||
).invoke()
|
||||
|
||||
appStore.waitUntilIdle()
|
||||
assertTrue(appStore.state.wallpaperState.availableWallpapers.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN 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.currentWallpaper } returns ""
|
||||
coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null
|
||||
|
||||
WallpapersUseCases.DefaultInitializeWallpaperUseCase(
|
||||
appStore,
|
||||
mockDownloader,
|
||||
mockFileManager,
|
||||
mockSettings,
|
||||
"en-US",
|
||||
possibleWallpapers = fakeRemoteWallpapers
|
||||
).invoke()
|
||||
|
||||
for (fakeRemoteWallpaper in fakeRemoteWallpapers) {
|
||||
coVerify { mockDownloader.downloadWallpaper(fakeRemoteWallpaper) }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN 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.currentWallpaper } returns ""
|
||||
coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null
|
||||
|
||||
WallpapersUseCases.DefaultInitializeWallpaperUseCase(
|
||||
appStore,
|
||||
mockDownloader,
|
||||
mockFileManager,
|
||||
mockSettings,
|
||||
"en-US",
|
||||
possibleWallpapers = fakeRemoteWallpapers
|
||||
).invoke()
|
||||
|
||||
appStore.waitUntilIdle()
|
||||
assertTrue(appStore.state.wallpaperState.currentWallpaper is Wallpaper.Default)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN 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.currentWallpaper } returns selectedWallpaper.name
|
||||
coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null
|
||||
|
||||
WallpapersUseCases.DefaultInitializeWallpaperUseCase(
|
||||
appStore,
|
||||
mockDownloader,
|
||||
mockFileManager,
|
||||
mockSettings,
|
||||
"en-US",
|
||||
possibleWallpapers = possibleWallpapers
|
||||
).invoke()
|
||||
|
||||
appStore.waitUntilIdle()
|
||||
assertEquals(selectedWallpaper, appStore.state.wallpaperState.currentWallpaper)
|
||||
assertEquals(possibleWallpapers, appStore.state.wallpaperState.availableWallpapers)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN selected wallpaper usecase invoked THEN storage updated and store receives dispatch`() {
|
||||
val selectedWallpaper = makeFakeRemoteWallpaper(TimeRelation.LATER, "selected")
|
||||
val slot = slot<String>()
|
||||
coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null
|
||||
every { mockSettings.currentWallpaper } returns ""
|
||||
every { mockSettings.currentWallpaper = capture(slot) } just runs
|
||||
|
||||
WallpapersUseCases.DefaultSelectWallpaperUseCase(
|
||||
mockSettings,
|
||||
appStore
|
||||
).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")!!)
|
||||
}
|
||||
|
||||
private enum class TimeRelation {
|
||||
BEFORE,
|
||||
NOW,
|
||||
LATER,
|
||||
}
|
||||
|
||||
/**
|
||||
* [timeRelation] should specify a time relative to the time the tests are run
|
||||
*/
|
||||
private fun makeFakeRemoteWallpaper(
|
||||
timeRelation: TimeRelation,
|
||||
name: String = "name",
|
||||
isInPromo: Boolean = true
|
||||
): Wallpaper.Remote {
|
||||
fakeCalendar.time = baseFakeDate
|
||||
when (timeRelation) {
|
||||
TimeRelation.BEFORE -> fakeCalendar.add(Calendar.DATE, -5)
|
||||
TimeRelation.NOW -> Unit
|
||||
TimeRelation.LATER -> fakeCalendar.add(Calendar.DATE, 5)
|
||||
}
|
||||
val relativeTime = fakeCalendar.time
|
||||
return if (isInPromo) {
|
||||
Wallpaper.Remote.House(name = name, expirationDate = relativeTime)
|
||||
} else {
|
||||
Wallpaper.Remote.Firefox(name = name)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue