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