No issue: Use SendTabFeature and FxaPushSupportFeature
parent
a45821bac5
commit
0768fde945
|
@ -8,23 +8,17 @@ import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.annotation.VisibleForTesting.PRIVATE
|
import androidx.annotation.VisibleForTesting.PRIVATE
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
|
||||||
import mozilla.components.browser.storage.sync.PlacesBookmarksStorage
|
import mozilla.components.browser.storage.sync.PlacesBookmarksStorage
|
||||||
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
|
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
|
||||||
import mozilla.components.concept.push.Bus
|
|
||||||
import mozilla.components.concept.sync.AccountObserver
|
import mozilla.components.concept.sync.AccountObserver
|
||||||
import mozilla.components.concept.sync.AuthType
|
import mozilla.components.concept.sync.AuthType
|
||||||
import mozilla.components.concept.sync.DeviceCapability
|
import mozilla.components.concept.sync.DeviceCapability
|
||||||
import mozilla.components.concept.sync.DeviceEvent
|
|
||||||
import mozilla.components.concept.sync.DeviceEventsObserver
|
|
||||||
import mozilla.components.concept.sync.DevicePushSubscription
|
|
||||||
import mozilla.components.concept.sync.DeviceType
|
import mozilla.components.concept.sync.DeviceType
|
||||||
import mozilla.components.concept.sync.OAuthAccount
|
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.AutoPushFeature
|
||||||
import mozilla.components.feature.push.AutoPushSubscription
|
|
||||||
import mozilla.components.feature.push.PushConfig
|
import mozilla.components.feature.push.PushConfig
|
||||||
import mozilla.components.feature.push.PushSubscriptionObserver
|
|
||||||
import mozilla.components.feature.push.PushType
|
|
||||||
import mozilla.components.lib.crash.CrashReporter
|
import mozilla.components.lib.crash.CrashReporter
|
||||||
import mozilla.components.lib.dataprotect.SecureAbove22Preferences
|
import mozilla.components.lib.dataprotect.SecureAbove22Preferences
|
||||||
import mozilla.components.service.fxa.DeviceConfig
|
import mozilla.components.service.fxa.DeviceConfig
|
||||||
|
@ -115,16 +109,6 @@ class BackgroundServices(
|
||||||
GlobalSyncableStoreProvider.configureKeyStorage(secureAbove22Preferences)
|
GlobalSyncableStoreProvider.configureKeyStorage(secureAbove22Preferences)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val deviceEventObserver = object : DeviceEventsObserver {
|
|
||||||
private val logger = Logger("DeviceEventsObserver")
|
|
||||||
override fun onEvents(events: List<DeviceEvent>) {
|
|
||||||
logger.info("Received ${events.size} device event(s)")
|
|
||||||
events.filterIsInstance<DeviceEvent.TabReceived>().forEach {
|
|
||||||
notificationManager.showReceivedTabs(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val telemetryAccountObserver = TelemetryAccountObserver(
|
private val telemetryAccountObserver = TelemetryAccountObserver(
|
||||||
context,
|
context,
|
||||||
context.components.analytics.metrics
|
context.components.analytics.metrics
|
||||||
|
@ -132,8 +116,6 @@ class BackgroundServices(
|
||||||
|
|
||||||
val accountAbnormalities = AccountAbnormalities(context, crashReporter)
|
val accountAbnormalities = AccountAbnormalities(context, crashReporter)
|
||||||
|
|
||||||
private val pushAccountObserver by lazy { push?.let { PushAccountObserver(it) } }
|
|
||||||
|
|
||||||
val accountManager = makeAccountManager(context, serverConfig, deviceConfig, syncConfig)
|
val accountManager = makeAccountManager(context, serverConfig, deviceConfig, syncConfig)
|
||||||
|
|
||||||
@VisibleForTesting(otherwise = PRIVATE)
|
@VisibleForTesting(otherwise = PRIVATE)
|
||||||
|
@ -185,11 +167,6 @@ class BackgroundServices(
|
||||||
).also { accountManager ->
|
).also { accountManager ->
|
||||||
// TODO this needs to change once we have a SyncManager
|
// TODO this needs to change once we have a SyncManager
|
||||||
context.settings().fxaHasSyncedItems = syncConfig?.supportedEngines?.isNotEmpty() ?: false
|
context.settings().fxaHasSyncedItems = syncConfig?.supportedEngines?.isNotEmpty() ?: false
|
||||||
accountManager.registerForDeviceEvents(
|
|
||||||
deviceEventObserver,
|
|
||||||
ProcessLifecycleOwner.get(),
|
|
||||||
false
|
|
||||||
)
|
|
||||||
|
|
||||||
// Register a telemetry account observer to keep track of FxA auth metrics.
|
// Register a telemetry account observer to keep track of FxA auth metrics.
|
||||||
accountManager.register(telemetryAccountObserver)
|
accountManager.register(telemetryAccountObserver)
|
||||||
|
@ -200,39 +177,13 @@ class BackgroundServices(
|
||||||
|
|
||||||
// Enable push if it's configured.
|
// Enable push if it's configured.
|
||||||
push?.let { autoPushFeature ->
|
push?.let { autoPushFeature ->
|
||||||
// Register the push account observer so we know how to update our push subscriptions.
|
FxaPushSupportFeature(context, accountManager, autoPushFeature)
|
||||||
accountManager.register(pushAccountObserver!!)
|
|
||||||
|
|
||||||
val logger = Logger("AutoPushFeature")
|
|
||||||
|
|
||||||
// Notify observers for Services' messages.
|
|
||||||
autoPushFeature.registerForPushMessages(
|
|
||||||
PushType.Services,
|
|
||||||
object : Bus.Observer<PushType, String> {
|
|
||||||
override fun onEvent(type: PushType, message: String) {
|
|
||||||
accountManager.authenticatedAccount()?.deviceConstellation()
|
|
||||||
?.processRawEventAsync(message)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Notify observers for subscription changes.
|
|
||||||
autoPushFeature.registerForSubscriptions(object : PushSubscriptionObserver {
|
|
||||||
override fun onSubscriptionAvailable(subscription: AutoPushSubscription) {
|
|
||||||
// Update for only the services subscription.
|
|
||||||
if (subscription.type == PushType.Services) {
|
|
||||||
logger.info("New push subscription received for FxA")
|
|
||||||
accountManager.authenticatedAccount()?.deviceConstellation()
|
|
||||||
?.setDevicePushSubscriptionAsync(
|
|
||||||
DevicePushSubscription(
|
|
||||||
endpoint = subscription.endpoint,
|
|
||||||
publicKey = subscription.publicKey,
|
|
||||||
authKey = subscription.authKey
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SendTabFeature(accountManager) { device, tabs ->
|
||||||
|
notificationManager.showReceivedTabs(context, device, tabs)
|
||||||
|
}
|
||||||
|
|
||||||
accountAbnormalities.accountManagerInitializedAsync(
|
accountAbnormalities.accountManagerInitializedAsync(
|
||||||
accountManager,
|
accountManager,
|
||||||
accountManager.initAsync()
|
accountManager.initAsync()
|
||||||
|
@ -242,7 +193,7 @@ class BackgroundServices(
|
||||||
/**
|
/**
|
||||||
* Provides notification functionality, manages notification channels.
|
* Provides notification functionality, manages notification channels.
|
||||||
*/
|
*/
|
||||||
val notificationManager by lazy {
|
private val notificationManager by lazy {
|
||||||
NotificationManager(context)
|
NotificationManager(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -290,30 +241,3 @@ class TelemetryAccountObserver(
|
||||||
context.settings().fxaSignedIn = false
|
context.settings().fxaSignedIn = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* When we login/logout of FxA, we need to update our push subscriptions to match the newly
|
|
||||||
* logged in account.
|
|
||||||
*
|
|
||||||
* We added the push service to the AccountManager observer so that we can control when the
|
|
||||||
* service will start/stop. Firebase was added when landing the push service to ensure it works
|
|
||||||
* as expected without causing any (as many) side effects.
|
|
||||||
*
|
|
||||||
* In order to use Firebase with Leanplum and other marketing features, we need it always
|
|
||||||
* running so we cannot leave this code in place when we implement those features.
|
|
||||||
*
|
|
||||||
* We should have this removed when we are more confident
|
|
||||||
* of the send-tab/push feature: https://github.com/mozilla-mobile/fenix/issues/4063
|
|
||||||
*/
|
|
||||||
@VisibleForTesting(otherwise = PRIVATE)
|
|
||||||
class PushAccountObserver(private val push: AutoPushFeature) : AccountObserver {
|
|
||||||
override fun onLoggedOut() {
|
|
||||||
push.unsubscribeForType(PushType.Services)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAuthenticated(account: OAuthAccount, authType: AuthType) {
|
|
||||||
if (authType != AuthType.Existing) {
|
|
||||||
push.subscribeForType(PushType.Services)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ import android.os.Build.VERSION.SDK_INT
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import mozilla.components.concept.sync.DeviceEvent
|
import mozilla.components.concept.sync.Device
|
||||||
import mozilla.components.concept.sync.TabData
|
import mozilla.components.concept.sync.TabData
|
||||||
import mozilla.components.support.base.log.logger.Logger
|
import mozilla.components.support.base.log.logger.Logger
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
|
@ -49,11 +49,11 @@ class NotificationManager(private val context: Context) {
|
||||||
|
|
||||||
private val logger = Logger("NotificationManager")
|
private val logger = Logger("NotificationManager")
|
||||||
|
|
||||||
fun showReceivedTabs(event: DeviceEvent.TabReceived) {
|
fun showReceivedTabs(context: Context, device: Device?, tabs: List<TabData>) {
|
||||||
// In the future, experiment with displaying multiple tabs from the same device as as Notification Groups.
|
// In the future, experiment with displaying multiple tabs from the same device as as Notification Groups.
|
||||||
// For now, a single notification per tab received will suffice.
|
// For now, a single notification per tab received will suffice.
|
||||||
logger.debug("Showing ${event.entries.size} tab(s) received from deviceID=${event.from?.id}")
|
logger.debug("Showing ${tabs.size} tab(s) received from deviceID=${device?.id}")
|
||||||
event.entries.forEach { tab ->
|
tabs.forEach { tab ->
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(tab.url))
|
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(tab.url))
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
intent.putExtra(RECEIVE_TABS_TAG, true)
|
intent.putExtra(RECEIVE_TABS_TAG, true)
|
||||||
|
@ -62,7 +62,7 @@ class NotificationManager(private val context: Context) {
|
||||||
|
|
||||||
val builder = NotificationCompat.Builder(context, RECEIVE_TABS_CHANNEL_ID)
|
val builder = NotificationCompat.Builder(context, RECEIVE_TABS_CHANNEL_ID)
|
||||||
.setSmallIcon(R.drawable.ic_status_logo)
|
.setSmallIcon(R.drawable.ic_status_logo)
|
||||||
.setTitle(event, tab)
|
.setSendTabTitle(context, device, tab)
|
||||||
.setWhen(System.currentTimeMillis())
|
.setWhen(System.currentTimeMillis())
|
||||||
.setContentText(tab.url)
|
.setContentText(tab.url)
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
|
@ -104,12 +104,18 @@ class NotificationManager(private val context: Context) {
|
||||||
notificationManager.createNotificationChannel(channel)
|
notificationManager.createNotificationChannel(channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun NotificationCompat.Builder.setTitle(
|
private fun NotificationCompat.Builder.setSendTabTitle(
|
||||||
event: DeviceEvent.TabReceived,
|
context: Context,
|
||||||
|
device: Device?,
|
||||||
tab: TabData
|
tab: TabData
|
||||||
): NotificationCompat.Builder {
|
): NotificationCompat.Builder {
|
||||||
event.from?.let { device ->
|
device?.let {
|
||||||
setContentTitle(context.getString(R.string.fxa_tab_received_from_notification_name, device.displayName))
|
setContentTitle(
|
||||||
|
context.getString(
|
||||||
|
R.string.fxa_tab_received_from_notification_name,
|
||||||
|
it.displayName
|
||||||
|
)
|
||||||
|
)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ import mozilla.components.concept.sync.AuthType
|
||||||
import mozilla.components.concept.sync.OAuthAccount
|
import mozilla.components.concept.sync.OAuthAccount
|
||||||
import mozilla.components.feature.push.AutoPushFeature
|
import mozilla.components.feature.push.AutoPushFeature
|
||||||
import mozilla.components.feature.push.PushConfig
|
import mozilla.components.feature.push.PushConfig
|
||||||
import mozilla.components.feature.push.PushType
|
|
||||||
import mozilla.components.service.fxa.DeviceConfig
|
import mozilla.components.service.fxa.DeviceConfig
|
||||||
import mozilla.components.service.fxa.ServerConfig
|
import mozilla.components.service.fxa.ServerConfig
|
||||||
import mozilla.components.service.fxa.SyncConfig
|
import mozilla.components.service.fxa.SyncConfig
|
||||||
|
@ -76,53 +75,6 @@ class BackgroundServicesTest {
|
||||||
assertNull(backgroundServices.syncConfig)
|
assertNull(backgroundServices.syncConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `push account observer`() {
|
|
||||||
val push = mockk<AutoPushFeature>()
|
|
||||||
val observer = PushAccountObserver(push)
|
|
||||||
val registry = ObserverRegistry<AccountObserver>()
|
|
||||||
registry.register(observer)
|
|
||||||
val account = mockk<OAuthAccount>()
|
|
||||||
|
|
||||||
// Being explicit here (vs using 'any()') ensures that any change to which PushType variants
|
|
||||||
// are being subscribed/unsubscribed will break these tests, forcing developer to expand them.
|
|
||||||
every { push.subscribeForType(PushType.Services) } just Runs
|
|
||||||
every { push.unsubscribeForType(PushType.Services) } just Runs
|
|
||||||
|
|
||||||
// 'Existing' auth type doesn't trigger subscription - we're already subscribed.
|
|
||||||
registry.notifyObservers { onAuthenticated(account, AuthType.Existing) }
|
|
||||||
verify(exactly = 0) { push.subscribeForType(any()) }
|
|
||||||
|
|
||||||
// Every other auth type does.
|
|
||||||
registry.notifyObservers { onAuthenticated(account, AuthType.Signin) }
|
|
||||||
verify(exactly = 1) { push.subscribeForType(eq(PushType.Services)) }
|
|
||||||
|
|
||||||
registry.notifyObservers { onAuthenticated(account, AuthType.Signup) }
|
|
||||||
verify(exactly = 2) { push.subscribeForType(eq(PushType.Services)) }
|
|
||||||
|
|
||||||
registry.notifyObservers { onAuthenticated(account, AuthType.Recovered) }
|
|
||||||
verify(exactly = 3) { push.subscribeForType(eq(PushType.Services)) }
|
|
||||||
|
|
||||||
registry.notifyObservers { onAuthenticated(account, AuthType.Shared) }
|
|
||||||
verify(exactly = 4) { push.subscribeForType(eq(PushType.Services)) }
|
|
||||||
|
|
||||||
registry.notifyObservers { onAuthenticated(account, AuthType.Pairing) }
|
|
||||||
verify(exactly = 5) { push.subscribeForType(eq(PushType.Services)) }
|
|
||||||
|
|
||||||
registry.notifyObservers { onAuthenticated(account, AuthType.OtherExternal(null)) }
|
|
||||||
verify(exactly = 6) { push.subscribeForType(eq(PushType.Services)) }
|
|
||||||
|
|
||||||
registry.notifyObservers { onAuthenticated(account, AuthType.OtherExternal("someAction")) }
|
|
||||||
verify(exactly = 7) { push.subscribeForType(eq(PushType.Services)) }
|
|
||||||
|
|
||||||
// None of the above unsubscribed.
|
|
||||||
verify(exactly = 0) { push.unsubscribeForType(any()) }
|
|
||||||
|
|
||||||
// Finally, log-out should unsubscribe.
|
|
||||||
registry.notifyObservers { onLoggedOut() }
|
|
||||||
verify(exactly = 1) { push.unsubscribeForType(eq(PushType.Services)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `telemetry account observer`() {
|
fun `telemetry account observer`() {
|
||||||
val metrics = mockk<MetricController>()
|
val metrics = mockk<MetricController>()
|
||||||
|
|
Loading…
Reference in New Issue