11package io .sentry .android .core .performance ;
22
3+ import android .app .Activity ;
34import android .app .Application ;
45import android .content .ContentProvider ;
6+ import android .os .Bundle ;
7+ import android .os .Handler ;
8+ import android .os .Looper ;
59import android .os .SystemClock ;
10+ import androidx .annotation .NonNull ;
611import androidx .annotation .Nullable ;
12+ import androidx .annotation .VisibleForTesting ;
713import io .sentry .ITransactionProfiler ;
14+ import io .sentry .SentryDate ;
15+ import io .sentry .SentryNanotimeDate ;
816import io .sentry .TracesSamplingDecision ;
917import io .sentry .android .core .ContextUtils ;
1018import io .sentry .android .core .SentryAndroidOptions ;
1321import java .util .HashMap ;
1422import java .util .List ;
1523import java .util .Map ;
24+ import java .util .concurrent .TimeUnit ;
1625import org .jetbrains .annotations .ApiStatus ;
1726import org .jetbrains .annotations .NotNull ;
1827import org .jetbrains .annotations .TestOnly ;
2332 * transformed into SDK specific txn/span data structures.
2433 */
2534@ ApiStatus .Internal
26- public class AppStartMetrics {
35+ public class AppStartMetrics implements Application . ActivityLifecycleCallbacks {
2736
2837 public enum AppStartType {
2938 UNKNOWN ,
@@ -45,6 +54,8 @@ public enum AppStartType {
4554 private final @ NotNull List <ActivityLifecycleTimeSpan > activityLifecycles ;
4655 private @ Nullable ITransactionProfiler appStartProfiler = null ;
4756 private @ Nullable TracesSamplingDecision appStartSamplingDecision = null ;
57+ private @ Nullable SentryDate onCreateTime = null ;
58+ private boolean appLaunchTooLong = false ;
4859
4960 public static @ NotNull AppStartMetrics getInstance () {
5061
@@ -102,6 +113,11 @@ public boolean isAppLaunchedInForeground() {
102113 return appLaunchedInForeground ;
103114 }
104115
116+ @ VisibleForTesting
117+ public void setAppLaunchedInForeground (final boolean appLaunchedInForeground ) {
118+ this .appLaunchedInForeground = appLaunchedInForeground ;
119+ }
120+
105121 /**
106122 * Provides all collected content provider onCreate time spans
107123 *
@@ -137,12 +153,20 @@ public long getClassLoadedUptimeMs() {
137153 // Only started when sdk version is >= N
138154 final @ NotNull TimeSpan appStartSpan = getAppStartTimeSpan ();
139155 if (appStartSpan .hasStarted ()) {
140- return appStartSpan ;
156+ return validateAppStartSpan ( appStartSpan ) ;
141157 }
142158 }
143159
144160 // fallback: use sdk init time span, as it will always have a start time set
145- return getSdkInitTimeSpan ();
161+ return validateAppStartSpan (getSdkInitTimeSpan ());
162+ }
163+
164+ private @ NotNull TimeSpan validateAppStartSpan (final @ NotNull TimeSpan appStartSpan ) {
165+ // If the app launch took too long or it was launched in the background we return an empty span
166+ if (appLaunchTooLong || !appLaunchedInForeground ) {
167+ return new TimeSpan ();
168+ }
169+ return appStartSpan ;
146170 }
147171
148172 @ TestOnly
@@ -158,6 +182,9 @@ public void clear() {
158182 }
159183 appStartProfiler = null ;
160184 appStartSamplingDecision = null ;
185+ appLaunchTooLong = false ;
186+ appLaunchedInForeground = false ;
187+ onCreateTime = null ;
161188 }
162189
163190 public @ Nullable ITransactionProfiler getAppStartProfiler () {
@@ -195,10 +222,57 @@ public static void onApplicationCreate(final @NotNull Application application) {
195222 final @ NotNull AppStartMetrics instance = getInstance ();
196223 if (instance .applicationOnCreate .hasNotStarted ()) {
197224 instance .applicationOnCreate .setStartedAt (now );
225+ application .registerActivityLifecycleCallbacks (instance );
198226 instance .appLaunchedInForeground = ContextUtils .isForegroundImportance ();
227+ new Handler (Looper .getMainLooper ())
228+ .post (
229+ () -> {
230+ // if no activity has ever been created, app was launched in background
231+ if (instance .onCreateTime == null ) {
232+ instance .appLaunchedInForeground = false ;
233+ }
234+ });
235+ }
236+ }
237+
238+ @ Override
239+ public void onActivityCreated (@ NonNull Activity activity , @ Nullable Bundle savedInstanceState ) {
240+ // An activity already called onCreate()
241+ if (!appLaunchedInForeground || onCreateTime != null ) {
242+ return ;
243+ }
244+ onCreateTime = new SentryNanotimeDate ();
245+
246+ final long spanStartMillis = appStartSpan .getStartTimestampMs ();
247+ final long spanEndMillis =
248+ appStartSpan .hasStopped ()
249+ ? appStartSpan .getProjectedStopTimestampMs ()
250+ : System .currentTimeMillis ();
251+ final long durationMillis = spanEndMillis - spanStartMillis ;
252+ // If the app was launched more than 1 minute ago, it's likely wrong
253+ if (durationMillis > TimeUnit .MINUTES .toMillis (1 )) {
254+ appLaunchTooLong = true ;
199255 }
200256 }
201257
258+ @ Override
259+ public void onActivityStarted (@ NonNull Activity activity ) {}
260+
261+ @ Override
262+ public void onActivityResumed (@ NonNull Activity activity ) {}
263+
264+ @ Override
265+ public void onActivityPaused (@ NonNull Activity activity ) {}
266+
267+ @ Override
268+ public void onActivityStopped (@ NonNull Activity activity ) {}
269+
270+ @ Override
271+ public void onActivitySaveInstanceState (@ NonNull Activity activity , @ NonNull Bundle outState ) {}
272+
273+ @ Override
274+ public void onActivityDestroyed (@ NonNull Activity activity ) {}
275+
202276 /**
203277 * Called by instrumentation
204278 *
0 commit comments