Skip to content

Commit 45c7ce0

Browse files
authored
Merge c8abed5 into 373370e
2 parents 373370e + c8abed5 commit 45c7ce0

File tree

3 files changed

+113
-3
lines changed

3 files changed

+113
-3
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Fixes
6+
7+
- Fix Ensure app start type is set, even when ActivityLifecycleIntegration is not running ([#4216](https://github.com/getsentry/sentry-java/pull/4216))
8+
39
## 7.22.0
410

511
### Fixes

sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99
import android.content.pm.ProviderInfo;
1010
import android.net.Uri;
1111
import android.os.Build;
12+
import android.os.Bundle;
1213
import android.os.Handler;
1314
import android.os.Looper;
1415
import android.os.Process;
1516
import android.os.SystemClock;
16-
import androidx.annotation.NonNull;
1717
import io.sentry.ILogger;
1818
import io.sentry.ITransactionProfiler;
1919
import io.sentry.JsonSerializer;
@@ -33,6 +33,7 @@
3333
import java.io.FileNotFoundException;
3434
import java.io.InputStreamReader;
3535
import java.io.Reader;
36+
import java.util.concurrent.TimeUnit;
3637
import java.util.concurrent.atomic.AtomicBoolean;
3738
import org.jetbrains.annotations.ApiStatus;
3839
import org.jetbrains.annotations.NotNull;
@@ -204,8 +205,36 @@ private void onAppLaunched(
204205

205206
activityCallback =
206207
new ActivityLifecycleCallbacksAdapter() {
208+
209+
@Override
210+
public void onActivityCreated(
211+
@NotNull Activity activity, @Nullable Bundle savedInstanceState) {
212+
// In case the SDK gets initialized async or the
213+
// ActivityLifecycleIntegration is not enabled (e.g on RN due to Context not being
214+
// instanceof Application)
215+
// the app start type never gets set
216+
if (appStartMetrics.getAppStartType() == AppStartMetrics.AppStartType.UNKNOWN) {
217+
// We consider pre-loaded application loads as warm starts
218+
// This usually happens e.g. due to BroadcastReceivers triggering
219+
// Application.onCreate only, but no Activity.onCreate
220+
final long now = SystemClock.uptimeMillis();
221+
final long durationMs =
222+
now - appStartMetrics.getAppStartTimeSpan().getStartUptimeMs();
223+
if (durationMs > TimeUnit.SECONDS.toMillis(1)) {
224+
appStartMetrics.restartAppStart(now);
225+
appStartMetrics.setAppStartType(AppStartMetrics.AppStartType.WARM);
226+
} else {
227+
// Otherwise a non-null bundle determines the behavior
228+
appStartMetrics.setAppStartType(
229+
savedInstanceState == null
230+
? AppStartMetrics.AppStartType.COLD
231+
: AppStartMetrics.AppStartType.WARM);
232+
}
233+
}
234+
}
235+
207236
@Override
208-
public void onActivityStarted(@NonNull Activity activity) {
237+
public void onActivityStarted(@NotNull Activity activity) {
209238
if (firstDrawDone.get()) {
210239
return;
211240
}

sentry-android-core/src/test/java/io/sentry/android/core/SentryPerformanceProviderTest.kt

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package io.sentry.android.core
22

3+
import android.app.Activity
34
import android.app.Application
5+
import android.app.Application.ActivityLifecycleCallbacks
46
import android.content.pm.ProviderInfo
57
import android.os.Build
8+
import android.os.Bundle
69
import androidx.test.ext.junit.runners.AndroidJUnit4
710
import io.sentry.ILogger
811
import io.sentry.JsonSerializer
@@ -26,6 +29,7 @@ import java.nio.file.Files
2629
import kotlin.test.AfterTest
2730
import kotlin.test.BeforeTest
2831
import kotlin.test.Test
32+
import kotlin.test.assertEquals
2933
import kotlin.test.assertFailsWith
3034
import kotlin.test.assertFalse
3135
import kotlin.test.assertNotNull
@@ -48,6 +52,7 @@ class SentryPerformanceProviderTest {
4852
val providerInfo = ProviderInfo()
4953
val logger = mock<ILogger>()
5054
lateinit var configFile: File
55+
var activityLifecycleCallback: ActivityLifecycleCallbacks? = null
5156

5257
fun getSut(sdkVersion: Int = Build.VERSION_CODES.S, authority: String = AUTHORITY, handleFile: ((config: File) -> Unit)? = null): SentryPerformanceProvider {
5358
val buildInfoProvider: BuildInfoProvider = mock()
@@ -56,7 +61,10 @@ class SentryPerformanceProviderTest {
5661
whenever(mockContext.applicationContext).thenReturn(mockContext)
5762
configFile = File(sentryCache, Sentry.APP_START_PROFILING_CONFIG_FILE_NAME)
5863
handleFile?.invoke(configFile)
59-
64+
whenever(mockContext.registerActivityLifecycleCallbacks(any())).then {
65+
activityLifecycleCallback = it.arguments[0] as ActivityLifecycleCallbacks
66+
return@then Unit
67+
}
6068
providerInfo.authority = authority
6169
return SentryPerformanceProvider(logger, buildInfoProvider).apply {
6270
attachInfo(mockContext, providerInfo)
@@ -232,6 +240,73 @@ class SentryPerformanceProviderTest {
232240
assertFalse(AppStartMetrics.getInstance().appStartProfiler!!.isRunning)
233241
}
234242

243+
@Test
244+
fun `Sets app launch type to cold`() {
245+
val provider = fixture.getSut()
246+
val activity = mock<Activity>()
247+
provider.onCreate()
248+
249+
assertEquals(AppStartMetrics.AppStartType.UNKNOWN, AppStartMetrics.getInstance().appStartType)
250+
251+
// when the first activity has no bundle
252+
fixture.activityLifecycleCallback!!.onActivityCreated(activity, null)
253+
254+
// then the app start is considered cold
255+
assertEquals(AppStartMetrics.AppStartType.COLD, AppStartMetrics.getInstance().appStartType)
256+
257+
// when any subsequent activity launches
258+
fixture.activityLifecycleCallback!!.onActivityCreated(activity, mock<Bundle>())
259+
260+
// then the app start is still considered cold
261+
assertEquals(AppStartMetrics.AppStartType.COLD, AppStartMetrics.getInstance().appStartType)
262+
}
263+
264+
@Test
265+
fun `Sets app launch type to warm if process init is too old`() {
266+
val provider = fixture.getSut()
267+
val activity = mock<Activity>()
268+
provider.onCreate()
269+
270+
assertEquals(AppStartMetrics.AppStartType.UNKNOWN, AppStartMetrics.getInstance().appStartType)
271+
272+
AppStartMetrics.getInstance().appStartTimeSpan.setStartedAt(
273+
AppStartMetrics.getInstance().appStartTimeSpan.startUptimeMs - 20000
274+
)
275+
276+
// when the first activity has no bundle
277+
fixture.activityLifecycleCallback!!.onActivityCreated(activity, null)
278+
279+
// then the app start is considered warm
280+
assertEquals(AppStartMetrics.AppStartType.WARM, AppStartMetrics.getInstance().appStartType)
281+
282+
// when any subsequent activity launches
283+
fixture.activityLifecycleCallback!!.onActivityCreated(activity, mock<Bundle>())
284+
285+
// then the app start is still considered warm
286+
assertEquals(AppStartMetrics.AppStartType.WARM, AppStartMetrics.getInstance().appStartType)
287+
}
288+
289+
@Test
290+
fun `Sets app launch type to warm`() {
291+
val provider = fixture.getSut()
292+
val activity = mock<Activity>()
293+
provider.onCreate()
294+
295+
assertEquals(AppStartMetrics.AppStartType.UNKNOWN, AppStartMetrics.getInstance().appStartType)
296+
297+
// when the first activity has a bundle
298+
fixture.activityLifecycleCallback!!.onActivityCreated(activity, mock<Bundle>())
299+
300+
// then the app start is considered WARM
301+
assertEquals(AppStartMetrics.AppStartType.WARM, AppStartMetrics.getInstance().appStartType)
302+
303+
// when any subsequent activity launches
304+
fixture.activityLifecycleCallback!!.onActivityCreated(activity, null)
305+
306+
// then the app start is still considered warm
307+
assertEquals(AppStartMetrics.AppStartType.WARM, AppStartMetrics.getInstance().appStartType)
308+
}
309+
235310
private fun writeConfig(
236311
configFile: File,
237312
profilingEnabled: Boolean = true,

0 commit comments

Comments
 (0)