For #8034: Create a post-visual completeness executor
Create an object that will execute its enqued tasks when Fenix is visually complete.master
parent
af19913a16
commit
f9b716286a
|
@ -37,6 +37,7 @@ import org.mozilla.fenix.components.Components
|
||||||
import org.mozilla.fenix.components.metrics.MetricServiceType
|
import org.mozilla.fenix.components.metrics.MetricServiceType
|
||||||
import org.mozilla.fenix.ext.settings
|
import org.mozilla.fenix.ext.settings
|
||||||
import org.mozilla.fenix.session.NotificationSessionObserver
|
import org.mozilla.fenix.session.NotificationSessionObserver
|
||||||
|
import org.mozilla.fenix.session.PerformanceActivityLifecycleCallbacks
|
||||||
import org.mozilla.fenix.session.VisibilityLifecycleCallback
|
import org.mozilla.fenix.session.VisibilityLifecycleCallback
|
||||||
import org.mozilla.fenix.utils.BrowsersCache
|
import org.mozilla.fenix.utils.BrowsersCache
|
||||||
import org.mozilla.fenix.utils.Settings
|
import org.mozilla.fenix.utils.Settings
|
||||||
|
@ -143,6 +144,10 @@ open class FenixApplication : LocaleAwareApplication() {
|
||||||
// if ((System.currentTimeMillis() - settings().lastPlacesStorageMaintenance) > ONE_DAY_MILLIS) {
|
// if ((System.currentTimeMillis() - settings().lastPlacesStorageMaintenance) > ONE_DAY_MILLIS) {
|
||||||
// runStorageMaintenance()
|
// runStorageMaintenance()
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
registerActivityLifecycleCallbacks(
|
||||||
|
PerformanceActivityLifecycleCallbacks(components.performance.visualCompletenessTaskManager)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// See https://github.com/mozilla-mobile/fenix/issues/7227 for context.
|
// See https://github.com/mozilla-mobile/fenix/issues/7227 for context.
|
||||||
|
|
|
@ -15,13 +15,14 @@ import androidx.annotation.VisibleForTesting
|
||||||
import androidx.annotation.VisibleForTesting.PROTECTED
|
import androidx.annotation.VisibleForTesting.PROTECTED
|
||||||
import androidx.appcompat.app.ActionBar
|
import androidx.appcompat.app.ActionBar
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
import androidx.core.view.doOnPreDraw
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.NavDestination
|
import androidx.navigation.NavDestination
|
||||||
import androidx.navigation.NavDirections
|
import androidx.navigation.NavDirections
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
import androidx.navigation.ui.AppBarConfiguration
|
import androidx.navigation.ui.AppBarConfiguration
|
||||||
import androidx.navigation.ui.NavigationUI
|
import androidx.navigation.ui.NavigationUI
|
||||||
import kotlinx.android.synthetic.main.activity_home.navigationToolbarStub
|
import kotlinx.android.synthetic.main.activity_home.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import mozilla.components.browser.search.SearchEngine
|
import mozilla.components.browser.search.SearchEngine
|
||||||
|
@ -69,6 +70,7 @@ import org.mozilla.fenix.settings.logins.SavedLoginsFragmentDirections
|
||||||
import org.mozilla.fenix.theme.DefaultThemeManager
|
import org.mozilla.fenix.theme.DefaultThemeManager
|
||||||
import org.mozilla.fenix.theme.ThemeManager
|
import org.mozilla.fenix.theme.ThemeManager
|
||||||
import org.mozilla.fenix.utils.BrowsersCache
|
import org.mozilla.fenix.utils.BrowsersCache
|
||||||
|
import org.mozilla.fenix.utils.StartupTaskManager
|
||||||
|
|
||||||
@SuppressWarnings("TooManyFunctions", "LargeClass")
|
@SuppressWarnings("TooManyFunctions", "LargeClass")
|
||||||
open class HomeActivity : LocaleAwareAppCompatActivity() {
|
open class HomeActivity : LocaleAwareAppCompatActivity() {
|
||||||
|
@ -77,6 +79,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
|
||||||
lateinit var themeManager: ThemeManager
|
lateinit var themeManager: ThemeManager
|
||||||
lateinit var browsingModeManager: BrowsingModeManager
|
lateinit var browsingModeManager: BrowsingModeManager
|
||||||
|
|
||||||
|
private var isVisuallyComplete = false
|
||||||
|
|
||||||
|
private var visualCompletenessTaskManager: StartupTaskManager? = null
|
||||||
|
|
||||||
private var sessionObserver: SessionManager.Observer? = null
|
private var sessionObserver: SessionManager.Observer? = null
|
||||||
|
|
||||||
private val hotStartMonitor = HotStartPerformanceMonitor()
|
private val hotStartMonitor = HotStartPerformanceMonitor()
|
||||||
|
@ -107,6 +113,18 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
|
||||||
|
|
||||||
setupThemeAndBrowsingMode(getModeFromIntentOrLastKnown(intent))
|
setupThemeAndBrowsingMode(getModeFromIntentOrLastKnown(intent))
|
||||||
setContentView(R.layout.activity_home)
|
setContentView(R.layout.activity_home)
|
||||||
|
|
||||||
|
// Must be after we set the content view
|
||||||
|
if (isVisuallyComplete) {
|
||||||
|
rootContainer.doOnPreDraw {
|
||||||
|
// This delay is temporary. We are delaying 5 seconds until the performance
|
||||||
|
// team can locate the real point of visual completeness.
|
||||||
|
it.postDelayed({
|
||||||
|
visualCompletenessTaskManager!!.start()
|
||||||
|
}, delay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Performance.instrumentColdStartupToHomescreenTime(this)
|
Performance.instrumentColdStartupToHomescreenTime(this)
|
||||||
|
|
||||||
externalSourceIntentProcessors.any { it.process(intent, navHost.navController, this.intent) }
|
externalSourceIntentProcessors.any { it.process(intent, navHost.navController, this.intent) }
|
||||||
|
@ -390,6 +408,15 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
|
||||||
navHost.navController.navigate(action)
|
navHost.navController.navigate(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The root container is null at this point, so let the HomeActivity know that
|
||||||
|
* we are visually complete.
|
||||||
|
*/
|
||||||
|
fun postVisualCompletenessQueue(visualCompletenessTaskManager: StartupTaskManager) {
|
||||||
|
isVisuallyComplete = true
|
||||||
|
this.visualCompletenessTaskManager = visualCompletenessTaskManager
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val OPEN_TO_BROWSER = "open_to_browser"
|
const val OPEN_TO_BROWSER = "open_to_browser"
|
||||||
const val OPEN_TO_BROWSER_AND_LOAD = "open_to_browser_and_load"
|
const val OPEN_TO_BROWSER_AND_LOAD = "open_to_browser_and_load"
|
||||||
|
@ -397,5 +424,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
|
||||||
const val PRIVATE_BROWSING_MODE = "private_browsing_mode"
|
const val PRIVATE_BROWSING_MODE = "private_browsing_mode"
|
||||||
const val EXTRA_DELETE_PRIVATE_TABS = "notification_delete_and_open"
|
const val EXTRA_DELETE_PRIVATE_TABS = "notification_delete_and_open"
|
||||||
const val EXTRA_OPENED_FROM_NOTIFICATION = "notification_open"
|
const val EXTRA_OPENED_FROM_NOTIFICATION = "notification_open"
|
||||||
|
const val delay = 5000L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,4 +78,5 @@ class Components(private val context: Context) {
|
||||||
val publicSuffixList by lazy { PublicSuffixList(context) }
|
val publicSuffixList by lazy { PublicSuffixList(context) }
|
||||||
val clipboardHandler by lazy { ClipboardHandler(context) }
|
val clipboardHandler by lazy { ClipboardHandler(context) }
|
||||||
val migrationStore by lazy { MigrationStore() }
|
val migrationStore by lazy { MigrationStore() }
|
||||||
|
val performance by lazy { PerformanceComponent() }
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
/* 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 org.mozilla.fenix.utils.StartupTaskManager
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component group for all functionality related to performance.
|
||||||
|
*/
|
||||||
|
class PerformanceComponent {
|
||||||
|
val visualCompletenessTaskManager by lazy { StartupTaskManager() }
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/* 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.utils
|
||||||
|
|
||||||
|
import org.mozilla.gecko.util.ThreadUtils
|
||||||
|
import java.lang.IllegalStateException
|
||||||
|
|
||||||
|
typealias StartupTask = () -> Unit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A queue of tasks that are performed at specific points during Fenix startup.
|
||||||
|
*
|
||||||
|
* This queue contains a list of startup tasks. Each task in the queue will be started once Fenix
|
||||||
|
* is visually complete.
|
||||||
|
*
|
||||||
|
* This class is not thread safe and should only be called from the main thread.
|
||||||
|
*/
|
||||||
|
class StartupTaskManager {
|
||||||
|
private var tasks = mutableListOf<StartupTask>()
|
||||||
|
private var hasStarted = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a task to the queue.
|
||||||
|
* Each task will execute on the main thread.
|
||||||
|
*
|
||||||
|
* @param task: The task to add to the queue.
|
||||||
|
*/
|
||||||
|
@Synchronized
|
||||||
|
fun add(task: StartupTask) {
|
||||||
|
ThreadUtils.assertOnUiThread()
|
||||||
|
if (hasStarted) {
|
||||||
|
throw IllegalStateException("New tasks should not be added because queue already " +
|
||||||
|
"started, and these newly added tasks will not execute.")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.add(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start all tasks in the queue. When all the tasks have been started,
|
||||||
|
* clear the queue.
|
||||||
|
*/
|
||||||
|
fun start() {
|
||||||
|
ThreadUtils.assertOnUiThread()
|
||||||
|
hasStarted = true
|
||||||
|
|
||||||
|
tasks.forEach { it.invoke() }
|
||||||
|
|
||||||
|
// Anything captured by the lambda will remain captured if we hold on to these tasks,
|
||||||
|
// which takes up more memory than we need to.
|
||||||
|
tasks.clear()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
/* 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.session
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.Application
|
||||||
|
import android.os.Bundle
|
||||||
|
import org.mozilla.fenix.HomeActivity
|
||||||
|
import org.mozilla.fenix.IntentReceiverActivity
|
||||||
|
import org.mozilla.fenix.browser.BrowserPerformanceTestActivity
|
||||||
|
import org.mozilla.fenix.settings.account.AuthIntentReceiverActivity
|
||||||
|
import org.mozilla.fenix.utils.StartupTaskManager
|
||||||
|
import org.mozilla.fenix.widget.VoiceSearchActivity
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These callbacks handle binding performance code to the activity lifecycle.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("EmptyFunctionBlock")
|
||||||
|
class PerformanceActivityLifecycleCallbacks(
|
||||||
|
private val visualCompletenessTaskManager: StartupTaskManager
|
||||||
|
) : Application.ActivityLifecycleCallbacks {
|
||||||
|
|
||||||
|
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
|
||||||
|
ifNecessaryPostVisualCompleteness(activity)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if it is a terminal activity, or false if it
|
||||||
|
* an activity used in transition.
|
||||||
|
*/
|
||||||
|
private fun isTransientActivity(activity: Activity): Boolean {
|
||||||
|
// These are the current list of non terminal activites.
|
||||||
|
// They have been whitelisted in case new activities are added to the application
|
||||||
|
// to ensure these new activities would not crash the application.
|
||||||
|
return isTransientActivityInMigrationVariant(activity) ||
|
||||||
|
(activity is IntentReceiverActivity) ||
|
||||||
|
(activity is VoiceSearchActivity) ||
|
||||||
|
(activity is AuthIntentReceiverActivity) ||
|
||||||
|
(activity is BrowserPerformanceTestActivity)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This starts the StartupTaskManager, eitther delayed or right away, if the activity is a
|
||||||
|
* terminal activity. If not, do nothing.
|
||||||
|
*/
|
||||||
|
private fun ifNecessaryPostVisualCompleteness(activity: Activity) {
|
||||||
|
fun shouldStartVisualCompletenessQueueImmediately(): Boolean {
|
||||||
|
return !isTransientActivity(activity)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activity is HomeActivity) {
|
||||||
|
// We should delay the visualCompletenessTaskManager when reaching the HomeActivity
|
||||||
|
// to ensure all tasks are delayed until after visual completeness
|
||||||
|
activity.postVisualCompletenessQueue(visualCompletenessTaskManager)
|
||||||
|
} else if (shouldStartVisualCompletenessQueueImmediately()) {
|
||||||
|
// If we do not go through the home activity, we have to start the tasks
|
||||||
|
// immediately to avoid spending time implementing it.
|
||||||
|
visualCompletenessTaskManager.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityStarted(activity: Activity?) {}
|
||||||
|
override fun onActivityStopped(activity: Activity?) {}
|
||||||
|
override fun onActivityResumed(activity: Activity?) {}
|
||||||
|
override fun onActivityPaused(activity: Activity?) {}
|
||||||
|
override fun onActivitySaveInstanceState(activity: Activity?, bundle: Bundle?) {}
|
||||||
|
override fun onActivityDestroyed(activity: Activity?) {}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* The source files are different from migration and non migration variants.
|
||||||
|
* We use this property to extend the [isTransientActivity] implementation for the
|
||||||
|
* migration variants.
|
||||||
|
*/
|
||||||
|
var isTransientActivityInMigrationVariant: (Activity) -> Boolean = { false }
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,11 +7,19 @@ package org.mozilla.fenix
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import mozilla.components.support.migration.FennecMigrator
|
import mozilla.components.support.migration.FennecMigrator
|
||||||
|
import org.mozilla.fenix.session.PerformanceActivityLifecycleCallbacks
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An application class which knows how to migrate Fennec data.
|
* An application class which knows how to migrate Fennec data.
|
||||||
*/
|
*/
|
||||||
class MigratingFenixApplication : FenixApplication() {
|
class MigratingFenixApplication : FenixApplication() {
|
||||||
|
|
||||||
|
init {
|
||||||
|
PerformanceActivityLifecycleCallbacks.isTransientActivityInMigrationVariant = {
|
||||||
|
if (it is MigrationDecisionActivity) true else false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val migrator by lazy {
|
val migrator by lazy {
|
||||||
FennecMigrator.Builder(this, this.components.analytics.crashReporter)
|
FennecMigrator.Builder(this, this.components.analytics.crashReporter)
|
||||||
.migrateOpenTabs(this.components.core.sessionManager)
|
.migrateOpenTabs(this.components.core.sessionManager)
|
||||||
|
|
Loading…
Reference in New Issue