diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index 3aea224cf..c66b2ea1c 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -232,6 +232,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { setAppAllStartTelemetry(intent.toSafeIntent()) + components.services.reviewPromptController.trackApplicationLaunch() + StartupTimeline.onActivityCreateEndHome(this) // DO NOT MOVE ANYTHING BELOW HERE. } diff --git a/app/src/main/java/org/mozilla/fenix/components/ReviewPromptController.kt b/app/src/main/java/org/mozilla/fenix/components/ReviewPromptController.kt new file mode 100644 index 000000000..36109b893 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/components/ReviewPromptController.kt @@ -0,0 +1,76 @@ +/* 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.components + +import android.app.Activity +import android.content.Context +import androidx.annotation.VisibleForTesting +import com.google.android.play.core.ktx.launchReview +import com.google.android.play.core.ktx.requestReview +import com.google.android.play.core.review.ReviewManagerFactory +import org.mozilla.fenix.utils.Settings + +/** + * Interface that describes the settings needed to track the Review Prompt. + */ +interface ReviewSettings { + var numberOfAppLaunches: Int + val isDefaultBrowser: Boolean + var lastReviewPromptTimeInMillis: Long +} + +/** + * Wraps `Settings` to conform to `ReviewSettings`. + */ +class FenixReviewSettings( + val settings: Settings +): ReviewSettings { + override var numberOfAppLaunches: Int + get() = settings.numberOfAppLaunches + set(value) { settings.numberOfAppLaunches = value } + override val isDefaultBrowser: Boolean + get() = settings.isDefaultBrowser() + override var lastReviewPromptTimeInMillis: Long + get() = settings.lastReviewPromptTimeInMillis + set(value) { settings.lastReviewPromptTimeInMillis = value } +} + +/** + * Controls the Review Prompt behavior. + */ +class ReviewPromptController( + private val context: Context, + private val reviewSettings: ReviewSettings, + private val timeNowInMillis: () -> Long = { System.currentTimeMillis() } +) { + suspend fun promptReview(activity: Activity) { + if (shouldShowPrompt()) { + val manager = ReviewManagerFactory.create(context) + val reviewInfo = manager.requestReview() + manager.launchReview(activity, reviewInfo) + + reviewSettings.lastReviewPromptTimeInMillis = timeNowInMillis() + } + } + + fun trackApplicationLaunch() { + reviewSettings.numberOfAppLaunches = reviewSettings.numberOfAppLaunches + 1 + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + fun shouldShowPrompt(): Boolean { + if (!reviewSettings.isDefaultBrowser) { return false } + + val hasOpenedFiveTimes = reviewSettings.numberOfAppLaunches >= 5 + val apprxFourMonthsAgo = timeNowInMillis() - (APPRX_MONTH_IN_MILLIS * 4) + val hasNotBeenPromptedLastFourMonths = reviewSettings.lastReviewPromptTimeInMillis <= apprxFourMonthsAgo + + return hasOpenedFiveTimes && hasNotBeenPromptedLastFourMonths + } + + companion object { + private const val APPRX_MONTH_IN_MILLIS: Long = 1000L * 60L * 60L * 24L * 30L + } +} \ No newline at end of file diff --git a/app/src/main/java/org/mozilla/fenix/components/Services.kt b/app/src/main/java/org/mozilla/fenix/components/Services.kt index cdd549f86..fd099ffdd 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Services.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Services.kt @@ -14,6 +14,7 @@ import mozilla.components.feature.app.links.AppLinksInterceptor import mozilla.components.service.fxa.manager.FxaAccountManager import org.mozilla.fenix.R import org.mozilla.fenix.ext.getPreferenceKey +import org.mozilla.fenix.ext.settings import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.utils.Mockable @@ -44,4 +45,11 @@ class Services( } ) } + + val reviewPromptController by lazy { + ReviewPromptController( + context, + FenixReviewSettings(context.settings()) + ) + } } 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 c5d679575..cac266fe2 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -176,6 +176,8 @@ class HomeFragment : Fragment() { if (!onboarding.userHasBeenOnboarded()) { requireComponents.analytics.metrics.track(Event.OpenedAppFirstRun) } + + requireComponents.services.reviewPromptController.promptReview(requireActivity()) } } @@ -570,26 +572,10 @@ class HomeFragment : Fragment() { recommendPrivateBrowsingShortcut() } - // In-app review prompt - requireContext().settings().incrementNumTimesOpenedAfterInstall() - handleInAppReviewPrompt() - // We only want this observer live just before we navigate away to the collection creation screen requireComponents.core.tabCollectionStorage.unregister(collectionStorageObserver) } - private fun handleInAppReviewPrompt() { - if (requireContext().settings().shouldShowUserFeedbackPrompt) { - lifecycleScope.launch { - val manager = ReviewManagerFactory.create(requireContext()) - val reviewInfo = manager.requestReview() - manager.launchReview(requireActivity(), reviewInfo) - - requireContext().settings().incrementNumTimesFeedbackPromptShown() - } - } - } - private fun dispatchModeChanges(mode: Mode) { if (mode != Mode.fromBrowsingMode(browsingModeManager.mode)) { homeFragmentStore.dispatch(HomeFragmentAction.ModeChange(mode)) 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 c9d4f0fb3..c953fdd50 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -102,11 +102,21 @@ class Settings(private val appContext: Context) : PreferencesHolder { appContext.getSharedPreferences(FENIX_PREFERENCES, MODE_PRIVATE) var showTopFrecentSites by featureFlagPreference( - appContext.getPreferenceKey(R.string.pref_key_enable_top_frecent_sites), + appContext.getPreferenceKey(R.string.pref_key_enable_top_frecent_sites), default = false, featureFlag = FeatureFlags.topFrecentSite ) + var numberOfAppLaunches by intPreference( + appContext.getPreferenceKey(R.string.pref_key_times_app_opened), + default = 0 + ) + + var lastReviewPromptTimeInMillis by longPreference( + appContext.getPreferenceKey(R.string.pref_key_last_review_prompt_shown_time), + default = 0L + ) + var waitToShowPageUntilFirstPaint by featureFlagPreference( appContext.getPreferenceKey(R.string.pref_key_wait_first_paint), default = false, @@ -795,62 +805,6 @@ class Settings(private val appContext: Context) : PreferencesHolder { 0 ) - private val numTimesFeedbackPromptShown: Int - get() = preferences.getInt( - appContext.getPreferenceKey(R.string.pref_key_feedback_prompt_shown), - 0 - ) - - fun incrementNumTimesFeedbackPromptShown() { - preferences.edit().putInt( - appContext.getPreferenceKey(R.string.pref_key_feedback_prompt_shown), - numTimesFeedbackPromptShown + 1 - ).apply() - } - - private val numTimesOpenedAfterInstall: Int - get() = preferences.getInt( - appContext.getPreferenceKey(R.string.pref_key_times_opened_after_install), - 0 - ) - - fun incrementNumTimesOpenedAfterInstall() { - preferences.edit().putInt( - appContext.getPreferenceKey(R.string.pref_key_times_opened_after_install), - numTimesOpenedAfterInstall + 1 - ).apply() - } - - private val timeWhenPromptWasLastShown: Int - get() = preferences.getInt( - appContext.getPreferenceKey(R.string.pref_key_time_prompt_shown), - 0 - ) - - @RequiresApi(Build.VERSION_CODES.O) - fun incrementTimeWhenPromptWasLastShown() { - preferences.edit().putInt( - appContext.getPreferenceKey(R.string.pref_key_time_prompt_shown), - LocalDate.now().dayOfYear - ).apply() - } - - /* - * User feedback prompt is shown when Firefox is set as the default browser and after the - * 5th time the user opened the app. This prompt should only be shown once every four months. - */ - @RequiresApi(Build.VERSION_CODES.O) - val shouldShowUserFeedbackPrompt: Boolean = - numTimesOpenedAfterInstall >= 5 - && isDefaultBrowser() - && (hasBeenFourMonthsSince(timeWhenPromptWasLastShown)) - - @RequiresApi(Build.VERSION_CODES.O) - private fun hasBeenFourMonthsSince(timeWhenPromptWasLastShown: Int): Boolean { - val numDays = LocalDate.now().dayOfYear - timeWhenPromptWasLastShown - return numDays >= MIN_DAYS_SINCE_FEEDBACK_PROMPT - } - val showPrivateModeContextualFeatureRecommender: Boolean get() { val focusInstalled = MozillaProductDetector diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index 21e4e4b21..0043ba7b3 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -58,9 +58,8 @@ pref_key_open_in_app_opened pref_key_install_pwa_opened pref_key_install_pwa_visits - pref_key_feedback_prompt_shown - pref_key_times_opened_after_install - pref_key_time_prompt_shown + pref_key_times_app_opened + pref_key_last_review_prompt_shown_time pref_key_telemetry