For #13507 - Performance fixes for the ReviewPromptController

pull/35/head
Jeff Boek 4 years ago
parent c6687d976e
commit c73870b794

@ -43,6 +43,7 @@ import mozilla.components.support.webextensions.WebExtensionSupport
import org.mozilla.fenix.StrictModeManager.enableStrictMode import org.mozilla.fenix.StrictModeManager.enableStrictMode
import org.mozilla.fenix.components.Components import org.mozilla.fenix.components.Components
import org.mozilla.fenix.components.metrics.MetricServiceType import org.mozilla.fenix.components.metrics.MetricServiceType
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.resetPoliciesAfter import org.mozilla.fenix.ext.resetPoliciesAfter
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.perf.StorageStatsMetrics import org.mozilla.fenix.perf.StorageStatsMetrics
@ -217,6 +218,12 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
} }
} }
fun queueReviewPrompt() {
GlobalScope.launch(Dispatchers.IO) {
components.reviewPromptController.trackApplicationLaunch()
}
}
initQueue() initQueue()
// We init these items in the visual completeness queue to avoid them initing in the critical // We init these items in the visual completeness queue to avoid them initing in the critical
@ -224,6 +231,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
queueInitExperiments() queueInitExperiments()
queueInitStorageAndServices() queueInitStorageAndServices()
queueMetrics() queueMetrics()
queueReviewPrompt()
} }
private fun startMetricsIfEnabled() { private fun startMetricsIfEnabled() {

@ -232,8 +232,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
setAppAllStartTelemetry(intent.toSafeIntent()) setAppAllStartTelemetry(intent.toSafeIntent())
components.services.reviewPromptController.trackApplicationLaunch()
StartupTimeline.onActivityCreateEndHome(this) // DO NOT MOVE ANYTHING BELOW HERE. StartupTimeline.onActivityCreateEndHome(this) // DO NOT MOVE ANYTHING BELOW HERE.
} }

@ -19,6 +19,7 @@ import mozilla.components.support.migration.state.MigrationStore
import org.mozilla.fenix.BuildConfig import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.components.metrics.AppAllSourceStartTelemetry import org.mozilla.fenix.components.metrics.AppAllSourceStartTelemetry
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.utils.ClipboardHandler import org.mozilla.fenix.utils.ClipboardHandler
import org.mozilla.fenix.utils.Mockable import org.mozilla.fenix.utils.Mockable
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
@ -114,4 +115,11 @@ class Components(private val context: Context) {
val wifiConnectionMonitor by lazy { WifiConnectionMonitor(context.getSystemService()!!) } val wifiConnectionMonitor by lazy { WifiConnectionMonitor(context.getSystemService()!!) }
val settings by lazy { Settings(context) } val settings by lazy { Settings(context) }
val reviewPromptController by lazy {
ReviewPromptController(
context,
FenixReviewSettings(settings)
)
}
} }

@ -10,6 +10,8 @@ import androidx.annotation.VisibleForTesting
import com.google.android.play.core.ktx.launchReview import com.google.android.play.core.ktx.launchReview
import com.google.android.play.core.ktx.requestReview import com.google.android.play.core.ktx.requestReview
import com.google.android.play.core.review.ReviewManagerFactory import com.google.android.play.core.review.ReviewManagerFactory
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.withContext
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
/** /**
@ -47,9 +49,15 @@ class ReviewPromptController(
private val tryPromptReview: suspend (Activity) -> Unit = { private val tryPromptReview: suspend (Activity) -> Unit = {
val manager = ReviewManagerFactory.create(context) val manager = ReviewManagerFactory.create(context)
val reviewInfo = manager.requestReview() val reviewInfo = manager.requestReview()
manager.launchReview(it, reviewInfo)
withContext(Main) {
manager.launchReview(it, reviewInfo)
}
} }
) { ) {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
@Volatile var reviewPromptIsReady = false
suspend fun promptReview(activity: Activity) { suspend fun promptReview(activity: Activity) {
if (shouldShowPrompt()) { if (shouldShowPrompt()) {
tryPromptReview(activity) tryPromptReview(activity)
@ -59,10 +67,19 @@ class ReviewPromptController(
fun trackApplicationLaunch() { fun trackApplicationLaunch() {
reviewSettings.numberOfAppLaunches = reviewSettings.numberOfAppLaunches + 1 reviewSettings.numberOfAppLaunches = reviewSettings.numberOfAppLaunches + 1
// We only want to show the the prompt after we've finished "launching" the application.
reviewPromptIsReady = true
} }
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun shouldShowPrompt(): Boolean { fun shouldShowPrompt(): Boolean {
if (!reviewPromptIsReady) {
return false
} else {
// We only want to try to show it once to avoid unnecessary disk reads
reviewPromptIsReady = false
}
if (!reviewSettings.isDefaultBrowser) { return false } if (!reviewSettings.isDefaultBrowser) { return false }
val hasOpenedFiveTimes = reviewSettings.numberOfAppLaunches >= NUMBER_OF_LAUNCHES_REQUIRED val hasOpenedFiveTimes = reviewSettings.numberOfAppLaunches >= NUMBER_OF_LAUNCHES_REQUIRED

@ -14,7 +14,6 @@ import mozilla.components.feature.app.links.AppLinksInterceptor
import mozilla.components.service.fxa.manager.FxaAccountManager import mozilla.components.service.fxa.manager.FxaAccountManager
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.getPreferenceKey import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.utils.Mockable import org.mozilla.fenix.utils.Mockable
@ -45,11 +44,4 @@ class Services(
} }
) )
} }
val reviewPromptController by lazy {
ReviewPromptController(
context,
FenixReviewSettings(context.settings())
)
}
} }

@ -173,8 +173,6 @@ class HomeFragment : Fragment() {
if (!onboarding.userHasBeenOnboarded()) { if (!onboarding.userHasBeenOnboarded()) {
requireComponents.analytics.metrics.track(Event.OpenedAppFirstRun) requireComponents.analytics.metrics.track(Event.OpenedAppFirstRun)
} }
requireComponents.services.reviewPromptController.promptReview(requireActivity())
} }
} }
@ -571,6 +569,10 @@ class HomeFragment : Fragment() {
// We only want this observer live just before we navigate away to the collection creation screen // We only want this observer live just before we navigate away to the collection creation screen
requireComponents.core.tabCollectionStorage.unregister(collectionStorageObserver) requireComponents.core.tabCollectionStorage.unregister(collectionStorageObserver)
lifecycleScope.launch(IO) {
requireComponents.reviewPromptController.promptReview(requireActivity())
}
} }
private fun dispatchModeChanges(mode: Mode) { private fun dispatchModeChanges(mode: Mode) {

@ -100,7 +100,7 @@ class Settings(private val appContext: Context) : PreferencesHolder {
appContext.getSharedPreferences(FENIX_PREFERENCES, MODE_PRIVATE) appContext.getSharedPreferences(FENIX_PREFERENCES, MODE_PRIVATE)
var showTopFrecentSites by featureFlagPreference( 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, default = false,
featureFlag = FeatureFlags.topFrecentSite featureFlag = FeatureFlags.topFrecentSite
) )

@ -43,7 +43,9 @@ class ReviewPromptControllerTest {
{ promptWasCalled = true } { promptWasCalled = true }
) )
controller.reviewPromptIsReady = true
controller.promptReview(HomeActivity()) controller.promptReview(HomeActivity())
assertEquals(settings.lastReviewPromptTimeInMillis, 0L) assertEquals(settings.lastReviewPromptTimeInMillis, 0L)
assertFalse(promptWasCalled) assertFalse(promptWasCalled)
} }
@ -64,11 +66,57 @@ class ReviewPromptControllerTest {
{ promptWasCalled = true } { promptWasCalled = true }
) )
controller.reviewPromptIsReady = true
controller.promptReview(HomeActivity()) controller.promptReview(HomeActivity())
assertEquals(100L, settings.lastReviewPromptTimeInMillis) assertEquals(100L, settings.lastReviewPromptTimeInMillis)
assertTrue(promptWasCalled) assertTrue(promptWasCalled)
} }
@Test
fun promptReviewWillNotBeCalledIfNotReady() = runBlockingTest {
var promptWasCalled = false
val settings = TestReviewSettings(
numberOfAppLaunches = 5,
isDefault = true,
lastReviewPromptTimeInMillis = 0L
)
val controller = ReviewPromptController(
testContext,
settings,
{ 100L },
{ promptWasCalled = true }
)
controller.promptReview(HomeActivity())
assertFalse(promptWasCalled)
}
@Test
fun promptReviewWillUnreadyPromptAfterCalled() = runBlockingTest {
var promptWasCalled = false
val settings = TestReviewSettings(
numberOfAppLaunches = 5,
isDefault = true,
lastReviewPromptTimeInMillis = 0L
)
val controller = ReviewPromptController(
testContext,
settings,
{ 100L },
{ promptWasCalled = true }
)
controller.reviewPromptIsReady = true
assertTrue(controller.reviewPromptIsReady)
controller.promptReview(HomeActivity())
assertFalse(controller.reviewPromptIsReady)
assertTrue(promptWasCalled)
}
@Test @Test
fun trackApplicationLaunch() { fun trackApplicationLaunch() {
val settings = TestReviewSettings( val settings = TestReviewSettings(
@ -80,12 +128,16 @@ class ReviewPromptControllerTest {
val controller = ReviewPromptController( val controller = ReviewPromptController(
testContext, testContext,
settings, settings,
{ 100L } { 0L }
) )
assertFalse(controller.reviewPromptIsReady)
assertEquals(4, settings.numberOfAppLaunches) assertEquals(4, settings.numberOfAppLaunches)
controller.trackApplicationLaunch() controller.trackApplicationLaunch()
assertEquals(5, settings.numberOfAppLaunches) assertEquals(5, settings.numberOfAppLaunches)
assertTrue(controller.reviewPromptIsReady)
} }
@Test @Test
@ -99,28 +151,31 @@ class ReviewPromptControllerTest {
val controller = ReviewPromptController( val controller = ReviewPromptController(
testContext, testContext,
settings, settings,
{ 1598416882805L } { TEST_TIME_NOW }
) )
// Test first success criteria // Test first success criteria
controller.reviewPromptIsReady = true
assertTrue(controller.shouldShowPrompt()) assertTrue(controller.shouldShowPrompt())
// Test with last prompt approx 4 months earlier // Test with last prompt approx 4 months earlier
settings.apply { settings.apply {
numberOfAppLaunches = 5 numberOfAppLaunches = 5
isDefault = true isDefault = true
lastReviewPromptTimeInMillis = 1588048882804L lastReviewPromptTimeInMillis = MORE_THAN_4_MONTHS_FROM_TEST_TIME_NOW
} }
controller.reviewPromptIsReady = true
assertTrue(controller.shouldShowPrompt()) assertTrue(controller.shouldShowPrompt())
// Test without being the default browser // Test without being the default browser
settings.apply { settings.apply {
numberOfAppLaunches = 5 numberOfAppLaunches = 5
isDefault = false isDefault = false
lastReviewPromptTimeInMillis = 1595824882805L lastReviewPromptTimeInMillis = 0L
} }
controller.reviewPromptIsReady = true
assertFalse(controller.shouldShowPrompt()) assertFalse(controller.shouldShowPrompt())
// Test with number of app launches < 5 // Test with number of app launches < 5
@ -130,15 +185,23 @@ class ReviewPromptControllerTest {
lastReviewPromptTimeInMillis = 0L lastReviewPromptTimeInMillis = 0L
} }
controller.reviewPromptIsReady = true
assertFalse(controller.shouldShowPrompt()) assertFalse(controller.shouldShowPrompt())
// Test with last prompt less than 4 months ago // Test with last prompt less than 4 months ago
settings.apply { settings.apply {
numberOfAppLaunches = 5 numberOfAppLaunches = 5
isDefault = true isDefault = true
lastReviewPromptTimeInMillis = 1595824882905L lastReviewPromptTimeInMillis = LESS_THAN_4_MONTHS_FROM_TEST_TIME_NOW
} }
controller.reviewPromptIsReady = true
assertFalse(controller.shouldShowPrompt()) assertFalse(controller.shouldShowPrompt())
} }
companion object {
private const val TEST_TIME_NOW = 1598416882805L
private const val MORE_THAN_4_MONTHS_FROM_TEST_TIME_NOW = 1588048882804L
private const val LESS_THAN_4_MONTHS_FROM_TEST_TIME_NOW = 1595824882905L
}
} }

Loading…
Cancel
Save