Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Ensure app start type is set, even when ActivityLifecycleIntegration is not running #4216

Open
wants to merge 17 commits into
base: 7.x.x
Choose a base branch
from
Open
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