Skip to content

Commit f80b9e8

Browse files
authored
Fix do not initialize SDK for Jetpack Compose Preview builds (#4324)
* Fix do not initialize SDK for Jetpack Compose Preview builds * Update Changelog
1 parent 286b57f commit f80b9e8

File tree

6 files changed

+91
-1
lines changed

6 files changed

+91
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
- Use thread context classloader when available ([#4320](https://github.com/getsentry/sentry-java/pull/4320))
88
- This ensures correct resource loading in environments like Spring Boot where the thread context classloader is used for resource loading.
9+
- Fix do not initialize SDK for Jetpack Compose Preview builds ([#4324](https://github.com/getsentry/sentry-java/pull/4324))
910

1011
## 8.7.0
1112

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ public final class io/sentry/android/core/BuildInfoProvider {
185185
}
186186

187187
public final class io/sentry/android/core/ContextUtils {
188+
public static fun appIsLibraryForComposePreview (Landroid/content/Context;)Z
188189
public static fun getApplicationContext (Landroid/content/Context;)Landroid/content/Context;
189190
public static fun isForegroundImportance ()Z
190191
}

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import android.annotation.SuppressLint;
88
import android.app.ActivityManager;
99
import android.content.BroadcastReceiver;
10+
import android.content.ComponentName;
1011
import android.content.Context;
1112
import android.content.Intent;
1213
import android.content.IntentFilter;
@@ -27,6 +28,7 @@
2728
import java.io.IOException;
2829
import java.util.Arrays;
2930
import java.util.HashMap;
31+
import java.util.List;
3032
import java.util.Map;
3133
import org.jetbrains.annotations.ApiStatus;
3234
import org.jetbrains.annotations.NotNull;
@@ -285,6 +287,36 @@ public static boolean isForegroundImportance() {
285287
return isForegroundImportance.getValue();
286288
}
287289

290+
/**
291+
* Determines if the app is a packaged android library for running Compose Preview Mode
292+
*
293+
* @param context the context
294+
* @return true, if the app is actually a library running as an app for Compose Preview Mode
295+
*/
296+
@ApiStatus.Internal
297+
public static boolean appIsLibraryForComposePreview(final @NotNull Context context) {
298+
// Jetpack Compose Preview (aka "Run Preview on Device")
299+
// uses the androidTest flavor for android library modules,
300+
// so let's fail-fast by checking this first
301+
if (context.getPackageName().endsWith(".test")) {
302+
try {
303+
final @NotNull ActivityManager activityManager =
304+
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
305+
final @NotNull List<ActivityManager.AppTask> appTasks = activityManager.getAppTasks();
306+
for (final ActivityManager.AppTask task : appTasks) {
307+
final @Nullable ComponentName component = task.getTaskInfo().baseIntent.getComponent();
308+
if (component != null
309+
&& component.getClassName().equals("androidx.compose.ui.tooling.PreviewActivity")) {
310+
return true;
311+
}
312+
}
313+
} catch (Throwable t) {
314+
// ignored
315+
}
316+
}
317+
return false;
318+
}
319+
288320
/**
289321
* Get the device's current kernel version, as a string. Attempts to read /proc/version, and falls
290322
* back to the 'os.version' System Property.

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ public boolean onCreate() {
2121
logger.log(SentryLevel.FATAL, "App. Context from ContentProvider is null");
2222
return false;
2323
}
24-
if (ManifestMetadataReader.isAutoInit(context, logger)) {
24+
25+
if (ManifestMetadataReader.isAutoInit(context, logger)
26+
&& !ContextUtils.appIsLibraryForComposePreview(context)) {
2527
SentryAndroid.init(context, logger);
2628
SentryIntegrationPackageStorage.getInstance().addIntegration("AutoInit");
2729
}

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import android.app.ActivityManager
55
import android.app.ActivityManager.MemoryInfo
66
import android.app.ActivityManager.RunningAppProcessInfo
77
import android.content.BroadcastReceiver
8+
import android.content.ComponentName
89
import android.content.Context
10+
import android.content.Intent
911
import android.content.IntentFilter
1012
import android.content.pm.ApplicationInfo
1113
import android.content.pm.PackageInfo
@@ -270,4 +272,35 @@ class ContextUtilsTest {
270272
val appContext = ContextUtils.getApplicationContext(contextMock)
271273
assertSame(appContextMock, appContext)
272274
}
275+
276+
@Test
277+
fun `appIsLibraryForComposePreview is correctly determined`() {
278+
fun getMockContext(
279+
packageName: String,
280+
activityClassName: String
281+
): Context {
282+
val context = mock<Context>()
283+
val activityManager = mock<ActivityManager>()
284+
whenever(context.packageName).thenReturn(packageName)
285+
whenever(context.getSystemService(eq(Context.ACTIVITY_SERVICE))).thenReturn(
286+
activityManager
287+
)
288+
val taskInfo = ActivityManager.RecentTaskInfo()
289+
taskInfo.baseIntent = Intent().setComponent(
290+
ComponentName(
291+
"com.example.library",
292+
activityClassName
293+
)
294+
)
295+
val appTask = mock<ActivityManager.AppTask>()
296+
whenever(appTask.taskInfo).thenReturn(taskInfo)
297+
whenever(activityManager.appTasks).thenReturn(listOf(appTask))
298+
299+
return context
300+
}
301+
302+
assertTrue(ContextUtils.appIsLibraryForComposePreview(getMockContext("com.example.library.test", "androidx.compose.ui.tooling.PreviewActivity")))
303+
assertFalse(ContextUtils.appIsLibraryForComposePreview(getMockContext("com.example.library.test", "com.example.HomeActivity")))
304+
assertFalse(ContextUtils.appIsLibraryForComposePreview(getMockContext("com.example.library", "androidx.compose.ui.tooling.PreviewActivity")))
305+
}
273306
}

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
77
import io.sentry.Sentry
88
import io.sentry.test.callMethod
99
import org.junit.runner.RunWith
10+
import org.mockito.Mockito
11+
import org.mockito.kotlin.any
1012
import kotlin.test.BeforeTest
1113
import kotlin.test.Test
1214
import kotlin.test.assertFailsWith
1315
import kotlin.test.assertFalse
1416
import kotlin.test.assertTrue
17+
import kotlin.use
1518

1619
@RunWith(AndroidJUnit4::class)
1720
class SentryInitProviderTest {
@@ -153,6 +156,24 @@ class SentryInitProviderTest {
153156
assertFalse(sentryOptions.isEnableNdk)
154157
}
155158

159+
@Test
160+
fun `skips init in compose preview mode`() {
161+
val providerInfo = ProviderInfo()
162+
163+
assertFalse(Sentry.isEnabled())
164+
providerInfo.authority = AUTHORITY
165+
166+
val metaData = Bundle()
167+
metaData.putString(ManifestMetadataReader.DSN, "https://key@sentry.io/123")
168+
val mockContext = ContextUtilsTestHelper.mockMetaData(metaData = metaData)
169+
170+
Mockito.mockStatic(ContextUtils::class.java).use { contextUtils ->
171+
contextUtils.`when`<Boolean> { ContextUtils.appIsLibraryForComposePreview(any()) }.thenReturn(true)
172+
sentryInitProvider.attachInfo(mockContext, providerInfo)
173+
}
174+
assertFalse(Sentry.isEnabled())
175+
}
176+
156177
companion object {
157178
private const val AUTHORITY = "io.sentry.sample.SentryInitProvider"
158179
}

0 commit comments

Comments
 (0)