diff --git a/app/build.gradle b/app/build.gradle
index 564db3d20..2a7e8fa51 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -439,6 +439,9 @@ dependencies {
implementation Deps.google_ads_id // Required for the Google Advertising ID
+ implementation Deps.google_play_store // Required for in-app reviews
+ implementation Deps.google_play_core_ktx // Required for in-app reviews
+
androidTestImplementation Deps.uiautomator
// Removed pending AndroidX fixes
androidTestImplementation "tools.fastlane:screengrab:2.0.0"
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 5cdf05181..c5d679575 100644
--- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt
@@ -42,6 +42,9 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE
import com.google.android.material.snackbar.Snackbar
+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 kotlinx.android.synthetic.main.fragment_home.*
import kotlinx.android.synthetic.main.fragment_home.view.*
import kotlinx.android.synthetic.main.no_collections_message.view.*
@@ -567,10 +570,26 @@ 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 e4fb1892e..c9d4f0fb3 100644
--- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
+++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
@@ -12,6 +12,7 @@ import android.content.SharedPreferences
import android.content.pm.ShortcutManager
import android.os.Build
import android.view.accessibility.AccessibilityManager
+import androidx.annotation.RequiresApi
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.PRIVATE
import androidx.lifecycle.LifecycleOwner
@@ -39,6 +40,7 @@ import org.mozilla.fenix.settings.logins.SavedLoginsSortingStrategyMenu
import org.mozilla.fenix.settings.logins.SortingStrategy
import org.mozilla.fenix.settings.registerOnSharedPreferenceChangeListener
import java.security.InvalidParameterException
+import java.time.LocalDate
private const val AUTOPLAY_USER_SETTING = "AUTOPLAY_USER_SETTING"
@@ -63,6 +65,7 @@ class Settings(private val appContext: Context) : PreferencesHolder {
private const val ALLOWED_INT = 2
private const val CFR_COUNT_CONDITION_FOCUS_INSTALLED = 1
private const val CFR_COUNT_CONDITION_FOCUS_NOT_INSTALLED = 3
+ private const val MIN_DAYS_SINCE_FEEDBACK_PROMPT = 120
private fun Action.toInt() = when (this) {
Action.BLOCKED -> BLOCKED_INT
@@ -792,6 +795,62 @@ 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 a84532b30..21e4e4b21 100644
--- a/app/src/main/res/values/preference_keys.xml
+++ b/app/src/main/res/values/preference_keys.xml
@@ -58,6 +58,9 @@
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_telemetry
diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt
index 7118dad51..eae088417 100644
--- a/buildSrc/src/main/java/Dependencies.kt
+++ b/buildSrc/src/main/java/Dependencies.kt
@@ -42,6 +42,9 @@ object Versions {
const val google_ads_id_version = "16.0.0"
+ const val google_play_store_version = "1.8.0"
+ const val google_play_core_ktx_version = "1.8.1"
+
const val airbnb_lottie = "3.4.0"
}
@@ -209,6 +212,10 @@ object Deps {
const val google_ads_id = "com.google.android.gms:play-services-ads-identifier:${Versions.google_ads_id_version}"
+ // Required for in-app reviews
+ const val google_play_store = "com.google.android.play:core:${Versions.google_play_store_version}"
+ const val google_play_core_ktx = "com.google.android.play:core-ktx:${Versions.google_play_core_ktx_version}"
+
const val lottie = "com.airbnb.android:lottie:${Versions.airbnb_lottie}"
const val detektApi = "io.gitlab.arturbosch.detekt:detekt-api:${Versions.detekt}"