From 6659eb8b9ee1237394e60a29d9958ebeacb92f55 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Mon, 27 Jul 2026 15:40:08 +0200 Subject: [PATCH 1/7] fix(android): Drop inflated app start for background-spawned processes on API 35+ When the OS spawns the process for background work (FCM push, job, service, broadcast, etc.) and the user opens the app later, the app start stayed anchored at background process creation, inflating the reported cold start by the whole idle gap. On API 35+ we now use ApplicationStartInfo.getReason() to detect background process starts and mark them as not launched in foreground, so the first created activity re-classifies them as a warm start anchored at activity creation. --- CHANGELOG.md | 2 + .../core/performance/AppStartMetrics.java | 34 +++++++ .../performance/AppStartMetricsTestApi35.kt | 98 +++++++++++++++++++ 3 files changed, 134 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2e8119a422..fe0bf56071a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ### Fixes +- Prevent inflated app start when the process is spawned in the background (API 35+) ([#5843](https://github.com/getsentry/sentry-java/pull/5843)) + - When the OS starts the process for background work (e.g. an FCM push, job or service) and the user opens the app later, the app start stayed anchored at background process creation, so the first activity reported the whole idle gap as an inflated cold start. Background process starts (`ApplicationStartInfo.getReason()` of `push`, `job`, `service`, `broadcast`, `alarm`, `backup`, `boot_complete`, `content_provider`) are now marked as not launched in foreground, so the first activity re-classifies them as a warm start anchored at activity creation instead. - Prevent concurrent PixelCopy access during Session Replay masking and bitmap cleanup ([#5808](https://github.com/getsentry/sentry-java/pull/5808)) - Release `MediaMuxer` when the replay video encoder fails to start to avoid a resource leak ([#5607](https://github.com/getsentry/sentry-java/pull/5607)) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java b/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java index 0f4b646856a..444b3c6686e 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java @@ -208,6 +208,30 @@ public void setAppStartType(final @NotNull AppStartType appStartType) { } } + /** + * Whether the given {@link ApplicationStartInfo#getReason()} indicates the OS started the process + * for background work (e.g. a push message, job or service) rather than a user-initiated launch. + * + *

Unknown/future reasons are treated as user-initiated to avoid discarding valid app starts. + */ + private static boolean isBackgroundStartReason(final int reason) { + switch (reason) { + case ApplicationStartInfo.START_REASON_ALARM: + case ApplicationStartInfo.START_REASON_BACKUP: + case ApplicationStartInfo.START_REASON_BOOT_COMPLETE: + case ApplicationStartInfo.START_REASON_BROADCAST: + case ApplicationStartInfo.START_REASON_CONTENT_PROVIDER: + case ApplicationStartInfo.START_REASON_JOB: + case ApplicationStartInfo.START_REASON_PUSH: + case ApplicationStartInfo.START_REASON_SERVICE: + return true; + // START_REASON_LAUNCHER, START_REASON_LAUNCHER_RECENTS, START_REASON_START_ACTIVITY, + // START_REASON_OTHER and any future reason are considered user-initiated. + default: + return false; + } + } + public boolean isAppLaunchedInForeground() { return appLaunchedInForeground.getValue(); } @@ -500,6 +524,16 @@ public void registerLifecycleCallbacks(final @NotNull Application application) { appStartType = AppStartType.WARM; } } + // If the OS spawned the process for background work (push/job/service/broadcast/...) + // rather than a user launch, the app start stays anchored at background process + // creation. Without this, the first activity created once the user finally opens the + // app would report the whole idle gap as an inflated cold start. Marking the launch as + // not-in-foreground makes onActivityCreated re-classify it as a warm start anchored at + // activity creation instead. On API 35+ this replaces the main-looper idle check, which + // is skipped here once the start type is known. + if (isBackgroundStartReason(info.getReason())) { + appLaunchedInForeground.setValue(false); + } } } catch (RuntimeException ignored) { // getHistoricalProcessStartReasons may throw different kinds of exceptions, namely: diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTestApi35.kt b/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTestApi35.kt index 0624e70b898..3c1c719b70c 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTestApi35.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTestApi35.kt @@ -1,11 +1,13 @@ package io.sentry.android.core.performance +import android.app.Activity import android.app.ActivityManager.RunningAppProcessInfo import android.app.Application import android.app.ApplicationStartInfo import android.os.Build import android.os.Handler import android.os.Looper +import android.os.SystemClock import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import io.sentry.android.core.SentryShadowActivityManager @@ -16,6 +18,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNull +import kotlin.test.assertTrue import org.junit.Before import org.junit.runner.RunWith import org.mockito.kotlin.mock @@ -263,6 +266,101 @@ class AppStartMetricsTestApi35 { assertNull(metrics.appStartReason) } + @Test + fun `background start reason marks app as not launched in foreground`() { + val mockStartInfo = mock() + whenever(mockStartInfo.startupState).thenReturn(ApplicationStartInfo.STARTUP_STATE_STARTED) + whenever(mockStartInfo.startType).thenReturn(ApplicationStartInfo.START_TYPE_COLD) + whenever(mockStartInfo.reason).thenReturn(ApplicationStartInfo.START_REASON_PUSH) + SentryShadowActivityManager.setHistoricalProcessStartReasons(listOf(mockStartInfo)) + val metrics = AppStartMetrics.getInstance() + + val app = ApplicationProvider.getApplicationContext() + metrics.registerLifecycleCallbacks(app) + + assertFalse(metrics.isAppLaunchedInForeground) + } + + @Test + fun `all background start reasons mark app as not launched in foreground`() { + val backgroundReasons = + listOf( + ApplicationStartInfo.START_REASON_ALARM, + ApplicationStartInfo.START_REASON_BACKUP, + ApplicationStartInfo.START_REASON_BOOT_COMPLETE, + ApplicationStartInfo.START_REASON_BROADCAST, + ApplicationStartInfo.START_REASON_CONTENT_PROVIDER, + ApplicationStartInfo.START_REASON_JOB, + ApplicationStartInfo.START_REASON_PUSH, + ApplicationStartInfo.START_REASON_SERVICE, + ) + + val app = ApplicationProvider.getApplicationContext() + for (reason in backgroundReasons) { + AppStartMetrics.getInstance().clear() + SentryShadowActivityManager.reset() + + val mockStartInfo = mock() + whenever(mockStartInfo.startupState).thenReturn(ApplicationStartInfo.STARTUP_STATE_STARTED) + whenever(mockStartInfo.startType).thenReturn(ApplicationStartInfo.START_TYPE_COLD) + whenever(mockStartInfo.reason).thenReturn(reason) + SentryShadowActivityManager.setHistoricalProcessStartReasons(listOf(mockStartInfo)) + + AppStartMetrics.getInstance().registerLifecycleCallbacks(app) + + assertFalse( + AppStartMetrics.getInstance().isAppLaunchedInForeground, + "reason $reason should not be launched in foreground", + ) + } + } + + @Test + fun `user-initiated start reason keeps app launched in foreground`() { + val mockStartInfo = mock() + whenever(mockStartInfo.startupState).thenReturn(ApplicationStartInfo.STARTUP_STATE_STARTED) + whenever(mockStartInfo.startType).thenReturn(ApplicationStartInfo.START_TYPE_COLD) + whenever(mockStartInfo.reason).thenReturn(ApplicationStartInfo.START_REASON_LAUNCHER) + SentryShadowActivityManager.setHistoricalProcessStartReasons(listOf(mockStartInfo)) + SentryShadowActivityManager.setImportance(RunningAppProcessInfo.IMPORTANCE_FOREGROUND) + val metrics = AppStartMetrics.getInstance() + + val app = ApplicationProvider.getApplicationContext() + metrics.registerLifecycleCallbacks(app) + + assertTrue(metrics.isAppLaunchedInForeground) + } + + @Test + fun `background-spawned start is re-classified as warm on the first activity`() { + val mockStartInfo = mock() + whenever(mockStartInfo.startupState).thenReturn(ApplicationStartInfo.STARTUP_STATE_STARTED) + whenever(mockStartInfo.startType).thenReturn(ApplicationStartInfo.START_TYPE_COLD) + whenever(mockStartInfo.reason).thenReturn(ApplicationStartInfo.START_REASON_PUSH) + SentryShadowActivityManager.setHistoricalProcessStartReasons(listOf(mockStartInfo)) + val metrics = AppStartMetrics.getInstance() + // App start span anchored at background process creation. + metrics.appStartTimeSpan.setStartedAt(42) + + val app = ApplicationProvider.getApplicationContext() + metrics.registerLifecycleCallbacks(app) + + // Background spawn detected: not launched in foreground, cold type from ApplicationStartInfo. + assertFalse(metrics.isAppLaunchedInForeground) + assertEquals(AppStartMetrics.AppStartType.COLD, metrics.appStartType) + + // User opens the app 20s later (well under the 1-minute warm threshold that previously was the + // only way to catch this). + val activityCreatedUptimeMs = 20_000L + SystemClock.setCurrentTimeMillis(activityCreatedUptimeMs) + metrics.onActivityCreated(mock(), null) + + // The inflated cold start is re-classified as a warm start re-anchored at activity creation. + assertEquals(AppStartMetrics.AppStartType.WARM, metrics.appStartType) + assertTrue(metrics.isAppLaunchedInForeground) + assertEquals(activityCreatedUptimeMs, metrics.appStartTimeSpan.startUptimeMs) + } + private fun waitForMainLooperIdle() { Handler(Looper.getMainLooper()).post {} Shadows.shadowOf(Looper.getMainLooper()).idle() From 1101a9c9683f1c4cfbf5b0efee966bb4112bba0d Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Mon, 27 Jul 2026 15:41:08 +0200 Subject: [PATCH 2/7] docs: fix changelog PR link --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe0bf56071a..fdba4624676 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ ### Fixes -- Prevent inflated app start when the process is spawned in the background (API 35+) ([#5843](https://github.com/getsentry/sentry-java/pull/5843)) +- Prevent inflated app start when the process is spawned in the background (API 35+) ([#5841](https://github.com/getsentry/sentry-java/pull/5841)) - When the OS starts the process for background work (e.g. an FCM push, job or service) and the user opens the app later, the app start stayed anchored at background process creation, so the first activity reported the whole idle gap as an inflated cold start. Background process starts (`ApplicationStartInfo.getReason()` of `push`, `job`, `service`, `broadcast`, `alarm`, `backup`, `boot_complete`, `content_provider`) are now marked as not launched in foreground, so the first activity re-classifies them as a warm start anchored at activity creation instead. - Prevent concurrent PixelCopy access during Session Replay masking and bitmap cleanup ([#5808](https://github.com/getsentry/sentry-java/pull/5808)) - Release `MediaMuxer` when the replay video encoder fails to start to avoid a resource leak ([#5607](https://github.com/getsentry/sentry-java/pull/5607)) From c546da6040166e2e9b7e15448df3dcb71d010428 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Mon, 27 Jul 2026 16:37:21 +0200 Subject: [PATCH 3/7] docs: trim code comments --- .../core/performance/AppStartMetrics.java | 21 +++++++------------ .../performance/AppStartMetricsTestApi35.kt | 6 ++---- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java b/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java index 444b3c6686e..729982385a1 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java @@ -209,10 +209,9 @@ public void setAppStartType(final @NotNull AppStartType appStartType) { } /** - * Whether the given {@link ApplicationStartInfo#getReason()} indicates the OS started the process - * for background work (e.g. a push message, job or service) rather than a user-initiated launch. - * - *

Unknown/future reasons are treated as user-initiated to avoid discarding valid app starts. + * Whether {@link ApplicationStartInfo#getReason()} indicates the OS spawned the process for + * background work (push, job, service, ...) rather than a user launch. Unknown/future reasons are + * treated as user-initiated to avoid discarding valid app starts. */ private static boolean isBackgroundStartReason(final int reason) { switch (reason) { @@ -225,9 +224,7 @@ private static boolean isBackgroundStartReason(final int reason) { case ApplicationStartInfo.START_REASON_PUSH: case ApplicationStartInfo.START_REASON_SERVICE: return true; - // START_REASON_LAUNCHER, START_REASON_LAUNCHER_RECENTS, START_REASON_START_ACTIVITY, - // START_REASON_OTHER and any future reason are considered user-initiated. - default: + default: // launcher/recents/start_activity/other and future reasons: user-initiated return false; } } @@ -524,13 +521,9 @@ public void registerLifecycleCallbacks(final @NotNull Application application) { appStartType = AppStartType.WARM; } } - // If the OS spawned the process for background work (push/job/service/broadcast/...) - // rather than a user launch, the app start stays anchored at background process - // creation. Without this, the first activity created once the user finally opens the - // app would report the whole idle gap as an inflated cold start. Marking the launch as - // not-in-foreground makes onActivityCreated re-classify it as a warm start anchored at - // activity creation instead. On API 35+ this replaces the main-looper idle check, which - // is skipped here once the start type is known. + // Background-spawned processes (push/job/service/...) keep the app start anchored at + // process creation. Marking them not-in-foreground makes onActivityCreated re-classify + // them as a warm start anchored at activity creation, avoiding an inflated cold start. if (isBackgroundStartReason(info.getReason())) { appLaunchedInForeground.setValue(false); } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTestApi35.kt b/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTestApi35.kt index 3c1c719b70c..f53c557246c 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTestApi35.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTestApi35.kt @@ -345,17 +345,15 @@ class AppStartMetricsTestApi35 { val app = ApplicationProvider.getApplicationContext() metrics.registerLifecycleCallbacks(app) - // Background spawn detected: not launched in foreground, cold type from ApplicationStartInfo. assertFalse(metrics.isAppLaunchedInForeground) assertEquals(AppStartMetrics.AppStartType.COLD, metrics.appStartType) - // User opens the app 20s later (well under the 1-minute warm threshold that previously was the - // only way to catch this). + // User opens the app 20s later (under the 1-minute warm threshold). val activityCreatedUptimeMs = 20_000L SystemClock.setCurrentTimeMillis(activityCreatedUptimeMs) metrics.onActivityCreated(mock(), null) - // The inflated cold start is re-classified as a warm start re-anchored at activity creation. + // Re-classified as a warm start re-anchored at activity creation. assertEquals(AppStartMetrics.AppStartType.WARM, metrics.appStartType) assertTrue(metrics.isAppLaunchedInForeground) assertEquals(activityCreatedUptimeMs, metrics.appStartTimeSpan.startUptimeMs) From 4ead0d6cbbfa6082bb7841c9032b0bb2e2548024 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Tue, 28 Jul 2026 09:46:46 +0200 Subject: [PATCH 4/7] docs: trim changelog entry --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdba4624676..ce86ff1e58c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,7 @@ ### Fixes -- Prevent inflated app start when the process is spawned in the background (API 35+) ([#5841](https://github.com/getsentry/sentry-java/pull/5841)) - - When the OS starts the process for background work (e.g. an FCM push, job or service) and the user opens the app later, the app start stayed anchored at background process creation, so the first activity reported the whole idle gap as an inflated cold start. Background process starts (`ApplicationStartInfo.getReason()` of `push`, `job`, `service`, `broadcast`, `alarm`, `backup`, `boot_complete`, `content_provider`) are now marked as not launched in foreground, so the first activity re-classifies them as a warm start anchored at activity creation instead. +- Prevent inflated cold app start when the OS spawns the process in the background (e.g. FCM push) on API 35+ ([#5841](https://github.com/getsentry/sentry-java/pull/5841)) - Prevent concurrent PixelCopy access during Session Replay masking and bitmap cleanup ([#5808](https://github.com/getsentry/sentry-java/pull/5808)) - Release `MediaMuxer` when the replay video encoder fails to start to avoid a resource leak ([#5607](https://github.com/getsentry/sentry-java/pull/5607)) From 37e6c31e71bdbe524796926ac13bd2c213757133 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Thu, 30 Jul 2026 11:58:14 +0200 Subject: [PATCH 5/7] Update Changelog --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31608b4a28e..c8512c7ba62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Fixes + +- Prevent inflated cold app start when the OS spawns the process in the background (e.g. FCM push) on API 35+ ([#5841](https://github.com/getsentry/sentry-java/pull/5841)) + ### Performance - Remove an unused lock from `SentryPerformanceProvider`, which was allocated on every cold start in `ContentProvider.onCreate` without ever being acquired ([#5871](https://github.com/getsentry/sentry-java/pull/5871)) @@ -29,7 +33,6 @@ ### Fixes -- Prevent inflated cold app start when the OS spawns the process in the background (e.g. FCM push) on API 35+ ([#5841](https://github.com/getsentry/sentry-java/pull/5841)) - Use the original app build's ProGuard UUID for ANR profile chunks ([#5852](https://github.com/getsentry/sentry-java/pull/5852)) - Fix potential ANR/deadlock in Session Replay when `checkCanRecord` runs on the replay executor thread ([#5837](https://github.com/getsentry/sentry-java/pull/5837)) - Prevent concurrent PixelCopy access during Session Replay masking and bitmap cleanup ([#5808](https://github.com/getsentry/sentry-java/pull/5808)) From 3277ac2b32bf5bdbb2af8d8386c47caa874922fd Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Thu, 30 Jul 2026 11:59:52 +0200 Subject: [PATCH 6/7] Reverse isBackgroundStartReason to isForegroundStartReason --- .../core/performance/AppStartMetrics.java | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java b/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java index 729982385a1..97f5c5fd6ec 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java @@ -14,6 +14,7 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import io.sentry.IContinuousProfiler; import io.sentry.ISentryLifecycleToken; @@ -209,12 +210,19 @@ public void setAppStartType(final @NotNull AppStartType appStartType) { } /** - * Whether {@link ApplicationStartInfo#getReason()} indicates the OS spawned the process for - * background work (push, job, service, ...) rather than a user launch. Unknown/future reasons are - * treated as user-initiated to avoid discarding valid app starts. + * Whether {@link ApplicationStartInfo#getReason()} indicates the OS spawned the app process + * because of an intentional user interaction. + * + * @return true if the user actively launched the app, false if the app was launched in + * background, and null if unknown. */ - private static boolean isBackgroundStartReason(final int reason) { + @RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM) + private static @Nullable Boolean isForegroundStartReason(final int reason) { switch (reason) { + case ApplicationStartInfo.START_REASON_LAUNCHER: + case ApplicationStartInfo.START_REASON_LAUNCHER_RECENTS: + case ApplicationStartInfo.START_REASON_START_ACTIVITY: + return true; case ApplicationStartInfo.START_REASON_ALARM: case ApplicationStartInfo.START_REASON_BACKUP: case ApplicationStartInfo.START_REASON_BOOT_COMPLETE: @@ -223,9 +231,10 @@ private static boolean isBackgroundStartReason(final int reason) { case ApplicationStartInfo.START_REASON_JOB: case ApplicationStartInfo.START_REASON_PUSH: case ApplicationStartInfo.START_REASON_SERVICE: - return true; - default: // launcher/recents/start_activity/other and future reasons: user-initiated return false; + case ApplicationStartInfo.START_REASON_OTHER: + default: + return null; } } @@ -520,12 +529,7 @@ public void registerLifecycleCallbacks(final @NotNull Application application) { } else { appStartType = AppStartType.WARM; } - } - // Background-spawned processes (push/job/service/...) keep the app start anchored at - // process creation. Marking them not-in-foreground makes onActivityCreated re-classify - // them as a warm start anchored at activity creation, avoiding an inflated cold start. - if (isBackgroundStartReason(info.getReason())) { - appLaunchedInForeground.setValue(false); + appLaunchedInForeground.setValue(isForegroundStartReason(info.getReason())); } } } catch (RuntimeException ignored) { From 7dd2291f74b279e6e6528010851d5ebbdd68e026 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Thu, 30 Jul 2026 12:11:07 +0200 Subject: [PATCH 7/7] Add extra test --- .../core/performance/AppStartMetricsTestApi35.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTestApi35.kt b/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTestApi35.kt index f53c557246c..a7eace97371 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTestApi35.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTestApi35.kt @@ -331,6 +331,22 @@ class AppStartMetricsTestApi35 { assertTrue(metrics.isAppLaunchedInForeground) } + @Test + fun `unknown start reason falls back to foreground importance check`() { + val mockStartInfo = mock() + whenever(mockStartInfo.startupState).thenReturn(ApplicationStartInfo.STARTUP_STATE_STARTED) + whenever(mockStartInfo.startType).thenReturn(ApplicationStartInfo.START_TYPE_COLD) + whenever(mockStartInfo.reason).thenReturn(ApplicationStartInfo.START_REASON_OTHER) + SentryShadowActivityManager.setHistoricalProcessStartReasons(listOf(mockStartInfo)) + SentryShadowActivityManager.setImportance(RunningAppProcessInfo.IMPORTANCE_FOREGROUND) + val metrics = AppStartMetrics.getInstance() + + val app = ApplicationProvider.getApplicationContext() + metrics.registerLifecycleCallbacks(app) + + assertTrue(metrics.isAppLaunchedInForeground) + } + @Test fun `background-spawned start is re-classified as warm on the first activity`() { val mockStartInfo = mock()