Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Fixes

- Fix Ensure app start type is set, even when ActivityLifecycleIntegration is not running ([#4216](https://github.com/getsentry/sentry-java/pull/4216))

## 7.22.0

### Fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
import android.content.pm.ProviderInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.os.SystemClock;
import androidx.annotation.NonNull;
import io.sentry.ILogger;
import io.sentry.ITransactionProfiler;
import io.sentry.JsonSerializer;
Expand All @@ -33,6 +33,7 @@
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
Expand Down Expand Up @@ -204,8 +205,32 @@ private void onAppLaunched(

activityCallback =
new ActivityLifecycleCallbacksAdapter() {

@Override
public void onActivityCreated(
@NotNull Activity activity, @Nullable Bundle savedInstanceState) {
if (appStartMetrics.getAppStartType() == AppStartMetrics.AppStartType.UNKNOWN) {
// We consider pre-loaded application loads as warm starts
// This usually happens e.g. due to BroadcastReceivers triggering
// Application.onCreate only, but no Activity.onCreate
final long now = SystemClock.uptimeMillis();
final long durationMs =
now - appStartMetrics.getAppStartTimeSpan().getStartUptimeMs();
if (durationMs > TimeUnit.SECONDS.toMillis(1)) {
appStartMetrics.restartAppStart(now);
appStartMetrics.setAppStartType(AppStartMetrics.AppStartType.WARM);
} else {
// Otherwise a non-null bundle determines the behavior
appStartMetrics.setAppStartType(
savedInstanceState == null
? AppStartMetrics.AppStartType.COLD
: AppStartMetrics.AppStartType.WARM);
}
}
}

@Override
public void onActivityStarted(@NonNull Activity activity) {
public void onActivityStarted(@NotNull Activity activity) {
if (firstDrawDone.get()) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package io.sentry.android.core

import android.app.Activity
import android.app.Application
import android.app.Application.ActivityLifecycleCallbacks
import android.content.pm.ProviderInfo
import android.os.Build
import android.os.Bundle
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.sentry.ILogger
import io.sentry.JsonSerializer
Expand All @@ -26,6 +29,7 @@ import java.nio.file.Files
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
Expand All @@ -48,6 +52,7 @@ class SentryPerformanceProviderTest {
val providerInfo = ProviderInfo()
val logger = mock<ILogger>()
lateinit var configFile: File
var activityLifecycleCallback: ActivityLifecycleCallbacks? = null

fun getSut(sdkVersion: Int = Build.VERSION_CODES.S, authority: String = AUTHORITY, handleFile: ((config: File) -> Unit)? = null): SentryPerformanceProvider {
val buildInfoProvider: BuildInfoProvider = mock()
Expand All @@ -56,7 +61,10 @@ class SentryPerformanceProviderTest {
whenever(mockContext.applicationContext).thenReturn(mockContext)
configFile = File(sentryCache, Sentry.APP_START_PROFILING_CONFIG_FILE_NAME)
handleFile?.invoke(configFile)

whenever(mockContext.registerActivityLifecycleCallbacks(any())).then {
activityLifecycleCallback = it.arguments[0] as ActivityLifecycleCallbacks
return@then Unit
}
providerInfo.authority = authority
return SentryPerformanceProvider(logger, buildInfoProvider).apply {
attachInfo(mockContext, providerInfo)
Expand Down Expand Up @@ -232,6 +240,73 @@ class SentryPerformanceProviderTest {
assertFalse(AppStartMetrics.getInstance().appStartProfiler!!.isRunning)
}

@Test
fun `Sets app launch type to cold`() {
val provider = fixture.getSut()
val activity = mock<Activity>()
provider.onCreate()

assertEquals(AppStartMetrics.AppStartType.UNKNOWN, AppStartMetrics.getInstance().appStartType)

// when the first activity has no bundle
fixture.activityLifecycleCallback!!.onActivityCreated(activity, null)

// then the app start is considered cold
assertEquals(AppStartMetrics.AppStartType.COLD, AppStartMetrics.getInstance().appStartType)

// when any subsequent activity launches
fixture.activityLifecycleCallback!!.onActivityCreated(activity, mock<Bundle>())

// then the app start is still considered cold
assertEquals(AppStartMetrics.AppStartType.COLD, AppStartMetrics.getInstance().appStartType)
}

@Test
fun `Sets app launch type to warm if process init is too old`() {
val provider = fixture.getSut()
val activity = mock<Activity>()
provider.onCreate()

assertEquals(AppStartMetrics.AppStartType.UNKNOWN, AppStartMetrics.getInstance().appStartType)

AppStartMetrics.getInstance().appStartTimeSpan.setStartedAt(
AppStartMetrics.getInstance().appStartTimeSpan.startUptimeMs - 20000
)

// when the first activity has no bundle
fixture.activityLifecycleCallback!!.onActivityCreated(activity, null)

// then the app start is considered warm
assertEquals(AppStartMetrics.AppStartType.WARM, AppStartMetrics.getInstance().appStartType)

// when any subsequent activity launches
fixture.activityLifecycleCallback!!.onActivityCreated(activity, mock<Bundle>())

// then the app start is still considered warm
assertEquals(AppStartMetrics.AppStartType.WARM, AppStartMetrics.getInstance().appStartType)
}

@Test
fun `Sets app launch type to warm`() {
val provider = fixture.getSut()
val activity = mock<Activity>()
provider.onCreate()

assertEquals(AppStartMetrics.AppStartType.UNKNOWN, AppStartMetrics.getInstance().appStartType)

// when the first activity has a bundle
fixture.activityLifecycleCallback!!.onActivityCreated(activity, mock<Bundle>())

// then the app start is considered WARM
assertEquals(AppStartMetrics.AppStartType.WARM, AppStartMetrics.getInstance().appStartType)

// when any subsequent activity launches
fixture.activityLifecycleCallback!!.onActivityCreated(activity, null)

// then the app start is still considered warm
assertEquals(AppStartMetrics.AppStartType.WARM, AppStartMetrics.getInstance().appStartType)
}

private fun writeConfig(
configFile: File,
profilingEnabled: Boolean = true,
Expand Down
Loading