From e96eb2568215f7b55439d2e493cef2abc8d5faf8 Mon Sep 17 00:00:00 2001 From: Elise Richards Date: Mon, 24 Aug 2020 14:34:27 -0500 Subject: [PATCH] Allow play store in app review to be shown to users --- app/build.gradle | 3 + .../org/mozilla/fenix/home/HomeFragment.kt | 19 ++++++ .../java/org/mozilla/fenix/utils/Settings.kt | 59 +++++++++++++++++++ app/src/main/res/values/preference_keys.xml | 3 + buildSrc/src/main/java/Dependencies.kt | 7 +++ 5 files changed, 91 insertions(+) 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}"