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 22, 2020
1 parent fd90afc commit ce95adf
Show file tree
Hide file tree
Showing 12 changed files with 456 additions and 176 deletions.
32 changes: 23 additions & 9 deletions app/metrics.yaml
Expand Up @@ -18,28 +18,42 @@ 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].
unknown is for tasks switcher or other unanticipated startup source.
Error is for impossible cases.
extra_keys:
source:
description: |
The method used to open Fenix. Possible values are `app_icon`,
`custom_tab`, `link` or `unknown`
`custom_tab`, `link`, `unknown` 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`, `unknown` or `error`.
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
- https://github.com/mozilla-mobile/fenix/issues/12573
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/12114#pullrequestreview-445245341
- https://github.com/mozilla-mobile/fenix/pull/13494#issuecomment-678038501
data_sensitivity:
- interaction
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
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
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
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.

@@ -0,0 +1,159 @@
/* 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.AppAllStartup
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.ERROR
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Source.CUSTOM_TAB
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Source.LINK
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Source.UNKNOWN

import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type
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.HOT

/**
* 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
* [AppAllStartup] and finally report them on Activity's onResume() function.
*/
@Suppress("TooManyFunctions")
class AppStartupTelemetry(private val metrics: MetricController) : LifecycleObserver {

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

private var isMetricRecordedSinceAppWasForegrounded = false
private var wasAppCreateCalledBeforeActivityCreate = false

private var onCreateData: AppAllStartup? = null
private var onRestartData: Pair<Type, Boolean>? = null
private var onNewIntentData: Source? = null

fun onFenixApplicationOnCreate() {
wasAppCreateCalledBeforeActivityCreate = true
}

fun onHomeActivityOnCreate(safeIntent: SafeIntent, hasSavedInstanceState: Boolean) {
onCreateData = AppAllStartup(
getStartupSourceFromIntent(safeIntent, false),
getAppStartupType(),
hasSavedInstanceState
)
wasAppCreateCalledBeforeActivityCreate = false
}

fun onExternalAppBrowserOnCreate(safeIntent: SafeIntent, hasSavedInstanceState: Boolean) {
onCreateData = AppAllStartup(
getStartupSourceFromIntent(safeIntent, true),
getAppStartupType(),
hasSavedInstanceState
)
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() {
onRestartData = Pair(HOT, 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) {
onNewIntentData = 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 -> UNKNOWN
}
}

/**
* 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) {
val appAllStartup: AppAllStartup = if (onCreateData != null) {
onCreateData!!
} else if (onRestartData != null && onNewIntentData != null) {
mergeOnRestartAndOnNewIntentIntoStartup()
} else {
getErrorStartup()
}
metrics.track(appAllStartup)
isMetricRecordedSinceAppWasForegrounded = true
}
// we don't want any weird previous states to persist on our next metric record.
onCreateData = null
onNewIntentData = null
onRestartData = null
}

private fun mergeOnRestartAndOnNewIntentIntoStartup(): AppAllStartup {
return AppAllStartup(
onNewIntentData!!,
onRestartData!!.first,
onRestartData!!.second
)
}
private fun getErrorStartup(): AppAllStartup {
return if (onNewIntentData == null && onRestartData == null) {
// this should never happen when sending a metric
// maybe code is faulty?
AppAllStartup(ERROR, Type.ERROR, false)
} else {
AppAllStartup(
onNewIntentData ?: UNKNOWN,
onRestartData?.first ?: Type.UNKNOWN,
onRestartData?.second ?: false
)
}
}

@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
}
}
15 changes: 12 additions & 3 deletions app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt
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 AppAllStartup(
val source: Source,
val type: Type,
val hasSavedInstance: Boolean
) : Event() {
enum class Source { APP_ICON, LINK, CUSTOM_TAB, ERROR, UNKNOWN }
enum class Type { COLD, WARM, HOT, ERROR, UNKNOWN }

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.hasSavedInstanceState to hasSavedInstance.toString()
)
}

data class CollectionSaveButtonPressed(val fromScreen: String) : Event() {
Expand Down
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.AppAllStartup -> EventWrapper(
{ Events.appOpenedAllStartup.record(it) },
{ Events.appOpenedAllStartupKeys.valueOf(it) }
)
Expand Down
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 ce95adf

Please sign in to comment.