@@ -75,6 +75,11 @@ public class AppStartTrace implements ActivityLifecycleCallbacks, LifecycleObser
7575 private static final @ NonNull Timer PERF_CLASS_LOAD_TIME = new Clock ().getTime ();
7676 private static final long MAX_LATENCY_BEFORE_UI_INIT = TimeUnit .MINUTES .toMicros (1 );
7777
78+ // If the `mainThreadRunnableTime` was set within this duration, the assumption
79+ // is that it was called immediately before `onActivityCreated` in foreground starts on API 34+.
80+ // See b/339891952.
81+ private static final long MAX_BACKGROUND_RUNNABLE_DELAY = TimeUnit .MILLISECONDS .toMicros (50 );
82+
7883 // Core pool size 0 allows threads to shut down if they're idle
7984 private static final int CORE_POOL_SIZE = 0 ;
8085 private static final int MAX_POOL_SIZE = 1 ; // Only need single thread
@@ -111,6 +116,9 @@ public class AppStartTrace implements ActivityLifecycleCallbacks, LifecycleObser
111116 private final @ Nullable Timer processStartTime ;
112117 private final @ Nullable Timer firebaseClassLoadTime ;
113118 private Timer onCreateTime = null ;
119+
120+ // TODO(b/339891952): Explore simplifying Timers in app start trace to use timestamps.
121+ private Timer mainThreadRunnableTime = null ;
114122 private Timer onStartTime = null ;
115123 private Timer onResumeTime = null ;
116124 private Timer firstForegroundTime = null ;
@@ -319,8 +327,44 @@ private void recordOnDrawFrontOfQueue() {
319327 logExperimentTrace (this .experimentTtid );
320328 }
321329
330+ /**
331+ * Sets the `isStartedFromBackground` flag to `true` if the `mainThreadRunnableTime` was set
332+ * from the `StartFromBackgroundRunnable`.
333+ * <p>
334+ * If it's prior to API 34, it's always set to true if `mainThreadRunnableTime` was set.
335+ * <p>
336+ * If it's on or after API 34, and it was called less than `MAX_BACKGROUND_RUNNABLE_DELAY`
337+ * before `onActivityCreated`, the
338+ * assumption is that it was called immediately before the activity lifecycle callbacks in a
339+ * foreground start.
340+ * See b/339891952.
341+ */
342+ private void resolveIsStartedFromBackground () {
343+ // If the mainThreadRunnableTime is null, either the runnable hasn't run, or this check has
344+ // already been made.
345+ if (mainThreadRunnableTime == null ) {
346+ return ;
347+ }
348+
349+ // If the `mainThreadRunnableTime` was set prior to API 34, it's always assumed that's it's
350+ // a background start.
351+ // Otherwise it's assumed to be a background start if the runnable was set more than
352+ // `MAX_BACKGROUND_RUNNABLE_DELAY`
353+ // before the first `onActivityCreated` call.
354+ // TODO(b/339891952): Investigate removing the API check.
355+ if ((Build .VERSION .SDK_INT < 34 )
356+ || (mainThreadRunnableTime .getDurationMicros () > MAX_BACKGROUND_RUNNABLE_DELAY )) {
357+ isStartedFromBackground = true ;
358+ }
359+
360+ // Set this to null to prevent additional checks.
361+ mainThreadRunnableTime = null ;
362+ }
363+
322364 @ Override
323365 public synchronized void onActivityCreated (Activity activity , Bundle savedInstanceState ) {
366+ resolveIsStartedFromBackground ();
367+
324368 if (isStartedFromBackground || onCreateTime != null // An activity already called onCreate()
325369 ) {
326370 return ;
@@ -559,9 +603,9 @@ public static boolean isScreenOn(Context appContext) {
559603 /**
560604 * We use StartFromBackgroundRunnable to detect if app is started from background or foreground.
561605 * If app is started from background, we do not generate AppStart trace. This runnable is posted
562- * to main UI thread from FirebasePerfEarly. If app is started from background, this runnable will
563- * be executed before any activity's onCreate() method. If app is started from foreground,
564- * activity's onCreate() method is executed before this runnable .
606+ * to main UI thread from FirebasePerfEarly. If `onActivityCreate` has never been called, we
607+ * record the timestamp - which allows `onActivityCreate` to determine whether it was a background
608+ * app start or not .
565609 */
566610 public static class StartFromBackgroundRunnable implements Runnable {
567611 private final AppStartTrace trace ;
@@ -572,9 +616,9 @@ public StartFromBackgroundRunnable(final AppStartTrace trace) {
572616
573617 @ Override
574618 public void run () {
575- // if no activity has ever been created .
619+ // Only set the `mainThreadRunnableTime` if `onActivityCreate` has never been called .
576620 if (trace .onCreateTime == null ) {
577- trace .isStartedFromBackground = true ;
621+ trace .mainThreadRunnableTime = new Timer () ;
578622 }
579623 }
580624 }
@@ -614,7 +658,7 @@ Timer getOnResumeTime() {
614658 }
615659
616660 @ VisibleForTesting
617- void setIsStartFromBackground ( ) {
618- isStartedFromBackground = true ;
661+ void setMainThreadRunnableTime ( Timer timer ) {
662+ mainThreadRunnableTime = timer ;
619663 }
620664}
0 commit comments