Skip to content

Commit 2b54c67

Browse files
authored
Merge 3596b1c into 9762f09
2 parents 9762f09 + 3596b1c commit 2b54c67

27 files changed

+99
-29
lines changed

buildSrc/src/main/java/Config.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ object Config {
5353
val appCompat = "androidx.appcompat:appcompat:1.3.0"
5454
val timber = "com.jakewharton.timber:timber:4.7.1"
5555
val okhttp = "com.squareup.okhttp3:okhttp:$okHttpVersion"
56-
val leakCanary = "com.squareup.leakcanary:leakcanary-android:2.8.1"
56+
val leakCanary = "com.squareup.leakcanary:leakcanary-android:2.14"
5757
val constraintLayout = "androidx.constraintlayout:constraintlayout:2.1.3"
5858

5959
private val lifecycleVersion = "2.2.0"
@@ -197,6 +197,7 @@ object Config {
197197
val hsqldb = "org.hsqldb:hsqldb:2.6.1"
198198
val javaFaker = "com.github.javafaker:javafaker:1.0.2"
199199
val msgpack = "org.msgpack:msgpack-core:0.9.8"
200+
val leakCanaryInstrumentation = "com.squareup.leakcanary:leakcanary-android-instrumentation:2.14"
200201
}
201202

202203
object QualityPlugins {

sentry-android-core/api/sentry-android-core.api

+1
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ public final class io/sentry/android/core/BuildInfoProvider {
158158
}
159159

160160
public final class io/sentry/android/core/ContextUtils {
161+
public static fun getApplicationContext (Landroid/content/Context;)Landroid/content/Context;
161162
public static fun isForegroundImportance ()Z
162163
}
163164

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

+1-4
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,7 @@ static void loadDefaultAndMetadataOptions(
9090
final @NotNull BuildInfoProvider buildInfoProvider) {
9191
Objects.requireNonNull(context, "The context is required.");
9292

93-
// it returns null if ContextImpl, so let's check for nullability
94-
if (context.getApplicationContext() != null) {
95-
context = context.getApplicationContext();
96-
}
93+
context = ContextUtils.getApplicationContext(context);
9794

9895
Objects.requireNonNull(options, "The options object is required.");
9996
Objects.requireNonNull(logger, "The ILogger object is required.");

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,9 @@ public AndroidTransactionProfiler(
8787
final boolean isProfilingEnabled,
8888
final int profilingTracesHz,
8989
final @NotNull ISentryExecutorService executorService) {
90-
this.context = Objects.requireNonNull(context, "The application context is required");
90+
this.context =
91+
Objects.requireNonNull(
92+
ContextUtils.getApplicationContext(context), "The application context is required");
9193
this.logger = Objects.requireNonNull(logger, "ILogger is required");
9294
this.frameMetricsCollector =
9395
Objects.requireNonNull(frameMetricsCollector, "SentryFrameMetricsCollector is required");

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public final class AnrIntegration implements Integration, Closeable {
3333
private final @NotNull Object startLock = new Object();
3434

3535
public AnrIntegration(final @NotNull Context context) {
36-
this.context = context;
36+
this.context = ContextUtils.getApplicationContext(context);
3737
}
3838

3939
/**

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public AnrV2EventProcessor(
9797
final @NotNull SentryAndroidOptions options,
9898
final @NotNull BuildInfoProvider buildInfoProvider,
9999
final @Nullable SecureRandom random) {
100-
this.context = context;
100+
this.context = ContextUtils.getApplicationContext(context);
101101
this.options = options;
102102
this.buildInfoProvider = buildInfoProvider;
103103
this.random = random;

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public AnrV2Integration(final @NotNull Context context) {
6363

6464
AnrV2Integration(
6565
final @NotNull Context context, final @NotNull ICurrentDateProvider dateProvider) {
66-
this.context = context;
66+
this.context = ContextUtils.getApplicationContext(context);
6767
this.dateProvider = dateProvider;
6868
}
6969

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ public final class AppComponentsBreadcrumbsIntegration
2929
private @Nullable SentryAndroidOptions options;
3030

3131
public AppComponentsBreadcrumbsIntegration(final @NotNull Context context) {
32-
this.context = Objects.requireNonNull(context, "Context is required");
32+
this.context =
33+
Objects.requireNonNull(ContextUtils.getApplicationContext(context), "Context is required");
3334
}
3435

3536
@Override

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

+15
Original file line numberDiff line numberDiff line change
@@ -383,4 +383,19 @@ static void setAppPackageInfo(
383383
}
384384
app.setPermissions(permissions);
385385
}
386+
387+
/**
388+
* Get the app context
389+
*
390+
* @return the app context, or if not available, the provided context
391+
*/
392+
@NotNull
393+
public static Context getApplicationContext(final @NotNull Context context) {
394+
// it returns null if ContextImpl, so let's check for nullability
395+
final @Nullable Context appContext = context.getApplicationContext();
396+
if (appContext != null) {
397+
return appContext;
398+
}
399+
return context;
400+
}
386401
}

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ public DefaultAndroidEventProcessor(
4747
final @NotNull Context context,
4848
final @NotNull BuildInfoProvider buildInfoProvider,
4949
final @NotNull SentryAndroidOptions options) {
50-
this.context = Objects.requireNonNull(context, "The application context is required.");
50+
this.context =
51+
Objects.requireNonNull(
52+
ContextUtils.getApplicationContext(context), "The application context is required.");
5153
this.buildInfoProvider =
5254
Objects.requireNonNull(buildInfoProvider, "The BuildInfoProvider is required.");
5355
this.options = Objects.requireNonNull(options, "The options object is required.");
@@ -57,7 +59,7 @@ public DefaultAndroidEventProcessor(
5759
// some device info performs disk I/O, but it's result is cached, let's pre-cache it
5860
final @NotNull ExecutorService executorService = Executors.newSingleThreadExecutor();
5961
this.deviceInfoUtil =
60-
executorService.submit(() -> DeviceInfoUtil.getInstance(context, options));
62+
executorService.submit(() -> DeviceInfoUtil.getInstance(this.context, options));
6163
executorService.shutdown();
6264
}
6365

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public static DeviceInfoUtil getInstance(
7676
if (instance == null) {
7777
synchronized (DeviceInfoUtil.class) {
7878
if (instance == null) {
79-
instance = new DeviceInfoUtil(context.getApplicationContext(), options);
79+
instance = new DeviceInfoUtil(ContextUtils.getApplicationContext(context), options);
8080
}
8181
}
8282
}

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ public NetworkBreadcrumbsIntegration(
4242
final @NotNull Context context,
4343
final @NotNull BuildInfoProvider buildInfoProvider,
4444
final @NotNull ILogger logger) {
45-
this.context = Objects.requireNonNull(context, "Context is required");
45+
this.context =
46+
Objects.requireNonNull(ContextUtils.getApplicationContext(context), "Context is required");
4647
this.buildInfoProvider =
4748
Objects.requireNonNull(buildInfoProvider, "BuildInfoProvider is required");
4849
this.logger = Objects.requireNonNull(logger, "ILogger is required");

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ public final class PhoneStateBreadcrumbsIntegration implements Integration, Clos
2828
private final @NotNull Object startLock = new Object();
2929

3030
public PhoneStateBreadcrumbsIntegration(final @NotNull Context context) {
31-
this.context = Objects.requireNonNull(context, "Context is required");
31+
this.context =
32+
Objects.requireNonNull(ContextUtils.getApplicationContext(context), "Context is required");
3233
}
3334

3435
@Override

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

+2-3
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,9 @@ private void launchAppStartProfiler(final @NotNull AppStartMetrics appStartMetri
159159

160160
final @NotNull ITransactionProfiler appStartProfiler =
161161
new AndroidTransactionProfiler(
162-
context.getApplicationContext(),
162+
context,
163163
buildInfoProvider,
164-
new SentryFrameMetricsCollector(
165-
context.getApplicationContext(), logger, buildInfoProvider),
164+
new SentryFrameMetricsCollector(context, logger, buildInfoProvider),
166165
logger,
167166
profilingOptions.getProfilingTracesDirPath(),
168167
profilingOptions.isProfilingEnabled(),

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ public SystemEventsBreadcrumbsIntegration(final @NotNull Context context) {
7777

7878
public SystemEventsBreadcrumbsIntegration(
7979
final @NotNull Context context, final @NotNull List<String> actions) {
80-
this.context = Objects.requireNonNull(context, "Context is required");
80+
this.context =
81+
Objects.requireNonNull(ContextUtils.getApplicationContext(context), "Context is required");
8182
this.actions = Objects.requireNonNull(actions, "Actions list is required");
8283
}
8384

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ public final class TempSensorBreadcrumbsIntegration
3434
private final @NotNull Object startLock = new Object();
3535

3636
public TempSensorBreadcrumbsIntegration(final @NotNull Context context) {
37-
this.context = Objects.requireNonNull(context, "Context is required");
37+
this.context =
38+
Objects.requireNonNull(ContextUtils.getApplicationContext(context), "Context is required");
3839
}
3940

4041
@Override

sentry-android-core/src/main/java/io/sentry/android/core/internal/debugmeta/AssetsDebugMetaLoader.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import android.content.res.AssetManager;
77
import io.sentry.ILogger;
88
import io.sentry.SentryLevel;
9+
import io.sentry.android.core.ContextUtils;
910
import io.sentry.internal.debugmeta.IDebugMetaLoader;
1011
import java.io.BufferedInputStream;
1112
import java.io.FileNotFoundException;
@@ -24,7 +25,7 @@ public final class AssetsDebugMetaLoader implements IDebugMetaLoader {
2425
private final @NotNull ILogger logger;
2526

2627
public AssetsDebugMetaLoader(final @NotNull Context context, final @NotNull ILogger logger) {
27-
this.context = context;
28+
this.context = ContextUtils.getApplicationContext(context);
2829
this.logger = logger;
2930
}
3031

sentry-android-core/src/main/java/io/sentry/android/core/internal/modules/AssetsModulesLoader.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import android.content.Context;
44
import io.sentry.ILogger;
55
import io.sentry.SentryLevel;
6+
import io.sentry.android.core.ContextUtils;
67
import io.sentry.internal.modules.ModulesLoader;
78
import java.io.FileNotFoundException;
89
import java.io.IOException;
@@ -19,7 +20,7 @@ public final class AssetsModulesLoader extends ModulesLoader {
1920

2021
public AssetsModulesLoader(final @NotNull Context context, final @NotNull ILogger logger) {
2122
super(logger);
22-
this.context = context;
23+
this.context = ContextUtils.getApplicationContext(context);
2324
}
2425

2526
@Override

sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidConnectionStatusProvider.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import io.sentry.ILogger;
1313
import io.sentry.SentryLevel;
1414
import io.sentry.android.core.BuildInfoProvider;
15+
import io.sentry.android.core.ContextUtils;
1516
import java.util.HashMap;
1617
import java.util.Map;
1718
import org.jetbrains.annotations.ApiStatus;
@@ -37,7 +38,7 @@ public AndroidConnectionStatusProvider(
3738
@NotNull Context context,
3839
@NotNull ILogger logger,
3940
@NotNull BuildInfoProvider buildInfoProvider) {
40-
this.context = context;
41+
this.context = ContextUtils.getApplicationContext(context);
4142
this.logger = logger;
4243
this.buildInfoProvider = buildInfoProvider;
4344
this.registeredCallbacks = new HashMap<>();

sentry-android-core/src/main/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollector.java

+6-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import io.sentry.SentryLevel;
1818
import io.sentry.SentryOptions;
1919
import io.sentry.android.core.BuildInfoProvider;
20+
import io.sentry.android.core.ContextUtils;
2021
import io.sentry.util.Objects;
2122
import java.lang.ref.WeakReference;
2223
import java.lang.reflect.Field;
@@ -84,15 +85,17 @@ public SentryFrameMetricsCollector(
8485
final @NotNull ILogger logger,
8586
final @NotNull BuildInfoProvider buildInfoProvider,
8687
final @NotNull WindowFrameMetricsManager windowFrameMetricsManager) {
87-
Objects.requireNonNull(context, "The context is required");
88+
final @NotNull Context appContext =
89+
Objects.requireNonNull(
90+
ContextUtils.getApplicationContext(context), "The context is required");
8891
this.logger = Objects.requireNonNull(logger, "Logger is required");
8992
this.buildInfoProvider =
9093
Objects.requireNonNull(buildInfoProvider, "BuildInfoProvider is required");
9194
this.windowFrameMetricsManager =
9295
Objects.requireNonNull(windowFrameMetricsManager, "WindowFrameMetricsManager is required");
9396

9497
// registerActivityLifecycleCallbacks is only available if Context is an AppContext
95-
if (!(context instanceof Application)) {
98+
if (!(appContext instanceof Application)) {
9699
return;
97100
}
98101
// FrameMetrics api is only available since sdk version N
@@ -110,7 +113,7 @@ public SentryFrameMetricsCollector(
110113

111114
// We have to register the lifecycle callback, even if no profile is started, otherwise when we
112115
// start a profile, we wouldn't have the current activity and couldn't get the frameMetrics.
113-
((Application) context).registerActivityLifecycleCallbacks(this);
116+
((Application) appContext).registerActivityLifecycleCallbacks(this);
114117

115118
// Most considerations regarding timestamps of frames are inspired from JankStats library:
116119
// https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:metrics/metrics-performance/src/main/java/androidx/metrics/performance/JankStatsApi24Impl.kt

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

+18
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import kotlin.test.assertEquals
2929
import kotlin.test.assertFalse
3030
import kotlin.test.assertNotNull
3131
import kotlin.test.assertNull
32+
import kotlin.test.assertSame
3233
import kotlin.test.assertTrue
3334

3435
@Config(sdk = [33])
@@ -213,4 +214,21 @@ class ContextUtilsTest {
213214
)
214215
assertFalse(ContextUtils.isForegroundImportance())
215216
}
217+
218+
@Test
219+
fun `getApplicationContext returns context if app context is null`() {
220+
val contextMock = mock<Context>()
221+
val appContext = ContextUtils.getApplicationContext(contextMock)
222+
assertSame(contextMock, appContext)
223+
}
224+
225+
@Test
226+
fun `getApplicationContext returns app context`() {
227+
val contextMock = mock<Context>()
228+
val appContextMock = mock<Context>()
229+
whenever(contextMock.applicationContext).thenReturn(appContextMock)
230+
231+
val appContext = ContextUtils.getApplicationContext(contextMock)
232+
assertSame(appContextMock, appContext)
233+
}
216234
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ class SentryAndroidTest {
362362
optionsConfig: (SentryAndroidOptions) -> Unit = {},
363363
callback: (session: Session?) -> Unit
364364
) {
365-
Mockito.mockStatic(ContextUtils::class.java).use { mockedContextUtils ->
365+
Mockito.mockStatic(ContextUtils::class.java, Mockito.CALLS_REAL_METHODS).use { mockedContextUtils ->
366366
mockedContextUtils.`when`<Any> { ContextUtils.isForegroundImportance() }
367367
.thenReturn(inForeground)
368368
SentryAndroid.init(context) { options ->

sentry-android-integration-tests/sentry-uitest-android/build.gradle.kts

+2
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ dependencies {
109109
implementation(Config.Libs.androidxRecylerView)
110110
implementation(Config.Libs.constraintLayout)
111111
implementation(Config.TestLibs.espressoIdlingResource)
112+
implementation(Config.Libs.leakCanary)
112113

113114
compileOnly(Config.CompileOnly.nopen)
114115
errorprone(Config.CompileOnly.nopenChecker)
@@ -123,6 +124,7 @@ dependencies {
123124
androidTestImplementation(Config.TestLibs.androidxTestCoreKtx)
124125
androidTestImplementation(Config.TestLibs.mockWebserver)
125126
androidTestImplementation(Config.TestLibs.androidxJunit)
127+
androidTestImplementation(Config.TestLibs.leakCanaryInstrumentation)
126128
androidTestUtil(Config.TestLibs.androidxTestOrchestrator)
127129
}
128130

sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/BaseUiTest.kt

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ abstract class BaseUiTest {
7777
*/
7878
protected fun initSentry(
7979
relayWaitForRequests: Boolean = false,
80+
context: Context = this.context,
8081
optionsConfiguration: ((options: SentryAndroidOptions) -> Unit)? = null
8182
) {
8283
relay.waitForRequests = relayWaitForRequests

sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/SdkInitTests.kt

+15
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import io.sentry.android.core.AndroidLogger
99
import io.sentry.android.core.SentryAndroidOptions
1010
import io.sentry.assertEnvelopeTransaction
1111
import io.sentry.protocol.SentryTransaction
12+
import leakcanary.LeakAssertions
1213
import org.junit.runner.RunWith
1314
import kotlin.test.Test
1415
import kotlin.test.assertEquals
@@ -154,4 +155,18 @@ class SdkInitTests : BaseUiTest() {
154155
val restartMs = afterRestart - beforeRestart
155156
assertTrue(restartMs > 3000, "Expected more than 3000 ms for SDK close and restart. Got $restartMs ms")
156157
}
158+
159+
@Test
160+
fun initViaActivityDoesNotLeak() {
161+
val activityScenario = launchActivity<ComposeActivity>()
162+
activityScenario.moveToState(Lifecycle.State.RESUMED)
163+
164+
activityScenario.onActivity { activity ->
165+
initSentry(context = activity)
166+
}
167+
168+
activityScenario.moveToState(Lifecycle.State.DESTROYED)
169+
170+
LeakAssertions.assertNoLeaks()
171+
}
157172
}

sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt

+3-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import io.sentry.android.replay.capture.SessionCaptureStrategy
2323
import io.sentry.android.replay.gestures.GestureRecorder
2424
import io.sentry.android.replay.gestures.TouchRecorderCallback
2525
import io.sentry.android.replay.util.MainLooperHandler
26+
import io.sentry.android.replay.util.appContext
2627
import io.sentry.android.replay.util.sample
2728
import io.sentry.android.replay.util.submitSafely
2829
import io.sentry.cache.PersistingScopeObserver
@@ -51,7 +52,7 @@ public class ReplayIntegration(
5152

5253
// needed for the Java's call site
5354
constructor(context: Context, dateProvider: ICurrentDateProvider) : this(
54-
context,
55+
context.appContext(),
5556
dateProvider,
5657
null,
5758
null,
@@ -67,7 +68,7 @@ public class ReplayIntegration(
6768
replayCaptureStrategyProvider: ((isFullSession: Boolean) -> CaptureStrategy)? = null,
6869
mainLooperHandler: MainLooperHandler? = null,
6970
gestureRecorderProvider: (() -> GestureRecorder)? = null
70-
) : this(context, dateProvider, recorderProvider, recorderConfigProvider, replayCacheProvider) {
71+
) : this(context.appContext(), dateProvider, recorderProvider, recorderConfigProvider, replayCacheProvider) {
7172
this.replayCaptureStrategyProvider = replayCaptureStrategyProvider
7273
this.mainLooperHandler = mainLooperHandler ?: MainLooperHandler()
7374
this.gestureRecorderProvider = gestureRecorderProvider
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package io.sentry.android.replay.util
2+
3+
import android.content.Context
4+
5+
internal fun Context.appContext() = this.applicationContext ?: this

0 commit comments

Comments
 (0)