Files
IronFox/patches/fenix-unifiedpush.patch
celenity 01f0d2379c WIP: v145.0 (Part 2...)
Signed-off-by: celenity <celenity@celenity.dev>
2025-11-07 19:49:07 -05:00

391 lines
18 KiB
Diff

diff --git a/mobile/android/fenix/.buildconfig.yml b/mobile/android/fenix/.buildconfig.yml
index 1753bcbd86..6cb5632fe9 100644
--- a/mobile/android/fenix/.buildconfig.yml
+++ b/mobile/android/fenix/.buildconfig.yml
@@ -93,6 +93,7 @@ projects:
- components:ui-icons
- components:ui-tabcounter
- components:ui-widgets
+ - components:feature-unifiedpush
variants:
- apks:
- abi: arm64-v8a
diff --git a/mobile/android/fenix/app/build.gradle b/mobile/android/fenix/app/build.gradle
index af2ddca5a6..8bdcdc0a61 100644
--- a/mobile/android/fenix/app/build.gradle
+++ b/mobile/android/fenix/app/build.gradle
@@ -743,6 +743,7 @@ dependencies {
androidTestUtil libs.androidx.test.orchestrator
lintChecks project(':components:tooling-lint')
+ implementation project(':components:feature-unifiedpush')
}
protobuf {
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/FenixApplication.kt
index bd75990cd8..12f6bd31f0 100644
--- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/FenixApplication.kt
+++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/FenixApplication.kt
@@ -37,7 +37,7 @@ import mozilla.components.browser.storage.sync.GlobalPlacesDependencyProvider
import mozilla.components.concept.base.crash.Breadcrumb
import mozilla.components.concept.engine.webextension.WebExtension
import mozilla.components.concept.engine.webextension.isUnsupported
-import mozilla.components.concept.push.PushProcessor
+// import mozilla.components.concept.push.PushProcessor
import mozilla.components.concept.storage.FrecencyThresholdOption
import mozilla.components.feature.addons.migration.DefaultSupportedAddonsChecker
import mozilla.components.feature.addons.update.GlobalAddonDependencyProvider
@@ -518,7 +518,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
logger.info("AutoPushFeature is configured, initializing it...")
// Install the AutoPush singleton to receive messages.
- PushProcessor.install(it)
+// PushProcessor.install(it)
WebPushEngineIntegration(components.core.engine, it).start()
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt
index 68190994b3..2b59856daf 100644
--- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt
+++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt
@@ -226,8 +226,8 @@ class BackgroundServices(
accountManager.register(AccountManagerReadyObserver(accountManagerAvailableQueue))
// Enable push if it's configured.
- push.feature?.let { autoPushFeature ->
- FxaPushSupportFeature(context, accountManager, autoPushFeature, crashReporter)
+ push.feature?.let { pushFeature ->
+ FxaPushSupportFeature(context, accountManager, pushFeature)
.initialize()
}
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/Push.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/Push.kt
index f99ef999cf..10b8f0b500 100644
--- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/Push.kt
+++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/Push.kt
@@ -8,31 +8,89 @@ import android.content.Context
import androidx.core.net.toUri
import mozilla.components.feature.push.AutoPushFeature
import mozilla.components.feature.push.Protocol
-import mozilla.components.feature.push.PushConfig
-import mozilla.components.lib.crash.CrashReporter
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.perf.lazyMonitored
import org.mozilla.fenix.push.FirebasePushService
+import android.app.Activity
+import android.os.StrictMode
+import mozilla.components.concept.push.Pusher
+import mozilla.components.feature.push.AutoPushConfig
+import mozilla.components.feature.unifiedpush.UnifiedPushFeature
+import mozilla.components.feature.unifiedpush.UnifiedPushNotification
+import mozilla.components.support.base.android.NotificationsDelegate
+import org.mozilla.fenix.ext.components
+import org.mozilla.fenix.perf.StrictModeManager
+import org.mozilla.fenix.utils.Settings
/**
* Component group for push services. These components use services that strongly depend on
* push messaging (e.g. WebPush, SendTab).
*/
-class Push(val context: Context, crashReporter: CrashReporter) {
- val feature by lazyMonitored {
- pushConfig?.let { config ->
+class Push(val context: Context, val settings: Settings = context.settings(), val strictMode: StrictModeManager = context.components.strictMode, val notificationsDelegate: NotificationsDelegate = context.components.notificationsDelegate, val iAutoPushFeature: AutoPushFeature? = null, val iUnifiedPushFeature: UnifiedPushFeature? = null) {
+ private val logger = Logger("Push")
+
+ val feature: Pusher?
+ get() = strictMode.allowViolation(StrictMode::allowThreadDiskReads) {
+ if (settings.useUnifiedPush) {
+ unifiedPushFeature.also { feature ->
+ if (feature.isAvailable(context)) {
+ logger.info("Using UnifiedPush")
+ autoPushFeature?.let {
+ it.initialize()
+ feature.migrateFrom(it)
+ }
+ } else {
+ logger.warn(
+ "UnifiedPush distributor has been uninstalled, " +
+ "the feature is disabled for the moment."
+ )
+ val notificationId = UnifiedPushNotification.getNotificationId(context)
+ val notification =
+ UnifiedPushNotification.createMissingServiceNotification(context)
+ notificationsDelegate.notify(
+ notificationId = notificationId,
+ notification = notification
+ )
+ settings.useUnifiedPush = false
+ }
+ }
+ } else {
+ logger.info("Using AutoPush")
+ autoPushFeature?.also { feature ->
+ feature.migrateFrom(unifiedPushFeature)
+ }
+ }
+ }
+
+ private val autoPushFeature by lazyMonitored {
+ iAutoPushFeature
+ ?: autoPushConfig?.let { config ->
AutoPushFeature(
context = context,
- service = pushService,
+ service = firebasePushService,
config = config,
- crashReporter = crashReporter,
)
}
}
- private val pushConfig: PushConfig? by lazyMonitored {
+ private val unifiedPushFeature by lazyMonitored {
+ iUnifiedPushFeature
+ ?: UnifiedPushFeature(
+ context = context,
+ disableRateLimit = true,
+ )
+ }
+
+ private fun Pusher.migrateFrom(old: Pusher) {
+ old.forEachScopes { scope, vapid ->
+ this.subscribe(scope, vapid)
+ }
+ old.shutdown()
+ }
+
+ private val autoPushConfig: AutoPushConfig? by lazyMonitored {
val logger = Logger("PushConfig")
val projectIdKey = context.getString(R.string.pref_key_push_project_id)
val resId = context.resources.getIdentifier(projectIdKey, "string", context.packageName)
@@ -43,12 +101,12 @@ class Push(val context: Context, crashReporter: CrashReporter) {
logger.debug("Creating push configuration for autopush.")
val projectId = context.resources.getString(resId)
- val serverOverride = context.settings().overridePushServer
+ val serverOverride = settings.overridePushServer
if (serverOverride.isEmpty()) {
- PushConfig(projectId)
+ AutoPushConfig(projectId)
} else {
val uri = serverOverride.toUri()
- PushConfig(
+ AutoPushConfig(
projectId,
serverHost = uri.getHost() ?: "",
protocol = if (uri.getScheme() == "http") {
@@ -61,5 +119,13 @@ class Push(val context: Context, crashReporter: CrashReporter) {
}
}
- private val pushService by lazyMonitored { FirebasePushService() }
+ private val firebasePushService by lazyMonitored { FirebasePushService() }
+
+ fun switchToUnifiedPush(activity: Activity, callback: (Boolean) -> Unit) {
+ unifiedPushFeature.useDefaultDistributor(activity, callback)
+ }
+
+ fun switchToAutoPush(context: Context) {
+ unifiedPushFeature.removeCurrentDistributor(context)
+ }
}
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/push/PushFxaIntegration.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/push/PushFxaIntegration.kt
index f25cf4442e..090d1c7982 100644
--- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/push/PushFxaIntegration.kt
+++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/push/PushFxaIntegration.kt
@@ -13,12 +13,13 @@ import mozilla.components.concept.sync.AuthType
import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.feature.accounts.push.FxaPushSupportFeature
import mozilla.components.feature.accounts.push.SendTabFeature
-import mozilla.components.feature.push.AutoPushFeature
-import mozilla.components.feature.push.PushScope
import mozilla.components.service.fxa.manager.FxaAccountManager
import mozilla.components.service.fxa.manager.ext.withConstellationIfExists
import org.mozilla.fenix.components.BackgroundServices
import org.mozilla.fenix.components.Push
+import mozilla.components.concept.push.PushObserver
+import mozilla.components.concept.push.PushScope
+import mozilla.components.concept.push.Pusher
/**
* A lazy initializer for FxaAccountManager if it isn't already initialized.
@@ -54,7 +55,7 @@ import org.mozilla.fenix.components.Push
* assurances, and most importantly, maintainable.
*/
class PushFxaIntegration(
- private val pushFeature: AutoPushFeature,
+ private val pushFeature: Pusher,
lazyAccountManager: Lazy<FxaAccountManager>,
) {
private val observer =
@@ -79,8 +80,8 @@ class PushFxaIntegration(
*/
internal class OneTimePushMessageObserver(
private val lazyAccountManager: Lazy<FxaAccountManager>,
- private val pushFeature: AutoPushFeature,
-) : AutoPushFeature.Observer {
+ private val pushFeature: Pusher,
+) : PushObserver {
override fun onMessageReceived(scope: PushScope, message: ByteArray?) {
// Ignore empty push messages.
val rawBytes = message ?: return
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/push/WebPushEngineIntegration.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/push/WebPushEngineIntegration.kt
index 5980161e96..3fde1ab08d 100644
--- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/push/WebPushEngineIntegration.kt
+++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/push/WebPushEngineIntegration.kt
@@ -12,23 +12,25 @@ import mozilla.components.concept.engine.Engine
import mozilla.components.concept.engine.webpush.WebPushDelegate
import mozilla.components.concept.engine.webpush.WebPushHandler
import mozilla.components.concept.engine.webpush.WebPushSubscription
-import mozilla.components.feature.push.AutoPushFeature
-import mozilla.components.feature.push.AutoPushSubscription
-import mozilla.components.feature.push.PushScope
import mozilla.components.support.base.log.logger.Logger
+import mozilla.components.concept.push.PushObserver
+import mozilla.components.concept.push.PushScope
+import mozilla.components.concept.push.PushSubscription
+import mozilla.components.concept.push.PushSubscriptionProcessor
+import mozilla.components.concept.push.Pusher
/**
* Engine integration with the push feature to enable WebPush support.
*/
class WebPushEngineIntegration(
private val engine: Engine,
- private val pushFeature: AutoPushFeature,
+ private val pushFeature: Pusher,
private val coroutineScope: CoroutineScope = MainScope(),
stringDecoder: (String) -> ByteArray =
{ s -> Base64.decode(s.toByteArray(), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP) },
byteArrayEncoder: (ByteArray) -> String =
{ ba -> Base64.encodeToString(ba, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP) },
-) : AutoPushFeature.Observer {
+) : PushObserver {
private var handler: WebPushHandler? = null
private val delegate = WebPushEngineDelegate(pushFeature, stringDecoder, byteArrayEncoder)
@@ -57,7 +59,7 @@ class WebPushEngineIntegration(
}
internal class WebPushEngineDelegate(
- private val pushFeature: AutoPushFeature,
+ private val pushFeature: PushSubscriptionProcessor,
private val stringDecoder: (String) -> ByteArray,
private val byteArrayEncoder: (ByteArray) -> String,
) : WebPushDelegate {
@@ -101,7 +103,7 @@ internal class WebPushEngineDelegate(
}
}
-internal fun AutoPushSubscription.toEnginePushSubscription(stringDecoder: (String) -> ByteArray) = WebPushSubscription(
+internal fun PushSubscription.toEnginePushSubscription(stringDecoder: (String) -> ByteArray) = WebPushSubscription(
scope = this.scope,
publicKey = stringDecoder(this.publicKey),
endpoint = this.endpoint,
diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/PushTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/PushTest.kt
new file mode 100644
index 000000000000..35838390c423
--- /dev/null
+++ b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/PushTest.kt
@@ -0,0 +1,97 @@
+package org.mozilla.fenix.components
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import kotlinx.coroutines.test.runTest
+import mozilla.components.concept.push.PushScope
+import mozilla.components.feature.push.AutoPushFeature
+import mozilla.components.feature.unifiedpush.UnifiedPushFeature
+import mozilla.components.support.base.android.NotificationsDelegate
+import mozilla.components.support.test.any
+import mozilla.components.support.test.eq
+import mozilla.components.support.test.mock
+import mozilla.components.support.test.robolectric.testContext
+import mozilla.components.support.test.whenever
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.ArgumentMatchers.anyInt
+import org.mozilla.fenix.helpers.perf.TestStrictModeManager
+import org.mozilla.fenix.utils.Settings
+
+@RunWith(AndroidJUnit4::class)
+class PushTest {
+
+ @Test
+ fun `UnifiedPush feature is disabled when the distributor is uninstalled, and a notification is shown`() = runTest {
+ val notificationsDelegate = mock<NotificationsDelegate>()
+ val unifiedPushFeature = mock<UnifiedPushFeature>()
+ // The distributor isn't available anymore
+ whenever(unifiedPushFeature.isAvailable(any())).thenReturn(false)
+ val settings = Settings(testContext)
+ settings.useUnifiedPush = true
+ assertTrue(settings.useUnifiedPush)
+ Push(
+ testContext,
+ crashReporter = mock(),
+ settings = settings,
+ strictMode = TestStrictModeManager(),
+ notificationsDelegate = notificationsDelegate,
+ iUnifiedPushFeature = unifiedPushFeature
+ ).feature
+ assertFalse(settings.useUnifiedPush)
+ verify(notificationsDelegate).notify(any(), anyInt(), any(), any(), any(), eq(false))
+ }
+
+ @Test
+ fun `AutoPush subscriptions are migrated to UnifiedPush when UnifiedPush is enabled`() = runTest {
+ val unifiedPushFeature = mock<UnifiedPushFeature>()
+ val autoPushFeature = mock<AutoPushFeature>()
+ whenever(unifiedPushFeature.isAvailable(any())).thenReturn(true)
+ whenever(autoPushFeature.forEachScopes(any())).then { i ->
+ i.getArgument<(PushScope, String?) -> Unit>(0)("test", "test")
+ }
+ val settings = Settings(testContext)
+ // UnifiedPush is enabled
+ settings.useUnifiedPush = true
+ assertTrue(settings.useUnifiedPush)
+ Push(
+ testContext,
+ crashReporter = mock(),
+ settings = settings,
+ strictMode = TestStrictModeManager(),
+ notificationsDelegate = mock(),
+ iUnifiedPushFeature = unifiedPushFeature,
+ iAutoPushFeature = autoPushFeature
+ ).feature
+ verify(autoPushFeature).forEachScopes(any())
+ verify(autoPushFeature).shutdown()
+ verify(unifiedPushFeature).subscribe(eq("test"), eq("test"), any(), any())
+ }
+
+ @Test
+ fun `UnifiedPush subscriptions are migrated to AutoPush when UnifiedPush is disabled`() = runTest {
+ val unifiedPushFeature = mock<UnifiedPushFeature>()
+ val autoPushFeature = mock<AutoPushFeature>()
+ whenever(unifiedPushFeature.forEachScopes(any())).then { i ->
+ i.getArgument<(PushScope, String?) -> Unit>(0)("test", "test")
+ }
+ val settings = Settings(testContext)
+ // UnifiedPush is disabled
+ settings.useUnifiedPush = false
+ assertFalse(settings.useUnifiedPush)
+ Push(
+ testContext,
+ crashReporter = mock(),
+ settings = settings,
+ strictMode = TestStrictModeManager(),
+ notificationsDelegate = mock(),
+ iUnifiedPushFeature = unifiedPushFeature,
+ iAutoPushFeature = autoPushFeature
+ ).feature
+ verify(unifiedPushFeature).forEachScopes(any())
+ verify(unifiedPushFeature).shutdown()
+ verify(autoPushFeature).subscribe(eq("test"), eq("test"), any(), any())
+ }
+}