1
0
Fork 0
fenix/app/src/main/java/org/mozilla/fenix/FenixApplication.kt

278 lines
11 KiB
Kotlin
Raw Normal View History

2019-01-24 22:07:52 +01:00
/* 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/. */
2019-01-24 22:07:52 +01:00
package org.mozilla.fenix
import android.annotation.SuppressLint
import android.app.Application
2019-06-23 19:13:52 +02:00
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.os.StrictMode
2019-04-02 02:53:37 +02:00
import androidx.appcompat.app.AppCompatDelegate
import io.reactivex.plugins.RxJavaPlugins
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
2019-07-18 00:09:47 +02:00
import mozilla.appservices.Megazord
import mozilla.components.concept.push.PushProcessor
import mozilla.components.service.experiments.Experiments
import mozilla.components.service.fretboard.Fretboard
import mozilla.components.service.fretboard.source.kinto.KintoExperimentSource
import mozilla.components.service.fretboard.storage.flatfile.FlatFileExperimentStorage
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.ktx.android.content.isMainProcess
import mozilla.components.support.ktx.android.content.runOnlyInMainProcess
2019-07-18 00:09:47 +02:00
import mozilla.components.support.rusthttp.RustHttpConfig
import mozilla.components.support.rustlog.RustLog
import org.mozilla.fenix.GleanMetrics.ExperimentsMetrics
import org.mozilla.fenix.components.Components
2019-04-02 02:53:37 +02:00
import org.mozilla.fenix.utils.Settings
import java.io.File
@SuppressLint("Registered")
@Suppress("TooManyFunctions")
open class FenixApplication : Application() {
lateinit var fretboard: Fretboard
lateinit var experimentLoader: Deferred<Boolean>
var experimentLoaderComplete: Boolean = false
open val components by lazy { Components(this) }
override fun onCreate() {
super.onCreate()
setupApplication()
}
open fun setupApplication() {
setupCrashReporting()
2019-04-02 02:53:37 +02:00
setDayNightTheme()
2019-07-18 00:09:47 +02:00
setupMegazord()
setupLogging()
registerRxExceptionHandling()
enableStrictMode()
if (!isMainProcess()) {
// If this is not the main process then do not continue with the initialization here. Everything that
// follows only needs to be done in our app's main process and should not be done in other processes like
// a GeckoView child process or the crash handling process. Most importantly we never want to end up in a
// situation where we create a GeckoRuntime from the Gecko child process.
return
}
2019-08-01 16:20:45 +02:00
// Make sure the engine is initialized and ready to use.
components.core.engine.warmUp()
// We want to call this function as early as possible, but only once and
// on the main process, as it uses Gecko to fetch experiments from the server.
experimentLoader = loadExperiments()
// Enable the service-experiments component
if (Settings.getInstance(this).isExperimentationEnabled) {
Experiments.initialize(
applicationContext,
mozilla.components.service.experiments.Configuration(
httpClient = lazy(LazyThreadSafetyMode.NONE) { components.core.client }
)
)
}
// When the `fenix-test-2019-08-05` experiment is active, record its branch in Glean
// telemetry. This will be used to validate that the experiment system correctly enrolls
// clients and segments them into branches. Note that this will not take effect the first
// time the application has launched, since there won't be enough time for the experiments
// library to get a list of experiments. It will take effect the second time the
// application is launched.
Experiments.withExperiment("fenix-test-2019-08-05") { branchName ->
ExperimentsMetrics.activeExperiment.set(branchName)
}
setupLeakCanary()
if (Settings.getInstance(this).isTelemetryEnabled) {
components.analytics.metrics.start()
}
// Sets the PushFeature as the singleton instance for push messages to go to.
// We need the push feature setup here to deliver messages in the case where the service
// starts up the app first.
if (FeatureFlags.sendTabEnabled && components.backgroundServices.pushConfig != null) {
PushProcessor.install(components.backgroundServices.push)
}
}
private fun registerRxExceptionHandling() {
RxJavaPlugins.setErrorHandler {
it.cause?.run {
throw this
} ?: throw it
}
}
/**
* Wait until all experiments are loaded
*
* This function will cause the caller to block until the experiments are loaded.
* It could be used in any number of reasons, but the most likely scenario is that
* a calling function needs to access the loaded experiments and wants to
* make sure that the experiments are loaded from the server before doing so.
*
* Because this function is synchronized, it can only be accessed by one thread
* at a time. Anyone trying to check the loaded status will wait if someone is
* already waiting. This is okay because the thread waiting for access to the
* function will immediately see that the loader is complete upon gaining the
* opportunity to run the function.
*/
@Synchronized
public fun waitForExperimentsToLoad() {
// Do we know that we are already complete?
if (!experimentLoaderComplete) {
// No? Have we completed since the last call?
if (!experimentLoader.isCompleted) {
// No? Well, let's wait.
runBlocking {
experimentLoader.await()
}
}
// Set this so we don't have to wait on the next call.
experimentLoaderComplete = true
}
}
protected open fun setupLeakCanary() {
// no-op, LeakCanary is disabled by default
}
open fun toggleLeakCanary(newValue: Boolean) {
// no-op, LeakCanary is disabled by default
}
2019-07-18 00:09:47 +02:00
private fun setupLogging() {
// We want the log messages of all builds to go to Android logcat
Log.addSink(AndroidLogSink())
2019-07-18 00:09:47 +02:00
// We want rust logging to go through the log sinks.
// This has to happen after initializing the megazord.
RustLog.enable()
}
private fun loadExperiments(): Deferred<Boolean> {
val experimentsFile = File(filesDir, EXPERIMENTS_JSON_FILENAME)
val experimentSource = KintoExperimentSource(
EXPERIMENTS_BASE_URL,
EXPERIMENTS_BUCKET_NAME,
EXPERIMENTS_COLLECTION_NAME,
components.core.client
)
// TODO add ValueProvider to keep clientID in sync with Glean when ready
fretboard = Fretboard(experimentSource, FlatFileExperimentStorage(experimentsFile))
return GlobalScope.async(Dispatchers.IO) {
fretboard.loadExperiments()
Logger.debug("Bucket is ${fretboard.getUserBucket(this@FenixApplication)}")
Logger.debug("Experiments active: ${fretboard.getExperimentsMap(this@FenixApplication)}")
fretboard.updateExperiments()
return@async true
}
}
private fun setupCrashReporting() {
components
.analytics
.crashReporter
.install(this)
}
/**
* Initiate Megazord sequence! Megazord Battle Mode!
*
2019-07-18 00:09:47 +02:00
* The application-services combined libraries are known as the "megazord". The default megazord
* contains several features that fenix doesn't need, and so we swap out with a customized fenix-specific
* version of the megazord. The best explanation for what this is, and why it's done is the a-s
* documentation on the topic:
* - https://github.com/mozilla/application-services/blob/master/docs/design/megazords.md
* - https://mozilla.github.io/application-services/docs/applications/consuming-megazord-libraries.html
*/
2019-07-18 00:09:47 +02:00
private fun setupMegazord() {
// Note: This must be called as soon as possible
Megazord.init()
// This (and enabling RustLog) may be delayed if needed for performance reasons
RustHttpConfig.setClient(lazy { components.core.client })
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
runOnlyInMainProcess {
components.core.sessionManager.onLowMemory()
}
}
2019-04-02 02:53:37 +02:00
@SuppressLint("WrongConstant")
// Suppressing erroneous lint warning about using MODE_NIGHT_AUTO_BATTERY, a likely library bug
2019-04-02 02:53:37 +02:00
private fun setDayNightTheme() {
2019-06-23 19:13:52 +02:00
val settings = Settings.getInstance(this)
2019-04-02 02:53:37 +02:00
when {
2019-06-23 19:13:52 +02:00
settings.shouldUseLightTheme -> {
2019-04-02 02:53:37 +02:00
AppCompatDelegate.setDefaultNightMode(
AppCompatDelegate.MODE_NIGHT_NO
)
}
2019-06-23 19:13:52 +02:00
settings.shouldUseDarkTheme -> {
2019-04-02 02:53:37 +02:00
AppCompatDelegate.setDefaultNightMode(
AppCompatDelegate.MODE_NIGHT_YES
)
}
2019-06-23 19:13:52 +02:00
SDK_INT < Build.VERSION_CODES.P && settings.shouldUseAutoBatteryTheme -> {
2019-04-02 02:53:37 +02:00
AppCompatDelegate.setDefaultNightMode(
AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
)
}
2019-06-23 19:13:52 +02:00
SDK_INT >= Build.VERSION_CODES.P && settings.shouldFollowDeviceTheme -> {
2019-04-02 02:53:37 +02:00
AppCompatDelegate.setDefaultNightMode(
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
)
}
// First run of app no default set, set the default to Follow System for 28+ and Normal Mode otherwise
else -> {
2019-06-23 19:13:52 +02:00
if (SDK_INT >= Build.VERSION_CODES.P) {
2019-04-02 02:53:37 +02:00
AppCompatDelegate.setDefaultNightMode(
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
)
settings.shouldFollowDeviceTheme = true
2019-04-02 02:53:37 +02:00
} else {
AppCompatDelegate.setDefaultNightMode(
AppCompatDelegate.MODE_NIGHT_NO
)
settings.shouldUseLightTheme = true
2019-04-02 02:53:37 +02:00
}
}
}
}
private fun enableStrictMode() {
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.build()
)
var builder = StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.detectLeakedRegistrationObjects()
.detectActivityLeaks()
.detectFileUriExposure()
.penaltyLog()
if (SDK_INT >= Build.VERSION_CODES.O) builder = builder.detectContentUriWithoutPermission()
if (SDK_INT >= Build.VERSION_CODES.P) builder = builder.detectNonSdkApiUsage()
StrictMode.setVmPolicy(builder.build())
}
}
}