From 19611e049a909fc1dfe79ae776ac6c426c8e3b0f Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Tue, 16 Dec 2025 21:04:32 +0100 Subject: [PATCH 1/5] fix(device): Fix ANR when collecting device context --- .../api/sentry-android-core.api | 2 ++ .../sentry/android/core/DeviceInfoUtil.java | 28 ++++++++++++------- .../android/core/ManifestMetadataReader.java | 8 ++++++ .../android/core/SentryAndroidOptions.java | 11 ++++++++ .../core/ManifestMetadataReaderTest.kt | 25 +++++++++++++++++ 5 files changed, 64 insertions(+), 10 deletions(-) diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index 08895e6713f..eda1eef2069 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -336,6 +336,7 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr public fun isAttachScreenshot ()Z public fun isAttachViewHierarchy ()Z public fun isCollectAdditionalContext ()Z + public fun isCollectExternalStorageContext ()Z public fun isEnableActivityLifecycleBreadcrumbs ()Z public fun isEnableActivityLifecycleTracingAutoFinish ()Z public fun isEnableAppComponentBreadcrumbs ()Z @@ -360,6 +361,7 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr public fun setBeforeScreenshotCaptureCallback (Lio/sentry/android/core/SentryAndroidOptions$BeforeCaptureCallback;)V public fun setBeforeViewHierarchyCaptureCallback (Lio/sentry/android/core/SentryAndroidOptions$BeforeCaptureCallback;)V public fun setCollectAdditionalContext (Z)V + public fun setCollectExternalStorageContext (Z)V public fun setDebugImagesLoader (Lio/sentry/android/core/IDebugImagesLoader;)V public fun setEnableActivityLifecycleBreadcrumbs (Z)V public fun setEnableActivityLifecycleTracingAutoFinish (Z)V diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java b/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java index 166f89bbc61..121505a0d7c 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java @@ -9,6 +9,7 @@ import android.content.IntentFilter; import android.os.BatteryManager; import android.os.Build; +import android.os.Environment; import android.os.LocaleList; import android.os.StatFs; import android.os.SystemClock; @@ -148,7 +149,7 @@ public Device collectDeviceInformation( // setting such values require IO hence we don't run for transactions if (collectDeviceIO && options.isCollectAdditionalContext()) { - setDeviceIO(device, collectDynamicData); + setDeviceIO(device, collectDynamicData, options.isCollectExternalStorageContext()); } return device; @@ -195,7 +196,10 @@ public ContextUtils.SplitApksInfo getSplitApksInfo() { return splitApksInfo; } - private void setDeviceIO(final @NotNull Device device, final boolean includeDynamicData) { + private void setDeviceIO( + final @NotNull Device device, + final boolean includeDynamicData, + final boolean includeExternalStorage) { final Intent batteryIntent = getBatteryIntent(); if (batteryIntent != null) { device.setBatteryLevel(getBatteryLevel(batteryIntent, options)); @@ -232,18 +236,22 @@ private void setDeviceIO(final @NotNull Device device, final boolean includeDyna .getRuntimeManager() .runWithRelaxedPolicy( () -> { - final @Nullable File internalStorageFile = context.getExternalFilesDir(null); - if (internalStorageFile != null) { - StatFs internalStorageStat = new StatFs(internalStorageFile.getPath()); + final @Nullable File dataDir = Environment.getDataDirectory(); + ; + if (dataDir != null) { + StatFs internalStorageStat = new StatFs(dataDir.getPath()); device.setStorageSize(getTotalInternalStorage(internalStorageStat)); device.setFreeStorage(getUnusedInternalStorage(internalStorageStat)); } - final @Nullable StatFs externalStorageStat = - getExternalStorageStat(internalStorageFile); - if (externalStorageStat != null) { - device.setExternalStorageSize(getTotalExternalStorage(externalStorageStat)); - device.setExternalFreeStorage(getUnusedExternalStorage(externalStorageStat)); + if (includeExternalStorage) { + final @Nullable File internalStorageFile = context.getExternalFilesDir(null); + final @Nullable StatFs externalStorageStat = + getExternalStorageStat(internalStorageFile); + if (externalStorageStat != null) { + device.setExternalStorageSize(getTotalExternalStorage(externalStorageStat)); + device.setExternalFreeStorage(getUnusedExternalStorage(externalStorageStat)); + } } }); diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java index a6392d4895e..3acd0a779e1 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java @@ -86,6 +86,7 @@ final class ManifestMetadataReader { static final String ATTACH_VIEW_HIERARCHY = "io.sentry.attach-view-hierarchy"; static final String CLIENT_REPORTS_ENABLE = "io.sentry.send-client-reports"; static final String COLLECT_ADDITIONAL_CONTEXT = "io.sentry.additional-context"; + static final String COLLECT_EXTERNAL_STORAGE_CONTEXT = "io.sentry.external-storage-context"; static final String SEND_DEFAULT_PII = "io.sentry.send-default-pii"; @@ -338,6 +339,13 @@ static void applyMetadata( COLLECT_ADDITIONAL_CONTEXT, options.isCollectAdditionalContext())); + options.setCollectExternalStorageContext( + readBool( + metadata, + logger, + COLLECT_EXTERNAL_STORAGE_CONTEXT, + options.isCollectExternalStorageContext())); + if (options.getTracesSampleRate() == null) { final double tracesSampleRate = readDouble(metadata, logger, TRACES_SAMPLE_RATE); if (tracesSampleRate != -1) { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java index 221495172eb..2fdf40fb166 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java @@ -121,6 +121,9 @@ public final class SentryAndroidOptions extends SentryOptions { */ private boolean collectAdditionalContext = true; + /** Enables or disables collecting of external storage context. */ + private boolean collectExternalStorageContext = true; + /** * Controls how many seconds to wait for sending events in case there were Startup Crashes in the * previous run. Sentry SDKs normally send events from a background queue, but in the case of @@ -414,6 +417,14 @@ public void setCollectAdditionalContext(boolean collectAdditionalContext) { this.collectAdditionalContext = collectAdditionalContext; } + public boolean isCollectExternalStorageContext() { + return collectExternalStorageContext; + } + + public void setCollectExternalStorageContext(final boolean collectExternalStorageContext) { + this.collectExternalStorageContext = collectExternalStorageContext; + } + public boolean isEnableFramesTracking() { return enableFramesTracking; } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt index ed58f268679..7c542d4577c 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt @@ -1158,6 +1158,31 @@ class ManifestMetadataReaderTest { assertTrue(fixture.options.isCollectAdditionalContext) } + @Test + fun `applyMetadata reads collect external storage to options`() { + // Arrange + val bundle = bundleOf(ManifestMetadataReader.COLLECT_EXTERNAL_STORAGE_CONTEXT to false) + val context = fixture.getContext(metaData = bundle) + + // Act + ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) + + // Assert + assertFalse(fixture.options.isCollectExternalStorageContext) + } + + @Test + fun `applyMetadata reads collect external storage and keep default value if not found`() { + // Arrange + val context = fixture.getContext() + + // Act + ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) + + // Assert + assertTrue(fixture.options.isCollectExternalStorageContext) + } + @Test fun `applyMetadata reads send default pii and keep default value if not found`() { // Arrange From 53ee6854d8ac4dcc6341e8fd585b1f0e56f5ff71 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Tue, 16 Dec 2025 22:56:02 +0100 Subject: [PATCH 2/5] fix formatting --- .../src/main/java/io/sentry/android/core/DeviceInfoUtil.java | 1 - 1 file changed, 1 deletion(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java b/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java index 121505a0d7c..5c06a558103 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java @@ -237,7 +237,6 @@ private void setDeviceIO( .runWithRelaxedPolicy( () -> { final @Nullable File dataDir = Environment.getDataDirectory(); - ; if (dataDir != null) { StatFs internalStorageStat = new StatFs(dataDir.getPath()); device.setStorageSize(getTotalInternalStorage(internalStorageStat)); From 6de15f80dd42c763cd533c67ee3cfd54233602dd Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Tue, 16 Dec 2025 23:06:07 +0100 Subject: [PATCH 3/5] disable external storage collection --- .../main/java/io/sentry/android/core/SentryAndroidOptions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java index 2fdf40fb166..816eb03417e 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java @@ -122,7 +122,7 @@ public final class SentryAndroidOptions extends SentryOptions { private boolean collectAdditionalContext = true; /** Enables or disables collecting of external storage context. */ - private boolean collectExternalStorageContext = true; + private boolean collectExternalStorageContext = false; /** * Controls how many seconds to wait for sending events in case there were Startup Crashes in the From 56bd5d30e5a5a70cf121a13f20ddca4bee831765 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Tue, 16 Dec 2025 23:09:02 +0100 Subject: [PATCH 4/5] Changelog --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 774aa0b6305..3b9151ff971 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,17 @@ ## Unreleased +### Fixes + +- Fix ANRs when collecting device context ([#4970](https://github.com/getsentry/sentry-java/pull/4970)) + - **IMPORTANT:** This disables collecting external storage size (total/free) by default, to enable it back + use `options.isCollectExternalStorageContext = true` or `` + ### Improvements - Discard envelopes on `4xx` and `5xx` response ([#4950](https://github.com/getsentry/sentry-java/pull/4950)) - This aims to not overwhelm Sentry after an outage or load shedding (including HTTP 429) where too many events are sent at once - ## 8.29.0 ### Fixes From 6324627ab670f05a08e2c52d6f3be590e074fe66 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Wed, 17 Dec 2025 10:58:18 +0100 Subject: [PATCH 5/5] Fix tests --- .../io/sentry/android/core/ManifestMetadataReaderTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt index 7c542d4577c..06d284c5dd7 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt @@ -1161,14 +1161,14 @@ class ManifestMetadataReaderTest { @Test fun `applyMetadata reads collect external storage to options`() { // Arrange - val bundle = bundleOf(ManifestMetadataReader.COLLECT_EXTERNAL_STORAGE_CONTEXT to false) + val bundle = bundleOf(ManifestMetadataReader.COLLECT_EXTERNAL_STORAGE_CONTEXT to true) val context = fixture.getContext(metaData = bundle) // Act ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) // Assert - assertFalse(fixture.options.isCollectExternalStorageContext) + assertTrue(fixture.options.isCollectExternalStorageContext) } @Test @@ -1180,7 +1180,7 @@ class ManifestMetadataReaderTest { ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) // Assert - assertTrue(fixture.options.isCollectExternalStorageContext) + assertFalse(fixture.options.isCollectExternalStorageContext) } @Test