BackgroundServices.kt 9.16 KB
Newer Older
1
2
3
4
5
6
7
/* 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
8
import android.os.Build
9
10
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.PRIVATE
Grisha Kruglov's avatar
Grisha Kruglov committed
11
import mozilla.components.browser.storage.sync.PlacesBookmarksStorage
12
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
13
import mozilla.components.browser.storage.sync.RemoteTabsStorage
14
import mozilla.components.concept.sync.AccountObserver
15
import mozilla.components.concept.sync.AuthType
16
import mozilla.components.concept.sync.DeviceCapability
17
import mozilla.components.concept.sync.DeviceType
18
import mozilla.components.concept.sync.OAuthAccount
19
20
import mozilla.components.feature.accounts.push.FxaPushSupportFeature
import mozilla.components.feature.accounts.push.SendTabFeature
21
import mozilla.components.feature.syncedtabs.storage.SyncedTabsStorage
22
import mozilla.components.lib.crash.CrashReporter
23
24
25
import mozilla.components.service.fxa.DeviceConfig
import mozilla.components.service.fxa.ServerConfig
import mozilla.components.service.fxa.SyncConfig
26
import mozilla.components.service.fxa.SyncEngine
27
import mozilla.components.service.fxa.manager.FxaAccountManager
28
import mozilla.components.service.fxa.manager.SCOPE_SESSION
29
import mozilla.components.service.fxa.manager.SCOPE_SYNC
30
import mozilla.components.service.fxa.manager.SyncEnginesStorage
31
import mozilla.components.service.fxa.sync.GlobalSyncableStoreProvider
32
import mozilla.components.service.sync.logins.SyncableLoginsStorage
33
import org.mozilla.fenix.Config
34
import org.mozilla.fenix.FeatureFlags
35
import org.mozilla.fenix.R
36
import org.mozilla.fenix.components.metrics.Event
37
import org.mozilla.fenix.components.metrics.MetricController
38
import org.mozilla.fenix.ext.components
39
import org.mozilla.fenix.ext.settings
40
import org.mozilla.fenix.sync.SyncedTabsIntegration
41
import org.mozilla.fenix.utils.Mockable
42
import org.mozilla.fenix.utils.RunWhenReadyQueue
43
44
45
46
47

/**
 * Component group for background services. These are the components that need to be accessed from within a
 * background worker.
 */
48
@Mockable
49
class BackgroundServices(
50
    private val context: Context,
51
    private val push: Push,
52
    crashReporter: CrashReporter,
53
54
    historyStorage: Lazy<PlacesHistoryStorage>,
    bookmarkStorage: Lazy<PlacesBookmarksStorage>,
55
56
    passwordsStorage: Lazy<SyncableLoginsStorage>,
    remoteTabsStorage: Lazy<RemoteTabsStorage>
57
) {
58
59
60
    // Allows executing tasks which depend on the account manager, but do not need to eagerly initialize it.
    val accountManagerAvailableQueue = RunWhenReadyQueue()

61
    fun defaultDeviceName(context: Context): String =
Isabel Rios's avatar
Isabel Rios committed
62
        context.getString(
63
            R.string.default_device_name_2,
Isabel Rios's avatar
Isabel Rios committed
64
65
66
67
            context.getString(R.string.app_name),
            Build.MANUFACTURER,
            Build.MODEL
        )
68

69
    val serverConfig = FxaServer.config(context)
70
    private val deviceConfig = DeviceConfig(
Yeon Taek Jeong's avatar
Yeon Taek Jeong committed
71
        name = defaultDeviceName(context),
72
73
74
75
76
        type = DeviceType.MOBILE,

        // NB: flipping this flag back and worth is currently not well supported and may need hand-holding.
        // Consult with the android-components peers before changing.
        // See https://github.com/mozilla/application-services/issues/1308
77
78
79
80
81
82
        capabilities = setOf(DeviceCapability.SEND_TAB),

        // Enable encryption for account state on supported API levels (23+).
        // Just on Nightly and local builds for now.
        // Enabling this for all channels is tracked in https://github.com/mozilla-mobile/fenix/issues/6704
        secureStateAtRest = Config.channel.isNightlyOrDebug
83
84
    )
    // If sync has been turned off on the server then disable syncing.
85
    @Suppress("ConstantConditionIf")
86
    @VisibleForTesting(otherwise = PRIVATE)
87
88
    val supportedEngines = if (FeatureFlags.syncedTabs) {
        setOf(SyncEngine.History, SyncEngine.Bookmarks, SyncEngine.Passwords, SyncEngine.Tabs)
89
    } else {
90
        setOf(SyncEngine.History, SyncEngine.Bookmarks, SyncEngine.Passwords)
91
    }
92
    private val syncConfig = SyncConfig(supportedEngines, syncPeriodInMinutes = 240L) // four hours
93

94
    init {
95
96
        /* Make the "history", "bookmark", "passwords", and "tabs" stores accessible to workers
           spawned by the sync manager. */
Grisha Kruglov's avatar
Grisha Kruglov committed
97
98
        GlobalSyncableStoreProvider.configureStore(SyncEngine.History to historyStorage)
        GlobalSyncableStoreProvider.configureStore(SyncEngine.Bookmarks to bookmarkStorage)
99
        GlobalSyncableStoreProvider.configureStore(SyncEngine.Passwords to passwordsStorage)
100
101
102
103

        if (FeatureFlags.syncedTabs) {
            GlobalSyncableStoreProvider.configureStore(SyncEngine.Tabs to remoteTabsStorage)
        }
104
105
    }

106
107
108
109
    private val telemetryAccountObserver = TelemetryAccountObserver(
        context,
        context.components.analytics.metrics
    )
Grisha Kruglov's avatar
Grisha Kruglov committed
110

111
112
    val accountAbnormalities = AccountAbnormalities(context, crashReporter)

113
    val accountManager by lazy { makeAccountManager(context, serverConfig, deviceConfig, syncConfig) }
114

115
116
117
118
    val syncedTabsStorage by lazy {
        SyncedTabsStorage(accountManager, context.components.core.store, remoteTabsStorage.value)
    }

119
120
121
122
123
124
125
    @VisibleForTesting(otherwise = PRIVATE)
    fun makeAccountManager(
        context: Context,
        serverConfig: ServerConfig,
        deviceConfig: DeviceConfig,
        syncConfig: SyncConfig?
    ) = FxaAccountManager(
126
        context,
127
128
129
        serverConfig,
        deviceConfig,
        syncConfig,
130
131
132
133
134
135
        setOf(
            // We don't need to specify sync scope explicitly, but `syncConfig` may be disabled due to
            // an 'experiments' flag. In that case, sync scope necessary for syncing won't be acquired
            // during authentication unless we explicitly specify it below.
            // This is a good example of an information leak at the API level.
            // See https://github.com/mozilla-mobile/android-components/issues/3732
136
137
138
139
            SCOPE_SYNC,
            // Necessary to enable "Manage Account" functionality and ability to generate OAuth
            // codes for certain scopes.
            SCOPE_SESSION
140
        )
141
    ).also { accountManager ->
Grisha Kruglov's avatar
Grisha Kruglov committed
142
        // TODO this needs to change once we have a SyncManager
143
144
145
        context.settings().fxaHasSyncedItems = accountManager.authenticatedAccount()?.let {
            SyncEnginesStorage(context).getStatus().any { it.value }
        } ?: false
146

Grisha Kruglov's avatar
Grisha Kruglov committed
147
        // Register a telemetry account observer to keep track of FxA auth metrics.
148
        accountManager.register(telemetryAccountObserver)
Grisha Kruglov's avatar
Grisha Kruglov committed
149

150
151
152
153
        // Register an "abnormal fxa behaviour" middleware to keep track of events such as
        // unexpected logouts.
        accountManager.register(accountAbnormalities)

154
        // Enable push if it's configured.
155
        push.feature?.let { autoPushFeature ->
156
157
            FxaPushSupportFeature(context, accountManager, autoPushFeature)
        }
158

159
160
        SendTabFeature(accountManager) { device, tabs ->
            notificationManager.showReceivedTabs(context, device, tabs)
161
        }
162

163
164
        SyncedTabsIntegration(context, accountManager).launch()

165
166
167
168
        accountAbnormalities.accountManagerInitializedAsync(
            accountManager,
            accountManager.initAsync()
        )
169
170
    }.also {
        accountManagerAvailableQueue.ready()
171
    }
172
173
174
175

    /**
     * Provides notification functionality, manages notification channels.
     */
176
    private val notificationManager by lazy {
177
178
        NotificationManager(context)
    }
179
}
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214

@VisibleForTesting(otherwise = PRIVATE)
class TelemetryAccountObserver(
    private val context: Context,
    private val metricController: MetricController
) : AccountObserver {
    override fun onAuthenticated(account: OAuthAccount, authType: AuthType) {
        when (authType) {
            // User signed-in into an existing FxA account.
            AuthType.Signin ->
                metricController.track(Event.SyncAuthSignIn)

            // User created a new FxA account.
            AuthType.Signup ->
                metricController.track(Event.SyncAuthSignUp)

            // User paired to an existing account via QR code scanning.
            AuthType.Pairing ->
                metricController.track(Event.SyncAuthPaired)

            // User signed-in into an FxA account shared from another locally installed app
            // (e.g. Fennec).
            AuthType.Shared ->
                metricController.track(Event.SyncAuthFromShared)

            // Account Manager recovered a broken FxA auth state, without direct user involvement.
            AuthType.Recovered ->
                metricController.track(Event.SyncAuthRecovered)

            // User signed-in into an FxA account via unknown means.
            // Exact mechanism identified by the 'action' param.
            is AuthType.OtherExternal ->
                metricController.track(Event.SyncAuthOtherExternal)
        }
        // Used by Leanplum as a context variable.
215
        context.settings().fxaSignedIn = true
216
217
218
219
220
    }

    override fun onLoggedOut() {
        metricController.track(Event.SyncAuthSignOut)
        // Used by Leanplum as a context variable.
221
        context.settings().fxaSignedIn = false
222
223
    }
}