diff --git a/app/metrics.yaml b/app/metrics.yaml index 3991e7a737..6bdec56096 100644 --- a/app/metrics.yaml +++ b/app/metrics.yaml @@ -6954,3 +6954,66 @@ search_terms: notification_emails: - android-probes@mozilla.com expires: "2022-12-01" + +wallpapers: + discovered_wallpaper_feature: + type: boolean + description: | + Whether or not the user has discovered the wallpaper feature. + bugs: + - https://github.com/mozilla-mobile/fenix/issues/23381 + data_reviews: + - https://github.com/mozilla-mobile/fenix/pull/23382 + notification_emails: + - android-probes@mozilla.com + data_sensitivity: + - interaction + expires: "2023-05-01" + new_wallpaper_applied: + type: labeled_counter + description: | + How many and which type of wallpapers were applied. + bugs: + - https://github.com/mozilla-mobile/fenix/issues/23381 + data_reviews: + - https://github.com/mozilla-mobile/fenix/pull/23382 + notification_emails: + - android-probes@mozilla.com + data_sensitivity: + - interaction + expires: "2023-05-01" + selected_wallpaper: + type: event + description: | + Records the wallpaper that is active at session start. + extra_keys: + name: + description: The name of the selected wallpaper + type: string + is_themed: + description: Whether the selected wallpaper is part of a theme. + type: boolean + bugs: + - https://github.com/mozilla-mobile/fenix/issues/23381 + data_reviews: + - https://github.com/mozilla-mobile/fenix/pull/23382 + notification_emails: + - android-probes@mozilla.com + data_sensitivity: + - interaction + expires: "2023-05-01" + wallpaper_reset_to_default: + type: event + description: | + The user has reset their wallpaper back to the default background. + This means the default is active on startup, and has changed since the + previous session. + bugs: + - https://github.com/mozilla-mobile/fenix/issues/23381 + data_reviews: + - https://github.com/mozilla-mobile/fenix/pull/23382 + data_sensitivity: + - interaction + notification_emails: + - android-probes@mozilla.com + expires: "2023-05-01" diff --git a/app/src/main/java/org/mozilla/fenix/components/Components.kt b/app/src/main/java/org/mozilla/fenix/components/Components.kt index 5451bdc5a0..e662f5007b 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Components.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Components.kt @@ -145,7 +145,7 @@ class Components(private val context: Context) { } val wallpaperManager by lazyMonitored { - WallpaperManager(settings, WallpapersAssetsStorage(context)) + WallpaperManager(settings, WallpapersAssetsStorage(context), analytics.metrics) } val analytics by lazyMonitored { Analytics(context) } diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt index ffe14641ea..0ba0429ba8 100644 --- a/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt +++ b/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt @@ -676,6 +676,13 @@ sealed class Event { sealed class Search + sealed class Wallpaper : Event() { + object DiscoveredFeature : Wallpaper() + data class NewWallpaperApplied(val wallpaper: org.mozilla.fenix.wallpapers.Wallpaper) : Wallpaper() + data class WallpaperSelected(val wallpaper: org.mozilla.fenix.wallpapers.Wallpaper) : Wallpaper() + object WallpaperResetToDefault : Wallpaper() + } + internal open val extras: Map<*, String>? get() = null } diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt index bd090d2dac..db1fb94155 100644 --- a/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt +++ b/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt @@ -58,6 +58,7 @@ import org.mozilla.fenix.GleanMetrics.ToolbarSettings import org.mozilla.fenix.GleanMetrics.TopSites import org.mozilla.fenix.GleanMetrics.TrackingProtection import org.mozilla.fenix.GleanMetrics.VoiceSearch +import org.mozilla.fenix.GleanMetrics.Wallpapers import org.mozilla.fenix.ext.components private class EventWrapper>( @@ -907,6 +908,25 @@ private val Event.wrapper: EventWrapper<*>? is Event.JumpBackInGroupTapped -> EventWrapper( { SearchTerms.jumpBackInGroupTapped.record(it) } ) + is Event.Wallpaper.DiscoveredFeature -> EventWrapper( + { Wallpapers.discoveredWallpaperFeature.set(true) } + ) + is Event.Wallpaper.NewWallpaperApplied -> EventWrapper( + { Wallpapers.newWallpaperApplied[this.wallpaper.name].add() } + ) + is Event.Wallpaper.WallpaperSelected -> EventWrapper( + { + Wallpapers.selectedWallpaper.record( + Wallpapers.SelectedWallpaperExtra( + name = this.wallpaper.name, + isThemed = this.wallpaper.isThemed, + ), + ) + }, + ) + Event.Wallpaper.WallpaperResetToDefault -> EventWrapper( + { Wallpapers.wallpaperResetToDefault.record() } + ) // Don't record other events in Glean: is Event.AddBookmark -> null diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index cc76390360..73d15c1fe5 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -767,6 +767,7 @@ class HomeFragment : Fragment() { if (shouldEnableWallpaper() && context.settings().wallpapersSwitchedByLogoTap) { binding.wordmark.setOnClickListener { val manager = requireComponents.wallpaperManager + manager.recordDiscoveredMetric() manager.updateWallpaper( wallpaperContainer = binding.homeLayout, newWallpaper = manager.switchToNextWallpaper() 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 28277b5671..becf4a3751 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 @@ -36,6 +36,7 @@ class WallpaperSettingsFragment : Fragment() { container: ViewGroup?, savedInstanceState: Bundle? ): View? { + wallpaperManager.recordDiscoveredMetric() return ComposeView(requireContext()).apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { 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 bd9ce45271..5392c47659 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -166,6 +166,11 @@ class Settings(private val appContext: Context) : PreferencesHolder { default = "" ) + var previousWallpaper by stringPreference( + appContext.getPreferenceKey(R.string.pref_key_previous_wallpaper), + default = WallpaperManager.defaultWallpaper.name + ) + var currentWallpaper by stringPreference( appContext.getPreferenceKey(R.string.pref_key_current_wallpaper), default = WallpaperManager.defaultWallpaper.name @@ -176,6 +181,11 @@ class Settings(private val appContext: Context) : PreferencesHolder { default = true ) + var wallpapersDiscovered by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_wallpapers_discovered), + default = false + ) + var openLinksInAPrivateTab by booleanPreference( appContext.getPreferenceKey(R.string.pref_key_open_links_in_a_private_tab), default = false 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 3a82302d72..6bd2785b23 100644 --- a/app/src/main/java/org/mozilla/fenix/wallpapers/Wallpaper.kt +++ b/app/src/main/java/org/mozilla/fenix/wallpapers/Wallpaper.kt @@ -10,10 +10,12 @@ package org.mozilla.fenix.wallpapers * @property portraitPath A file path for the portrait version of this wallpaper. * @property landscapePath A file path for the landscape version of this wallpaper. * @property isDark Indicates if the most predominant color on the wallpaper is dark. + * @property isThemed Whether the wallpaper belongs to a themed collection. */ data class Wallpaper( val name: String, val portraitPath: String, val landscapePath: String, - val isDark: Boolean + val isDark: Boolean, + val isThemed: Boolean = false, ) diff --git a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperManager.kt b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperManager.kt index b2f4d4cce0..865367cca8 100644 --- a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperManager.kt +++ b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperManager.kt @@ -14,15 +14,19 @@ import androidx.appcompat.app.AppCompatDelegate import mozilla.components.support.base.log.logger.Logger import mozilla.components.support.ktx.android.content.getColorFromAttr import org.mozilla.fenix.R +import org.mozilla.fenix.components.metrics.Event +import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.ext.asActivity import org.mozilla.fenix.utils.Settings /** * Provides access to available wallpapers and manages their states. */ +@Suppress("TooManyFunctions") class WallpaperManager( private val settings: Settings, - private val wallpaperStorage: WallpaperStorage + private val wallpaperStorage: WallpaperStorage, + private val metrics: MetricController, ) { val logger = Logger("WallpaperManager") var availableWallpapers: List = loadWallpapers() @@ -30,6 +34,7 @@ class WallpaperManager( var currentWallpaper: Wallpaper = getCurrentWallpaperFromSettings() set(value) { + settings.previousWallpaper = currentWallpaper.name settings.currentWallpaper = value.name field = value } @@ -50,6 +55,7 @@ class WallpaperManager( currentWallpaper = newWallpaper adjustTheme(wallpaperContainer.context) + recordWallpaperAppliedMetric(newWallpaper) } private fun adjustTheme(context: Context) { @@ -107,6 +113,8 @@ class WallpaperManager( defaultWallpaper } else { availableWallpapers.find { it.name == currentWallpaper } ?: defaultWallpaper + }.also { + recordSelectionMetrics(it) } } @@ -134,6 +142,28 @@ class WallpaperManager( } } + fun recordDiscoveredMetric() { + val hasSentMetric = settings.wallpapersDiscovered + if (!hasSentMetric) { + metrics.track(Event.Wallpaper.DiscoveredFeature) + settings.wallpapersDiscovered = true + } + } + + private fun recordWallpaperAppliedMetric(appliedWallpaper: Wallpaper) { + metrics.track(Event.Wallpaper.NewWallpaperApplied(appliedWallpaper)) + } + + private fun recordSelectionMetrics(currentWallpaper: Wallpaper) { + val previousWallpaperName = settings.previousWallpaper + if (previousWallpaperName != defaultWallpaper.name && currentWallpaper == defaultWallpaper) { + metrics.track(Event.Wallpaper.WallpaperResetToDefault) + // This metric will continue to be reported unless the previous wallpaper is reset + settings.previousWallpaper = currentWallpaper.name + } + metrics.track(Event.Wallpaper.WallpaperSelected(currentWallpaper)) + } + companion object { const val DEFAULT_RESOURCE = R.attr.homeBackground val defaultWallpaper = Wallpaper( diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index 6b294879c3..be06f9ccb0 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -189,8 +189,10 @@ pref_key_wallpapers + pref_key_previous_wallpaper pref_key_current_wallpaper pref_key_wallpapers_switched_by_logo_tap + pref_key_wallpapers_discovered pref_key_encryption_key_generated diff --git a/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperManagerTest.kt b/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperManagerTest.kt new file mode 100644 index 0000000000..ac35a3f8f5 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperManagerTest.kt @@ -0,0 +1,143 @@ +package org.mozilla.fenix.wallpapers + +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 org.junit.Assert.assertEquals +import org.junit.Test +import org.mozilla.fenix.components.metrics.Event +import org.mozilla.fenix.components.metrics.MetricController +import org.mozilla.fenix.utils.Settings + +class WallpaperManagerTest { + + private val fakeWallpaper = Wallpaper( + name = "fake wallpaper", + portraitPath = "", + landscapePath = "", + isDark = false, + isThemed = false, + ) + private val fakeThemedWallpaper = Wallpaper( + name = "fake themed wallpaper", + portraitPath = "", + landscapePath = "", + isDark = false, + isThemed = true, + ) + private val fakeWallpapers = listOf(fakeWallpaper, fakeThemedWallpaper) + + private val mockSettings: Settings = mockk() + private val mockStorage: WallpaperStorage = mockk { + every { loadAll() } returns fakeWallpapers + } + private val mockMetrics: MetricController = mockk() + + @Test + fun `WHEN wallpaper set THEN current wallpaper updated in settings`() { + every { mockMetrics.track(any()) } just runs + val currentCaptureSlot = slot() + val updatedWallpaper = fakeWallpaper + every { mockSettings.currentWallpaper } returns WallpaperManager.defaultWallpaper.name + every { mockSettings.currentWallpaper = capture(currentCaptureSlot) } just runs + every { mockSettings.previousWallpaper } returns WallpaperManager.defaultWallpaper.name + every { mockSettings.previousWallpaper = capture(slot()) } just runs + + val wallpaperManager = WallpaperManager(mockSettings, mockStorage, mockMetrics) + wallpaperManager.currentWallpaper = updatedWallpaper + + assertEquals(updatedWallpaper.name, currentCaptureSlot.captured) + } + + @Test + fun `WHEN wallpaper set THEN current wallpaper saved as previous in settings`() { + every { mockMetrics.track(any()) } just runs + val previousCaptureSlot = slot() + val currentWallpaper = fakeWallpaper + every { mockSettings.currentWallpaper } returns currentWallpaper.name + every { mockSettings.currentWallpaper = capture(slot()) } just runs + every { mockSettings.previousWallpaper } returns WallpaperManager.defaultWallpaper.name + every { mockSettings.previousWallpaper = capture(previousCaptureSlot) } just runs + + val wallpaperManager = WallpaperManager(mockSettings, mockStorage, mockMetrics) + wallpaperManager.currentWallpaper = WallpaperManager.defaultWallpaper + + assertEquals(currentWallpaper.name, previousCaptureSlot.captured) + } + + @Test + fun `WHEN wallpaper is initially fetched from settings THEN selection metric reported`() { + val defaultWallpaper = WallpaperManager.defaultWallpaper + every { mockSettings.previousWallpaper } returns defaultWallpaper.name + every { mockSettings.currentWallpaper } returns defaultWallpaper.name + every { mockMetrics.track(any()) } just runs + + WallpaperManager(mockSettings, mockStorage, mockMetrics) + + verify { mockMetrics.track(Event.Wallpaper.WallpaperSelected(defaultWallpaper)) } + } + + @Test + fun `GIVEN previous wallpaper was not default WHEN current wallpaper is loaded as default THEN selection metric reported and previous reset`() { + val defaultWallpaper = WallpaperManager.defaultWallpaper + val previousSlot = slot() + every { mockSettings.previousWallpaper } returns fakeWallpaper.name + every { mockSettings.previousWallpaper = capture(previousSlot) } just runs + every { mockSettings.currentWallpaper } returns defaultWallpaper.name + every { mockMetrics.track(any()) } just runs + + WallpaperManager(mockSettings, mockStorage, mockMetrics) + + assertEquals(defaultWallpaper.name, previousSlot.captured) + verify { mockMetrics.track(Event.Wallpaper.WallpaperResetToDefault) } + verify { mockMetrics.track(Event.Wallpaper.WallpaperSelected(defaultWallpaper)) } + } + + @Test + fun `WHEN wallpaper updated THEN wallpaper applied metric recorded`() { + val defaultWallpaper = WallpaperManager.defaultWallpaper + every { mockSettings.previousWallpaper } returns defaultWallpaper.name + every { mockSettings.previousWallpaper = capture(slot()) } just runs + every { mockSettings.currentWallpaper } returns defaultWallpaper.name + every { mockSettings.currentWallpaper = capture(slot()) } just runs + every { mockMetrics.track(any()) } just runs + + val manager = WallpaperManager(mockSettings, mockStorage, mockMetrics) + manager.updateWallpaper(mockk(relaxed = true), defaultWallpaper) + + verify { mockMetrics.track(Event.Wallpaper.NewWallpaperApplied(defaultWallpaper)) } + } + + @Test + fun `WHEN wallpaper discovered metric not previously recorded WHEN metric record attempted THEN metric sent`() { + every { mockSettings.wallpapersDiscovered } returns false + every { mockSettings.previousWallpaper } returns WallpaperManager.defaultWallpaper.name + every { mockSettings.currentWallpaper } returns WallpaperManager.defaultWallpaper.name + every { mockSettings.wallpapersDiscovered = true } just runs + every { mockMetrics.track(any()) } just runs + + val wallpaperManager = WallpaperManager(mockSettings, mockStorage, mockMetrics) + + wallpaperManager.recordDiscoveredMetric() + + verify { mockMetrics.track(Event.Wallpaper.DiscoveredFeature) } + verify { mockSettings.wallpapersDiscovered = true } + } + + @Test + fun `WHEN wallpaper discovered metric previously recorded WHEN metric record attempted THEN metric not sent`() { + every { mockSettings.wallpapersDiscovered } returns true + every { mockSettings.previousWallpaper } returns WallpaperManager.defaultWallpaper.name + every { mockSettings.currentWallpaper } returns WallpaperManager.defaultWallpaper.name + every { mockMetrics.track(any()) } just runs + + val wallpaperManager = WallpaperManager(mockSettings, mockStorage, mockMetrics) + + wallpaperManager.recordDiscoveredMetric() + + verify(exactly = 0) { mockMetrics.track(Event.Wallpaper.DiscoveredFeature) } + } +}