diff options
Diffstat (limited to 'mobile/android/fenix/app/src')
2 files changed, 165 insertions, 0 deletions
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 |