Skip to content
This repository has been archived by the owner on Feb 20, 2023. It is now read-only.

Commit

Permalink
for #12573, added startup type and hasSavedInstance keys to app_start…
Browse files Browse the repository at this point in the history
…up_type telemetry
  • Loading branch information
sraturi committed Aug 20, 2020
1 parent e4e16bc commit fa8836e
Show file tree
Hide file tree
Showing 11 changed files with 413 additions and 175 deletions.
29 changes: 20 additions & 9 deletions app/metrics.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,29 @@ events:
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 cover the following cases:
Case # 1 -> a). open a link(for example, gmail) with in-app
Browser (metric report custom_tab startup) b). press home button
c). open gmail again (which brings us back to in app browser).
Step c will not report startup metric. Case # 2 -> a). open fenix
b). press home button c). launch fenix through app switcher/recent
apps. step c will not report startup type.
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].
extra_keys:
source:
description: |
The method used to open Fenix. Possible values are `app_icon`,
`custom_tab`, `link` or `unknown`
`custom_tab`, `link` or `error`
startup_type:
description: |
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 `unknown`. `unknown` is for cases
not anticipated.
app created AND HomeActivity created = cold
app started AND HomeActivity created = warm
app started AND HomeActivity started = hot
app created AND HomeActivity started = error
has_saved_instance_state:
description: |
boolean value whether or not startup type has a savedInstance.
using savedInstance, HomeActivity's previous state can be restored.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/11830
data_reviews:
Expand All @@ -39,7 +50,7 @@ events:
notification_emails:
- esmyth@mozilla.com
- perf-android-fe@mozilla.com
expires: "2020-12-01"
expires: "2021-02-01"
app_received_intent:
type: event
description: |
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/org/mozilla/fenix/FenixApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
// }

initVisualCompletenessQueueAndQueueTasks()

components.appStartupTelemetry.onFenixApplicationOnCreate()
}

private fun initVisualCompletenessQueueAndQueueTasks() {
Expand Down
16 changes: 12 additions & 4 deletions app/src/main/java/org/mozilla/fenix/HomeActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -227,19 +227,27 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {

captureSnapshotTelemetryMetrics()

setAppAllStartTelemetry(intent.toSafeIntent())
startupTelemetryOnCreateCalled(intent.toSafeIntent(), savedInstanceState != null)

StartupTimeline.onActivityCreateEndHome(this) // DO NOT MOVE ANYTHING BELOW HERE.
}

protected open fun setAppAllStartTelemetry(safeIntent: SafeIntent) {
components.appAllSourceStartTelemetry.receivedIntentInHomeActivity(safeIntent)
protected open fun startupTelemetryOnCreateCalled(safeIntent: SafeIntent, hasSavedInstanceState: Boolean) {
components.appStartupTelemetry.onHomeActivityOnCreate(safeIntent, hasSavedInstanceState)
}

override fun onRestart() {
super.onRestart()

components.appStartupTelemetry.onHomeActivityOnRestart()
}

@CallSuper
override fun onResume() {
super.onResume()

components.appStartupTelemetry.onHomeActivityOnResume()

components.backgroundServices.accountManagerAvailableQueue.runIfReadyOrQueue {
lifecycleScope.launch {
// Make sure accountManager is initialized.
Expand Down Expand Up @@ -318,7 +326,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
.let(::getIntentAllSource)
?.also { components.analytics.metrics.track(Event.AppReceivedIntent(it)) }

setAppAllStartTelemetry(intent.toSafeIntent())
components.appStartupTelemetry.onHomeActivityOnNewIntent(intent.toSafeIntent())
}

/**
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/org/mozilla/fenix/components/Components.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import mozilla.components.lib.publicsuffixlist.PublicSuffixList
import mozilla.components.support.migration.state.MigrationStore
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.components.metrics.AppAllSourceStartTelemetry
import org.mozilla.fenix.components.metrics.AppStartupTelemetry
import org.mozilla.fenix.utils.ClipboardHandler
import org.mozilla.fenix.utils.Mockable
import org.mozilla.fenix.utils.Settings
Expand Down Expand Up @@ -82,7 +82,7 @@ class Components(private val context: Context) {
}
}

val appAllSourceStartTelemetry by lazy { AppAllSourceStartTelemetry(analytics.metrics) }
val appStartupTelemetry by lazy { AppStartupTelemetry(analytics.metrics) }

@Suppress("MagicNumber")
val addonUpdater by lazy {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/* 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.content.Intent
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ProcessLifecycleOwner
import mozilla.components.support.utils.SafeIntent
import org.mozilla.fenix.components.metrics.Event.AppStartupSourceAndType
import org.mozilla.fenix.components.metrics.Event.AppStartupSourceAndType.Source
import org.mozilla.fenix.components.metrics.Event.AppStartupSourceAndType.Source.APP_ICON
import org.mozilla.fenix.components.metrics.Event.AppStartupSourceAndType.Source.ERROR
import org.mozilla.fenix.components.metrics.Event.AppStartupSourceAndType.Source.CUSTOM_TAB
import org.mozilla.fenix.components.metrics.Event.AppStartupSourceAndType.Source.LINK

import org.mozilla.fenix.components.metrics.Event.AppStartupSourceAndType.Type
import org.mozilla.fenix.components.metrics.Event.AppStartupSourceAndType.Type.COLD
import org.mozilla.fenix.components.metrics.Event.AppStartupSourceAndType.Type.WARM

/**
* Tracks application startup source, type, and whether or not activity has savedInstance to restore
* the activity from. Sample metric = [source = COLD, type = APP_ICON, hasSavedInstance = false].
* The basic idea is to collect these metrics from different phases of startup through
* [AppStartupSourceAndType]and finally report them on Activity's onResume() function.
* If application wasn't backgrounded before onResume() is called, we don't report the metric.
*/
class AppStartupTelemetry(private val metrics: MetricController) : LifecycleObserver {

init {
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
}

private var metricDataContainer = AppStartupMetricDataContainer()

private var isMetricRecordedSinceAppWasForegrounded = false
private var wasAppCreateCalledBeforeActivityCreate = false

fun onFenixApplicationOnCreate() {
wasAppCreateCalledBeforeActivityCreate = true
}

fun onHomeActivityOnCreate(safeIntent: SafeIntent, hasSavedInstanceState: Boolean) {
metricDataContainer.hasSavedInstance = hasSavedInstanceState
metricDataContainer.type = getAppStartupType()
metricDataContainer.source = getStartupSourceFromIntent(safeIntent, false)

wasAppCreateCalledBeforeActivityCreate = false
}

fun onExternalAppBrowserOnCreate(safeIntent: SafeIntent, hasSavedInstanceState: Boolean) {
metricDataContainer.hasSavedInstance = hasSavedInstanceState
metricDataContainer.type = getAppStartupType()
metricDataContainer.source = getStartupSourceFromIntent(safeIntent, true)

wasAppCreateCalledBeforeActivityCreate = false
}

/**
* we are not setting [Source] in this method since source is derived from a [SafeIntent].
* therefore source either gets set in oncreate() or onNewIntent().
*/
fun onHomeActivityOnRestart() {
metricDataContainer.type = Type.HOT
metricDataContainer.hasSavedInstance = false
}

/**
* we are only setting [Source] in this method since source is derived from a [SafeIntent].
* other metric fields are set in onCreate() and onRestart()
*/
fun onHomeActivityOnNewIntent(safeIntent: SafeIntent) {
metricDataContainer.source = getStartupSourceFromIntent(safeIntent, false)
}

private fun getAppStartupType(): Type {
return if (wasAppCreateCalledBeforeActivityCreate) COLD else WARM
}

private fun getStartupSourceFromIntent(
intent: SafeIntent,
isExternalAppBrowserActivity: Boolean
): Source {
return when {
isExternalAppBrowserActivity -> CUSTOM_TAB
intent.isLauncherIntent -> APP_ICON
intent.action == Intent.ACTION_VIEW -> LINK
else -> ERROR
}
}

/**
* 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) {
metrics.track(metricDataContainer.getAppStartupSourceAndType())
isMetricRecordedSinceAppWasForegrounded = true
}
// we don't want any weird previous states to persist on our next metric record.
metricDataContainer = AppStartupMetricDataContainer()
}

@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun onApplicationOnStop() {
// application was backgrounded, we need to record the new metric type if
// application was to come to foreground again.
// Therefore we set the isMetricRecorded flag to false.
isMetricRecordedSinceAppWasForegrounded = false
}

private data class AppStartupMetricDataContainer(
var source: Source? = null,
var type: Type? = null,
var hasSavedInstance: Boolean? = null
) {

fun getAppStartupSourceAndType(): AppStartupSourceAndType {
setNullFieldsToError()
return AppStartupSourceAndType(source!!, type!!, hasSavedInstance!!)
}

private fun setNullFieldsToError() {
if (source == null) {
source = ERROR
}
if (type == null) {
type = Type.ERROR
}
if (hasSavedInstance == null) {
hasSavedInstance = false
}
}
}
}
15 changes: 12 additions & 3 deletions app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt
Original file line number Diff line number Diff line change
Expand Up @@ -319,11 +319,20 @@ sealed class Event {
get() = hashMapOf(Events.appOpenedAllStartupKeys.source to source.name)
}

data class AppOpenedAllSourceStartup(val source: Source) : Event() {
enum class Source { APP_ICON, LINK, CUSTOM_TAB, UNKNOWN }
data class AppStartupSourceAndType(
val source: Source,
val type: Type,
val hasSavedInstance: Boolean
) : Event() {
enum class Source { APP_ICON, LINK, CUSTOM_TAB, ERROR }
enum class Type { COLD, WARM, HOT, ERROR }

override val extras: Map<Events.appOpenedAllStartupKeys, String>?
get() = hashMapOf(Events.appOpenedAllStartupKeys.source to source.name)
get() = hashMapOf(
Events.appOpenedAllStartupKeys.source to source.toString(),
Events.appOpenedAllStartupKeys.startupType to type.toString(),
Events.appOpenedAllStartupKeys.startupType to hasSavedInstance.toString()
)
}

data class CollectionSaveButtonPressed(val fromScreen: String) : Event() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ private val Event.wrapper: EventWrapper<*>?
{ Events.appReceivedIntent.record(it) },
{ Events.appReceivedIntentKeys.valueOf(it) }
)
is Event.AppOpenedAllSourceStartup -> EventWrapper(
is Event.AppStartupSourceAndType -> EventWrapper(
{ Events.appOpenedAllStartup.record(it) },
{ Events.appOpenedAllStartupKeys.valueOf(it) }
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ open class ExternalAppBrowserActivity : HomeActivity() {

final override fun getIntentSessionId(intent: SafeIntent) = intent.getSessionId()

override fun setAppAllStartTelemetry(safeIntent: SafeIntent) {
components.appAllSourceStartTelemetry.receivedIntentInExternalAppBrowserActivity(safeIntent)
override fun startupTelemetryOnCreateCalled(safeIntent: SafeIntent, hasSavedInstanceState: Boolean) {
components.appStartupTelemetry.onExternalAppBrowserOnCreate(safeIntent, hasSavedInstanceState)
}

override fun getNavDirections(
Expand Down

0 comments on commit fa8836e

Please sign in to comment.