From ac8d05af898765571f7f7db4b8bfc8b73907652d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 26 Jun 2024 08:17:15 +0200 Subject: Merging upstream version 127.0.2. Signed-off-by: Daniel Baumann --- .../assets/extensions/webcompat/data/injections.js | 14 +++ .../css/bug1882040-disable-pull-to-refresh.css | 14 +++ .../main/assets/extensions/webcompat/manifest.json | 2 +- mobile/android/fenix/app/metrics.yaml | 14 +++ .../fenix/components/metrics/FirstSessionPing.kt | 39 +++++++ .../components/metrics/FirstSessionPingTest.kt | 126 +++++++++++++++++++++ .../mozilla/gecko/media/LollipopAsyncCodec.java | 14 ++- mobile/android/version.txt | 2 +- 8 files changed, 222 insertions(+), 3 deletions(-) create mode 100644 mobile/android/android-components/components/feature/webcompat/src/main/assets/extensions/webcompat/injections/css/bug1882040-disable-pull-to-refresh.css (limited to 'mobile/android') diff --git a/mobile/android/android-components/components/feature/webcompat/src/main/assets/extensions/webcompat/data/injections.js b/mobile/android/android-components/components/feature/webcompat/src/main/assets/extensions/webcompat/data/injections.js index 87b1da747b..a188a40f8d 100644 --- a/mobile/android/android-components/components/feature/webcompat/src/main/assets/extensions/webcompat/data/injections.js +++ b/mobile/android/android-components/components/feature/webcompat/src/main/assets/extensions/webcompat/data/injections.js @@ -1056,6 +1056,20 @@ const AVAILABLE_INJECTIONS = [ ], }, }, + { + id: "1882040", + platform: "android", + domain: "YouTube Shorts", + bug: "1882040", + contentScripts: { + matches: ["*://m.youtube.com/shorts/*"], + css: [ + { + file: "injections/css/bug1882040-disable-pull-to-refresh.css", + }, + ], + }, + }, ]; module.exports = AVAILABLE_INJECTIONS; diff --git a/mobile/android/android-components/components/feature/webcompat/src/main/assets/extensions/webcompat/injections/css/bug1882040-disable-pull-to-refresh.css b/mobile/android/android-components/components/feature/webcompat/src/main/assets/extensions/webcompat/injections/css/bug1882040-disable-pull-to-refresh.css new file mode 100644 index 0000000000..b075f96212 --- /dev/null +++ b/mobile/android/android-components/components/feature/webcompat/src/main/assets/extensions/webcompat/injections/css/bug1882040-disable-pull-to-refresh.css @@ -0,0 +1,14 @@ +/* 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/. */ + +/** + * m.youtube.com/shorts - pull-to-refresh breaks scrolling + * Bug #1882040 - https://bugzilla.mozilla.org/show_bug.cgi?id=1882040 + * + * Pull-to-refresh is breaking scrolling on the YouTube Shorts mobile page. + * The easiest work-around is to inject this CSS to disable it for now. + */ +html { + overscroll-behavior: contain; +} diff --git a/mobile/android/android-components/components/feature/webcompat/src/main/assets/extensions/webcompat/manifest.json b/mobile/android/android-components/components/feature/webcompat/src/main/assets/extensions/webcompat/manifest.json index 6f6d519d7c..8353eee2f4 100644 --- a/mobile/android/android-components/components/feature/webcompat/src/main/assets/extensions/webcompat/manifest.json +++ b/mobile/android/android-components/components/feature/webcompat/src/main/assets/extensions/webcompat/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "Mozilla Android Components - Web Compatibility Interventions", "description": "Urgent post-release fixes for web compatibility.", - "version": "125.0.0", + "version": "125.2.0", "browser_specific_settings": { "gecko": { "id": "webcompat@mozilla.org", 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 diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/LollipopAsyncCodec.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/LollipopAsyncCodec.java index aaf8810bbb..444ee633b6 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/LollipopAsyncCodec.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/LollipopAsyncCodec.java @@ -8,6 +8,7 @@ import android.media.MediaCodec; import android.media.MediaCodecInfo.CodecCapabilities; import android.media.MediaCrypto; import android.media.MediaFormat; +import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -134,7 +135,18 @@ import org.mozilla.gecko.util.HardwareCodecCapabilityUtils; } /* package */ LollipopAsyncCodec(final String name) throws IOException { - mCodec = MediaCodec.createByCodecName(name); + // Create the codec. + // We wrap the call to MediaCodec.createByCodecName in a pair of + // clearCallingIdentity / restoreCallingIdentity, so that the resource + // gets attributed to this process and not to whichever process was calling us. + // This works around a battery usage attribution bug in Android 14+, + // see bug 1902077. + final long token = Binder.clearCallingIdentity(); + try { + mCodec = MediaCodec.createByCodecName(name); + } finally { + Binder.restoreCallingIdentity(token); + } } @Override diff --git a/mobile/android/version.txt b/mobile/android/version.txt index 29810a755b..6f1605c380 100644 --- a/mobile/android/version.txt +++ b/mobile/android/version.txt @@ -1 +1 @@ -127.0.1 +127.0.2 -- cgit v1.2.3