From 8a6cf5ab5be865d7914da601444fac08492a9bab Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Fri, 15 Aug 2025 13:47:52 -0400 Subject: [PATCH 01/14] Add TODO --- .../main/java/com/google/firebase/perf/FirebasePerfEarly.java | 1 + 1 file changed, 1 insertion(+) diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerfEarly.java b/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerfEarly.java index 5b89deaad82..1436c863977 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerfEarly.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerfEarly.java @@ -48,6 +48,7 @@ public FirebasePerfEarly( if (startupTime != null) { AppStartTrace appStartTrace = AppStartTrace.getInstance(); appStartTrace.registerActivityLifecycleCallbacks(context); + // TODO(b/339891952): Investigate why this runs *before* the activity lifecycle callbacks. uiExecutor.execute(new AppStartTrace.StartFromBackgroundRunnable(appStartTrace)); } From f4434c2f34b10884bc0868675a177fc481cdbfa9 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 19 Aug 2025 17:25:37 -0400 Subject: [PATCH 02/14] Add a delay to detect background starts --- .../firebase/perf/metrics/AppStartTrace.java | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java index 7574f989d92..233c3367aca 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java @@ -75,6 +75,8 @@ public class AppStartTrace implements ActivityLifecycleCallbacks, LifecycleObser private static final @NonNull Timer PERF_CLASS_LOAD_TIME = new Clock().getTime(); private static final long MAX_LATENCY_BEFORE_UI_INIT = TimeUnit.MINUTES.toMicros(1); + private static final long MAX_BACKGROUND_THREAD_DELAY = TimeUnit.MILLISECONDS.toMicros(100); + // Core pool size 0 allows threads to shut down if they're idle private static final int CORE_POOL_SIZE = 0; private static final int MAX_POOL_SIZE = 1; // Only need single thread @@ -111,6 +113,8 @@ public class AppStartTrace implements ActivityLifecycleCallbacks, LifecycleObser private final @Nullable Timer processStartTime; private final @Nullable Timer firebaseClassLoadTime; private Timer onCreateTime = null; + + private Timer mainThreadRunnableTime = null; private Timer onStartTime = null; private Timer onResumeTime = null; private Timer firstForegroundTime = null; @@ -319,10 +323,28 @@ private void recordOnDrawFrontOfQueue() { logExperimentTrace(this.experimentTtid); } + private boolean isStartedFromBackground() { + // This a fix for b/339891952 where the runnable on the background thread can run before the + // activity lifecycle callbacks. + if (mainThreadRunnableTime == null) { + return false; + } + + if (isStartedFromBackground + && (mainThreadRunnableTime.getDurationMicros() < MAX_BACKGROUND_THREAD_DELAY)) { + // Reset it to false as it was executed pre-emptively. + isStartedFromBackground = false; + } + + return isStartedFromBackground; + } + @Override public synchronized void onActivityCreated(Activity activity, Bundle savedInstanceState) { - if (isStartedFromBackground || onCreateTime != null // An activity already called onCreate() + if (isStartedFromBackground() || onCreateTime != null // An activity already called onCreate() ) { + AndroidLogger.getInstance() + .debug("onActivityCreated: isStartedFromBackground - " + isStartedFromBackground()); return; } @@ -337,7 +359,7 @@ public synchronized void onActivityCreated(Activity activity, Bundle savedInstan @Override public synchronized void onActivityStarted(Activity activity) { - if (isStartedFromBackground + if (isStartedFromBackground() || onStartTime != null // An activity already called onStart() || isTooLateToInitUI) { return; @@ -347,7 +369,7 @@ public synchronized void onActivityStarted(Activity activity) { @Override public synchronized void onActivityResumed(Activity activity) { - if (isStartedFromBackground || isTooLateToInitUI) { + if (isStartedFromBackground() || isTooLateToInitUI) { return; } @@ -440,7 +462,7 @@ private void logAppStartTrace() { @Override public void onActivityPaused(Activity activity) { - if (isStartedFromBackground + if (isStartedFromBackground() || isTooLateToInitUI || !configResolver.getIsExperimentTTIDEnabled()) { return; @@ -458,7 +480,7 @@ public void onActivityStopped(Activity activity) {} @Keep @OnLifecycleEvent(Lifecycle.Event.ON_START) public void onAppEnteredForeground() { - if (isStartedFromBackground || isTooLateToInitUI || firstForegroundTime != null) { + if (isStartedFromBackground() || isTooLateToInitUI || firstForegroundTime != null) { return; } // firstForeground is equivalent to the first Activity onStart. This marks the beginning of @@ -476,7 +498,7 @@ public void onAppEnteredForeground() { @Keep @OnLifecycleEvent(Lifecycle.Event.ON_STOP) public void onAppEnteredBackground() { - if (isStartedFromBackground || isTooLateToInitUI || firstBackgroundTime != null) { + if (isStartedFromBackground() || isTooLateToInitUI || firstBackgroundTime != null) { return; } firstBackgroundTime = clock.getTime(); @@ -574,6 +596,9 @@ public StartFromBackgroundRunnable(final AppStartTrace trace) { public void run() { // if no activity has ever been created. if (trace.onCreateTime == null) { + trace.mainThreadRunnableTime = new Timer(); + AndroidLogger.getInstance() + .debug("StartFromBackgroundThreadRunnable: " + trace.onCreateTime); trace.isStartedFromBackground = true; } } From ad64ce903c89412bb73e9386d47ec7e6901a7937 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 19 Aug 2025 17:28:24 -0400 Subject: [PATCH 03/14] Remove logs --- .../java/com/google/firebase/perf/metrics/AppStartTrace.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java index 233c3367aca..c011853476e 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java @@ -343,8 +343,6 @@ private boolean isStartedFromBackground() { public synchronized void onActivityCreated(Activity activity, Bundle savedInstanceState) { if (isStartedFromBackground() || onCreateTime != null // An activity already called onCreate() ) { - AndroidLogger.getInstance() - .debug("onActivityCreated: isStartedFromBackground - " + isStartedFromBackground()); return; } @@ -597,8 +595,6 @@ public void run() { // if no activity has ever been created. if (trace.onCreateTime == null) { trace.mainThreadRunnableTime = new Timer(); - AndroidLogger.getInstance() - .debug("StartFromBackgroundThreadRunnable: " + trace.onCreateTime); trace.isStartedFromBackground = true; } } From 5e50f5eea017cc10397ba5df9707c582bd58ceaf Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 19 Aug 2025 17:49:15 -0400 Subject: [PATCH 04/14] Add unit test --- .../firebase/perf/FirebasePerfEarly.java | 1 - .../firebase/perf/metrics/AppStartTrace.java | 5 ++++ .../perf/metrics/AppStartTraceTest.java | 29 +++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerfEarly.java b/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerfEarly.java index 1436c863977..5b89deaad82 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerfEarly.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerfEarly.java @@ -48,7 +48,6 @@ public FirebasePerfEarly( if (startupTime != null) { AppStartTrace appStartTrace = AppStartTrace.getInstance(); appStartTrace.registerActivityLifecycleCallbacks(context); - // TODO(b/339891952): Investigate why this runs *before* the activity lifecycle callbacks. uiExecutor.execute(new AppStartTrace.StartFromBackgroundRunnable(appStartTrace)); } diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java index c011853476e..2283bf45a3e 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java @@ -638,4 +638,9 @@ Timer getOnResumeTime() { void setIsStartFromBackground() { isStartedFromBackground = true; } + + @VisibleForTesting + void setMainThreadRunnableTime(Timer timer) { + mainThreadRunnableTime = timer; + } } diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java index 36ae3d10116..264f4bc22d3 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java @@ -18,6 +18,7 @@ import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -258,6 +259,34 @@ public void testStartFromBackground() { ArgumentMatchers.nullable(ApplicationProcessState.class)); } + @Test + public void testStartFromBackground_invertedOrder() { + FakeScheduledExecutorService fakeExecutorService = new FakeScheduledExecutorService(); + Timer fakeTimer = spy(new Timer(currentTime)); + AppStartTrace trace = + new AppStartTrace(transportManager, clock, configResolver, fakeExecutorService); + trace.registerActivityLifecycleCallbacks(appContext); + trace.setIsStartFromBackground(); + trace.setMainThreadRunnableTime(fakeTimer); + + when(fakeTimer.getDurationMicros()).thenReturn(99L); + trace.onActivityCreated(activity1, bundle); + Assert.assertNotNull(trace.getOnCreateTime()); + ++currentTime; + trace.onActivityStarted(activity1); + Assert.assertNotNull(trace.getOnStartTime()); + ++currentTime; + trace.onActivityResumed(activity1); + Assert.assertNotNull(trace.getOnResumeTime()); + // There should be a trace sent since the delay between the main thread and onActivityCreated + // is limited. + fakeExecutorService.runAll(); + verify(transportManager, times(1)) + .log( + traceArgumentCaptor.capture(), + ArgumentMatchers.nullable(ApplicationProcessState.class)); + } + @Test @Config(sdk = 26) public void timeToInitialDisplay_isLogged() { From c187fa7dc59636a164ad23fc1e69be6be71308eb Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 19 Aug 2025 17:52:21 -0400 Subject: [PATCH 05/14] Additional unit test --- .../perf/metrics/AppStartTraceTest.java | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java index 264f4bc22d3..f0b342c6af5 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java @@ -252,6 +252,7 @@ public void testStartFromBackground() { ++currentTime; trace.onActivityResumed(activity1); Assert.assertNull(trace.getOnResumeTime()); + fakeExecutorService.runAll(); // There should be no trace sent. verify(transportManager, times(0)) .log( @@ -278,10 +279,37 @@ public void testStartFromBackground_invertedOrder() { ++currentTime; trace.onActivityResumed(activity1); Assert.assertNotNull(trace.getOnResumeTime()); + fakeExecutorService.runAll(); + // There should be no trace sent. + verify(transportManager, times(1)) + .log( + traceArgumentCaptor.capture(), + ArgumentMatchers.nullable(ApplicationProcessState.class)); + } + + @Test + public void testStartFromBackground_delayedInvertedOrder() { + FakeScheduledExecutorService fakeExecutorService = new FakeScheduledExecutorService(); + Timer fakeTimer = spy(new Timer(currentTime)); + AppStartTrace trace = + new AppStartTrace(transportManager, clock, configResolver, fakeExecutorService); + trace.registerActivityLifecycleCallbacks(appContext); + trace.setIsStartFromBackground(); + trace.setMainThreadRunnableTime(fakeTimer); + + when(fakeTimer.getDurationMicros()).thenReturn(TimeUnit.MILLISECONDS.toMicros(100) + 1); + trace.onActivityCreated(activity1, bundle); + Assert.assertNull(trace.getOnCreateTime()); + ++currentTime; + trace.onActivityStarted(activity1); + Assert.assertNull(trace.getOnStartTime()); + ++currentTime; + trace.onActivityResumed(activity1); + Assert.assertNull(trace.getOnResumeTime()); // There should be a trace sent since the delay between the main thread and onActivityCreated // is limited. fakeExecutorService.runAll(); - verify(transportManager, times(1)) + verify(transportManager, times(0)) .log( traceArgumentCaptor.capture(), ArgumentMatchers.nullable(ApplicationProcessState.class)); From b90accd1a3f372c3d027bf1f55fcfe98ef3f7921 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 19 Aug 2025 17:58:20 -0400 Subject: [PATCH 06/14] Refactor --- .../firebase/perf/metrics/AppStartTrace.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java index 2283bf45a3e..fc8f106fb4e 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java @@ -323,11 +323,11 @@ private void recordOnDrawFrontOfQueue() { logExperimentTrace(this.experimentTtid); } - private boolean isStartedFromBackground() { + private void resolveIsStartedFromBackground() { // This a fix for b/339891952 where the runnable on the background thread can run before the // activity lifecycle callbacks. if (mainThreadRunnableTime == null) { - return false; + return; } if (isStartedFromBackground @@ -335,13 +335,13 @@ private boolean isStartedFromBackground() { // Reset it to false as it was executed pre-emptively. isStartedFromBackground = false; } - - return isStartedFromBackground; } @Override public synchronized void onActivityCreated(Activity activity, Bundle savedInstanceState) { - if (isStartedFromBackground() || onCreateTime != null // An activity already called onCreate() + resolveIsStartedFromBackground(); + + if (isStartedFromBackground || onCreateTime != null // An activity already called onCreate() ) { return; } @@ -357,7 +357,7 @@ public synchronized void onActivityCreated(Activity activity, Bundle savedInstan @Override public synchronized void onActivityStarted(Activity activity) { - if (isStartedFromBackground() + if (isStartedFromBackground || onStartTime != null // An activity already called onStart() || isTooLateToInitUI) { return; @@ -367,7 +367,7 @@ public synchronized void onActivityStarted(Activity activity) { @Override public synchronized void onActivityResumed(Activity activity) { - if (isStartedFromBackground() || isTooLateToInitUI) { + if (isStartedFromBackground || isTooLateToInitUI) { return; } @@ -460,7 +460,7 @@ private void logAppStartTrace() { @Override public void onActivityPaused(Activity activity) { - if (isStartedFromBackground() + if (isStartedFromBackground || isTooLateToInitUI || !configResolver.getIsExperimentTTIDEnabled()) { return; @@ -478,7 +478,7 @@ public void onActivityStopped(Activity activity) {} @Keep @OnLifecycleEvent(Lifecycle.Event.ON_START) public void onAppEnteredForeground() { - if (isStartedFromBackground() || isTooLateToInitUI || firstForegroundTime != null) { + if (isStartedFromBackground || isTooLateToInitUI || firstForegroundTime != null) { return; } // firstForeground is equivalent to the first Activity onStart. This marks the beginning of @@ -496,7 +496,7 @@ public void onAppEnteredForeground() { @Keep @OnLifecycleEvent(Lifecycle.Event.ON_STOP) public void onAppEnteredBackground() { - if (isStartedFromBackground() || isTooLateToInitUI || firstBackgroundTime != null) { + if (isStartedFromBackground || isTooLateToInitUI || firstBackgroundTime != null) { return; } firstBackgroundTime = clock.getTime(); From 82d705bc97d5aecb866e579c9cc52f47dbd1144c Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 19 Aug 2025 18:01:42 -0400 Subject: [PATCH 07/14] Add changelog --- firebase-perf/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/firebase-perf/CHANGELOG.md b/firebase-perf/CHANGELOG.md index b4db8bb6bc4..c7f69f22a43 100644 --- a/firebase-perf/CHANGELOG.md +++ b/firebase-perf/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased * [fixed] Fixed an ANR on app launch. [#4831] +* [fixed] Fix app start traces on API 34+. [#5920] # 22.0.0 * [changed] **Breaking Change**: Updated minSdkVersion to API level 23 or higher. From a7e7239c38df90aed15b287f78f307f5c6db48bb Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 19 Aug 2025 18:05:27 -0400 Subject: [PATCH 08/14] Rename --- .../java/com/google/firebase/perf/metrics/AppStartTrace.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java index fc8f106fb4e..3e8f0b03457 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java @@ -75,7 +75,7 @@ public class AppStartTrace implements ActivityLifecycleCallbacks, LifecycleObser private static final @NonNull Timer PERF_CLASS_LOAD_TIME = new Clock().getTime(); private static final long MAX_LATENCY_BEFORE_UI_INIT = TimeUnit.MINUTES.toMicros(1); - private static final long MAX_BACKGROUND_THREAD_DELAY = TimeUnit.MILLISECONDS.toMicros(100); + private static final long MAX_BACKGROUND_RUNNABLE_DELAY = TimeUnit.MILLISECONDS.toMicros(100); // Core pool size 0 allows threads to shut down if they're idle private static final int CORE_POOL_SIZE = 0; @@ -331,7 +331,7 @@ private void resolveIsStartedFromBackground() { } if (isStartedFromBackground - && (mainThreadRunnableTime.getDurationMicros() < MAX_BACKGROUND_THREAD_DELAY)) { + && (mainThreadRunnableTime.getDurationMicros() < MAX_BACKGROUND_RUNNABLE_DELAY)) { // Reset it to false as it was executed pre-emptively. isStartedFromBackground = false; } From 98986f036f3ff7b079a065add50a98c21507d977 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 19 Aug 2025 18:16:05 -0400 Subject: [PATCH 09/14] nit --- firebase-perf/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-perf/CHANGELOG.md b/firebase-perf/CHANGELOG.md index c7f69f22a43..4954be336b5 100644 --- a/firebase-perf/CHANGELOG.md +++ b/firebase-perf/CHANGELOG.md @@ -1,6 +1,6 @@ # Unreleased * [fixed] Fixed an ANR on app launch. [#4831] -* [fixed] Fix app start traces on API 34+. [#5920] +* [fixed] Fixed app start traces on API 34+. [#5920] # 22.0.0 * [changed] **Breaking Change**: Updated minSdkVersion to API level 23 or higher. From 4f853fa96d7700c5ab6eda889bc5e30cbb3e46d4 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Wed, 20 Aug 2025 10:05:05 -0400 Subject: [PATCH 10/14] Update method documentation. --- .../java/com/google/firebase/perf/metrics/AppStartTrace.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java index 3e8f0b03457..4c51d73f942 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java @@ -580,8 +580,7 @@ public static boolean isScreenOn(Context appContext) { * We use StartFromBackgroundRunnable to detect if app is started from background or foreground. * If app is started from background, we do not generate AppStart trace. This runnable is posted * to main UI thread from FirebasePerfEarly. If app is started from background, this runnable will - * be executed before any activity's onCreate() method. If app is started from foreground, - * activity's onCreate() method is executed before this runnable. + * be executed earlier than 100ms of any activity's onCreate() method. */ public static class StartFromBackgroundRunnable implements Runnable { private final AppStartTrace trace; From 65aba3239df37d365c3afe99fd14d5f82f23832e Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Wed, 20 Aug 2025 10:15:43 -0400 Subject: [PATCH 11/14] Simplify change --- .../firebase/perf/metrics/AppStartTrace.java | 15 +++------- .../perf/metrics/AppStartTraceTest.java | 28 ++----------------- 2 files changed, 6 insertions(+), 37 deletions(-) diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java index 4c51d73f942..189e6c95106 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java @@ -324,16 +324,14 @@ private void recordOnDrawFrontOfQueue() { } private void resolveIsStartedFromBackground() { - // This a fix for b/339891952 where the runnable on the background thread can run before the - // activity lifecycle callbacks. + // If the runnable hasn't run, it isn't a background start. if (mainThreadRunnableTime == null) { return; } - if (isStartedFromBackground - && (mainThreadRunnableTime.getDurationMicros() < MAX_BACKGROUND_RUNNABLE_DELAY)) { - // Reset it to false as it was executed pre-emptively. - isStartedFromBackground = false; + // Set it to true if the runnable ran more than 100ms prior to onActivityCreated() + if (mainThreadRunnableTime.getDurationMicros() >= MAX_BACKGROUND_RUNNABLE_DELAY) { + isStartedFromBackground = true; } } @@ -633,11 +631,6 @@ Timer getOnResumeTime() { return onResumeTime; } - @VisibleForTesting - void setIsStartFromBackground() { - isStartedFromBackground = true; - } - @VisibleForTesting void setMainThreadRunnableTime(Timer timer) { mainThreadRunnableTime = timer; diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java index f0b342c6af5..3177eb310b5 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java @@ -239,35 +239,12 @@ public void testDelayedAppStart() { } @Test - public void testStartFromBackground() { - FakeScheduledExecutorService fakeExecutorService = new FakeScheduledExecutorService(); - AppStartTrace trace = - new AppStartTrace(transportManager, clock, configResolver, fakeExecutorService); - trace.setIsStartFromBackground(); - trace.onActivityCreated(activity1, bundle); - Assert.assertNull(trace.getOnCreateTime()); - ++currentTime; - trace.onActivityStarted(activity1); - Assert.assertNull(trace.getOnStartTime()); - ++currentTime; - trace.onActivityResumed(activity1); - Assert.assertNull(trace.getOnResumeTime()); - fakeExecutorService.runAll(); - // There should be no trace sent. - verify(transportManager, times(0)) - .log( - traceArgumentCaptor.capture(), - ArgumentMatchers.nullable(ApplicationProcessState.class)); - } - - @Test - public void testStartFromBackground_invertedOrder() { + public void testStartFromBackground_within100ms() { FakeScheduledExecutorService fakeExecutorService = new FakeScheduledExecutorService(); Timer fakeTimer = spy(new Timer(currentTime)); AppStartTrace trace = new AppStartTrace(transportManager, clock, configResolver, fakeExecutorService); trace.registerActivityLifecycleCallbacks(appContext); - trace.setIsStartFromBackground(); trace.setMainThreadRunnableTime(fakeTimer); when(fakeTimer.getDurationMicros()).thenReturn(99L); @@ -288,13 +265,12 @@ public void testStartFromBackground_invertedOrder() { } @Test - public void testStartFromBackground_delayedInvertedOrder() { + public void testStartFromBackground_moreThan100ms() { FakeScheduledExecutorService fakeExecutorService = new FakeScheduledExecutorService(); Timer fakeTimer = spy(new Timer(currentTime)); AppStartTrace trace = new AppStartTrace(transportManager, clock, configResolver, fakeExecutorService); trace.registerActivityLifecycleCallbacks(appContext); - trace.setIsStartFromBackground(); trace.setMainThreadRunnableTime(fakeTimer); when(fakeTimer.getDurationMicros()).thenReturn(TimeUnit.MILLISECONDS.toMicros(100) + 1); From 713a0015519afcfb46bc0f1cbac4846b4309c2ac Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Wed, 20 Aug 2025 10:18:19 -0400 Subject: [PATCH 12/14] Fix comment --- .../com/google/firebase/perf/metrics/AppStartTraceTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java index 3177eb310b5..25ad845231a 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java @@ -257,7 +257,8 @@ public void testStartFromBackground_within100ms() { trace.onActivityResumed(activity1); Assert.assertNotNull(trace.getOnResumeTime()); fakeExecutorService.runAll(); - // There should be no trace sent. + // There should be a trace sent since the delay between the main thread and onActivityCreated + // is limited. verify(transportManager, times(1)) .log( traceArgumentCaptor.capture(), @@ -282,8 +283,7 @@ public void testStartFromBackground_moreThan100ms() { ++currentTime; trace.onActivityResumed(activity1); Assert.assertNull(trace.getOnResumeTime()); - // There should be a trace sent since the delay between the main thread and onActivityCreated - // is limited. + // There should be no trace sent. fakeExecutorService.runAll(); verify(transportManager, times(0)) .log( From 07495da4b38a0535be525053d5952e3042f8ab6d Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Wed, 20 Aug 2025 10:21:45 -0400 Subject: [PATCH 13/14] Make change to always set the main thread runnable time --- .../com/google/firebase/perf/metrics/AppStartTrace.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java index 189e6c95106..5e70bf864dd 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java @@ -589,11 +589,7 @@ public StartFromBackgroundRunnable(final AppStartTrace trace) { @Override public void run() { - // if no activity has ever been created. - if (trace.onCreateTime == null) { - trace.mainThreadRunnableTime = new Timer(); - trace.isStartedFromBackground = true; - } + trace.mainThreadRunnableTime = new Timer(); } } From 03effc9a18b5654a2a63c195d53be1cfa9787e2a Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Wed, 20 Aug 2025 10:32:35 -0400 Subject: [PATCH 14/14] Fix multiple checks --- .../com/google/firebase/perf/metrics/AppStartTrace.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java index 5e70bf864dd..ceac4b39335 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java @@ -324,15 +324,19 @@ private void recordOnDrawFrontOfQueue() { } private void resolveIsStartedFromBackground() { - // If the runnable hasn't run, it isn't a background start. + // If the mainThreadRunnableTime is null, either the runnable hasn't run, or this check has + // already been made. if (mainThreadRunnableTime == null) { return; } // Set it to true if the runnable ran more than 100ms prior to onActivityCreated() - if (mainThreadRunnableTime.getDurationMicros() >= MAX_BACKGROUND_RUNNABLE_DELAY) { + if (mainThreadRunnableTime.getDurationMicros() > MAX_BACKGROUND_RUNNABLE_DELAY) { isStartedFromBackground = true; } + + // Set this to null to prevent additional checks if `onActivityCreated()` is called again. + mainThreadRunnableTime = null; } @Override