diff --git a/app/metrics.yaml b/app/metrics.yaml
index 13b301405..907747b21 100644
--- a/app/metrics.yaml
+++ b/app/metrics.yaml
@@ -1789,3 +1789,70 @@ pocket:
notification_emails:
- fenix-core@mozilla.com
expires: "2020-09-01"
+
+installation:
+ campaign:
+ type: string
+ send_in_pings:
+ - installation
+ description: >
+ Campaign
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/7295
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/8074
+ notification_emails:
+ - fenix-core@mozilla.com
+ expires: "2020-09-01"
+ network:
+ type: string
+ send_in_pings:
+ - installation
+ description: >
+ Network
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/7295
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/8074
+ notification_emails:
+ - fenix-core@mozilla.com
+ expires: "2020-09-01"
+ adgroup:
+ type: string
+ send_in_pings:
+ - installation
+ description: >
+ AdGroup
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/7295
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/8074
+ notification_emails:
+ - fenix-core@mozilla.com
+ expires: "2020-09-01"
+ creative:
+ send_in_pings:
+ - installation
+ type: string
+ description: >
+ Creative
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/7295
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/8074
+ notification_emails:
+ - fenix-core@mozilla.com
+ expires: "2020-09-01"
+ timestamp:
+ send_in_pings:
+ - installation
+ type: string
+ description: >
+ Timestamp
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/7295
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/8074
+ notification_emails:
+ - fenix-core@mozilla.com
+ expires: "2020-09-01"
\ No newline at end of file
diff --git a/app/pings.yaml b/app/pings.yaml
index f8d3605a2..83439e2a5 100644
--- a/app/pings.yaml
+++ b/app/pings.yaml
@@ -18,3 +18,14 @@ activation:
- https://github.com/mozilla-mobile/fenix/pull/1707#issuecomment-486972209
notification_emails:
- fenix-core@mozilla.com
+
+installation:
+ description: >
+ Intended for counting user installs.
+ include_client_id: false
+ bugs:
+ - 7295
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/1707#issuecomment-486972209
+ notification_emails:
+ - fenix-core@mozilla.com
diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/AdjustMetricsService.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/AdjustMetricsService.kt
index 4cf7bb352..28e8afd09 100644
--- a/app/src/main/java/org/mozilla/fenix/components/metrics/AdjustMetricsService.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/metrics/AdjustMetricsService.kt
@@ -38,9 +38,27 @@ class AdjustMetricsService(private val application: Application) : MetricsServic
)
config.setOnAttributionChangedListener {
- it.campaign?.let { campaign ->
- application.applicationContext.settings().adjustCampaignId = campaign
+ if (!it.network.isNullOrEmpty()) {
+ application.applicationContext.settings().adjustNetwork =
+ it.network
}
+ if (!it.adgroup.isNullOrEmpty()) {
+ application.applicationContext.settings().adjustAdGroup =
+ it.adgroup
+ }
+ if (!it.creative.isNullOrEmpty()) {
+ application.applicationContext.settings().adjustCreative =
+ it.creative
+ }
+ if (!it.campaign.isNullOrEmpty()) {
+ application.applicationContext.settings().adjustCampaignId =
+ it.campaign
+ }
+ if (application.applicationContext.settings().adjustInstallTimestamp.isEmpty()) {
+ application.applicationContext.settings().adjustInstallTimestamp =
+ System.currentTimeMillis().toString()
+ }
+ InstallationPing(application).checkAndSend()
}
config.setLogLevel(LogLevel.SUPRESS)
diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt
index 4a0b76932..927b765ad 100644
--- a/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt
@@ -571,6 +571,7 @@ class GleanMetricsService(private val context: Context) : MetricsService {
}
activationPing.checkAndSend()
+ InstallationPing(context).checkAndSend()
}
override fun stop() {
diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/InstallationPing.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/InstallationPing.kt
new file mode 100644
index 000000000..4d444123e
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/components/metrics/InstallationPing.kt
@@ -0,0 +1,93 @@
+/* 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.metrics
+
+import android.content.Context
+import android.content.SharedPreferences
+import androidx.annotation.VisibleForTesting
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import mozilla.components.support.base.log.logger.Logger
+import org.mozilla.fenix.GleanMetrics.Installation
+import org.mozilla.fenix.GleanMetrics.Pings
+import org.mozilla.fenix.ext.settings
+
+class InstallationPing(private val context: Context) {
+
+ private val prefs: SharedPreferences by lazy {
+ context.getSharedPreferences(
+ "${this.javaClass.canonicalName}.prefs", Context.MODE_PRIVATE
+ )
+ }
+
+ /**
+ * Checks whether or not the installation ping was already
+ * triggered by the application.
+ *
+ * Note that this only tells us that Fenix triggered the
+ * ping and then delegated the transmission to Glean. We
+ * have no way to tell if it was actually sent or not.
+ *
+ * @return true if it was already triggered, false otherwise.
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal fun wasAlreadyTriggered(): Boolean {
+ return prefs.getBoolean("ping_sent", false)
+ }
+
+ /**
+ * Marks the "installation" ping as triggered by the application.
+ * This ensures the ping is not triggered again at the next app
+ * start.
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal fun markAsTriggered() {
+ prefs.edit().putBoolean("ping_sent", true).apply()
+ }
+
+ /**
+ * Fills the metrics and triggers the 'installation' ping.
+ * This is a separate function to simplify unit-testing.
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal fun triggerPing() {
+ if (checkMetricsNotEmpty()
+ ) {
+ Installation.campaign.set(context.settings().adjustCampaignId)
+ Installation.adgroup.set(context.settings().adjustAdGroup)
+ Installation.creative.set(context.settings().adjustCreative)
+ Installation.network.set(context.settings().adjustNetwork)
+ Installation.timestamp.set(context.settings().adjustInstallTimestamp)
+ CoroutineScope(Dispatchers.IO).launch {
+ Pings.installation.submit()
+ markAsTriggered()
+ }
+ }
+ }
+
+ /**
+ * Check that required metrics are not empty before attempting to send ping.
+ * */
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal fun checkMetricsNotEmpty(): Boolean = listOf(
+ context.settings().adjustAdGroup,
+ context.settings().adjustCreative,
+ context.settings().adjustNetwork
+ ).all { it.isNotEmpty() }
+
+ /**
+ * Trigger sending the `installation` ping if it wasn't sent already.
+ * Then, mark it so that it doesn't get triggered next time Fenix
+ * starts.
+ */
+ fun checkAndSend() {
+ if (wasAlreadyTriggered()) {
+ Logger.debug("InstallationPing - already generated")
+ return
+ }
+ triggerPing()
+ }
+}
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 9667b179b..61a9f5827 100644
--- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
+++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
@@ -95,6 +95,26 @@ class Settings private constructor(
default = ""
)
+ var adjustNetwork by stringPreference(
+ appContext.getPreferenceKey(R.string.pref_key_adjust_network),
+ default = ""
+ )
+
+ var adjustAdGroup by stringPreference(
+ appContext.getPreferenceKey(R.string.pref_key_adjust_adgroup),
+ default = ""
+ )
+
+ var adjustCreative by stringPreference(
+ appContext.getPreferenceKey(R.string.pref_key_adjust_creative),
+ default = ""
+ )
+
+ var adjustInstallTimestamp by stringPreference(
+ appContext.getPreferenceKey(R.string.pref_key_adjust_install_timestamp),
+ default = ""
+ )
+
var openLinksInAPrivateTab by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_open_links_in_a_private_tab),
default = false
diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml
index 6349daabe..d56ebd301 100644
--- a/app/src/main/res/values/preference_keys.xml
+++ b/app/src/main/res/values/preference_keys.xml
@@ -133,6 +133,11 @@
pref_key_bounce_quick_action
pref_key_reader_mode_notification
pref_key_adjust_campaign
+ pref_key_adjust_network
+ pref_key_adjust_adgroup
+ pref_key_adjust_creative
+ pref_key_adjust_install_timestamp
+
pref_key_testing_stage
pref_key_encryption_key_generated
diff --git a/app/src/test/java/org/mozilla/fenix/components/metrics/InstallationPingTest.kt b/app/src/test/java/org/mozilla/fenix/components/metrics/InstallationPingTest.kt
new file mode 100644
index 000000000..dce61eba6
--- /dev/null
+++ b/app/src/test/java/org/mozilla/fenix/components/metrics/InstallationPingTest.kt
@@ -0,0 +1,55 @@
+/* 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.metrics
+
+import android.content.Context
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import io.mockk.mockkStatic
+import io.mockk.spyk
+import io.mockk.verify
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Test
+import org.mozilla.fenix.TestApplication
+import org.mozilla.fenix.ext.settings
+import org.mozilla.fenix.utils.Settings
+import org.robolectric.annotation.Config
+
+@ExperimentalCoroutinesApi
+@Config(application = TestApplication::class)
+internal class InstallationPingTest {
+
+ @Test
+ fun `checkAndSend() triggers the ping if it wasn't marked as triggered`() = runBlockingTest {
+ val mockedContext: Context = mockk(relaxed = true)
+ val mockedSettings: Settings = mockk(relaxed = true)
+ mockkStatic("org.mozilla.fenix.ext.ContextKt")
+ every { mockedContext.settings() } returns mockedSettings
+ val mockAp = spyk(InstallationPing(mockedContext), recordPrivateCalls = true)
+ every { mockAp.checkMetricsNotEmpty() } returns true
+ every { mockAp.wasAlreadyTriggered() } returns false
+ every { mockAp.markAsTriggered() } just Runs
+
+ mockAp.checkAndSend()
+
+ verify(exactly = 1) { mockAp.triggerPing() }
+ // Marking the ping as triggered happens in a co-routine off the main thread,
+ // so wait a bit for it.
+ verify(exactly = 1) { mockAp.markAsTriggered() }
+ }
+
+ @Test
+ fun `checkAndSend() doesn't trigger the ping again if it was marked as triggered`() {
+ val mockAp = spyk(InstallationPing(mockk()), recordPrivateCalls = true)
+ every { mockAp.wasAlreadyTriggered() } returns true
+
+ mockAp.checkAndSend()
+
+ verify(exactly = 0) { mockAp.triggerPing() }
+ }
+}
diff --git a/docs/metrics.md b/docs/metrics.md
index 6be577f00..1b3feacac 100644
--- a/docs/metrics.md
+++ b/docs/metrics.md
@@ -10,6 +10,7 @@ This means you might have to go searching through the dependency tree to get a f
- [activation](#activation)
- [baseline](#baseline)
- [events](#events)
+ - [installation](#installation)
- [metrics](#metrics)
@@ -169,6 +170,19 @@ The following metrics are added to the ping:
| user_specified_search_engines.custom_engine_added |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user added a new custom search engine |[1](https://github.com/mozilla-mobile/fenix/pull/6918)||2020-09-01 |
| user_specified_search_engines.custom_engine_deleted |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user deleted a custom search engine |[1](https://github.com/mozilla-mobile/fenix/pull/6918)||2020-09-01 |
+## installation
+Intended for counting user installs.
+
+The following metrics are added to the ping:
+
+| Name | Type | Description | Data reviews | Extras | Expiration |
+| --- | --- | --- | --- | --- | --- |
+| installation.adgroup |[string](https://mozilla.github.io/glean/book/user/metrics/string.html) |AdGroup |[1](https://github.com/mozilla-mobile/fenix/pull/8074)||2020-09-01 |
+| installation.campaign |[string](https://mozilla.github.io/glean/book/user/metrics/string.html) |Campaign |[1](https://github.com/mozilla-mobile/fenix/pull/8074)||2020-09-01 |
+| installation.creative |[string](https://mozilla.github.io/glean/book/user/metrics/string.html) |Creative |[1](https://github.com/mozilla-mobile/fenix/pull/8074)||2020-09-01 |
+| installation.network |[string](https://mozilla.github.io/glean/book/user/metrics/string.html) |Network |[1](https://github.com/mozilla-mobile/fenix/pull/8074)||2020-09-01 |
+| installation.timestamp |[string](https://mozilla.github.io/glean/book/user/metrics/string.html) |Timestamp |[1](https://github.com/mozilla-mobile/fenix/pull/8074)||2020-09-01 |
+
## metrics
This is a built-in ping that is assembled out of the box by the Glean SDK.
See the Glean SDK documentation for the [`metrics` ping](https://mozilla.github.io/glean/book/user/pings/metrics.html).