diff --git a/CHANGELOG.md b/CHANGELOG.md index 4495100edb..42fad9afe9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,8 @@ - Set `-Dio.opentelemetry.context.contextStorageProvider=io.sentry.opentelemetry.SentryContextStorageProvider` on your `java` command - Sentry will then wrap the other `ContextStorageProvider` that has been configured by loading it through SPI - If no other `ContextStorageProvider` is available or there are problems loading it, we fall back to using `SentryOtelThreadLocalStorage` - +- Fallback to `context.applicationContext` if `Sentry.init(context)` is not instance of Application ([#4355](https://github.com/getsentry/sentry-java/pull/4355)) + ### Fixes - Update profile chunk rate limit and client report ([#4353](https://github.com/getsentry/sentry-java/pull/4353)) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java index b5cc3ddc93..a0d9d36b7e 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java @@ -357,23 +357,23 @@ static void installDefaultIntegrations( // it to set the replayId in case of an ANR options.addIntegration(AnrIntegrationFactory.create(context, buildInfoProvider)); - // registerActivityLifecycleCallbacks is only available if Context is an AppContext - if (context instanceof Application) { + // registerActivityLifecycleCallbacks is only available on AppContext + if (ContextUtils.getApplicationContext(context) instanceof Application) { + final Application application = (Application) ContextUtils.getApplicationContext(context); options.addIntegration( - new ActivityLifecycleIntegration( - (Application) context, buildInfoProvider, activityFramesTracker)); - options.addIntegration(new ActivityBreadcrumbsIntegration((Application) context)); - options.addIntegration(new CurrentActivityIntegration((Application) context)); - options.addIntegration(new UserInteractionIntegration((Application) context, loadClass)); + new ActivityLifecycleIntegration(application, buildInfoProvider, activityFramesTracker)); + options.addIntegration(new ActivityBreadcrumbsIntegration(application)); + options.addIntegration(new CurrentActivityIntegration(application)); + options.addIntegration(new UserInteractionIntegration(application, loadClass)); if (isFragmentAvailable) { - options.addIntegration(new FragmentLifecycleIntegration((Application) context, true, true)); + options.addIntegration(new FragmentLifecycleIntegration(application, true, true)); } } else { options .getLogger() .log( SentryLevel.WARNING, - "ActivityLifecycle, FragmentLifecycle and UserInteraction Integrations need an Application class to be installed."); + "ActivityLifecycle, FragmentLifecycle and UserInteraction Integrations need context or getApplicationContext() to be an Application class to be installed."); } if (isTimberAvailable) { diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt index 504d2bda83..5df48ece43 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt @@ -70,18 +70,21 @@ class AndroidOptionsInitializerTest { useRealContext: Boolean = false, configureOptions: SentryAndroidOptions.() -> Unit = {}, configureContext: Context.() -> Unit = {}, - assets: AssetManager? = null + assets: AssetManager? = null, + customMockContext: Context? = null, + isFragmentAvailable: Boolean = false ) { sentryOptions.executorService = ImmediateExecutorService() - mockContext = if (metadata != null) { - ContextUtilsTestHelper.mockMetaData( - mockContext = ContextUtilsTestHelper.createMockContext(hasAppContext), - metaData = metadata, - assets = assets - ) - } else { - ContextUtilsTestHelper.createMockContext(hasAppContext) - } + mockContext = customMockContext + ?: if (metadata != null) { + ContextUtilsTestHelper.mockMetaData( + mockContext = ContextUtilsTestHelper.createMockContext(hasAppContext), + metaData = metadata, + assets = assets + ) + } else { + ContextUtilsTestHelper.createMockContext(hasAppContext) + } whenever(mockContext.cacheDir).thenReturn(file) if (mockContext.applicationContext != null) { whenever(mockContext.applicationContext.cacheDir).thenReturn(file) @@ -101,7 +104,7 @@ class AndroidOptionsInitializerTest { BuildInfoProvider(AndroidLogger()), loadClass, activityFramesTracker, - false, + isFragmentAvailable, false, false ) @@ -880,4 +883,24 @@ class AndroidOptionsInitializerTest { assertFalse { fixture.sentryOptions.socketTagger is AndroidSocketTagger } assertFalse { fixture.sentryOptions.compositePerformanceCollector is DefaultCompositePerformanceCollector } } + + @Test + fun `When given context which is not instance of Application use applicationContext to initialize activityLifecycleCallbacks integrations`() { + fixture.initSut(isFragmentAvailable = true) + + assertNotNull(fixture.sentryOptions.integrations.firstOrNull { it is ActivityBreadcrumbsIntegration }) + assertNotNull(fixture.sentryOptions.integrations.firstOrNull { it is CurrentActivityIntegration }) + assertNotNull(fixture.sentryOptions.integrations.firstOrNull { it is UserInteractionIntegration }) + assertNotNull(fixture.sentryOptions.integrations.firstOrNull { it is FragmentLifecycleIntegration }) // Only when fragment is available + } + + @Test + fun `When given context applicationContext is not App do not initialize activityLifecycleCallbacks integrations`() { + fixture.initSut(customMockContext = mock()) + + assertNull(fixture.sentryOptions.integrations.firstOrNull { it is ActivityBreadcrumbsIntegration }) + assertNull(fixture.sentryOptions.integrations.firstOrNull { it is CurrentActivityIntegration }) + assertNull(fixture.sentryOptions.integrations.firstOrNull { it is UserInteractionIntegration }) + assertNull(fixture.sentryOptions.integrations.firstOrNull { it is FragmentLifecycleIntegration }) // Only when fragment is available + } }