diff --git a/app/metrics.yaml b/app/metrics.yaml index 0b983569a..a60f8e4f1 100644 --- a/app/metrics.yaml +++ b/app/metrics.yaml @@ -1187,6 +1187,40 @@ metrics: notification_emails: - fenix-core@mozilla.com expires: "2021-08-01" + start_reason_process_error: + type: boolean + description: | + The `AppStartReasonProvider.ProcessLifecycleObserver.onCreate` was + unexpectedly called twice. We can use this metric to validate our + assumptions about how these APIs are called. This probe can be removed + once we validate these assumptions. + bugs: + - https://github.com/mozilla-mobile/fenix/issues/18426 + data_reviews: + - TODO + data_sensitivity: + - technical + notification_emails: + - perf-android-fe@mozilla.com + - mcomella@mozilla.com + expires: "2021-08-11" + start_reason_activity_error: + type: boolean + description: | + The `AppStartReasonProvider.ActivityLifecycleCallbacks.onCreate` was + unexpectedly called twice. We can use this metric to validate our + assumptions about how these APIs are called. This probe can be removed + once we validate these assumptions. + bugs: + - https://github.com/mozilla-mobile/fenix/issues/18426 + data_reviews: + - TODO + data_sensitivity: + - technical + notification_emails: + - perf-android-fe@mozilla.com + - mcomella@mozilla.com + expires: "2021-08-11" preferences: show_search_suggestions: diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt index 3b0c50f1a..568d8a3ca 100644 --- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt +++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -51,6 +51,7 @@ import org.mozilla.fenix.components.metrics.MetricServiceType import org.mozilla.fenix.components.metrics.SecurePrefsTelemetry import org.mozilla.fenix.ext.measureNoInline import org.mozilla.fenix.ext.settings +import org.mozilla.fenix.perf.AppStartReasonProvider import org.mozilla.fenix.perf.ProfilerMarkerFactProcessor import org.mozilla.fenix.perf.StartupTimeline import org.mozilla.fenix.perf.StorageStatsMetrics @@ -190,6 +191,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider { // runStorageMaintenance() // } + components.appStartReasonProvider.registerInAppOnCreate(this) initVisualCompletenessQueueAndQueueTasks() components.appStartupTelemetry.onFenixApplicationOnCreate() diff --git a/app/src/main/java/org/mozilla/fenix/components/Components.kt b/app/src/main/java/org/mozilla/fenix/components/Components.kt index c451185f9..b63095a0b 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Components.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Components.kt @@ -28,6 +28,7 @@ import org.mozilla.fenix.perf.StrictModeManager import org.mozilla.fenix.components.metrics.AppStartupTelemetry import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.settings +import org.mozilla.fenix.perf.AppStartReasonProvider import org.mozilla.fenix.perf.lazyMonitored import org.mozilla.fenix.utils.ClipboardHandler import org.mozilla.fenix.utils.Mockable @@ -170,4 +171,6 @@ class Components(private val context: Context) { httpClient = core.client ) } + + val appStartReasonProvider by lazyMonitored { AppStartReasonProvider() } } diff --git a/app/src/main/java/org/mozilla/fenix/perf/AppStartReasonProvider.kt b/app/src/main/java/org/mozilla/fenix/perf/AppStartReasonProvider.kt new file mode 100644 index 000000000..1b54f1589 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/perf/AppStartReasonProvider.kt @@ -0,0 +1,101 @@ +/* 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.perf + +import android.app.Activity +import android.app.Application +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ProcessLifecycleOwner +import mozilla.components.support.base.log.logger.Logger +import org.mozilla.fenix.GleanMetrics.Metrics +import org.mozilla.fenix.android.DefaultActivityLifecycleCallbacks + +private val logger = Logger("AppStartReasonProvider") + +/** + * Provides the reason this [Application] instance was started: see [StartReason] for options + * and [reason] for details. + * + * [registerInAppOnCreate] must be called for this class to work correctly. + * + * This class relies on specific lifecycle method call orders and main thread Runnable scheduling + * that could potentially change between OEMs and OS versions: **be careful when using it.** This + * implementation was tested on the Moto G5 Android 8.1.0 and the Pixel 2 Android 11. + */ +class AppStartReasonProvider { + + enum class StartReason { + /** We don't know yet what caused this [Application] instance to be started. */ + TO_BE_DETERMINED, + + /** This [Application] instance was started due to an Activity trying to start. */ + ACTIVITY, + + /** + * This [Application] instance was started due to a component that is not an Activity: + * this may include Services, BroadcastReceivers, and ContentProviders. It may be possible + * to distinguish between these but it hasn't been necessary to do so yet. + */ + NON_ACTIVITY, + } + + /** + * The reason this [Application] instance was started. This will not be set immediately + * but is expected to be available by the time the first frame is drawn for the foreground Activity. + */ + var reason = StartReason.TO_BE_DETERMINED + private set + + /** + * Registers the handlers needed by this class: this is expected to be called from + * [Application.onCreate]. + */ + fun registerInAppOnCreate(application: Application) { + ProcessLifecycleOwner.get().lifecycle.addObserver(ProcessLifecycleObserver()) + application.registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks()) + } + + private inner class ProcessLifecycleObserver : DefaultLifecycleObserver { + override fun onCreate(owner: LifecycleOwner) { + Handler(Looper.getMainLooper()).post { + // If the Application was started by an Activity, this Runnable should execute + // after we learn the Activity was created. If the App was started by a Service, + // this Runnable should execute before the first Activity is created. + reason = when (reason) { + StartReason.TO_BE_DETERMINED -> StartReason.NON_ACTIVITY + StartReason.ACTIVITY -> reason /* the start reason is already known: do nothing. */ + StartReason.NON_ACTIVITY -> { + Metrics.startReasonProcessError.set(true) + logger.error("AppStartReasonProvider.Process...onCreate unexpectedly called twice") + reason + } + } + } + + owner.lifecycle.removeObserver(this) // we don't update the state further. + } + } + + private inner class ActivityLifecycleCallbacks : DefaultActivityLifecycleCallbacks { + override fun onActivityCreated(activity: Activity, bundle: Bundle?) { + // See ProcessLifecycleObserver.onCreate for details. + reason = when (reason) { + StartReason.TO_BE_DETERMINED -> StartReason.ACTIVITY + StartReason.NON_ACTIVITY -> reason /* the start reason is already known: do nothing. */ + StartReason.ACTIVITY -> { + Metrics.startReasonActivityError.set(true) + logger.error("AppStartReasonProvider.Activity...onCreate unexpectedly called twice") + reason + } + } + + activity.application.unregisterActivityLifecycleCallbacks(this) // we don't update the state further. + } + } +} diff --git a/docs/metrics.md b/docs/metrics.md index f96f3db54..2c88c57ea 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -331,6 +331,8 @@ In addition to those built-in metrics, the following metrics are added to the pi | metrics.recently_used_pwa_count |[counter](https://mozilla.github.io/glean/book/user/metrics/counter.html) |A counter that indicates how many PWAs a user has recently used. Threshold for "recency" set in HomeActivity#PWA_RECENTLY_USED_THRESHOLD. Currently we are not told by the OS when a PWA is removed by the user, so we use the "recently used" heuristic to judge how many PWAs are still active, as a proxy for "installed". This value will only be set if the user has at least *one* recently used PWA. If they have 0, this metric will not be sent, resulting in a null value during analysis on the server-side. To disambiguate between a failed `recently_used_pwa_count` metric and 0 recent PWAs, please see `has_recent_pwas`. |[mozilla-mobile/fenix#11982](https://github.com/mozilla-mobile/fenix/pull/11982#pullrequestreview-437963817), [mozilla-mobile/fenix#15713](https://github.com/mozilla-mobile/fenix/pull/15713#issuecomment-703972068)||2021-08-01 |2 | | metrics.search_count |[labeled_counter](https://mozilla.github.io/glean/book/user/metrics/labeled_counters.html) |The labels for this counter are `.`. If the search engine is bundled with Fenix `search-engine-name` will be the name of the search engine. If it's a custom search engine (defined: https://github.com/mozilla-mobile/fenix/issues/1607) the value will be `custom`. `source` will be: `action`, `suggestion`, `widget`, `shortcut`, `topsite` (depending on the source from which the search started). Also added the `other` option for the source but it should never enter on this case. |[mozilla-mobile/fenix#1677](https://github.com/mozilla-mobile/fenix/pull/1677), [mozilla-mobile/fenix#5216](https://github.com/mozilla-mobile/fenix/pull/5216), [mozilla-mobile/fenix#7310](https://github.com/mozilla-mobile/fenix/pull/7310), [mozilla-mobile/fenix#15713](https://github.com/mozilla-mobile/fenix/pull/15713#issuecomment-703972068)||2021-08-01 |1, 2 | | metrics.search_widget_installed |[boolean](https://mozilla.github.io/glean/book/user/metrics/boolean.html) |Whether or not the search widget is installed |[mozilla-mobile/fenix#10958](https://github.com/mozilla-mobile/fenix/pull/10958), [mozilla-mobile/fenix#15713](https://github.com/mozilla-mobile/fenix/pull/15713#issuecomment-703972068)||2021-08-01 |2 | +| metrics.start_reason_activity_error |[boolean](https://mozilla.github.io/glean/book/user/metrics/boolean.html) |The `AppStartReasonProvider.ActivityLifecycleCallbacks.onCreate` was unexpectedly called twice. We can use this metric to validate our assumptions about how these APIs are called. This probe can be removed once we validate these assumptions. |[Review 1](TODO)||2021-08-11 |1 | +| metrics.start_reason_process_error |[boolean](https://mozilla.github.io/glean/book/user/metrics/boolean.html) |The `AppStartReasonProvider.ProcessLifecycleObserver.onCreate` was unexpectedly called twice. We can use this metric to validate our assumptions about how these APIs are called. This probe can be removed once we validate these assumptions. |[Review 1](TODO)||2021-08-11 |1 | | metrics.tab_view_setting |[string](https://mozilla.github.io/glean/book/user/metrics/string.html) |A string that indicates the setting for tab view: GRID, LIST |[mozilla-mobile/fenix#15811](https://github.com/mozilla-mobile/fenix/pull/15811#issuecomment-706402952)||2021-08-01 |2 | | metrics.tabs_open_count |[counter](https://mozilla.github.io/glean/book/user/metrics/counter.html) |A counter that indicates how many NORMAL tabs a user has open. This value will only be set if the user has at least *one* open tab. If they have 0, this ping will not get sent, resulting in a null value. To disambiguate between a failed `tabs_open_count` ping and 0 open tabs, please see `has_open_tabs`. |[mozilla-mobile/fenix#12024](https://github.com/mozilla-mobile/fenix/pull/12024), [mozilla-mobile/fenix#15713](https://github.com/mozilla-mobile/fenix/pull/15713#issuecomment-703972068)||2021-08-01 |2 | | metrics.toolbar_position |[string](https://mozilla.github.io/glean/book/user/metrics/string.html) |A string that indicates the new position of the toolbar TOP or BOTTOM |[mozilla-mobile/fenix#6608](https://github.com/mozilla-mobile/fenix/pull/6608), [mozilla-mobile/fenix#15713](https://github.com/mozilla-mobile/fenix/pull/15713#issuecomment-703972068)||2021-08-01 |2 |