2121import io .sentry .android .core .SentryAndroidOptions ;
2222import io .sentry .android .core .internal .util .FirstDrawDoneListener ;
2323import io .sentry .util .AutoClosableReentrantLock ;
24+ import io .sentry .util .LazyEvaluator ;
2425import java .util .ArrayList ;
2526import java .util .Collections ;
2627import java .util .HashMap ;
@@ -56,7 +57,14 @@ public enum AppStartType {
5657 new AutoClosableReentrantLock ();
5758
5859 private @ NotNull AppStartType appStartType = AppStartType .UNKNOWN ;
59- private boolean appLaunchedInForeground ;
60+ private final LazyEvaluator <Boolean > appLaunchedInForeground =
61+ new LazyEvaluator <>(
62+ new LazyEvaluator .Evaluator <Boolean >() {
63+ @ Override
64+ public @ NotNull Boolean evaluate () {
65+ return ContextUtils .isForegroundImportance ();
66+ }
67+ });
6068 private volatile long firstPostUptimeMillis = -1 ;
6169
6270 private final @ NotNull TimeSpan appStartSpan ;
@@ -90,7 +98,6 @@ public AppStartMetrics() {
9098 applicationOnCreate = new TimeSpan ();
9199 contentProviderOnCreates = new HashMap <>();
92100 activityLifecycles = new ArrayList <>();
93- appLaunchedInForeground = ContextUtils .isForegroundImportance ();
94101 }
95102
96103 /**
@@ -141,12 +148,12 @@ public void setAppStartType(final @NotNull AppStartType appStartType) {
141148 }
142149
143150 public boolean isAppLaunchedInForeground () {
144- return appLaunchedInForeground ;
151+ return appLaunchedInForeground . getValue () ;
145152 }
146153
147154 @ VisibleForTesting
148155 public void setAppLaunchedInForeground (final boolean appLaunchedInForeground ) {
149- this .appLaunchedInForeground = appLaunchedInForeground ;
156+ this .appLaunchedInForeground . setValue ( appLaunchedInForeground ) ;
150157 }
151158
152159 /**
@@ -177,7 +184,7 @@ public void onAppStartSpansSent() {
177184 }
178185
179186 public boolean shouldSendStartMeasurements () {
180- return shouldSendStartMeasurements && appLaunchedInForeground ;
187+ return shouldSendStartMeasurements && appLaunchedInForeground . getValue () ;
181188 }
182189
183190 public long getClassLoadedUptimeMs () {
@@ -192,7 +199,7 @@ public long getClassLoadedUptimeMs() {
192199 final @ NotNull SentryAndroidOptions options ) {
193200 // If the app start type was never determined or app wasn't launched in foreground,
194201 // the app start is considered invalid
195- if (appStartType != AppStartType .UNKNOWN && appLaunchedInForeground ) {
202+ if (appStartType != AppStartType .UNKNOWN && appLaunchedInForeground . getValue () ) {
196203 if (options .isEnablePerformanceV2 ()) {
197204 // Only started when sdk version is >= N
198205 final @ NotNull TimeSpan appStartSpan = getAppStartTimeSpan ();
@@ -213,6 +220,16 @@ public long getClassLoadedUptimeMs() {
213220 return new TimeSpan ();
214221 }
215222
223+ @ TestOnly
224+ void setFirstPostUptimeMillis (final long firstPostUptimeMillis ) {
225+ this .firstPostUptimeMillis = firstPostUptimeMillis ;
226+ }
227+
228+ @ TestOnly
229+ long getFirstPostUptimeMillis () {
230+ return firstPostUptimeMillis ;
231+ }
232+
216233 @ TestOnly
217234 public void clear () {
218235 appStartType = AppStartType .UNKNOWN ;
@@ -230,7 +247,7 @@ public void clear() {
230247 }
231248 appStartContinuousProfiler = null ;
232249 appStartSamplingDecision = null ;
233- appLaunchedInForeground = false ;
250+ appLaunchedInForeground . setValue ( false ) ;
234251 isCallbackRegistered = false ;
235252 shouldSendStartMeasurements = true ;
236253 firstDrawDone .set (false );
@@ -312,7 +329,7 @@ public void registerLifecycleCallbacks(final @NotNull Application application) {
312329 return ;
313330 }
314331 isCallbackRegistered = true ;
315- appLaunchedInForeground = appLaunchedInForeground || ContextUtils . isForegroundImportance ();
332+ appLaunchedInForeground . resetValue ();
316333 application .registerActivityLifecycleCallbacks (instance );
317334 // We post on the main thread a task to post a check on the main thread. On Pixel devices
318335 // (possibly others) the first task posted on the main thread is called before the
@@ -335,7 +352,7 @@ private void checkCreateTimeOnMain() {
335352 () -> {
336353 // if no activity has ever been created, app was launched in background
337354 if (activeActivitiesCounter .get () == 0 ) {
338- appLaunchedInForeground = false ;
355+ appLaunchedInForeground . setValue ( false ) ;
339356
340357 // we stop the app start profilers, as they are useless and likely to timeout
341358 if (appStartProfiler != null && appStartProfiler .isRunning ()) {
@@ -352,6 +369,7 @@ private void checkCreateTimeOnMain() {
352369
353370 @ Override
354371 public void onActivityCreated (@ NonNull Activity activity , @ Nullable Bundle savedInstanceState ) {
372+ final long activityCreatedUptimeMillis = SystemClock .uptimeMillis ();
355373 CurrentActivityHolder .getInstance ().setActivity (activity );
356374
357375 // the first activity determines the app start type
@@ -360,25 +378,27 @@ public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle saved
360378
361379 // If the app (process) was launched more than 1 minute ago, consider it a warm start
362380 final long durationSinceAppStartMillis = nowUptimeMs - appStartSpan .getStartUptimeMs ();
363- if (!appLaunchedInForeground || durationSinceAppStartMillis > TimeUnit .MINUTES .toMillis (1 )) {
381+ if (!appLaunchedInForeground .getValue ()
382+ || durationSinceAppStartMillis > TimeUnit .MINUTES .toMillis (1 )) {
364383 appStartType = AppStartType .WARM ;
365-
366384 shouldSendStartMeasurements = true ;
367385 appStartSpan .reset ();
368- appStartSpan .start ();
369- appStartSpan .setStartedAt (nowUptimeMs );
370- CLASS_LOADED_UPTIME_MS = nowUptimeMs ;
386+ appStartSpan .setStartedAt (activityCreatedUptimeMillis );
387+ CLASS_LOADED_UPTIME_MS = activityCreatedUptimeMillis ;
371388 contentProviderOnCreates .clear ();
372389 applicationOnCreate .reset ();
373390 } else if (savedInstanceState != null ) {
374391 appStartType = AppStartType .WARM ;
375- } else if (firstPostUptimeMillis > 0 && nowUptimeMs > firstPostUptimeMillis ) {
392+ } else if (firstPostUptimeMillis != -1
393+ && activityCreatedUptimeMillis > firstPostUptimeMillis ) {
394+ // Application creation always queues Activity creation
395+ // So if Activity is created after our first measured post, it's a warm start
376396 appStartType = AppStartType .WARM ;
377397 } else {
378398 appStartType = AppStartType .COLD ;
379399 }
380400 }
381- appLaunchedInForeground = true ;
401+ appLaunchedInForeground . setValue ( true ) ;
382402 }
383403
384404 @ Override
@@ -417,9 +437,9 @@ public void onActivityDestroyed(@NonNull Activity activity) {
417437
418438 final int remainingActivities = activeActivitiesCounter .decrementAndGet ();
419439 // if the app is moving into background
420- // as the next Activity is considered like a new app start
440+ // as the next onActivityCreated will treat it as a new warm app start
421441 if (remainingActivities == 0 && !activity .isChangingConfigurations ()) {
422- appLaunchedInForeground = false ;
442+ appLaunchedInForeground . setValue ( true ) ;
423443 shouldSendStartMeasurements = true ;
424444 firstDrawDone .set (false );
425445 }
0 commit comments