Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

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

## 8.7.0

Expand Down
1 change: 1 addition & 0 deletions sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ public final class io/sentry/android/core/BuildInfoProvider {
}

public final class io/sentry/android/core/ContextUtils {
public static fun appIsLibraryForComposePreview (Landroid/content/Context;)Z
public static fun getApplicationContext (Landroid/content/Context;)Landroid/content/Context;
public static fun isForegroundImportance ()Z
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
Expand All @@ -27,6 +28,7 @@
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
Expand Down Expand Up @@ -285,6 +287,36 @@ public static boolean isForegroundImportance() {
return isForegroundImportance.getValue();
}

/**
* Determines if the app is a packaged android library for running Compose Preview Mode
*
* @param context the context
* @return true, if the app is actually a library running as an app for Compose Preview Mode
*/
@ApiStatus.Internal
public static boolean appIsLibraryForComposePreview(final @NotNull Context context) {
// Jetpack Compose Preview (aka "Run Preview on Device")
// uses the androidTest flavor for android library modules,
// so let's fail-fast by checking this first
if (context.getPackageName().endsWith(".test")) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will cover the majority of customers already, great find 💯

try {
final @NotNull ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
final @NotNull List<ActivityManager.AppTask> appTasks = activityManager.getAppTasks();
for (final ActivityManager.AppTask task : appTasks) {
final @Nullable ComponentName component = task.getTaskInfo().baseIntent.getComponent();
if (component != null
&& component.getClassName().equals("androidx.compose.ui.tooling.PreviewActivity")) {
return true;
}
}
} catch (Throwable t) {
// ignored
}
}
return false;
}

/**
* Get the device's current kernel version, as a string. Attempts to read /proc/version, and falls
* back to the 'os.version' System Property.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ public boolean onCreate() {
logger.log(SentryLevel.FATAL, "App. Context from ContentProvider is null");
return false;
}
if (ManifestMetadataReader.isAutoInit(context, logger)) {

if (ManifestMetadataReader.isAutoInit(context, logger)
&& !ContextUtils.appIsLibraryForComposePreview(context)) {
SentryAndroid.init(context, logger);
SentryIntegrationPackageStorage.getInstance().addIntegration("AutoInit");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import android.app.ActivityManager
import android.app.ActivityManager.MemoryInfo
import android.app.ActivityManager.RunningAppProcessInfo
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
Expand Down Expand Up @@ -270,4 +272,35 @@ class ContextUtilsTest {
val appContext = ContextUtils.getApplicationContext(contextMock)
assertSame(appContextMock, appContext)
}

@Test
fun `appIsLibraryForComposePreview is correctly determined`() {
fun getMockContext(
packageName: String,
activityClassName: String
): Context {
val context = mock<Context>()
val activityManager = mock<ActivityManager>()
whenever(context.packageName).thenReturn(packageName)
whenever(context.getSystemService(eq(Context.ACTIVITY_SERVICE))).thenReturn(
activityManager
)
val taskInfo = ActivityManager.RecentTaskInfo()
taskInfo.baseIntent = Intent().setComponent(
ComponentName(
"com.example.library",
activityClassName
)
)
val appTask = mock<ActivityManager.AppTask>()
whenever(appTask.taskInfo).thenReturn(taskInfo)
whenever(activityManager.appTasks).thenReturn(listOf(appTask))

return context
}

assertTrue(ContextUtils.appIsLibraryForComposePreview(getMockContext("com.example.library.test", "androidx.compose.ui.tooling.PreviewActivity")))
assertFalse(ContextUtils.appIsLibraryForComposePreview(getMockContext("com.example.library.test", "com.example.HomeActivity")))
assertFalse(ContextUtils.appIsLibraryForComposePreview(getMockContext("com.example.library", "androidx.compose.ui.tooling.PreviewActivity")))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import io.sentry.Sentry
import io.sentry.test.callMethod
import org.junit.runner.RunWith
import org.mockito.Mockito
import org.mockito.kotlin.any
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import kotlin.use

@RunWith(AndroidJUnit4::class)
class SentryInitProviderTest {
Expand Down Expand Up @@ -153,6 +156,24 @@ class SentryInitProviderTest {
assertFalse(sentryOptions.isEnableNdk)
}

@Test
fun `skips init in compose preview mode`() {
val providerInfo = ProviderInfo()

assertFalse(Sentry.isEnabled())
providerInfo.authority = AUTHORITY

val metaData = Bundle()
metaData.putString(ManifestMetadataReader.DSN, "https://key@sentry.io/123")
val mockContext = ContextUtilsTestHelper.mockMetaData(metaData = metaData)

Mockito.mockStatic(ContextUtils::class.java).use { contextUtils ->
contextUtils.`when`<Boolean> { ContextUtils.appIsLibraryForComposePreview(any()) }.thenReturn(true)
sentryInitProvider.attachInfo(mockContext, providerInfo)
}
assertFalse(Sentry.isEnabled())
}

companion object {
private const val AUTHORITY = "io.sentry.sample.SentryInitProvider"
}
Expand Down
Loading