For #27472 - Added telemetry data points for the Google Play Store review prompts

pull/543/head
t-p-white 2 years ago committed by mergify[bot]
parent ef2af26b69
commit 6228dad8e4

@ -8456,3 +8456,31 @@ recent_synced_tabs:
data_sensitivity:
- interaction
expires: 114
review_prompt:
prompt_attempt:
type: event
description: |
Data captured for each attempt to display the review prompt.
extra_keys:
prompt_was_displayed:
description: |
Whether the prompt was displayed to the user. Value
reported will be: 'true', 'false' or 'error'.
type: string
local_datetime:
description: |
The local datetime.
type: string
number_of_app_launches:
description: |
The total number of times the app has been launched.
type: quantity
bugs:
- https://github.com/mozilla-mobile/fenix/issues/27472
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/27596
notification_emails:
- android-probes@mozilla.com
data_sensitivity:
- interaction
expires: 121

@ -6,10 +6,15 @@ package org.mozilla.fenix.components
import android.app.Activity
import androidx.annotation.VisibleForTesting
import com.google.android.play.core.review.ReviewInfo
import com.google.android.play.core.review.ReviewManager
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.withContext
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.GleanMetrics.ReviewPrompt
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/**
* Interface that describes the settings needed to track the Review Prompt.
@ -50,6 +55,11 @@ class ReviewPromptController(
flow.addOnCompleteListener {
if (it.isSuccessful) {
manager.launchReviewFlow(activity, it.result)
recordReviewPromptEvent(
it.result.toString(),
reviewSettings.numberOfAppLaunches,
Date(),
)
}
}
}
@ -98,3 +108,44 @@ class ReviewPromptController(
private const val NUMBER_OF_MONTHS_TO_PASS = 4
}
}
/**
* Records a [ReviewPrompt] with the required data.
*
* **Note:** The docs for [ReviewManager.launchReviewFlow] state 'In some circumstances the review
* flow will not be shown to the user, e.g. they have already seen it recently, so do not assume that
* calling this method will always display the review dialog.'
* However, investigation has shown that a [ReviewInfo] instance with the flag:
* - 'isNoOp=true' indicates that the prompt has NOT been displayed.
* - 'isNoOp=false' indicates that a prompt has been displayed.
* [ReviewManager.launchReviewFlow] will modify the ReviewInfo instance which can be used to determine
* which of these flags is present.
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun recordReviewPromptEvent(
reviewInfoAsString: String,
numberOfAppLaunches: Int,
now: Date,
) {
val formattedLocalDatetime =
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()).format(now)
// The internals of ReviewInfo cannot be accessed directly or cast nicely, so lets simply use
// the object as a string.
// ReviewInfo is susceptible to changes outside of our control hence the catch-all 'else' statement.
val promptWasDisplayed = if (reviewInfoAsString.contains("isNoOp=true")) {
"false"
} else if (reviewInfoAsString.contains("isNoOp=false")) {
"true"
} else {
"error"
}
ReviewPrompt.promptAttempt.record(
ReviewPrompt.PromptAttemptExtra(
promptWasDisplayed = promptWasDisplayed,
localDatetime = formattedLocalDatetime,
numberOfAppLaunches = numberOfAppLaunches,
),
)
}

@ -8,14 +8,21 @@ import com.google.android.play.core.review.ReviewManager
import com.google.android.play.core.review.ReviewManagerFactory
import kotlinx.coroutines.test.runTest
import mozilla.components.support.test.robolectric.testContext
import org.junit.Test
import mozilla.telemetry.glean.testing.GleanTestRule
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.Before
import org.junit.Rule
import org.junit.runner.RunWith
import org.mozilla.fenix.GleanMetrics.ReviewPrompt
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class TestReviewSettings(
override var numberOfAppLaunches: Int = 0,
@ -29,6 +36,9 @@ class TestReviewSettings(
@RunWith(FenixRobolectricTestRunner::class)
class ReviewPromptControllerTest {
@get:Rule
val gleanTestRule = GleanTestRule(testContext)
private lateinit var reviewManager: ReviewManager
@Before
@ -208,9 +218,49 @@ class ReviewPromptControllerTest {
assertFalse(controller.shouldShowPrompt())
}
@Test
fun reviewPromptWasDisplayed() {
testRecordReviewPromptEventRecordsTheExpectedData("isNoOp=false", "true")
}
@Test
fun reviewPromptWasNotDisplayed() {
testRecordReviewPromptEventRecordsTheExpectedData("isNoOp=true", "false")
}
@Test
fun reviewPromptDisplayStateUnknown() {
testRecordReviewPromptEventRecordsTheExpectedData(expected = "error")
}
private fun testRecordReviewPromptEventRecordsTheExpectedData(
reviewInfoArg: String = "",
expected: String,
) {
val numberOfAppLaunches = 1
val reviewInfoAsString =
"ReviewInfo{pendingIntent=PendingIntent{5b613b1: android.os.BinderProxy@46c8096}, $reviewInfoArg}"
val datetime = Date(TEST_TIME_NOW)
val formattedNowLocalDatetime = SIMPLE_DATE_FORMAT.format(datetime)
assertNull(ReviewPrompt.promptAttempt.testGetValue())
recordReviewPromptEvent(reviewInfoAsString, numberOfAppLaunches, datetime)
val reviewPromptData = ReviewPrompt.promptAttempt.testGetValue()!!.last().extra!!
assertEquals(expected, reviewPromptData["prompt_was_displayed"])
assertEquals(numberOfAppLaunches, reviewPromptData["number_of_app_launches"]!!.toInt())
assertEquals(formattedNowLocalDatetime, reviewPromptData["local_datetime"])
}
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
private val SIMPLE_DATE_FORMAT by lazy {
SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ss",
Locale.getDefault(),
)
}
}
}

Loading…
Cancel
Save