@@ -75,6 +75,11 @@ public class AppStartTrace implements ActivityLifecycleCallbacks, LifecycleObser
75
75
private static final @ NonNull Timer PERF_CLASS_LOAD_TIME = new Clock ().getTime ();
76
76
private static final long MAX_LATENCY_BEFORE_UI_INIT = TimeUnit .MINUTES .toMicros (1 );
77
77
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
+
78
83
// Core pool size 0 allows threads to shut down if they're idle
79
84
private static final int CORE_POOL_SIZE = 0 ;
80
85
private static final int MAX_POOL_SIZE = 1 ; // Only need single thread
@@ -111,6 +116,9 @@ public class AppStartTrace implements ActivityLifecycleCallbacks, LifecycleObser
111
116
private final @ Nullable Timer processStartTime ;
112
117
private final @ Nullable Timer firebaseClassLoadTime ;
113
118
private Timer onCreateTime = null ;
119
+
120
+ // TODO(b/339891952): Explore simplifying Timers in app start trace to use timestamps.
121
+ private Timer mainThreadRunnableTime = null ;
114
122
private Timer onStartTime = null ;
115
123
private Timer onResumeTime = null ;
116
124
private Timer firstForegroundTime = null ;
@@ -319,8 +327,44 @@ private void recordOnDrawFrontOfQueue() {
319
327
logExperimentTrace (this .experimentTtid );
320
328
}
321
329
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
+
322
364
@ Override
323
365
public synchronized void onActivityCreated (Activity activity , Bundle savedInstanceState ) {
366
+ resolveIsStartedFromBackground ();
367
+
324
368
if (isStartedFromBackground || onCreateTime != null // An activity already called onCreate()
325
369
) {
326
370
return ;
@@ -559,9 +603,9 @@ public static boolean isScreenOn(Context appContext) {
559
603
/**
560
604
* We use StartFromBackgroundRunnable to detect if app is started from background or foreground.
561
605
* 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 .
565
609
*/
566
610
public static class StartFromBackgroundRunnable implements Runnable {
567
611
private final AppStartTrace trace ;
@@ -572,9 +616,9 @@ public StartFromBackgroundRunnable(final AppStartTrace trace) {
572
616
573
617
@ Override
574
618
public void run () {
575
- // if no activity has ever been created .
619
+ // Only set the `mainThreadRunnableTime` if `onActivityCreate` has never been called .
576
620
if (trace .onCreateTime == null ) {
577
- trace .isStartedFromBackground = true ;
621
+ trace .mainThreadRunnableTime = new Timer () ;
578
622
}
579
623
}
580
624
}
@@ -614,7 +658,7 @@ Timer getOnResumeTime() {
614
658
}
615
659
616
660
@ VisibleForTesting
617
- void setIsStartFromBackground ( ) {
618
- isStartedFromBackground = true ;
661
+ void setMainThreadRunnableTime ( Timer timer ) {
662
+ mainThreadRunnableTime = timer ;
619
663
}
620
664
}
0 commit comments