From 07d9eaa84f57405e1edda0f1a44d41a5604d0d07 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto <5731772+marandaneto@users.noreply.github.com> Date: Thu, 14 May 2020 18:09:45 +0200 Subject: [PATCH] fix: initing SDK on AttachBaseContext (#409) --- .../core/AndroidOptionsInitializer.java | 13 ++++--- .../AppComponentsBreadcrumbsIntegration.java | 18 ++++++++-- .../core/AndroidOptionsInitializerTest.kt | 35 +++++++++++++++++++ ...AppComponentsBreadcrumbsIntegrationTest.kt | 25 +++++++++++++ 4 files changed, 84 insertions(+), 7 deletions(-) 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 94ad54f74..342f80428 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 @@ -48,9 +48,12 @@ static void init( @NotNull Context context, final @NotNull ILogger logger) { Objects.requireNonNull(context, "The context is required."); - context = - Objects.requireNonNull( - context.getApplicationContext(), "The application context is required."); + + // it returns null if ContextImpl, so let's check for nullability + if (context.getApplicationContext() != null) { + context = context.getApplicationContext(); + } + Objects.requireNonNull(options, "The options object is required."); Objects.requireNonNull(logger, "The ILogger object is required."); @@ -113,7 +116,9 @@ private static void installDefaultIntegrations( options.addIntegration(new AnrIntegration()); options.addIntegration(new AppLifecycleIntegration()); - if (context instanceof Application) { // just a guard check, it should be an Application + + // registerActivityLifecycleCallbacks is only available if Context is an AppContext + if (context instanceof Application) { options.addIntegration(new ActivityBreadcrumbsIntegration((Application) context)); } else { options diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegration.java index 923a646cd..701ac2ecf 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegration.java @@ -45,14 +45,26 @@ public void register(final @NotNull IHub hub, final @NotNull SentryOptions optio this.options.isEnableAppComponentBreadcrumbs()); if (this.options.isEnableAppComponentBreadcrumbs()) { - context.registerComponentCallbacks(this); - options.getLogger().log(SentryLevel.DEBUG, "AppComponentsBreadcrumbsIntegration installed."); + try { + // if its a ContextImpl, registerComponentCallbacks can't be used + context.registerComponentCallbacks(this); + options + .getLogger() + .log(SentryLevel.DEBUG, "AppComponentsBreadcrumbsIntegration installed."); + } catch (Exception e) { + this.options.setEnableAppComponentBreadcrumbs(false); + options.getLogger().log(SentryLevel.INFO, e, "ComponentCallbacks2 is not available."); + } } } @Override public void close() throws IOException { - context.unregisterComponentCallbacks(this); + try { + // if its a ContextImpl, unregisterComponentCallbacks can't be used + context.unregisterComponentCallbacks(this); + } catch (Exception ignored) { + } if (options != null) { options.getLogger().log(SentryLevel.DEBUG, "AppComponentsBreadcrumbsIntegration removed."); 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 e5f560fa1..b4a9fbd45 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 @@ -1,8 +1,10 @@ package io.sentry.android.core +import android.app.Application import android.content.Context import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever import io.sentry.core.MainEventProcessor import io.sentry.core.SentryOptions @@ -12,6 +14,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotNull +import kotlin.test.assertNull import kotlin.test.assertTrue import org.junit.runner.RunWith @@ -207,6 +210,38 @@ class AndroidOptionsInitializerTest { assertNotNull(actual) } + @Test + fun `When given Context returns a non null ApplicationContext, uses it`() { + val sentryOptions = SentryAndroidOptions() + val mockApp = mock() + val mockContext = mock() + whenever(mockContext.applicationContext).thenReturn(mockApp) + + AndroidOptionsInitializer.init(sentryOptions, mockContext) + assertNotNull(mockContext) + } + + @Test + fun `When given Context returns a null ApplicationContext is null, keep given Context`() { + val sentryOptions = SentryAndroidOptions() + val mockContext = mock() + whenever(mockContext.applicationContext).thenReturn(null) + + AndroidOptionsInitializer.init(sentryOptions, mockContext) + assertNotNull(mockContext) + } + + @Test + fun `When given Context is not an Application class, do not add ActivityBreadcrumbsIntegration`() { + val sentryOptions = SentryAndroidOptions() + val mockContext = mock() + whenever(mockContext.applicationContext).thenReturn(null) + + AndroidOptionsInitializer.init(sentryOptions, mockContext) + val actual = sentryOptions.integrations.firstOrNull { it is ActivityBreadcrumbsIntegration } + assertNull(actual) + } + private fun createMockContext(): Context { val mockContext = ContextUtilsTest.createMockContext() whenever(mockContext.cacheDir).thenReturn(file) diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegrationTest.kt index 63dbb5cb2..c3c55759f 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegrationTest.kt @@ -9,11 +9,14 @@ import com.nhaarman.mockitokotlin2.check import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.never import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.whenever import io.sentry.core.Breadcrumb import io.sentry.core.IHub import io.sentry.core.SentryLevel +import java.lang.NullPointerException import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFalse import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) @@ -38,6 +41,17 @@ class AppComponentsBreadcrumbsIntegrationTest { verify(fixture.context).registerComponentCallbacks(any()) } + @Test + fun `When app components breadcrumb is enabled, but ComponentCallbacks is not ready, do not throw`() { + val sut = fixture.getSut() + val options = SentryAndroidOptions() + val hub = mock() + sut.register(hub, options) + whenever(fixture.context.registerComponentCallbacks(any())).thenThrow(NullPointerException()) + sut.register(hub, options) + assertFalse(options.isEnableAppComponentBreadcrumbs) + } + @Test fun `When app components breadcrumb is disabled, it doesn't register callback`() { val sut = fixture.getSut() @@ -59,6 +73,17 @@ class AppComponentsBreadcrumbsIntegrationTest { verify(fixture.context).unregisterComponentCallbacks(any()) } + @Test + fun `When app components breadcrumb is closed, but ComponentCallbacks is not ready, do not throw`() { + val sut = fixture.getSut() + val options = SentryAndroidOptions() + val hub = mock() + whenever(fixture.context.registerComponentCallbacks(any())).thenThrow(NullPointerException()) + whenever(fixture.context.unregisterComponentCallbacks(any())).thenThrow(NullPointerException()) + sut.register(hub, options) + sut.close() + } + @Test fun `When low memory event, a breadcrumb with type, category and level should be set`() { val sut = fixture.getSut()