From 95282dbfb8b5bad79d9dd1c8c05cc793d21e9c08 Mon Sep 17 00:00:00 2001 From: Stefano Date: Fri, 12 Sep 2025 16:57:06 +0200 Subject: [PATCH 1/5] SentryAndroid.init now sets strictMode to Lax and reset it after it finishes Removed few useless IO calls --- .../io/sentry/android/core/SentryAndroid.java | 8 +++++++ .../core/cache/AndroidEnvelopeCache.java | 17 ++++++------- .../core/internal/util/CpuInfoUtils.java | 2 -- .../io/sentry/uitest/android/SdkInitTests.kt | 8 +++++++ .../java/io/sentry/DirectoryProcessor.java | 24 +++++-------------- .../java/io/sentry/cache/EnvelopeCache.java | 15 ++++++------ .../test/java/io/sentry/EnvelopeSenderTest.kt | 11 +++++---- 7 files changed, 44 insertions(+), 41 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java index d183d4c45be..29e812fde70 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java @@ -4,6 +4,7 @@ import android.app.Application; import android.content.Context; import android.os.Process; +import android.os.StrictMode; import android.os.SystemClock; import io.sentry.ILogger; import io.sentry.IScopes; @@ -93,6 +94,10 @@ public static void init( @NotNull final Context context, @NotNull ILogger logger, @NotNull Sentry.OptionsConfiguration configuration) { + final @NotNull StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); + final @NotNull StrictMode.VmPolicy oldVmPolicy = StrictMode.getVmPolicy(); + StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.LAX); + StrictMode.setVmPolicy(StrictMode.VmPolicy.LAX); try (final @NotNull ISentryLifecycleToken ignored = staticLock.acquire()) { Sentry.init( OptionsContainer.create(SentryAndroidOptions.class), @@ -210,6 +215,9 @@ public static void init( throw new RuntimeException("Failed to initialize Sentry's SDK", e); } + + StrictMode.setThreadPolicy(oldPolicy); + StrictMode.setVmPolicy(oldVmPolicy); } /** diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/cache/AndroidEnvelopeCache.java b/sentry-android-core/src/main/java/io/sentry/android/core/cache/AndroidEnvelopeCache.java index 2829abc50c9..af57727a8d2 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/cache/AndroidEnvelopeCache.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/cache/AndroidEnvelopeCache.java @@ -19,6 +19,7 @@ import io.sentry.util.HintUtils; import io.sentry.util.Objects; import java.io.File; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.OutputStream; import org.jetbrains.annotations.ApiStatus; @@ -157,18 +158,18 @@ public static boolean hasStartupCrashMarker(final @NotNull SentryOptions options final File lastAnrMarker = new File(cacheDirPath, LAST_ANR_REPORT); try { - if (lastAnrMarker.exists() && lastAnrMarker.canRead()) { - final String content = FileUtils.readText(lastAnrMarker); - // we wrapped into try-catch already - //noinspection ConstantConditions - return content.equals("null") ? null : Long.parseLong(content.trim()); - } else { + final String content = FileUtils.readText(lastAnrMarker); + // we wrapped into try-catch already + //noinspection ConstantConditions + return content.equals("null") ? null : Long.parseLong(content.trim()); + } catch (Throwable e) { + if (e instanceof FileNotFoundException) { options .getLogger() .log(DEBUG, "Last ANR marker does not exist. %s.", lastAnrMarker.getAbsolutePath()); + } else { + options.getLogger().log(ERROR, "Error reading last ANR marker", e); } - } catch (Throwable e) { - options.getLogger().log(ERROR, "Error reading last ANR marker", e); } return null; } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CpuInfoUtils.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CpuInfoUtils.java index 019db99fc7d..c289a9c06ac 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CpuInfoUtils.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CpuInfoUtils.java @@ -51,8 +51,6 @@ private CpuInfoUtils() {} if (!cpuDir.getName().matches("cpu[0-9]+")) continue; File cpuMaxFreqFile = new File(cpuDir, CPUINFO_MAX_FREQ_PATH); - if (!cpuMaxFreqFile.exists() || !cpuMaxFreqFile.canRead()) continue; - long khz; try { String content = FileUtils.readText(cpuMaxFreqFile); diff --git a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/SdkInitTests.kt b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/SdkInitTests.kt index 07c9cd391a3..a21402c456f 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/SdkInitTests.kt +++ b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/SdkInitTests.kt @@ -1,5 +1,6 @@ package io.sentry.uitest.android +import android.os.StrictMode import androidx.lifecycle.Lifecycle import androidx.test.core.app.launchActivity import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -269,6 +270,13 @@ class SdkInitTests : BaseUiTest() { assertDefaultIntegrations() } + @Test + fun initNotThrowStrictMode() { + StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder().detectAll().penaltyDeath().build()) + StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder().detectAll().penaltyDeath().build()) + initSentry() + } + private fun assertDefaultIntegrations() { val integrations = mutableListOf( diff --git a/sentry/src/main/java/io/sentry/DirectoryProcessor.java b/sentry/src/main/java/io/sentry/DirectoryProcessor.java index a6bb258f30d..1bc277b18cd 100644 --- a/sentry/src/main/java/io/sentry/DirectoryProcessor.java +++ b/sentry/src/main/java/io/sentry/DirectoryProcessor.java @@ -40,34 +40,22 @@ public void processDirectory(final @NotNull File directory) { try { logger.log(SentryLevel.DEBUG, "Processing dir. %s", directory.getAbsolutePath()); - if (!directory.exists()) { + final File[] filteredListFiles = directory.listFiles((d, name) -> isRelevantFileName(name)); + if (filteredListFiles == null) { logger.log( - SentryLevel.WARNING, - "Directory '%s' doesn't exist. No cached events to send.", + SentryLevel.ERROR, + "Cache dir %s is null or is not a directory.", directory.getAbsolutePath()); return; } - if (!directory.isDirectory()) { - logger.log( - SentryLevel.ERROR, "Cache dir %s is not a directory.", directory.getAbsolutePath()); - return; - } - - final File[] listFiles = directory.listFiles(); - if (listFiles == null) { - logger.log(SentryLevel.ERROR, "Cache dir %s is null.", directory.getAbsolutePath()); - return; - } - - final File[] filteredListFiles = directory.listFiles((d, name) -> isRelevantFileName(name)); logger.log( SentryLevel.DEBUG, "Processing %d items from cache dir %s", - filteredListFiles != null ? filteredListFiles.length : 0, + filteredListFiles.length, directory.getAbsolutePath()); - for (File file : listFiles) { + for (File file : filteredListFiles) { // it ignores .sentry-native database folder and new ones that might come up if (!file.isFile()) { logger.log(SentryLevel.DEBUG, "File %s is not a File.", file.getAbsolutePath()); diff --git a/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java b/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java index d2a32d44cd0..893bef4ab90 100644 --- a/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java +++ b/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java @@ -336,18 +336,17 @@ public void discard(final @NotNull SentryEnvelope envelope) { Objects.requireNonNull(envelope, "Envelope is required."); final File envelopeFile = getEnvelopeFile(envelope); - if (envelopeFile.exists()) { + if (envelopeFile.delete()) { options .getLogger() .log(DEBUG, "Discarding envelope from cache: %s", envelopeFile.getAbsolutePath()); - - if (!envelopeFile.delete()) { - options - .getLogger() - .log(ERROR, "Failed to delete envelope: %s", envelopeFile.getAbsolutePath()); - } } else { - options.getLogger().log(DEBUG, "Envelope was not cached: %s", envelopeFile.getAbsolutePath()); + options + .getLogger() + .log( + DEBUG, + "Envelope was not cached or could not be deleted: %s", + envelopeFile.getAbsolutePath()); } } diff --git a/sentry/src/test/java/io/sentry/EnvelopeSenderTest.kt b/sentry/src/test/java/io/sentry/EnvelopeSenderTest.kt index 4ddc5561af8..dce7c3d5863 100644 --- a/sentry/src/test/java/io/sentry/EnvelopeSenderTest.kt +++ b/sentry/src/test/java/io/sentry/EnvelopeSenderTest.kt @@ -61,8 +61,8 @@ class EnvelopeSenderTest { val sut = fixture.getSut() sut.processDirectory(File("i don't exist")) verify(fixture.logger)!!.log( - eq(SentryLevel.WARNING), - eq("Directory '%s' doesn't exist. No cached events to send."), + eq(SentryLevel.ERROR), + eq("Cache dir %s is null or is not a directory."), any(), ) verifyNoMoreInteractions(fixture.scopes) @@ -79,14 +79,14 @@ class EnvelopeSenderTest { sut.processDirectory(testFile) verify(fixture.logger)!!.log( eq(SentryLevel.ERROR), - eq("Cache dir %s is not a directory."), + eq("Cache dir %s is null or is not a directory."), any(), ) verifyNoMoreInteractions(fixture.scopes) } @Test - fun `when directory has non event files, processDirectory logs that`() { + fun `when directory has non event files, processDirectory skips them`() { val sut = fixture.getSut() val testFile = File( @@ -96,7 +96,8 @@ class EnvelopeSenderTest { testFile.deleteOnExit() verify(fixture.logger)!!.log( eq(SentryLevel.DEBUG), - eq("File '%s' doesn't match extension expected."), + eq("Processing %d items from cache dir %s"), + eq(0), any(), ) verify(fixture.scopes, never())!!.captureEnvelope(any(), anyOrNull()) From 683bb3b265641218623e2c4a796bc998e4c8aca7 Mon Sep 17 00:00:00 2001 From: Stefano Date: Fri, 12 Sep 2025 16:59:01 +0200 Subject: [PATCH 2/5] updated changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16b1bb53246..3f4883b579f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ } ``` +### Fixes + +- Avoid StrictMode warnings ([#4724](https://github.com/getsentry/sentry-java/pull/4724)) + ### Improvements - Remove internal API status from get/setDistinctId ([#4708](https://github.com/getsentry/sentry-java/pull/4708)) From 3f3eb002563a6f7f799f68487155bd455b933977 Mon Sep 17 00:00:00 2001 From: Stefano Date: Fri, 12 Sep 2025 17:09:36 +0200 Subject: [PATCH 3/5] restored old policy in finally block --- .../src/main/java/io/sentry/android/core/SentryAndroid.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java index 29e812fde70..2c06263e155 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java @@ -214,10 +214,10 @@ public static void init( logger.log(SentryLevel.FATAL, "Fatal error during SentryAndroid.init(...)", e); throw new RuntimeException("Failed to initialize Sentry's SDK", e); + } finally { + StrictMode.setThreadPolicy(oldPolicy); + StrictMode.setVmPolicy(oldVmPolicy); } - - StrictMode.setThreadPolicy(oldPolicy); - StrictMode.setVmPolicy(oldVmPolicy); } /** From ad0dd38f1f7d7b6bdc3f70d308c7facf15c4dc1e Mon Sep 17 00:00:00 2001 From: Stefano Date: Mon, 13 Oct 2025 11:37:53 +0200 Subject: [PATCH 4/5] added SentryOptions.runtimeManager to run methods with Stricmode LAX policies --- .../core/AndroidOptionsInitializer.java | 16 ++++-- .../io/sentry/android/core/SentryAndroid.java | 8 --- .../core/SentryPerformanceProvider.java | 6 +- .../internal/util/AndroidRuntimeManager.java | 21 +++++++ .../core/AndroidOptionsInitializerTest.kt | 7 +++ .../util/AndroidRuntimeManagerTest.kt | 56 +++++++++++++++++++ .../io/sentry/uitest/android/SdkInitTests.kt | 2 + sentry/api/sentry.api | 15 +++++ sentry/src/main/java/io/sentry/Sentry.java | 9 +-- .../main/java/io/sentry/SentryOptions.java | 25 +++++++++ .../sentry/util/runtime/IRuntimeManager.java | 13 +++++ .../util/runtime/NeutralRuntimeManager.java | 12 ++++ .../util/runtime/NeutralRuntimeManagerTest.kt | 19 +++++++ 13 files changed, 191 insertions(+), 18 deletions(-) create mode 100644 sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidRuntimeManager.java create mode 100644 sentry-android-core/src/test/java/io/sentry/android/core/internal/util/AndroidRuntimeManagerTest.kt create mode 100644 sentry/src/main/java/io/sentry/util/runtime/IRuntimeManager.java create mode 100644 sentry/src/main/java/io/sentry/util/runtime/NeutralRuntimeManager.java create mode 100644 sentry/src/test/java/io/sentry/util/runtime/NeutralRuntimeManagerTest.kt 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 21dde74d3ee..d8ff5269a36 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 @@ -29,6 +29,7 @@ import io.sentry.android.core.internal.modules.AssetsModulesLoader; import io.sentry.android.core.internal.util.AndroidConnectionStatusProvider; import io.sentry.android.core.internal.util.AndroidCurrentDateProvider; +import io.sentry.android.core.internal.util.AndroidRuntimeManager; import io.sentry.android.core.internal.util.AndroidThreadChecker; import io.sentry.android.core.internal.util.SentryFrameMetricsCollector; import io.sentry.android.core.performance.AppStartMetrics; @@ -105,7 +106,7 @@ static void loadDefaultAndMetadataOptions( final @NotNull BuildInfoProvider buildInfoProvider) { Objects.requireNonNull(context, "The context is required."); - context = ContextUtils.getApplicationContext(context); + @NotNull final Context finalContext = ContextUtils.getApplicationContext(context); Objects.requireNonNull(options, "The options object is required."); Objects.requireNonNull(logger, "The ILogger object is required."); @@ -117,17 +118,22 @@ static void loadDefaultAndMetadataOptions( options.setDefaultScopeType(ScopeType.CURRENT); options.setOpenTelemetryMode(SentryOpenTelemetryMode.OFF); options.setDateProvider(new SentryAndroidDateProvider()); + options.setRuntimeManager(new AndroidRuntimeManager()); // set a lower flush timeout on Android to avoid ANRs options.setFlushTimeoutMillis(DEFAULT_FLUSH_TIMEOUT_MS); options.setFrameMetricsCollector( - new SentryFrameMetricsCollector(context, logger, buildInfoProvider)); + new SentryFrameMetricsCollector(finalContext, logger, buildInfoProvider)); - ManifestMetadataReader.applyMetadata(context, options, buildInfoProvider); - options.setCacheDirPath(getCacheDir(context).getAbsolutePath()); + ManifestMetadataReader.applyMetadata(finalContext, options, buildInfoProvider); - readDefaultOptionValues(options, context, buildInfoProvider); + options.setCacheDirPath( + options + .getRuntimeManager() + .runWithRelaxedPolicy(() -> getCacheDir(finalContext).getAbsolutePath())); + + readDefaultOptionValues(options, finalContext, buildInfoProvider); AppState.getInstance().registerLifecycleObserver(options); } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java index 2c06263e155..d183d4c45be 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java @@ -4,7 +4,6 @@ import android.app.Application; import android.content.Context; import android.os.Process; -import android.os.StrictMode; import android.os.SystemClock; import io.sentry.ILogger; import io.sentry.IScopes; @@ -94,10 +93,6 @@ public static void init( @NotNull final Context context, @NotNull ILogger logger, @NotNull Sentry.OptionsConfiguration configuration) { - final @NotNull StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); - final @NotNull StrictMode.VmPolicy oldVmPolicy = StrictMode.getVmPolicy(); - StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.LAX); - StrictMode.setVmPolicy(StrictMode.VmPolicy.LAX); try (final @NotNull ISentryLifecycleToken ignored = staticLock.acquire()) { Sentry.init( OptionsContainer.create(SentryAndroidOptions.class), @@ -214,9 +209,6 @@ public static void init( logger.log(SentryLevel.FATAL, "Fatal error during SentryAndroid.init(...)", e); throw new RuntimeException("Failed to initialize Sentry's SDK", e); - } finally { - StrictMode.setThreadPolicy(oldPolicy); - StrictMode.setVmPolicy(oldVmPolicy); } } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java index 3c162aab1ad..12ec55f8e1b 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java @@ -20,10 +20,12 @@ import io.sentry.SentryOptions; import io.sentry.TracesSampler; import io.sentry.TracesSamplingDecision; +import io.sentry.android.core.internal.util.AndroidRuntimeManager; import io.sentry.android.core.internal.util.SentryFrameMetricsCollector; import io.sentry.android.core.performance.AppStartMetrics; import io.sentry.android.core.performance.TimeSpan; import io.sentry.util.AutoClosableReentrantLock; +import io.sentry.util.runtime.IRuntimeManager; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -108,7 +110,9 @@ private void launchAppStartProfiler(final @NotNull AppStartMetrics appStartMetri return; } - final @NotNull File cacheDir = AndroidOptionsInitializer.getCacheDir(context); + final @NotNull IRuntimeManager runtimeManager = new AndroidRuntimeManager(); + final @NotNull File cacheDir = + runtimeManager.runWithRelaxedPolicy(() -> AndroidOptionsInitializer.getCacheDir(context)); final @NotNull File configFile = new File(cacheDir, APP_START_PROFILING_CONFIG_FILE_NAME); // No config exists: app start profiling is not enabled diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidRuntimeManager.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidRuntimeManager.java new file mode 100644 index 00000000000..e9d6485dc2e --- /dev/null +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidRuntimeManager.java @@ -0,0 +1,21 @@ +package io.sentry.android.core.internal.util; + +import android.os.StrictMode; +import io.sentry.util.runtime.IRuntimeManager; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +@ApiStatus.Internal +public final class AndroidRuntimeManager implements IRuntimeManager { + @Override + public T runWithRelaxedPolicy(final @NotNull IRuntimeManagerCallback toRun) { + final @NotNull StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); + final @NotNull StrictMode.VmPolicy oldVmPolicy = StrictMode.getVmPolicy(); + StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.LAX); + StrictMode.setVmPolicy(StrictMode.VmPolicy.LAX); + final @NotNull T t = toRun.run(); + StrictMode.setThreadPolicy(oldPolicy); + StrictMode.setVmPolicy(oldVmPolicy); + return t; + } +} 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 cd1a7cc26de..4f946c924e8 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 @@ -23,6 +23,7 @@ import io.sentry.android.core.internal.debugmeta.AssetsDebugMetaLoader import io.sentry.android.core.internal.gestures.AndroidViewGestureTargetLocator import io.sentry.android.core.internal.modules.AssetsModulesLoader import io.sentry.android.core.internal.util.AndroidConnectionStatusProvider +import io.sentry.android.core.internal.util.AndroidRuntimeManager import io.sentry.android.core.internal.util.AndroidThreadChecker import io.sentry.android.core.performance.AppStartMetrics import io.sentry.android.fragment.FragmentLifecycleIntegration @@ -885,4 +886,10 @@ class AndroidOptionsInitializerTest { fixture.sentryOptions.compositePerformanceCollector is DefaultCompositePerformanceCollector } } + + @Test + fun `AndroidRuntimeManager is set in the options`() { + fixture.initSut() + assertIs(fixture.sentryOptions.runtimeManager) + } } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/AndroidRuntimeManagerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/AndroidRuntimeManagerTest.kt new file mode 100644 index 00000000000..a1f572c4817 --- /dev/null +++ b/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/AndroidRuntimeManagerTest.kt @@ -0,0 +1,56 @@ +package io.sentry.android.core.internal.util + +import android.os.StrictMode +import androidx.test.ext.junit.runners.AndroidJUnit4 +import kotlin.test.AfterTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals +import kotlin.test.assertTrue +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class AndroidRuntimeManagerTest { + + val sut = AndroidRuntimeManager() + + @AfterTest + fun `clean up`() { + // Revert StrictMode policies to avoid issues with other tests + StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.LAX) + StrictMode.setVmPolicy(StrictMode.VmPolicy.LAX) + } + + @Test + fun `runWithRelaxedPolicy changes policy when running and restores it afterwards`() { + var called = false + val threadPolicy = StrictMode.ThreadPolicy.Builder().detectAll().penaltyDeath().build() + val vmPolicy = StrictMode.VmPolicy.Builder().detectAll().penaltyDeath().build() + assertNotEquals(StrictMode.ThreadPolicy.LAX, threadPolicy) + assertNotEquals(StrictMode.VmPolicy.LAX, vmPolicy) + + // Set and assert the StrictMode policies + StrictMode.setThreadPolicy(threadPolicy) + StrictMode.setVmPolicy(vmPolicy) + assertEquals(threadPolicy.toString(), StrictMode.getThreadPolicy().toString()) + assertEquals(vmPolicy.toString(), StrictMode.getVmPolicy().toString()) + + // Run the function and assert LAX policies + called = + sut.runWithRelaxedPolicy { + assertEquals( + StrictMode.ThreadPolicy.LAX.toString(), + StrictMode.getThreadPolicy().toString(), + ) + assertEquals(StrictMode.VmPolicy.LAX.toString(), StrictMode.getVmPolicy().toString()) + true + } + + // Policies should be reverted back + assertEquals(threadPolicy.toString(), StrictMode.getThreadPolicy().toString()) + assertEquals(vmPolicy.toString(), StrictMode.getVmPolicy().toString()) + + // Ensure the code ran + assertTrue(called) + } +} diff --git a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/SdkInitTests.kt b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/SdkInitTests.kt index a21402c456f..268108da5f5 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/SdkInitTests.kt +++ b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/SdkInitTests.kt @@ -274,7 +274,9 @@ class SdkInitTests : BaseUiTest() { fun initNotThrowStrictMode() { StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder().detectAll().penaltyDeath().build()) StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder().detectAll().penaltyDeath().build()) + val sampleScenario = launchActivity() initSentry() + sampleScenario.moveToState(Lifecycle.State.DESTROYED) } private fun assertDefaultIntegrations() { diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 17ccb16829c..fce0ea3c4dc 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -3356,6 +3356,7 @@ public class io/sentry/SentryOptions { public fun getReadTimeoutMillis ()I public fun getRelease ()Ljava/lang/String; public fun getReplayController ()Lio/sentry/ReplayController; + public fun getRuntimeManager ()Lio/sentry/util/runtime/IRuntimeManager; public fun getSampleRate ()Ljava/lang/Double; public fun getScopeObservers ()Ljava/util/List; public fun getSdkVersion ()Lio/sentry/protocol/SdkVersion; @@ -3498,6 +3499,7 @@ public class io/sentry/SentryOptions { public fun setReadTimeoutMillis (I)V public fun setRelease (Ljava/lang/String;)V public fun setReplayController (Lio/sentry/ReplayController;)V + public fun setRuntimeManager (Lio/sentry/util/runtime/IRuntimeManager;)V public fun setSampleRate (Ljava/lang/Double;)V public fun setSdkVersion (Lio/sentry/protocol/SdkVersion;)V public fun setSendClientReports (Z)V @@ -7108,6 +7110,19 @@ public final class io/sentry/util/UrlUtils$UrlDetails { public fun getUrlOrFallback ()Ljava/lang/String; } +public abstract interface class io/sentry/util/runtime/IRuntimeManager { + public abstract fun runWithRelaxedPolicy (Lio/sentry/util/runtime/IRuntimeManager$IRuntimeManagerCallback;)Ljava/lang/Object; +} + +public abstract interface class io/sentry/util/runtime/IRuntimeManager$IRuntimeManagerCallback { + public abstract fun run ()Ljava/lang/Object; +} + +public final class io/sentry/util/runtime/NeutralRuntimeManager : io/sentry/util/runtime/IRuntimeManager { + public fun ()V + public fun runWithRelaxedPolicy (Lio/sentry/util/runtime/IRuntimeManager$IRuntimeManagerCallback;)Ljava/lang/Object; +} + public abstract interface class io/sentry/util/thread/IThreadChecker { public abstract fun currentThreadSystemId ()J public abstract fun getCurrentThreadName ()Ljava/lang/String; diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 3c04a6aaa96..1f7adc6eb0e 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -579,7 +579,8 @@ private static boolean preInitConfigurations(final @NotNull SentryOptions option return true; } - @SuppressWarnings("FutureReturnValueIgnored") + // older AGP versions do not support method references + @SuppressWarnings({"FutureReturnValueIgnored", "Convert2MethodRef"}) private static void initConfigurations(final @NotNull SentryOptions options) { final @NotNull ILogger logger = options.getLogger(); logger.log(SentryLevel.INFO, "Initializing SDK with DSN: '%s'", options.getDsn()); @@ -591,7 +592,7 @@ private static void initConfigurations(final @NotNull SentryOptions options) { final String outboxPath = options.getOutboxPath(); if (outboxPath != null) { final File outboxDir = new File(outboxPath); - outboxDir.mkdirs(); + options.getRuntimeManager().runWithRelaxedPolicy(() -> outboxDir.mkdirs()); } else { logger.log(SentryLevel.INFO, "No outbox dir path is defined in options."); } @@ -599,7 +600,7 @@ private static void initConfigurations(final @NotNull SentryOptions options) { final String cacheDirPath = options.getCacheDirPath(); if (cacheDirPath != null) { final File cacheDir = new File(cacheDirPath); - cacheDir.mkdirs(); + options.getRuntimeManager().runWithRelaxedPolicy(() -> cacheDir.mkdirs()); final IEnvelopeCache envelopeCache = options.getEnvelopeDiskCache(); // only overwrite the cache impl if it's not already set if (envelopeCache instanceof NoOpEnvelopeCache) { @@ -612,7 +613,7 @@ private static void initConfigurations(final @NotNull SentryOptions options) { && profilingTracesDirPath != null) { final File profilingTracesDir = new File(profilingTracesDirPath); - profilingTracesDir.mkdirs(); + options.getRuntimeManager().runWithRelaxedPolicy(() -> profilingTracesDir.mkdirs()); try { options diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 781e287253b..fc0e38adfc7 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -27,6 +27,8 @@ import io.sentry.util.Platform; import io.sentry.util.SampleRateUtils; import io.sentry.util.StringUtils; +import io.sentry.util.runtime.IRuntimeManager; +import io.sentry.util.runtime.NeutralRuntimeManager; import io.sentry.util.thread.IThreadChecker; import io.sentry.util.thread.NoOpThreadChecker; import java.io.File; @@ -595,6 +597,9 @@ public class SentryOptions { private @NotNull ISocketTagger socketTagger = NoOpSocketTagger.getInstance(); + /** Runtime manager to manage runtime policies, like StrictMode on Android. */ + private @NotNull IRuntimeManager runtimeManager = new NeutralRuntimeManager(); + /** * Adds an event processor * @@ -2952,6 +2957,26 @@ public void setSocketTagger(final @Nullable ISocketTagger socketTagger) { this.socketTagger = socketTagger != null ? socketTagger : NoOpSocketTagger.getInstance(); } + /** + * Returns the IRuntimeManager + * + * @return the runtime manager + */ + @ApiStatus.Internal + public @NotNull IRuntimeManager getRuntimeManager() { + return runtimeManager; + } + + /** + * Sets the IRuntimeManager + * + * @param runtimeManager the runtime manager + */ + @ApiStatus.Internal + public void setRuntimeManager(final @NotNull IRuntimeManager runtimeManager) { + this.runtimeManager = runtimeManager; + } + /** * Load the lazy fields. Useful to load in the background, so that results are already cached. DO * NOT CALL THIS METHOD ON THE MAIN THREAD. diff --git a/sentry/src/main/java/io/sentry/util/runtime/IRuntimeManager.java b/sentry/src/main/java/io/sentry/util/runtime/IRuntimeManager.java new file mode 100644 index 00000000000..f6e27cce89b --- /dev/null +++ b/sentry/src/main/java/io/sentry/util/runtime/IRuntimeManager.java @@ -0,0 +1,13 @@ +package io.sentry.util.runtime; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +@ApiStatus.Internal +public interface IRuntimeManager { + T runWithRelaxedPolicy(final @NotNull IRuntimeManagerCallback toRun); + + interface IRuntimeManagerCallback { + T run(); + } +} diff --git a/sentry/src/main/java/io/sentry/util/runtime/NeutralRuntimeManager.java b/sentry/src/main/java/io/sentry/util/runtime/NeutralRuntimeManager.java new file mode 100644 index 00000000000..a6112ff60b3 --- /dev/null +++ b/sentry/src/main/java/io/sentry/util/runtime/NeutralRuntimeManager.java @@ -0,0 +1,12 @@ +package io.sentry.util.runtime; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +@ApiStatus.Internal +public final class NeutralRuntimeManager implements IRuntimeManager { + @Override + public T runWithRelaxedPolicy(final @NotNull IRuntimeManagerCallback toRun) { + return toRun.run(); + } +} diff --git a/sentry/src/test/java/io/sentry/util/runtime/NeutralRuntimeManagerTest.kt b/sentry/src/test/java/io/sentry/util/runtime/NeutralRuntimeManagerTest.kt new file mode 100644 index 00000000000..a645915d7ab --- /dev/null +++ b/sentry/src/test/java/io/sentry/util/runtime/NeutralRuntimeManagerTest.kt @@ -0,0 +1,19 @@ +package io.sentry.util.runtime + +import kotlin.test.Test +import kotlin.test.assertTrue + +class NeutralRuntimeManagerTest { + + val sut = NeutralRuntimeManager() + + @Test + fun `runWithRelaxedPolicy runs the code`() { + var called = false + + called = sut.runWithRelaxedPolicy { true } + + // Ensure the code ran + assertTrue(called) + } +} From c1b6bc6d61f9647b7364fdc2e6988519f6911ea8 Mon Sep 17 00:00:00 2001 From: Stefano Date: Mon, 13 Oct 2025 11:39:50 +0200 Subject: [PATCH 5/5] updated changelog --- CHANGELOG.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2306016139..e96ba647ec4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Fixes +- Avoid StrictMode warnings ([#4724](https://github.com/getsentry/sentry-java/pull/4724)) - Use logger from options for JVM profiler ([#4771](https://github.com/getsentry/sentry-java/pull/4771)) - Session Replay: Avoid deadlock when pausing replay if no connection ([#4788](https://github.com/getsentry/sentry-java/pull/4788)) @@ -61,10 +62,6 @@ ``` - Sentry now supports Spring Boot 4 M3 pre-release ([#4739](https://github.com/getsentry/sentry-java/pull/4739)) -### Fixes - -- Avoid StrictMode warnings ([#4724](https://github.com/getsentry/sentry-java/pull/4724)) - ### Improvements - Remove internal API status from get/setDistinctId ([#4708](https://github.com/getsentry/sentry-java/pull/4708))