summaryrefslogtreecommitdiffstats
path: root/mobile/android/fenix/app
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/fenix/app')
-rw-r--r--mobile/android/fenix/app/metrics.yaml14
-rw-r--r--mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/metrics/FirstSessionPing.kt39
-rw-r--r--mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/metrics/FirstSessionPingTest.kt126
3 files changed, 179 insertions, 0 deletions
diff --git a/mobile/android/fenix/app/metrics.yaml b/mobile/android/fenix/app/metrics.yaml
index a620eb4d39..2b94cb5cd4 100644
--- a/mobile/android/fenix/app/metrics.yaml
+++ b/mobile/android/fenix/app/metrics.yaml
@@ -7731,6 +7731,20 @@ first_session:
tags:
- Performance
- Attribution
+ install_source:
+ type: string
+ lifetime: application
+ description: |
+ Used to identify the source the app was installed from.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1898363
+ data_reviews:
+ - https://phabricator.services.mozilla.com/D212913
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
play_store_attribution:
install_referrer_response:
type: text
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/metrics/FirstSessionPing.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/metrics/FirstSessionPing.kt
index 0666ab06e7..d281f03c06 100644
--- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/metrics/FirstSessionPing.kt
+++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/metrics/FirstSessionPing.kt
@@ -4,8 +4,13 @@
package org.mozilla.fenix.components.metrics
+import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Build.VERSION.SDK_INT
+import androidx.annotation.RequiresApi
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -16,6 +21,7 @@ import org.mozilla.fenix.Config
import org.mozilla.fenix.GleanMetrics.Events
import org.mozilla.fenix.GleanMetrics.FirstSession
import org.mozilla.fenix.GleanMetrics.Pings
+import org.mozilla.fenix.ext.application
import org.mozilla.fenix.ext.settings
class FirstSessionPing(private val context: Context) {
@@ -70,6 +76,7 @@ class FirstSessionPing(private val context: Context) {
},
)
FirstSession.timestamp.set()
+ FirstSession.installSource.set(installSourcePackage())
}
CoroutineScope(Dispatchers.IO).launch {
@@ -81,6 +88,38 @@ class FirstSessionPing(private val context: Context) {
}
}
+ @SuppressLint("NewApi") // Lint cannot resolve 'sdk' as 'SDK_INT' as it's not referenced directly.
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal fun installSourcePackage(sdk: Int = SDK_INT) = with(context.application) {
+ if (sdk >= Build.VERSION_CODES.R) {
+ installSourcePackageForBuildMinR(packageManager, packageName)
+ } else {
+ installSourcePackageForBuildMaxQ(packageManager, packageName)
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.R)
+ private fun installSourcePackageForBuildMinR(
+ packageManager: PackageManager,
+ packageName: String,
+ ) = try {
+ packageManager.getInstallSourceInfo(packageName).installingPackageName
+ } catch (e: PackageManager.NameNotFoundException) {
+ Logger.debug("$packageName is not available to the caller")
+ null
+ }.orEmpty()
+
+ private fun installSourcePackageForBuildMaxQ(
+ packageManager: PackageManager,
+ packageName: String,
+ ) = try {
+ @Suppress("DEPRECATION")
+ packageManager.getInstallerPackageName(packageName)
+ } catch (e: IllegalArgumentException) {
+ Logger.debug("$packageName is not installed")
+ null
+ }.orEmpty()
+
/**
* Check that at least one of the metrics values is set before sending the ping.
* Note: it is normal for many of these values to not be set as campaigns do not always
diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/metrics/FirstSessionPingTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/metrics/FirstSessionPingTest.kt
index c3e920f2ca..10d3a20aa3 100644
--- a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/metrics/FirstSessionPingTest.kt
+++ b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/metrics/FirstSessionPingTest.kt
@@ -5,6 +5,9 @@
package org.mozilla.fenix.components.metrics
import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Build.VERSION.SDK_INT
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
@@ -12,7 +15,9 @@ import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.spyk
import io.mockk.verify
+import org.junit.Assert.assertEquals
import org.junit.Test
+import org.mozilla.fenix.FenixApplication
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.utils.Settings
@@ -20,10 +25,19 @@ internal class FirstSessionPingTest {
@Test
fun `checkAndSend() triggers the ping if it wasn't marked as triggered`() {
+ val mockedPackageManager: PackageManager = mockk(relaxed = true)
+ mockedPackageManager.configureMockInstallSourcePackage()
+
+ val mockedApplication: FenixApplication = mockk(relaxed = true)
+ every { mockedApplication.packageManager } returns mockedPackageManager
+
val mockedContext: Context = mockk(relaxed = true)
+ every { mockedContext.applicationContext } returns mockedApplication
+
val mockedSettings: Settings = mockk(relaxed = true)
mockkStatic("org.mozilla.fenix.ext.ContextKt")
every { mockedContext.settings() } returns mockedSettings
+
val mockAp = spyk(FirstSessionPing(mockedContext), recordPrivateCalls = true)
every { mockAp.checkMetricsNotEmpty() } returns true
every { mockAp.wasAlreadyTriggered() } returns false
@@ -46,4 +60,116 @@ internal class FirstSessionPingTest {
verify(exactly = 0) { mockAp.triggerPing() }
}
+
+ @Test
+ fun `WHEN build version is R installSourcePackage RETURNS the set package name`() {
+ val mockedPackageManager: PackageManager = mockk(relaxed = true)
+ val testPackageName = "test R"
+ mockedPackageManager.mockInstallSourcePackageForBuildMinR(testPackageName)
+
+ val mockedApplication: FenixApplication = mockk(relaxed = true)
+ every { mockedApplication.packageManager } returns mockedPackageManager
+
+ val mockedContext: Context = mockk(relaxed = true)
+ every { mockedContext.applicationContext } returns mockedApplication
+
+ val result = FirstSessionPing(mockedContext).installSourcePackage(Build.VERSION_CODES.R)
+ assertEquals(testPackageName, result)
+ }
+
+ @Test
+ fun `GIVEN packageManager throws an exception WHEN Build version is R installSourcePackage RETURNS an empty string`() {
+ val mockedPackageManager: PackageManager = mockk(relaxed = true)
+ every { mockedPackageManager.getInstallSourceInfo(any()).installingPackageName } throws PackageManager.NameNotFoundException()
+
+ val mockedApplication: FenixApplication = mockk(relaxed = true)
+ every { mockedApplication.packageManager } returns mockedPackageManager
+
+ val mockedContext: Context = mockk(relaxed = true)
+ every { mockedContext.applicationContext } returns mockedApplication
+
+ val result = FirstSessionPing(mockedContext).installSourcePackage(Build.VERSION_CODES.R)
+ assertEquals("", result)
+ }
+
+ @Test
+ fun `WHEN build version is more than R installSourcePackage RETURNS the set package name`() {
+ val mockedPackageManager: PackageManager = mockk(relaxed = true)
+ val testPackageName = "test > R"
+ mockedPackageManager.mockInstallSourcePackageForBuildMinR(testPackageName)
+
+ val mockedApplication: FenixApplication = mockk(relaxed = true)
+ every { mockedApplication.packageManager } returns mockedPackageManager
+
+ val mockedContext: Context = mockk(relaxed = true)
+ every { mockedContext.applicationContext } returns mockedApplication
+
+ val result =
+ FirstSessionPing(mockedContext).installSourcePackage(Build.VERSION_CODES.R.plus(1))
+ assertEquals(testPackageName, result)
+ }
+
+ @Test
+ fun `GIVEN packageManager throws an exception WHEN Build version is more than R installSourcePackage RETURNS an empty string`() {
+ val mockedPackageManager: PackageManager = mockk(relaxed = true)
+ every { mockedPackageManager.getInstallSourceInfo(any()).installingPackageName } throws PackageManager.NameNotFoundException()
+
+ val mockedApplication: FenixApplication = mockk(relaxed = true)
+ every { mockedApplication.packageManager } returns mockedPackageManager
+
+ val mockedContext: Context = mockk(relaxed = true)
+ every { mockedContext.applicationContext } returns mockedApplication
+
+ val result =
+ FirstSessionPing(mockedContext).installSourcePackage(Build.VERSION_CODES.R.plus(1))
+ assertEquals("", result)
+ }
+
+ @Test
+ fun `WHEN build version is less than R installSourcePackage RETURNS the set package name`() {
+ val mockedPackageManager: PackageManager = mockk(relaxed = true)
+ val testPackageName = "test < R"
+ mockedPackageManager.mockInstallSourcePackageForBuildMaxQ(testPackageName)
+
+ val mockedApplication: FenixApplication = mockk(relaxed = true)
+ every { mockedApplication.packageManager } returns mockedPackageManager
+
+ val mockedContext: Context = mockk(relaxed = true)
+ every { mockedContext.applicationContext } returns mockedApplication
+
+ val result =
+ FirstSessionPing(mockedContext).installSourcePackage(Build.VERSION_CODES.R.minus(1))
+ assertEquals(testPackageName, result)
+ }
+
+ @Test
+ fun `GIVEN packageManager throws an exception WHEN Build version is less than R installSourcePackage RETURNS an empty string`() {
+ val mockedPackageManager: PackageManager = mockk(relaxed = true)
+ @Suppress("DEPRECATION")
+ every { mockedPackageManager.getInstallerPackageName(any()) } throws IllegalArgumentException()
+
+ val mockedApplication: FenixApplication = mockk(relaxed = true)
+ every { mockedApplication.packageManager } returns mockedPackageManager
+
+ val mockedContext: Context = mockk(relaxed = true)
+ every { mockedContext.applicationContext } returns mockedApplication
+
+ val result =
+ FirstSessionPing(mockedContext).installSourcePackage(Build.VERSION_CODES.R.minus(1))
+ assertEquals("", result)
+ }
}
+
+private fun PackageManager.configureMockInstallSourcePackage() =
+ if (SDK_INT >= Build.VERSION_CODES.R) {
+ mockInstallSourcePackageForBuildMinR()
+ } else {
+ mockInstallSourcePackageForBuildMaxQ()
+ }
+
+private fun PackageManager.mockInstallSourcePackageForBuildMinR(packageName: String = "") =
+ every { getInstallSourceInfo(any()).installingPackageName } returns packageName
+
+@Suppress("DEPRECATION")
+private fun PackageManager.mockInstallSourcePackageForBuildMaxQ(packageName: String = "") =
+ every { getInstallerPackageName(any()) } returns packageName