mirror of
https://github.com/fork-maintainers/iceraven-browser
synced 2024-11-19 09:25:34 +00:00
[fenix] Closes https://github.com/mozilla-mobile/fenix/issues/2770: Allow receiving tabs from FxA devices (https://github.com/mozilla-mobile/fenix/pull/2772)
* Closes https://github.com/mozilla-mobile/fenix/issues/2770: Allow receiving tabs from FxA devices Now that we're on a-c 0.54, we can land this since it supports device capability migration. This patch adds a SEND_TAB device capability, making Fenix a valid target in the Send Tab device list on Desktop Firefox. Additionally, it adds a notification manager which manages notification channels and knows how to display "received tabs" notifications". * Post: remove unusued test file that's causing issues
This commit is contained in:
parent
dbe697d102
commit
86fefa5990
@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
|
- #2770 - Added ability to receive tabs from other FxA devices
|
||||||
- #919 - Enabled bookmark synchronization
|
- #919 - Enabled bookmark synchronization
|
||||||
- #916 - Added the ability to save and delete bookmarks
|
- #916 - Added the ability to save and delete bookmarks
|
||||||
- #356 - Added the ability to delete history
|
- #356 - Added the ability to delete history
|
||||||
|
@ -5,11 +5,15 @@
|
|||||||
package org.mozilla.fenix.components
|
package org.mozilla.fenix.components
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
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.sync.DeviceCapability
|
||||||
|
import mozilla.components.concept.sync.DeviceEvent
|
||||||
|
import mozilla.components.concept.sync.DeviceEventsObserver
|
||||||
import mozilla.components.concept.sync.DeviceType
|
import mozilla.components.concept.sync.DeviceType
|
||||||
import mozilla.components.feature.sync.BackgroundSyncManager
|
import mozilla.components.feature.sync.BackgroundSyncManager
|
||||||
import mozilla.components.feature.sync.GlobalSyncableStoreProvider
|
import mozilla.components.feature.sync.GlobalSyncableStoreProvider
|
||||||
@ -26,7 +30,8 @@ import org.mozilla.fenix.test.Mockable
|
|||||||
class BackgroundServices(
|
class BackgroundServices(
|
||||||
context: Context,
|
context: Context,
|
||||||
historyStorage: PlacesHistoryStorage,
|
historyStorage: PlacesHistoryStorage,
|
||||||
bookmarkStorage: PlacesBookmarksStorage
|
bookmarkStorage: PlacesBookmarksStorage,
|
||||||
|
notificationManager: NotificationManager
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
const val CLIENT_ID = "a2270f727f45f648"
|
const val CLIENT_ID = "a2270f727f45f648"
|
||||||
@ -51,13 +56,22 @@ class BackgroundServices(
|
|||||||
it.addStore("bookmarks")
|
it.addStore("bookmarks")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val deviceEventObserver = object : DeviceEventsObserver {
|
||||||
|
override fun onEvents(events: List<DeviceEvent>) {
|
||||||
|
events.filter { it is DeviceEvent.TabReceived }.forEach {
|
||||||
|
notificationManager.showReceivedTabs(it as DeviceEvent.TabReceived)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val accountManager = FxaAccountManager(
|
val accountManager = FxaAccountManager(
|
||||||
context,
|
context,
|
||||||
config,
|
config,
|
||||||
scopes,
|
scopes,
|
||||||
DeviceTuple("Fenix", DeviceType.MOBILE, emptyList()),
|
DeviceTuple("Fenix", DeviceType.MOBILE, listOf(DeviceCapability.SEND_TAB)),
|
||||||
syncManager
|
syncManager
|
||||||
).also {
|
).also {
|
||||||
|
it.registerForDeviceEvents(deviceEventObserver, ProcessLifecycleOwner.get(), true)
|
||||||
CoroutineScope(Dispatchers.Main).launch { it.initAsync().await() }
|
CoroutineScope(Dispatchers.Main).launch { it.initAsync().await() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,9 @@ import org.mozilla.fenix.test.Mockable
|
|||||||
*/
|
*/
|
||||||
@Mockable
|
@Mockable
|
||||||
class Components(private val context: Context) {
|
class Components(private val context: Context) {
|
||||||
val backgroundServices by lazy { BackgroundServices(context, core.historyStorage, core.bookmarksStorage) }
|
val backgroundServices by lazy {
|
||||||
|
BackgroundServices(context, core.historyStorage, core.bookmarksStorage, utils.notificationManager)
|
||||||
|
}
|
||||||
val services by lazy { Services(backgroundServices.accountManager, useCases.tabsUseCases) }
|
val services by lazy { Services(backgroundServices.accountManager, useCases.tabsUseCases) }
|
||||||
val core by lazy { Core(context) }
|
val core by lazy { Core(context) }
|
||||||
val search by lazy { Search(context) }
|
val search by lazy { Search(context) }
|
||||||
|
@ -0,0 +1,87 @@
|
|||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.components
|
||||||
|
|
||||||
|
import android.annotation.TargetApi
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import mozilla.components.concept.sync.DeviceEvent
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages notification channels and allows displaying different types of notifications.
|
||||||
|
*/
|
||||||
|
class NotificationManager(private val context: Context) {
|
||||||
|
companion object {
|
||||||
|
const val RECEIVE_TABS_TAG = "ReceivedTabs"
|
||||||
|
const val RECEIVE_TABS_CHANNEL_ID = "ReceivedTabsChannel"
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Create the notification channels we are going to use, but only on API 26+ because the NotificationChannel
|
||||||
|
// class is new and not in the support library.
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
createNotificationChannel(
|
||||||
|
RECEIVE_TABS_CHANNEL_ID,
|
||||||
|
// Pick 'high' because this is a user-triggered action that is expected to be part of a continuity flow.
|
||||||
|
// That is, user is expected to be waiting for this notification on their device; make it obvious.
|
||||||
|
NotificationManager.IMPORTANCE_HIGH,
|
||||||
|
// Name and description are shown in the 'app notifications' settings for the app.
|
||||||
|
context.getString(R.string.fxa_received_tab_channel_name),
|
||||||
|
context.getString(R.string.fxa_received_tab_channel_description)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showReceivedTabs(event: DeviceEvent.TabReceived) {
|
||||||
|
// 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.
|
||||||
|
event.entries.forEach { tab ->
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(tab.url))
|
||||||
|
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
|
||||||
|
|
||||||
|
val builder = NotificationCompat.Builder(context, RECEIVE_TABS_CHANNEL_ID)
|
||||||
|
.setSmallIcon(R.drawable.ic_notification)
|
||||||
|
.setContentTitle(tab.title)
|
||||||
|
.setContentText(tab.url)
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
// Explicitly set a priority for <API25 devices.
|
||||||
|
// On newer devices this is inherited from the channel.
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
|
||||||
|
// Pick a random ID for this notification so that different tabs do not clash.
|
||||||
|
@SuppressWarnings("MagicNumber")
|
||||||
|
val notificationId = (Math.random() * 100).toInt()
|
||||||
|
|
||||||
|
with(NotificationManagerCompat.from(context)) {
|
||||||
|
notify(RECEIVE_TABS_TAG, notificationId, builder.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.O)
|
||||||
|
private fun createNotificationChannel(
|
||||||
|
channelId: String,
|
||||||
|
importance: Int,
|
||||||
|
channelName: String,
|
||||||
|
channelDescription: String
|
||||||
|
) {
|
||||||
|
val channel = NotificationChannel(channelId, channelName, importance).apply {
|
||||||
|
description = channelDescription
|
||||||
|
}
|
||||||
|
// Register the channel with the system. Once this is done, we can't change importance or other notification
|
||||||
|
// channel behaviour. We will be able to change 'name' and 'description' if we so choose.
|
||||||
|
val notificationManager: NotificationManager =
|
||||||
|
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
notificationManager.createNotificationChannel(channel)
|
||||||
|
}
|
||||||
|
}
|
@ -32,4 +32,11 @@ class Utilities(
|
|||||||
val privateIntentProcessor by lazy {
|
val privateIntentProcessor by lazy {
|
||||||
IntentProcessor(sessionUseCases, sessionManager, searchUseCases, context, isPrivate = true)
|
IntentProcessor(sessionUseCases, sessionManager, searchUseCases, context, isPrivate = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides notification functionality, manages notification channels.
|
||||||
|
*/
|
||||||
|
val notificationManager by lazy {
|
||||||
|
NotificationManager(context)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,15 @@ class AccountSettingsFragment : PreferenceFragmentCompat(), CoroutineScope {
|
|||||||
|
|
||||||
private fun getClickListenerForSyncNow(): Preference.OnPreferenceClickListener {
|
private fun getClickListenerForSyncNow(): Preference.OnPreferenceClickListener {
|
||||||
return Preference.OnPreferenceClickListener {
|
return Preference.OnPreferenceClickListener {
|
||||||
|
// Trigger a sync.
|
||||||
requireComponents.backgroundServices.syncManager.syncNow()
|
requireComponents.backgroundServices.syncManager.syncNow()
|
||||||
|
// Poll for device events.
|
||||||
|
launch {
|
||||||
|
requireComponents.backgroundServices.accountManager.authenticatedAccount()
|
||||||
|
?.deviceConstellation()
|
||||||
|
?.refreshDeviceStateAsync()
|
||||||
|
?.await()
|
||||||
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,6 +160,12 @@
|
|||||||
<!-- Label summary showing never synced -->
|
<!-- Label summary showing never synced -->
|
||||||
<string name="sync_never_synced_summary">Last synced: never</string>
|
<string name="sync_never_synced_summary">Last synced: never</string>
|
||||||
|
|
||||||
|
<!-- Send Tab -->
|
||||||
|
<!-- Name of the "receive tabs" notification channel. Displayed in the "App notifications" system settings for the app -->
|
||||||
|
<string name="fxa_received_tab_channel_name">Received tabs</string>
|
||||||
|
<!-- Description of the "receive tabs" notification channel. Displayed in the "App notifications" system settings for the app -->
|
||||||
|
<string name="fxa_received_tab_channel_description">Notifications for tabs received from other Firefox devices.</string>
|
||||||
|
|
||||||
<!-- Advanced Preferences -->
|
<!-- Advanced Preferences -->
|
||||||
<!-- Preference for tracking protection settings -->
|
<!-- Preference for tracking protection settings -->
|
||||||
<string name="preferences_tracking_protection_settings">Tracking Protection</string>
|
<string name="preferences_tracking_protection_settings">Tracking Protection</string>
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
/* 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/. */
|
|
||||||
|
|
||||||
package org.mozilla.fenix.components
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import mozilla.components.browser.storage.sync.PlacesBookmarksStorage
|
|
||||||
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
|
|
||||||
import mozilla.components.feature.sync.BackgroundSyncManager
|
|
||||||
|
|
||||||
class TestBackgroundServices(
|
|
||||||
context: Context,
|
|
||||||
historyStorage: PlacesHistoryStorage,
|
|
||||||
bookmarksStorage: PlacesBookmarksStorage
|
|
||||||
) : BackgroundServices(context, historyStorage, bookmarksStorage) {
|
|
||||||
override val syncManager = BackgroundSyncManager("")
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user