Revision control
Copy as Markdown
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* 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
package org.mozilla.vrbrowser.browser
import android.content.Context
import android.net.Uri
import android.os.Build
import androidx.lifecycle.ProcessLifecycleOwner
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import mozilla.appservices.Megazord
import mozilla.appservices.rustlog.LogAdapterCannotEnable
import mozilla.components.concept.sync.*
import mozilla.components.service.fxa.*
import mozilla.components.service.fxa.manager.FxaAccountManager
import mozilla.components.service.fxa.sync.GlobalSyncableStoreProvider
import mozilla.components.support.base.log.Log
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.base.log.sink.AndroidLogSink
import mozilla.components.support.rusthttp.RustHttpConfig
import mozilla.components.support.rustlog.RustLog
import org.mozilla.geckoview.AllowOrDeny
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoSession
import org.mozilla.vrbrowser.R
import org.mozilla.vrbrowser.VRBrowserActivity
import org.mozilla.vrbrowser.browser.engine.EngineProvider
import org.mozilla.vrbrowser.telemetry.GleanMetricsService
import org.mozilla.vrbrowser.ui.widgets.WidgetManagerDelegate
import org.mozilla.vrbrowser.utils.ConnectivityReceiver
import org.mozilla.vrbrowser.utils.SystemUtils
class Services(val context: Context, places: Places): GeckoSession.NavigationDelegate {
private val LOGTAG = SystemUtils.createLogtag(Services::class.java)
companion object {
const val CLIENT_ID = "7ad9917f6c55fb77"
const val REDIRECT_URL = "urn:ietf:wg:oauth:2.0:oob:oauth-redirect-webchannel"
}
interface TabReceivedDelegate {
fun onTabsReceived(uri: List<TabData>)
}
var tabReceivedDelegate: TabReceivedDelegate? = null
// This makes bookmarks storage accessible to background sync workers.
init {
Megazord.init()
try {
RustLog.enable()
} catch (e: LogAdapterCannotEnable) {
android.util.Log.w(LOGTAG, "RustLog has been enabled.")
}
RustHttpConfig.setClient(lazy { EngineProvider.createClient(context) })
// Make sure we get logs out of our android-components.
Log.addSink(AndroidLogSink())
GlobalSyncableStoreProvider.configureStore(SyncEngine.Bookmarks to lazy {places.bookmarks})
GlobalSyncableStoreProvider.configureStore(SyncEngine.History to lazy {places.history})
}
// Process received device events, only handling received tabs for now.
// They'll come from other FxA devices (e.g. Firefox Desktop).
private val deviceEventObserver = object : AccountEventsObserver {
private val logTag = "DeviceEventsObserver"
override fun onEvents(events: List<AccountEvent>) {
CoroutineScope(Dispatchers.Main).launch {
Logger(logTag).info("Received ${events.size} device event(s)")
events
.filterIsInstance<AccountEvent.DeviceCommandIncoming>()
.map { it.command }
.filterIsInstance<DeviceCommandIncoming.TabReceived>()
.forEach { command ->
command.from?.deviceType?.let { GleanMetricsService.FxA.receivedTab(it) }
tabReceivedDelegate?.onTabsReceived(command.entries)
}
}
}
}
val serverConfig = ServerConfig(Server.RELEASE, CLIENT_ID, REDIRECT_URL)
val accountManager = FxaAccountManager(
context = context,
serverConfig = serverConfig,
deviceConfig = DeviceConfig(
// This is a default name, and can be changed once user is logged in.
// E.g. accountManager.authenticatedAccount()?.deviceConstellation()?.setDeviceNameAsync("new name")
name = "${context.getString(R.string.app_name)} on ${Build.MANUFACTURER} ${Build.MODEL}",
type = DeviceType.VR,
capabilities = setOf(DeviceCapability.SEND_TAB)
),
syncConfig = SyncConfig(setOf(SyncEngine.History, SyncEngine.Bookmarks, SyncEngine.Passwords), PeriodicSyncConfig(periodMinutes = 1440))
).also {
it.registerForAccountEvents(deviceEventObserver, ProcessLifecycleOwner.get(), true)
}
init {
if (ConnectivityReceiver.isNetworkAvailable(context)) {
init()
}
(context as WidgetManagerDelegate).servicesProvider.connectivityReceiver.addListener {
if (it) {
init()
}
}
}
private fun init() {
CoroutineScope(Dispatchers.Main).launch {
accountManager.start()
}
}
override fun onLoadRequest(geckoSession: GeckoSession, loadRequest: GeckoSession.NavigationDelegate.LoadRequest): GeckoResult<AllowOrDeny>? {
if (loadRequest.uri.startsWith(REDIRECT_URL)) {
val parsedUri = Uri.parse(loadRequest.uri)
parsedUri.getQueryParameter("code")?.let { code ->
val state = parsedUri.getQueryParameter("state") as String
val action = parsedUri.getQueryParameter("action") as String
val geckoResult = GeckoResult<AllowOrDeny>()
// Notify the state machine about our success.
CoroutineScope(Dispatchers.Main).launch {
val result = accountManager.finishAuthentication(FxaAuthData(action.toAuthType(), code = code, state = state))
if (!result) {
android.util.Log.e(LOGTAG, "Authentication finish error.")
geckoResult.complete(AllowOrDeny.DENY)
} else {
android.util.Log.e(LOGTAG, "Authentication successfully completed.")
geckoResult.complete(AllowOrDeny.ALLOW)
}
}
return geckoResult
}
return GeckoResult.deny()
}
return GeckoResult.allow()
}
}