diff --git a/app/src/androidTest/java/org/mozilla/fenix/perf/StartupExcessiveResourceUseTest.kt b/app/src/androidTest/java/org/mozilla/fenix/perf/StartupExcessiveResourceUseTest.kt
index 68b361cddd..08de5f2dcd 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/perf/StartupExcessiveResourceUseTest.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/perf/StartupExcessiveResourceUseTest.kt
@@ -21,7 +21,7 @@ import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.HomeActivityTestRule
// BEFORE INCREASING THESE VALUES, PLEASE CONSULT WITH THE PERF TEAM.
-private const val EXPECTED_SUPPRESSION_COUNT = 20
+private const val EXPECTED_SUPPRESSION_COUNT = 21
@Suppress("TopLevelPropertyNaming") // it's silly this would have a different naming convention b/c no const
private val EXPECTED_RUNBLOCKING_RANGE = 0..1 // CI has +1 counts compared to local runs: increment these together
private const val EXPECTED_RECYCLER_VIEW_CONSTRAINT_LAYOUT_CHILDREN = 4
diff --git a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt
index 629e36cfa5..81cc0d8d9a 100644
--- a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt
+++ b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt
@@ -5,8 +5,10 @@
package org.mozilla.fenix
import android.content.Context
+import android.os.StrictMode
import mozilla.components.support.locale.LocaleManager
import mozilla.components.support.locale.LocaleManager.getSystemDefault
+import org.mozilla.fenix.ext.components
/**
* A single source for setting feature flags that are mostly based on build type.
@@ -96,4 +98,16 @@ object FeatureFlags {
* Enables history improvement features.
*/
val historyImprovementFeatures = Config.channel.isNightlyOrDebug
+
+ /**
+ * Enables themed wallpapers feature.
+ */
+ fun isThemedWallpapersFeatureEnabled(context: Context): Boolean {
+ val strictMode = context.components.strictMode
+ return strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
+ val langTag = LocaleManager.getCurrentLocale(context)
+ ?.toLanguageTag() ?: getSystemDefault().toLanguageTag()
+ listOf("en-US", "es-US").contains(langTag) && Config.channel.isNightlyOrDebug
+ }
+ }
}
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 23b0b2b03e..e46d631e55 100644
--- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt
@@ -17,6 +17,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.View.AccessibilityDelegate
import android.view.ViewGroup
+import android.view.ViewTreeObserver
import android.view.accessibility.AccessibilityEvent
import android.widget.Button
import android.widget.LinearLayout
@@ -824,6 +825,25 @@ class HomeFragment : Fragment() {
// triggered to cause an automatic update on warm start (no tab selection occurs). So we
// update it manually here.
requireComponents.useCases.sessionUseCases.updateLastAccess()
+ if (shouldAnimateLogoForWallpaper()) {
+ _binding?.sessionControlRecyclerView?.viewTreeObserver?.addOnGlobalLayoutListener(
+ homeLayoutListenerForLogoAnimation
+ )
+ }
+ }
+
+ // To try to find a good time to show the logo animation, we are waiting until all
+ // the sub-recyclerviews (recentBookmarks, collections, recentTabs,recentVisits
+ // and pocketStories) on the home screen have been layout.
+ private val homeLayoutListenerForLogoAnimation = object : ViewTreeObserver.OnGlobalLayoutListener {
+ override fun onGlobalLayout() {
+ _binding?.let { safeBindings ->
+ requireComponents.wallpaperManager.animateLogoIfNeeded(safeBindings.wordmark)
+ safeBindings.sessionControlRecyclerView.viewTreeObserver.removeOnGlobalLayoutListener(
+ this
+ )
+ }
+ }
}
override fun onPause() {
@@ -1203,6 +1223,23 @@ class HomeFragment : Fragment() {
}
}
+ // We want to show the animation in a time when the user less distracted
+ // The Heuristics are:
+ // 1) The animation hasn't shown before.
+ // 2) The user has onboarded.
+ // 3) It's the third time the user enters the app.
+ // 4) The user is part of the right audience.
+ @Suppress("MagicNumber")
+ private fun shouldAnimateLogoForWallpaper(): Boolean {
+ val localContext = context ?: return false
+ val settings = localContext.settings()
+
+ return shouldEnableWallpaper() && settings.shouldAnimateFirefoxLogo &&
+ onboarding.userHasBeenOnboarded() &&
+ settings.numberOfAppLaunches >= 3 &&
+ FeatureFlags.isThemedWallpapersFeatureEnabled(localContext)
+ }
+
private fun shouldEnableWallpaper() =
FeatureFlags.showWallpapers && !(activity as HomeActivity).themeManager.currentTheme.isPrivate
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..da06462d17 100644
--- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
+++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
@@ -443,6 +443,16 @@ class Settings(private val appContext: Context) : PreferencesHolder {
featureFlag = FeatureFlags.inactiveTabs
)
+ /**
+ * Indicates if the Firefox logo on the home screen should be animated,
+ * to show users that they can change the wallpaper by tapping on the Firefox logo.
+ */
+ var shouldAnimateFirefoxLogo by featureFlagPreference(
+ appContext.getPreferenceKey(R.string.pref_key_show_logo_animation),
+ default = FeatureFlags.showWallpapers,
+ featureFlag = FeatureFlags.showWallpapers
+ )
+
/**
* Indicates if the user has enabled the search term tab groups feature.
*/
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 63e53d4fa4..0f6db9f7b4 100644
--- a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperManager.kt
+++ b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperManager.kt
@@ -4,11 +4,15 @@
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.drawable.BitmapDrawable
+import android.os.Handler
+import android.os.Looper
import android.view.View
import androidx.appcompat.app.AppCompatDelegate
import mozilla.components.support.base.log.logger.Logger
@@ -135,6 +139,39 @@ class WallpaperManager(
}
}
+ /**
+ * Animates the Firefox logo, if it hasn't been animated before, otherwise nothing will happen.
+ * After animating the first time, the [Settings.shouldAnimateFirefoxLogo] setting
+ * will be updated.
+ */
+ @Suppress("MagicNumber")
+ fun animateLogoIfNeeded(logo: View) {
+ if (!settings.shouldAnimateFirefoxLogo) {
+ return
+ }
+ Handler(Looper.getMainLooper()).postDelayed(
+ {
+ val animator1 = ObjectAnimator.ofFloat(logo, "rotation", 0f, 10f)
+ val animator2 = ObjectAnimator.ofFloat(logo, "rotation", 10f, 0f)
+ val animator3 = ObjectAnimator.ofFloat(logo, "rotation", 0f, 10f)
+ val animator4 = ObjectAnimator.ofFloat(logo, "rotation", 10f, 0f)
+
+ animator1.duration = 200
+ animator2.duration = 200
+ animator3.duration = 200
+ animator4.duration = 200
+
+ val set = AnimatorSet()
+
+ set.play(animator1).before(animator2).after(animator3).before(animator4)
+ set.start()
+
+ settings.shouldAnimateFirefoxLogo = false
+ },
+ ANIMATION_DELAY_MS
+ )
+ }
+
companion object {
const val DEFAULT_RESOURCE = R.attr.homeBackground
val defaultWallpaper = Wallpaper(
@@ -144,5 +181,6 @@ class WallpaperManager(
isDark = false,
themeCollection = WallpaperThemeCollection.None
)
+ private const val ANIMATION_DELAY_MS = 1500L
}
}
diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml
index 6b294879c3..f62d014aa0 100644
--- a/app/src/main/res/values/preference_keys.xml
+++ b/app/src/main/res/values/preference_keys.xml
@@ -191,6 +191,7 @@
pref_key_wallpapers
pref_key_current_wallpaper
pref_key_wallpapers_switched_by_logo_tap
+ pref_key_show_logo_animation
pref_key_encryption_key_generated