for #10069 added AppLaunchTimeMeasurement.kt to handle logic of startup time for cold, warm, and hot startup types.

pull/216/head
sraturi 4 years ago committed by Michael Comella
parent 6516eff5c8
commit 8f16f9cb37

@ -33,6 +33,10 @@ events:
app started AND HomeActivity created = warm app started AND HomeActivity created = warm
app started AND HomeActivity started = hot app started AND HomeActivity started = hot
app created AND HomeActivity started = error app created AND HomeActivity started = error
Some applications such as gmail launches the default browser in the
background. So when we eventually click a link, browser is already
started in the background. This means that custom_tab will mostly
report `warm` startup type.
source: source:
description: | description: |
The method used to open Fenix. Possible values are `app_icon`, The method used to open Fenix. Possible values are `app_icon`,
@ -48,6 +52,13 @@ events:
for example, when we are doing a hot start up, we cant have a for example, when we are doing a hot start up, we cant have a
savedInstanceState therefore we report only [APP_ICON, HOT] instead savedInstanceState therefore we report only [APP_ICON, HOT] instead
of [APP_ICON, HOT, false]. of [APP_ICON, HOT, false].
launch_time_nano_seconds:
description: |
the number of nano seconds application took to launch. This is the
time difference between application launch(user pressing app_icon,
launching a link) and until the first view is about to be drawn
on the screen. If the time is not captured, this extra key will
not be reported.
bugs: bugs:
- https://github.com/mozilla-mobile/fenix/issues/11830 - https://github.com/mozilla-mobile/fenix/issues/11830
- https://github.com/mozilla-mobile/fenix/issues/12573 - https://github.com/mozilla-mobile/fenix/issues/12573

@ -10,6 +10,7 @@ import android.content.res.Configuration
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.StrictMode import android.os.StrictMode
import android.os.SystemClock
import android.text.format.DateUtils import android.text.format.DateUtils
import android.util.AttributeSet import android.util.AttributeSet
import android.view.KeyEvent import android.view.KeyEvent
@ -112,6 +113,11 @@ import java.lang.ref.WeakReference
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@SuppressWarnings("TooManyFunctions", "LargeClass") @SuppressWarnings("TooManyFunctions", "LargeClass")
open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
// DO NOT MOVE ANYTHING ABOVE THIS, GETTING INIT TIME IS CRITICAL
// we need to store startup timestamp for warm startup. we cant directly store
// inside AppStartupTelemetry since that class lives inside components and
// components requires context to access.
protected val homeActivityInitTimeStampNanoSeconds = SystemClock.elapsedRealtimeNanos()
private var webExtScope: CoroutineScope? = null private var webExtScope: CoroutineScope? = null
lateinit var themeManager: ThemeManager lateinit var themeManager: ThemeManager
@ -235,13 +241,22 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
} }
protected open fun startupTelemetryOnCreateCalled(safeIntent: SafeIntent, hasSavedInstanceState: Boolean) { protected open fun startupTelemetryOnCreateCalled(safeIntent: SafeIntent, hasSavedInstanceState: Boolean) {
components.appStartupTelemetry.onHomeActivityOnCreate(safeIntent, hasSavedInstanceState) components.appStartupTelemetry.onHomeActivityOnCreate(
safeIntent,
hasSavedInstanceState,
homeActivityInitTimeStampNanoSeconds, rootContainer
)
} }
override fun onRestart() { override fun onRestart() {
// DO NOT MOVE ANYTHING ABOVE THIS..
// we are measuring startup time for hot startup type
startupTelemetryOnRestartCalled()
super.onRestart() super.onRestart()
}
components.appStartupTelemetry.onHomeActivityOnRestart() private fun startupTelemetryOnRestartCalled() {
components.appStartupTelemetry.onHomeActivityOnRestart(rootContainer)
} }
@CallSuper @CallSuper
@ -254,8 +269,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
message = "onResume()" message = "onResume()"
) )
components.appStartupTelemetry.onHomeActivityOnResume()
components.backgroundServices.accountManagerAvailableQueue.runIfReadyOrQueue { components.backgroundServices.accountManagerAvailableQueue.runIfReadyOrQueue {
lifecycleScope.launch { lifecycleScope.launch {
// Make sure accountManager is initialized. // Make sure accountManager is initialized.
@ -304,6 +317,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
"finishing" to isFinishing.toString() "finishing" to isFinishing.toString()
) )
) )
components.appStartupTelemetry.onStop()
} }
final override fun onPause() { final override fun onPause() {
@ -740,7 +755,11 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
if (components.core.engine.profiler?.isProfilerActive() == true) { if (components.core.engine.profiler?.isProfilerActive() == true) {
// Wrapping the `addMarker` method with `isProfilerActive` even though it's no-op when // Wrapping the `addMarker` method with `isProfilerActive` even though it's no-op when
// profiler is not active. That way, `text` argument will not create a string builder all the time. // profiler is not active. That way, `text` argument will not create a string builder all the time.
components.core.engine.profiler?.addMarker("HomeActivity.load", startTime, "newTab: $newTab") components.core.engine.profiler?.addMarker(
"HomeActivity.load",
startTime,
"newTab: $newTab"
)
} }
} }

@ -0,0 +1,80 @@
/* 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.metrics
import android.os.Process
import android.os.SystemClock
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.ERROR
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.WARM
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.HOT
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.COLD
import org.mozilla.fenix.perf.Stat
/**
* Handles the logic of figuring out launch time for cold, warm and hot startup.
*/
class AppLaunchTimeMeasurement(private val stats: Stat = Stat()) {
private var isOnPreDrawCalled = false
private var applicationOnCreateTimeStampNanoSeconds: Long? = null
private var homeActivityInitTimeStampNanoSeconds: Long? = null
private var homeActivityOnRestartTimeStampNanoSeconds: Long? = null
// we are considering screen to be visible after the first pre draw call.
private var homeActivityOnPreDrawTimeStampNanoSeconds: Long? = null
fun onHomeActivityOnCreate(activityInitNanos: Long) {
this.homeActivityInitTimeStampNanoSeconds = activityInitNanos
}
fun onHomeActivityOnRestart(activityOnRestartNanos: Long = SystemClock.elapsedRealtimeNanos()) {
homeActivityOnRestartTimeStampNanoSeconds = activityOnRestartNanos
}
fun onFirstFramePreDraw(activityOnPreDrawNanos: Long = SystemClock.elapsedRealtimeNanos()) {
isOnPreDrawCalled = true
homeActivityOnPreDrawTimeStampNanoSeconds = activityOnPreDrawNanos
}
/**
* if we have both start and finish time for launch, return the difference otherwise return null.
*/
suspend fun getApplicationLaunchTime(startupType: Type): Long? = withContext(Dispatchers.IO) {
when {
// one use case is user launching the app and quicky pressing back button. in that case
// there will be no onPredraw call but activity will call onStop().
!isOnPreDrawCalled -> {
null
}
else -> {
when (startupType) {
COLD -> {
applicationOnCreateTimeStampNanoSeconds =
stats.getProcessStartTimeStampNano(Process.myPid())
homeActivityOnPreDrawTimeStampNanoSeconds!!.minus(
applicationOnCreateTimeStampNanoSeconds!!
)
}
WARM -> {
homeActivityOnPreDrawTimeStampNanoSeconds!!.minus(
homeActivityInitTimeStampNanoSeconds!!
)
}
HOT -> {
homeActivityOnPreDrawTimeStampNanoSeconds!!.minus(
homeActivityOnRestartTimeStampNanoSeconds!!
)
}
ERROR -> {
null
}
}
}
}
}
}

@ -5,11 +5,14 @@
package org.mozilla.fenix.components.metrics package org.mozilla.fenix.components.metrics
import android.content.Intent import android.content.Intent
import android.view.View
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.core.view.doOnPreDraw
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner
import kotlinx.coroutines.runBlocking
import mozilla.components.support.utils.SafeIntent import mozilla.components.support.utils.SafeIntent
import org.mozilla.fenix.components.metrics.Event.AppAllStartup import org.mozilla.fenix.components.metrics.Event.AppAllStartup
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Source import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Source
@ -22,15 +25,21 @@ import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.ERROR
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.HOT import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.HOT
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.COLD import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.COLD
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.WARM import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.WARM
import java.lang.reflect.Modifier.PRIVATE
/** /**
* Tracks application startup source, type, and whether or not activity has savedInstance to restore * Tracks application startup source, type, launch time, and whether or not activity has
* the activity from. Sample metric = [source = COLD, type = APP_ICON, hasSavedInstance = false] * savedInstance to restore the activity from.
* Sample = [source = COLD, type = APP_ICON, hasSavedInstanceState = false,launchTimeNanoSeconds = 1824000000]
* The basic idea is to collect these metrics from different phases of startup through * The basic idea is to collect these metrics from different phases of startup through
* [AppAllStartup] and finally report them on Activity's onResume() function. * [AppAllStartup] and finally report them on Activity's onResume() function.
*/ */
@Suppress("TooManyFunctions") @Suppress("TooManyFunctions")
class AppStartupTelemetry(private val metrics: MetricController) : LifecycleObserver { class AppStartupTelemetry(
private val metrics: MetricController,
@VisibleForTesting(otherwise = PRIVATE)
var appLaunchTimeMeasurement: AppLaunchTimeMeasurement = AppLaunchTimeMeasurement()
) : LifecycleObserver {
init { init {
ProcessLifecycleOwner.get().lifecycle.addObserver(this) ProcessLifecycleOwner.get().lifecycle.addObserver(this)
@ -47,18 +56,42 @@ class AppStartupTelemetry(private val metrics: MetricController) : LifecycleObse
wasAppCreateCalledBeforeActivityCreate = true wasAppCreateCalledBeforeActivityCreate = true
} }
fun onHomeActivityOnCreate(safeIntent: SafeIntent, hasSavedInstanceState: Boolean) { fun onHomeActivityOnCreate(
setOnCreateData(safeIntent, hasSavedInstanceState, false) safeIntent: SafeIntent,
hasSavedInstanceState: Boolean,
homeActivityInitTimeStampNanoSeconds: Long,
rootContainer: View
) {
setOnCreateData(safeIntent, hasSavedInstanceState, homeActivityInitTimeStampNanoSeconds, false)
rootContainer.doOnPreDraw {
onPreDraw()
}
} }
fun onExternalAppBrowserOnCreate(safeIntent: SafeIntent, hasSavedInstanceState: Boolean) { fun onExternalAppBrowserOnCreate(
setOnCreateData(safeIntent, hasSavedInstanceState, true) safeIntent: SafeIntent,
hasSavedInstanceState: Boolean,
homeActivityInitTimeStampNanoSeconds: Long,
rootContainer: View
) {
setOnCreateData(safeIntent, hasSavedInstanceState, homeActivityInitTimeStampNanoSeconds, true)
rootContainer.doOnPreDraw {
onPreDraw()
}
} }
fun onHomeActivityOnRestart() { fun onHomeActivityOnRestart(rootContainer: View) {
// DO NOT MOVE ANYTHING ABOVE THIS..
// we are measuring startup time for hot startup type
appLaunchTimeMeasurement.onHomeActivityOnRestart()
// we are not setting [Source] in this method since source is derived from an intent. // we are not setting [Source] in this method since source is derived from an intent.
// therefore source gets set in onNewIntent(). // therefore source gets set in onNewIntent().
onRestartData = Pair(HOT, null) onRestartData = Pair(HOT, null)
rootContainer.doOnPreDraw {
onPreDraw()
}
} }
fun onHomeActivityOnNewIntent(safeIntent: SafeIntent) { fun onHomeActivityOnNewIntent(safeIntent: SafeIntent) {
@ -70,6 +103,7 @@ class AppStartupTelemetry(private val metrics: MetricController) : LifecycleObse
private fun setOnCreateData( private fun setOnCreateData(
safeIntent: SafeIntent, safeIntent: SafeIntent,
hasSavedInstanceState: Boolean, hasSavedInstanceState: Boolean,
homeActivityInitTimeStampNanoSeconds: Long,
isExternalAppBrowserActivity: Boolean isExternalAppBrowserActivity: Boolean
) { ) {
onCreateData = AppAllStartup( onCreateData = AppAllStartup(
@ -77,6 +111,7 @@ class AppStartupTelemetry(private val metrics: MetricController) : LifecycleObse
getAppStartupType(), getAppStartupType(),
hasSavedInstanceState hasSavedInstanceState
) )
appLaunchTimeMeasurement.onHomeActivityOnCreate(homeActivityInitTimeStampNanoSeconds)
wasAppCreateCalledBeforeActivityCreate = false wasAppCreateCalledBeforeActivityCreate = false
} }
@ -101,23 +136,14 @@ class AppStartupTelemetry(private val metrics: MetricController) : LifecycleObse
} }
} }
/** private suspend fun recordMetric() {
* The reason we record metric on resume is because we need to wait for onNewIntent(), and
* we are not guaranteed that onNewIntent() will be called before or after onStart() / onRestart().
* However we are guaranteed onResume() will be called after onNewIntent() and onStart(). Source:
* https://developer.android.com/reference/android/app/Activity#onNewIntent(android.content.Intent)
*/
fun onHomeActivityOnResume() {
recordMetric()
}
private fun recordMetric() {
if (!isMetricRecordedSinceAppWasForegrounded) { if (!isMetricRecordedSinceAppWasForegrounded) {
val appAllStartup: AppAllStartup = if (onCreateData != null) { val appAllStartup: AppAllStartup = if (onCreateData != null) {
onCreateData!! onCreateData!!
} else { } else {
mergeOnRestartAndOnNewIntentIntoStartup() mergeOnRestartAndOnNewIntentIntoStartup()
} }
appAllStartup.launchTime = appLaunchTimeMeasurement.getApplicationLaunchTime(appAllStartup.type)
metrics.track(appAllStartup) metrics.track(appAllStartup)
isMetricRecordedSinceAppWasForegrounded = true isMetricRecordedSinceAppWasForegrounded = true
} }
@ -125,6 +151,7 @@ class AppStartupTelemetry(private val metrics: MetricController) : LifecycleObse
onCreateData = null onCreateData = null
onNewIntentData = null onNewIntentData = null
onRestartData = null onRestartData = null
appLaunchTimeMeasurement = AppLaunchTimeMeasurement()
} }
private fun mergeOnRestartAndOnNewIntentIntoStartup(): AppAllStartup { private fun mergeOnRestartAndOnNewIntentIntoStartup(): AppAllStartup {
@ -143,4 +170,24 @@ class AppStartupTelemetry(private val metrics: MetricController) : LifecycleObse
// Therefore we set the isMetricRecorded flag to false. // Therefore we set the isMetricRecorded flag to false.
isMetricRecordedSinceAppWasForegrounded = false isMetricRecordedSinceAppWasForegrounded = false
} }
/**
*record the timestamp for the first frame drawn
*/
@VisibleForTesting(otherwise = PRIVATE)
fun onPreDraw() {
// DO NOT MOVE ANYTHING ABOVE THIS..
// we are measuring startup time here.
appLaunchTimeMeasurement.onFirstFramePreDraw()
}
/**
* record the metrics, blocking the main thread to make sure we get our metrics recorded before
* the application potentially closes.
*/
fun onStop() {
runBlocking {
recordMetric()
}
}
} }

@ -337,7 +337,8 @@ sealed class Event {
data class AppAllStartup( data class AppAllStartup(
val source: Source, val source: Source,
val type: Type, val type: Type,
val hasSavedInstanceState: Boolean? = null val hasSavedInstanceState: Boolean? = null,
var launchTime: Long? = null
) : Event() { ) : Event() {
enum class Source { APP_ICON, LINK, CUSTOM_TAB, UNKNOWN } enum class Source { APP_ICON, LINK, CUSTOM_TAB, UNKNOWN }
enum class Type { COLD, WARM, HOT, ERROR } enum class Type { COLD, WARM, HOT, ERROR }
@ -354,6 +355,10 @@ sealed class Event {
extrasMap[Events.appOpenedAllStartupKeys.hasSavedInstanceState] = extrasMap[Events.appOpenedAllStartupKeys.hasSavedInstanceState] =
hasSavedInstanceState.toString() hasSavedInstanceState.toString()
} }
if (launchTime != null) {
extrasMap[Events.appOpenedAllStartupKeys.launchTimeNanoSeconds] =
launchTime.toString()
}
return extrasMap return extrasMap
} }
} }

@ -6,6 +6,7 @@ package org.mozilla.fenix.customtabs
import androidx.navigation.NavDestination import androidx.navigation.NavDestination
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
import kotlinx.android.synthetic.main.activity_home.*
import mozilla.components.browser.session.runWithSession import mozilla.components.browser.session.runWithSession
import mozilla.components.concept.engine.manifest.WebAppManifestParser import mozilla.components.concept.engine.manifest.WebAppManifestParser
import mozilla.components.feature.intent.ext.getSessionId import mozilla.components.feature.intent.ext.getSessionId
@ -36,7 +37,12 @@ open class ExternalAppBrowserActivity : HomeActivity() {
final override fun getIntentSessionId(intent: SafeIntent) = intent.getSessionId() final override fun getIntentSessionId(intent: SafeIntent) = intent.getSessionId()
override fun startupTelemetryOnCreateCalled(safeIntent: SafeIntent, hasSavedInstanceState: Boolean) { override fun startupTelemetryOnCreateCalled(safeIntent: SafeIntent, hasSavedInstanceState: Boolean) {
components.appStartupTelemetry.onExternalAppBrowserOnCreate(safeIntent, hasSavedInstanceState) components.appStartupTelemetry.onExternalAppBrowserOnCreate(
safeIntent,
hasSavedInstanceState,
homeActivityInitTimeStampNanoSeconds,
rootContainer
)
} }
override fun getNavDirections( override fun getNavDirections(

@ -64,6 +64,10 @@ open class Stat {
return getStatText(pid).split(' ')[FIELD_POS_STARTTIME].toLong() return getStatText(pid).split(' ')[FIELD_POS_STARTTIME].toLong()
} }
fun getProcessStartTimeStampNano(pid: Int): Long {
return convertTicksToNanos(getProcessStartTimeTicks(pid)).toLong()
}
fun convertTicksToNanos(ticks: Long): Double = ticks * nanosPerClockTick fun convertTicksToNanos(ticks: Long): Double = ticks * nanosPerClockTick
fun convertNanosToTicks(nanos: Long): Double = nanos / nanosPerClockTick fun convertNanosToTicks(nanos: Long): Double = nanos / nanosPerClockTick
} }

@ -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.components.metrics
import android.os.SystemClock
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.HOT
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.COLD
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.ERROR
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.WARM
import org.mozilla.fenix.perf.Stat
class AppLaunchTimeMeasurementTest {
@MockK
private lateinit var statMock: Stat
private lateinit var appLaunchTimeMeasurement: AppLaunchTimeMeasurement
private val startTime = SystemClock.elapsedRealtimeNanos()
private val endTime = SystemClock.elapsedRealtimeNanos() + 1
@Before
fun setUp() {
MockKAnnotations.init(this)
appLaunchTimeMeasurement = AppLaunchTimeMeasurement(statMock)
every { statMock.getProcessStartTimeStampNano(any()) } returns startTime
}
@Test
fun `WHEN application is launched with cold startup THEN report the correct value`() {
runBlocking {
appLaunchTimeMeasurement.onFirstFramePreDraw(endTime)
val actualResult = endTime.minus(startTime)
assertTrue(appLaunchTimeMeasurement.getApplicationLaunchTime(COLD) == actualResult)
}
}
@Test
fun `WHEN application is launch with warm startup THEN report the correct value`() {
appLaunchTimeMeasurement.onHomeActivityOnCreate(startTime)
appLaunchTimeMeasurement.onFirstFramePreDraw(endTime)
val actualResult = endTime.minus(startTime)
runBlocking {
assertTrue(appLaunchTimeMeasurement.getApplicationLaunchTime(WARM) == actualResult)
}
}
@Test
fun `WHEN application is launch with hot startup THEN report the correct value`() {
appLaunchTimeMeasurement.onHomeActivityOnRestart(startTime)
appLaunchTimeMeasurement.onFirstFramePreDraw(endTime)
val actualResult = endTime.minus(startTime)
runBlocking {
assertTrue(appLaunchTimeMeasurement.getApplicationLaunchTime(HOT) == actualResult)
}
}
@Test
fun `WHEN getting launch time before onDraw() is called THEN report the correct value`() {
appLaunchTimeMeasurement.onHomeActivityOnCreate(startTime)
val actualResult = null
runBlocking {
assertTrue(appLaunchTimeMeasurement.getApplicationLaunchTime(ERROR) == actualResult)
}
}
}

@ -5,11 +5,15 @@
package org.mozilla.fenix.components.metrics package org.mozilla.fenix.components.metrics
import android.content.Intent import android.content.Intent
import io.mockk.MockKAnnotations import android.os.SystemClock
import io.mockk.clearMocks import android.view.View
import io.mockk.every
import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.verify import io.mockk.verify
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.every
import io.mockk.clearMocks
import mozilla.components.support.utils.toSafeIntent import mozilla.components.support.utils.toSafeIntent
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -18,6 +22,7 @@ import org.mozilla.fenix.GleanMetrics.Events
import org.mozilla.fenix.GleanMetrics.Events.appOpenedAllStartupKeys.hasSavedInstanceState import org.mozilla.fenix.GleanMetrics.Events.appOpenedAllStartupKeys.hasSavedInstanceState
import org.mozilla.fenix.GleanMetrics.Events.appOpenedAllStartupKeys.source import org.mozilla.fenix.GleanMetrics.Events.appOpenedAllStartupKeys.source
import org.mozilla.fenix.GleanMetrics.Events.appOpenedAllStartupKeys.type import org.mozilla.fenix.GleanMetrics.Events.appOpenedAllStartupKeys.type
import org.mozilla.fenix.GleanMetrics.Events.appOpenedAllStartupKeys.launchTimeNanoSeconds
import org.mozilla.fenix.components.metrics.Event.AppAllStartup import org.mozilla.fenix.components.metrics.Event.AppAllStartup
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Source import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Source
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Source.APP_ICON import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Source.APP_ICON
@ -32,17 +37,26 @@ import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.ERROR
class AppStartupTelemetryTest { class AppStartupTelemetryTest {
@MockK @MockK
private lateinit var metricController: MetricController private lateinit var metricControllerMock: MetricController
@MockK @MockK
private lateinit var intent: Intent private lateinit var intentMock: Intent
@RelaxedMockK
private lateinit var appLaunchTimeMeasurementMock: AppLaunchTimeMeasurement
@RelaxedMockK
private lateinit var rootContainerMock: View
private lateinit var appStartupTelemetry: AppStartupTelemetry private lateinit var appStartupTelemetry: AppStartupTelemetry
private val homeActivityInitTime = SystemClock.elapsedRealtimeNanos()
private val onPreDrawTime = SystemClock.elapsedRealtimeNanos() + 1
@Before @Before
fun setup() { fun setup() {
MockKAnnotations.init(this) MockKAnnotations.init(this)
appStartupTelemetry = AppStartupTelemetry(metricController) appStartupTelemetry = AppStartupTelemetry(metricControllerMock, appLaunchTimeMeasurementMock)
every { metricController.track(any()) } returns Unit
coEvery { appLaunchTimeMeasurementMock.getApplicationLaunchTime(any()) } returns onPreDrawTime.minus(homeActivityInitTime)
every { metricControllerMock.track(any()) } returns Unit
} }
@Test @Test
@ -50,11 +64,12 @@ class AppStartupTelemetryTest {
setupIntentMock(APP_ICON) setupIntentMock(APP_ICON)
appStartupTelemetry.onFenixApplicationOnCreate() appStartupTelemetry.onFenixApplicationOnCreate()
appStartupTelemetry.onHomeActivityOnCreate(intent.toSafeIntent(), false) appStartupTelemetry.onHomeActivityOnCreate(intentMock.toSafeIntent(), false, homeActivityInitTime, rootContainerMock)
appStartupTelemetry.onHomeActivityOnResume() appStartupTelemetry.onPreDraw()
appStartupTelemetry.onStop()
val validMetric = AppAllStartup(APP_ICON, COLD, false) val validMetric = AppAllStartup(APP_ICON, COLD, false, onPreDrawTime.minus(homeActivityInitTime))
verify(exactly = 1) { metricController.track(validMetric) } verify(exactly = 1) { metricControllerMock.track(validMetric) }
} }
@Test @Test
@ -62,11 +77,12 @@ class AppStartupTelemetryTest {
setupIntentMock(LINK) setupIntentMock(LINK)
appStartupTelemetry.onFenixApplicationOnCreate() appStartupTelemetry.onFenixApplicationOnCreate()
appStartupTelemetry.onHomeActivityOnCreate(intent.toSafeIntent(), false) appStartupTelemetry.onHomeActivityOnCreate(intentMock.toSafeIntent(), false, homeActivityInitTime, rootContainerMock)
appStartupTelemetry.onHomeActivityOnResume() appStartupTelemetry.onPreDraw()
appStartupTelemetry.onStop()
val validMetric = AppAllStartup(LINK, COLD, false) val validMetric = AppAllStartup(LINK, COLD, false, onPreDrawTime.minus(homeActivityInitTime))
verify(exactly = 1) { metricController.track(validMetric) } verify(exactly = 1) { metricControllerMock.track(validMetric) }
} }
@Test @Test
@ -74,11 +90,12 @@ class AppStartupTelemetryTest {
setupIntentMock(CUSTOM_TAB) setupIntentMock(CUSTOM_TAB)
appStartupTelemetry.onFenixApplicationOnCreate() appStartupTelemetry.onFenixApplicationOnCreate()
appStartupTelemetry.onExternalAppBrowserOnCreate(intent.toSafeIntent(), false) appStartupTelemetry.onExternalAppBrowserOnCreate(intentMock.toSafeIntent(), false, homeActivityInitTime, rootContainerMock)
appStartupTelemetry.onHomeActivityOnResume() appStartupTelemetry.onPreDraw()
appStartupTelemetry.onStop()
val validMetric = AppAllStartup(CUSTOM_TAB, COLD, false) val validMetric = AppAllStartup(CUSTOM_TAB, COLD, false, onPreDrawTime.minus(homeActivityInitTime))
verify(exactly = 1) { metricController.track(validMetric) } verify(exactly = 1) { metricControllerMock.track(validMetric) }
} }
@Test @Test
@ -86,11 +103,12 @@ class AppStartupTelemetryTest {
setupIntentMock(APP_ICON) setupIntentMock(APP_ICON)
launchApplicationAndPutApplicationInBackground() launchApplicationAndPutApplicationInBackground()
appStartupTelemetry.onHomeActivityOnCreate(intent.toSafeIntent(), false) appStartupTelemetry.onHomeActivityOnCreate(intentMock.toSafeIntent(), false, homeActivityInitTime, rootContainerMock)
appStartupTelemetry.onHomeActivityOnResume() appStartupTelemetry.onPreDraw()
appStartupTelemetry.onStop()
val validMetric = AppAllStartup(APP_ICON, WARM, false) val validMetric = AppAllStartup(APP_ICON, WARM, false, onPreDrawTime.minus(homeActivityInitTime))
verify(exactly = 1) { metricController.track(validMetric) } verify(exactly = 1) { metricControllerMock.track(validMetric) }
} }
@Test @Test
@ -98,11 +116,12 @@ class AppStartupTelemetryTest {
setupIntentMock(LINK) setupIntentMock(LINK)
launchApplicationAndPutApplicationInBackground() launchApplicationAndPutApplicationInBackground()
appStartupTelemetry.onHomeActivityOnCreate(intent.toSafeIntent(), false) appStartupTelemetry.onHomeActivityOnCreate(intentMock.toSafeIntent(), false, homeActivityInitTime, rootContainerMock)
appStartupTelemetry.onHomeActivityOnResume() appStartupTelemetry.onPreDraw()
appStartupTelemetry.onStop()
val validMetric = AppAllStartup(LINK, WARM, false) val validMetric = AppAllStartup(LINK, WARM, false, onPreDrawTime.minus(homeActivityInitTime))
verify(exactly = 1) { metricController.track(validMetric) } verify(exactly = 1) { metricControllerMock.track(validMetric) }
} }
@Test @Test
@ -110,11 +129,12 @@ class AppStartupTelemetryTest {
setupIntentMock(CUSTOM_TAB) setupIntentMock(CUSTOM_TAB)
launchApplicationAndPutApplicationInBackground() launchApplicationAndPutApplicationInBackground()
appStartupTelemetry.onExternalAppBrowserOnCreate(intent.toSafeIntent(), false) appStartupTelemetry.onExternalAppBrowserOnCreate(intentMock.toSafeIntent(), false, homeActivityInitTime, rootContainerMock)
appStartupTelemetry.onHomeActivityOnResume() appStartupTelemetry.onPreDraw()
appStartupTelemetry.onStop()
val validMetric = AppAllStartup(CUSTOM_TAB, WARM, false) val validMetric = AppAllStartup(CUSTOM_TAB, WARM, false, onPreDrawTime.minus(homeActivityInitTime))
verify(exactly = 1) { metricController.track(validMetric) } verify(exactly = 1) { metricControllerMock.track(validMetric) }
} }
@Test @Test
@ -122,12 +142,13 @@ class AppStartupTelemetryTest {
setupIntentMock(APP_ICON) setupIntentMock(APP_ICON)
launchApplicationAndPutApplicationInBackground() launchApplicationAndPutApplicationInBackground()
appStartupTelemetry.onHomeActivityOnRestart() appStartupTelemetry.onHomeActivityOnRestart(rootContainerMock)
appStartupTelemetry.onHomeActivityOnNewIntent(intent.toSafeIntent()) appStartupTelemetry.onHomeActivityOnNewIntent(intentMock.toSafeIntent())
appStartupTelemetry.onHomeActivityOnResume() appStartupTelemetry.onPreDraw()
appStartupTelemetry.onStop()
val validMetric = AppAllStartup(APP_ICON, HOT) val validMetric = AppAllStartup(APP_ICON, HOT, launchTime = onPreDrawTime.minus(homeActivityInitTime))
verify(exactly = 1) { metricController.track(validMetric) } verify(exactly = 1) { metricControllerMock.track(validMetric) }
} }
@Test @Test
@ -135,23 +156,25 @@ class AppStartupTelemetryTest {
setupIntentMock(LINK) setupIntentMock(LINK)
launchApplicationAndPutApplicationInBackground() launchApplicationAndPutApplicationInBackground()
appStartupTelemetry.onHomeActivityOnRestart() appStartupTelemetry.onHomeActivityOnRestart(rootContainerMock)
appStartupTelemetry.onHomeActivityOnNewIntent(intent.toSafeIntent()) appStartupTelemetry.onHomeActivityOnNewIntent(intentMock.toSafeIntent())
appStartupTelemetry.onHomeActivityOnResume() appStartupTelemetry.onPreDraw()
appStartupTelemetry.onStop()
val validMetric = AppAllStartup(LINK, HOT) val validMetric = AppAllStartup(LINK, HOT, launchTime = onPreDrawTime.minus(homeActivityInitTime))
verify(exactly = 1) { metricController.track(validMetric) } verify(exactly = 1) { metricControllerMock.track(validMetric) }
} }
@Test @Test
fun `WHEN application is launched and onResume() is called twice THEN metric is reported only once`() { fun `WHEN application is launched and onStop() is called twice THEN metric is reported only once`() {
setupIntentMock(LINK) setupIntentMock(LINK)
appStartupTelemetry.onExternalAppBrowserOnCreate(intent.toSafeIntent(), false) appStartupTelemetry.onExternalAppBrowserOnCreate(intentMock.toSafeIntent(), false, homeActivityInitTime, rootContainerMock)
appStartupTelemetry.onHomeActivityOnResume() appStartupTelemetry.onPreDraw()
appStartupTelemetry.onStop()
appStartupTelemetry.onHomeActivityOnResume() appStartupTelemetry.onStop()
verify(exactly = 1) { metricController.track(any()) } verify(exactly = 1) { metricControllerMock.track(any()) }
} }
@Test @Test
@ -159,11 +182,12 @@ class AppStartupTelemetryTest {
setupIntentMock(UNKNOWN) setupIntentMock(UNKNOWN)
launchApplicationAndPutApplicationInBackground() launchApplicationAndPutApplicationInBackground()
appStartupTelemetry.onHomeActivityOnCreate(intent.toSafeIntent(), false) appStartupTelemetry.onHomeActivityOnCreate(intentMock.toSafeIntent(), false, homeActivityInitTime, rootContainerMock)
appStartupTelemetry.onHomeActivityOnResume() appStartupTelemetry.onPreDraw()
appStartupTelemetry.onStop()
val validMetric = AppAllStartup(UNKNOWN, WARM, false) val validMetric = AppAllStartup(UNKNOWN, WARM, false, onPreDrawTime.minus(homeActivityInitTime))
verify(exactly = 1) { metricController.track(validMetric) } verify(exactly = 1) { metricControllerMock.track(validMetric) }
} }
@Test @Test
@ -171,20 +195,23 @@ class AppStartupTelemetryTest {
setupIntentMock(APP_ICON) setupIntentMock(APP_ICON)
launchApplicationAndPutApplicationInBackground() launchApplicationAndPutApplicationInBackground()
appStartupTelemetry.onHomeActivityOnCreate(intent.toSafeIntent(), true) appStartupTelemetry.onHomeActivityOnCreate(intentMock.toSafeIntent(), true, homeActivityInitTime, rootContainerMock)
appStartupTelemetry.onHomeActivityOnResume() appStartupTelemetry.onPreDraw()
appStartupTelemetry.onStop()
val validMetric = AppAllStartup(APP_ICON, WARM, true) val validMetric = AppAllStartup(APP_ICON, WARM, true, onPreDrawTime.minus(homeActivityInitTime))
verify(exactly = 1) { metricController.track(validMetric) } verify(exactly = 1) { metricControllerMock.track(validMetric) }
} }
private fun launchApplicationAndPutApplicationInBackground() { private fun launchApplicationAndPutApplicationInBackground() {
appStartupTelemetry.onFenixApplicationOnCreate() appStartupTelemetry.onFenixApplicationOnCreate()
appStartupTelemetry.onHomeActivityOnCreate(intent.toSafeIntent(), false) appStartupTelemetry.onHomeActivityOnCreate(intentMock.toSafeIntent(), false, homeActivityInitTime, rootContainerMock)
appStartupTelemetry.onHomeActivityOnResume() appStartupTelemetry.onPreDraw()
appStartupTelemetry.onStop()
appStartupTelemetry.appLaunchTimeMeasurement = appLaunchTimeMeasurementMock
// have to clear the mock function calls so it doesnt interfere with tests // have to clear the mock function calls so it doesnt interfere with tests
clearMocks(metricController, answers = false) clearMocks(metricControllerMock, answers = false)
appStartupTelemetry.onApplicationOnStop() appStartupTelemetry.onApplicationOnStop()
} }
@ -194,10 +221,11 @@ class AppStartupTelemetryTest {
setupIntentMock(UNKNOWN) setupIntentMock(UNKNOWN)
launchApplicationAndPutApplicationInBackground() launchApplicationAndPutApplicationInBackground()
appStartupTelemetry.onHomeActivityOnResume() appStartupTelemetry.onPreDraw()
appStartupTelemetry.onStop()
val validMetric = AppAllStartup(UNKNOWN, ERROR) val validMetric = AppAllStartup(UNKNOWN, ERROR, launchTime = onPreDrawTime.minus(homeActivityInitTime))
verify(exactly = 1) { metricController.track(validMetric) } verify(exactly = 1) { metricControllerMock.track(validMetric) }
} }
@Test @Test
@ -205,11 +233,25 @@ class AppStartupTelemetryTest {
setupIntentMock(APP_ICON) setupIntentMock(APP_ICON)
launchApplicationAndPutApplicationInBackground() launchApplicationAndPutApplicationInBackground()
appStartupTelemetry.onHomeActivityOnRestart() appStartupTelemetry.onHomeActivityOnRestart(rootContainerMock)
appStartupTelemetry.onHomeActivityOnResume() appStartupTelemetry.onPreDraw()
appStartupTelemetry.onStop()
val validMetric = AppAllStartup(UNKNOWN, HOT) val validMetric = AppAllStartup(UNKNOWN, HOT, launchTime = onPreDrawTime.minus(homeActivityInitTime))
verify(exactly = 1) { metricController.track(validMetric) } verify(exactly = 1) { metricControllerMock.track(validMetric) }
}
@Test
fun `WHEN application is launched and onStop is called before onPreDraw THEN records the correct values`() {
setupIntentMock(APP_ICON)
coEvery { appLaunchTimeMeasurementMock.getApplicationLaunchTime(any()) } returns null
appStartupTelemetry.onFenixApplicationOnCreate()
appStartupTelemetry.onHomeActivityOnCreate(intentMock.toSafeIntent(), false, homeActivityInitTime, rootContainerMock)
appStartupTelemetry.onStop()
val validMetric = AppAllStartup(APP_ICON, COLD, false)
verify(exactly = 1) { metricControllerMock.track(validMetric) }
} }
@Test @Test
@ -217,20 +259,22 @@ class AppStartupTelemetryTest {
setupIntentMock(APP_ICON) setupIntentMock(APP_ICON)
launchApplicationAndPutApplicationInBackground() launchApplicationAndPutApplicationInBackground()
appStartupTelemetry.onHomeActivityOnNewIntent(intent.toSafeIntent()) appStartupTelemetry.onHomeActivityOnNewIntent(intentMock.toSafeIntent())
appStartupTelemetry.onHomeActivityOnResume() appStartupTelemetry.onPreDraw()
appStartupTelemetry.onStop()
val validMetric = AppAllStartup(APP_ICON, ERROR) val validMetric = AppAllStartup(APP_ICON, ERROR, launchTime = onPreDrawTime.minus(homeActivityInitTime))
verify(exactly = 1) { metricController.track(validMetric) } verify(exactly = 1) { metricControllerMock.track(validMetric) }
} }
@Test @Test
fun `WHEN AppAllStartup does not have savedInstanceState THEN do not return savedInstanceState`() { fun `WHEN AppAllStartup does not have savedInstanceState THEN do not return savedInstanceState`() {
val expectedExtra: Map<Events.appOpenedAllStartupKeys, String>? = hashMapOf( val expectedExtra: Map<Events.appOpenedAllStartupKeys, String>? = hashMapOf(
source to APP_ICON.toString(), source to APP_ICON.toString(),
type to HOT.toString()) type to HOT.toString(),
launchTimeNanoSeconds to onPreDrawTime.minus(homeActivityInitTime).toString())
val appAllStartup = AppAllStartup(APP_ICON, HOT) val appAllStartup = AppAllStartup(APP_ICON, HOT, launchTime = onPreDrawTime.minus(homeActivityInitTime))
assertTrue(appAllStartup.extras!! == expectedExtra) assertTrue(appAllStartup.extras!! == expectedExtra)
} }
@ -240,9 +284,10 @@ class AppStartupTelemetryTest {
val expectedExtra: Map<Events.appOpenedAllStartupKeys, String>? = hashMapOf( val expectedExtra: Map<Events.appOpenedAllStartupKeys, String>? = hashMapOf(
source to APP_ICON.toString(), source to APP_ICON.toString(),
type to COLD.toString(), type to COLD.toString(),
hasSavedInstanceState to true.toString()) hasSavedInstanceState to true.toString(),
launchTimeNanoSeconds to onPreDrawTime.minus(homeActivityInitTime).toString())
val appAllStartup = AppAllStartup(APP_ICON, COLD, true) val appAllStartup = AppAllStartup(APP_ICON, COLD, true, onPreDrawTime.minus(homeActivityInitTime))
assertTrue(appAllStartup.extras!! == expectedExtra) assertTrue(appAllStartup.extras!! == expectedExtra)
} }
@ -250,16 +295,16 @@ class AppStartupTelemetryTest {
private fun setupIntentMock(source: Source) { private fun setupIntentMock(source: Source) {
when (source) { when (source) {
APP_ICON -> { APP_ICON -> {
every { intent.action } returns Intent.ACTION_MAIN every { intentMock.action } returns Intent.ACTION_MAIN
every { intent.categories } returns setOf(Intent.CATEGORY_LAUNCHER) every { intentMock.categories } returns setOf(Intent.CATEGORY_LAUNCHER)
} }
LINK, CUSTOM_TAB -> { LINK, CUSTOM_TAB -> {
every { intent.action } returns Intent.ACTION_VIEW every { intentMock.action } returns Intent.ACTION_VIEW
every { intent.categories } returns emptySet() every { intentMock.categories } returns emptySet()
} }
UNKNOWN -> { UNKNOWN -> {
every { intent.action } returns Intent.ACTION_MAIN every { intentMock.action } returns Intent.ACTION_MAIN
every { intent.categories } returns emptySet() every { intentMock.categories } returns emptySet()
} }
} }
} }

@ -99,7 +99,7 @@ The following metrics are added to the ping:
| download_notification.try_again |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user tapped on try again when a download fails in the download notification |[1](https://github.com/mozilla-mobile/fenix/pull/6554), [2](https://github.com/mozilla-mobile/fenix/pull/13958#issuecomment-676857877)||2021-04-01 |2 | | download_notification.try_again |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user tapped on try again when a download fails in the download notification |[1](https://github.com/mozilla-mobile/fenix/pull/6554), [2](https://github.com/mozilla-mobile/fenix/pull/13958#issuecomment-676857877)||2021-04-01 |2 |
| error_page.visited_error |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user encountered an error page |[1](https://github.com/mozilla-mobile/fenix/pull/2491#issuecomment-492414486), [2](https://github.com/mozilla-mobile/fenix/pull/13958#issuecomment-676857877)|<ul><li>error_type: The error type of the error page encountered</li></ul>|2021-04-01 |2 | | error_page.visited_error |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user encountered an error page |[1](https://github.com/mozilla-mobile/fenix/pull/2491#issuecomment-492414486), [2](https://github.com/mozilla-mobile/fenix/pull/13958#issuecomment-676857877)|<ul><li>error_type: The error type of the error page encountered</li></ul>|2021-04-01 |2 |
| events.app_opened |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user opened the app (from cold start, to the homescreen or browser) |[1](https://github.com/mozilla-mobile/fenix/pull/1067#issuecomment-474598673), [2](https://github.com/mozilla-mobile/fenix/pull/13958#issuecomment-676857877)|<ul><li>source: The method used to open Fenix. Possible values are: `app_icon`, `custom_tab` or `link` </li></ul>|2021-04-01 |2 | | events.app_opened |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user opened the app (from cold start, to the homescreen or browser) |[1](https://github.com/mozilla-mobile/fenix/pull/1067#issuecomment-474598673), [2](https://github.com/mozilla-mobile/fenix/pull/13958#issuecomment-676857877)|<ul><li>source: The method used to open Fenix. Possible values are: `app_icon`, `custom_tab` or `link` </li></ul>|2021-04-01 |2 |
| events.app_opened_all_startup |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user opened the app to the HomeActivity. The HomeActivity encompasses the home screen, browser screen, settings screen, collections and other screens in the nav_graph. This differs from the app_opened probe because it measures all startups, not just cold startup. Note: There is a short gap between the time application goes into background and the time android reports the application going into the background. Note: This metric does not record souce when app opened from task switcher: open application -> press home button -> open recent tasks -> choose fenix. In this case will report [source = unknown, type = hot, has_saved_instance_state = false]. |[1](https://github.com/mozilla-mobile/fenix/pull/12114#pullrequestreview-445245341), [2](https://github.com/mozilla-mobile/fenix/pull/13958#issuecomment-676857877), [3](https://github.com/mozilla-mobile/fenix/pull/13494#pullrequestreview-474050499)|<ul><li>has_saved_instance_state: boolean value whether or not startup type has a savedInstance. using savedInstance, HomeActivity's previous state can be restored. This is an optional key since it is not applicable to all the cases. for example, when we are doing a hot start up, we cant have a savedInstanceState therefore we report only [APP_ICON, HOT] instead of [APP_ICON, HOT, false]. </li><li>source: The method used to open Fenix. Possible values are `app_icon`, `custom_tab`, `link` or `unknown`. unknown is for startup sources where we can't pinpoint the cause. One UNKNOWN case is the app switcher where we don't know what variables to check to ensure this startup wasn't caused by something else. </li><li>type: the startup type for opening fenix. the application and HomeActivity either needs to be created or started again. possible values are `cold`, `warm`, `hot` or `error`. Error is for impossible cases. Please file a bug if you see the error case. app created AND HomeActivity created = cold app started AND HomeActivity created = warm app started AND HomeActivity started = hot app created AND HomeActivity started = error </li></ul>|2021-06-01 |2 | | events.app_opened_all_startup |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user opened the app to the HomeActivity. The HomeActivity encompasses the home screen, browser screen, settings screen, collections and other screens in the nav_graph. This differs from the app_opened probe because it measures all startups, not just cold startup. Note: There is a short gap between the time application goes into background and the time android reports the application going into the background. Note: This metric does not record souce when app opened from task switcher: open application -> press home button -> open recent tasks -> choose fenix. In this case will report [source = unknown, type = hot, has_saved_instance_state = false]. |[1](https://github.com/mozilla-mobile/fenix/pull/12114#pullrequestreview-445245341), [2](https://github.com/mozilla-mobile/fenix/pull/13958#issuecomment-676857877), [3](https://github.com/mozilla-mobile/fenix/pull/13494#pullrequestreview-474050499)|<ul><li>has_saved_instance_state: boolean value whether or not startup type has a savedInstance. using savedInstance, HomeActivity's previous state can be restored. This is an optional key since it is not applicable to all the cases. for example, when we are doing a hot start up, we cant have a savedInstanceState therefore we report only [APP_ICON, HOT] instead of [APP_ICON, HOT, false]. </li><li>launch_time_nano_seconds: the number of nano seconds application took to launch. This is the time difference between application launch(user pressing app_icon, launching a link) and until the first view is about to be drawn on the screen. If the time is not captured, this extra key will not be reported. </li><li>source: The method used to open Fenix. Possible values are `app_icon`, `custom_tab`, `link` or `unknown`. unknown is for startup sources where we can't pinpoint the cause. One UNKNOWN case is the app switcher where we don't know what variables to check to ensure this startup wasn't caused by something else. </li><li>type: the startup type for opening fenix. the application and HomeActivity either needs to be created or started again. possible values are `cold`, `warm`, `hot` or `error`. Error is for impossible cases. Please file a bug if you see the error case. app created AND HomeActivity created = cold app started AND HomeActivity created = warm app started AND HomeActivity started = hot app created AND HomeActivity started = error Some applications such as gmail launches the default browser in the background. So when we eventually click a link, browser is already started in the background. This means that custom_tab will mostly report `warm` startup type. </li></ul>|2021-06-01 |2 |
| events.app_received_intent |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |The system received an Intent for the HomeActivity. An intent is received an external entity wants to the app to display content. Intents can be received when the app is closed at which point the app will be opened or when the app is already opened at which point the already open app will make changes such as loading a url. This can be used loosely as a heuristic for when the user requested to open the app. The HomeActivity encompasses the home screen and browser screen but may include other screens. This differs from the app_opened probe because it measures all startups, not just cold startup. |[1](https://github.com/mozilla-mobile/fenix/pull/11940/), [2](https://github.com/mozilla-mobile/fenix/pull/13958#issuecomment-676857877)|<ul><li>source: The method used to open Fenix. Possible values are `app_icon`, `custom_tab`, `link` or `unknown` </li></ul>|2021-06-01 | | | events.app_received_intent |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |The system received an Intent for the HomeActivity. An intent is received an external entity wants to the app to display content. Intents can be received when the app is closed at which point the app will be opened or when the app is already opened at which point the already open app will make changes such as loading a url. This can be used loosely as a heuristic for when the user requested to open the app. The HomeActivity encompasses the home screen and browser screen but may include other screens. This differs from the app_opened probe because it measures all startups, not just cold startup. |[1](https://github.com/mozilla-mobile/fenix/pull/11940/), [2](https://github.com/mozilla-mobile/fenix/pull/13958#issuecomment-676857877)|<ul><li>source: The method used to open Fenix. Possible values are `app_icon`, `custom_tab`, `link` or `unknown` </li></ul>|2021-06-01 | |
| events.browser_menu_action |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A browser menu item was tapped |[1](https://github.com/mozilla-mobile/fenix/pull/1214#issue-264756708), [2](https://github.com/mozilla-mobile/fenix/pull/5098#issuecomment-529658996), [3](https://github.com/mozilla-mobile/fenix/pull/6310), [4](https://github.com/mozilla-mobile/fenix/pull/13958#issuecomment-676857877)|<ul><li>item: A string containing the name of the item the user tapped. These items include: Settings, Help, Desktop Site toggle on/off, Find in Page, New Tab, Private Tab, Share, Report Site Issue, Back/Forward button, Reload Button, Quit, Reader Mode On, Reader Mode Off, Open In app, Add To Top Sites, Add-ons Manager, Bookmarks, History </li></ul>|2021-04-01 |2 | | events.browser_menu_action |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A browser menu item was tapped |[1](https://github.com/mozilla-mobile/fenix/pull/1214#issue-264756708), [2](https://github.com/mozilla-mobile/fenix/pull/5098#issuecomment-529658996), [3](https://github.com/mozilla-mobile/fenix/pull/6310), [4](https://github.com/mozilla-mobile/fenix/pull/13958#issuecomment-676857877)|<ul><li>item: A string containing the name of the item the user tapped. These items include: Settings, Help, Desktop Site toggle on/off, Find in Page, New Tab, Private Tab, Share, Report Site Issue, Back/Forward button, Reload Button, Quit, Reader Mode On, Reader Mode Off, Open In app, Add To Top Sites, Add-ons Manager, Bookmarks, History </li></ul>|2021-04-01 |2 |
| events.entered_url |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user entered a url |[1](https://github.com/mozilla-mobile/fenix/pull/1067#issuecomment-474598673), [2](https://github.com/mozilla-mobile/fenix/pull/13958#issuecomment-676857877)|<ul><li>autocomplete: A boolean that tells us whether the URL was autofilled by an Autocomplete suggestion </li></ul>|2021-04-01 |2 | | events.entered_url |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user entered a url |[1](https://github.com/mozilla-mobile/fenix/pull/1067#issuecomment-474598673), [2](https://github.com/mozilla-mobile/fenix/pull/13958#issuecomment-676857877)|<ul><li>autocomplete: A boolean that tells us whether the URL was autofilled by an Autocomplete suggestion </li></ul>|2021-04-01 |2 |

Loading…
Cancel
Save