diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d66d755a7..0c3574f1fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Fixes +- Fix crash when `getHistoricalProcessStartReasons` is called from an isolated or wrong-userId process ([#5597](https://github.com/getsentry/sentry-java/pull/5597)) - Release `MediaMuxer` when a replay segment has no encodable frames to avoid a resource leak ([#5583](https://github.com/getsentry/sentry-java/pull/5583)) ## 8.44.1 diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java b/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java index 36cae8686c..828e103e8b 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java @@ -11,6 +11,7 @@ import android.os.Handler; import android.os.Looper; import android.os.SystemClock; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -467,18 +468,28 @@ public void registerLifecycleCallbacks(final @NotNull Application application) { final @Nullable ActivityManager activityManager = (ActivityManager) application.getSystemService(Context.ACTIVITY_SERVICE); if (activityManager != null) { - final List historicalProcessStartReasons = - activityManager.getHistoricalProcessStartReasons(1); - if (!historicalProcessStartReasons.isEmpty()) { - final @NotNull ApplicationStartInfo info = historicalProcessStartReasons.get(0); - cachedStartInfo = info; - if (info.getStartupState() == ApplicationStartInfo.STARTUP_STATE_STARTED) { - if (info.getStartType() == ApplicationStartInfo.START_TYPE_COLD) { - appStartType = AppStartType.COLD; - } else { - appStartType = AppStartType.WARM; + try { + final List historicalProcessStartReasons = + activityManager.getHistoricalProcessStartReasons(1); + if (!historicalProcessStartReasons.isEmpty()) { + final @NotNull ApplicationStartInfo info = historicalProcessStartReasons.get(0); + cachedStartInfo = info; + if (info.getStartupState() == ApplicationStartInfo.STARTUP_STATE_STARTED) { + if (info.getStartType() == ApplicationStartInfo.START_TYPE_COLD) { + appStartType = AppStartType.COLD; + } else { + appStartType = AppStartType.WARM; + } } } + } catch (RuntimeException ignored) { + // getHistoricalProcessStartReasons may throw different kinds of exceptions, namely: + // - SecurityException when called from an isolated process + // - IllegalArgumentException when called with a wrong userId + // - others + // See impl: + // https://cs.android.com/android/platform/superproject/+/android-latest-release:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java;l=10866-10893 + Log.w("AppStartMetrics", ignored); // no logger instance here, so we just Log } } } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryShadowActivityManager.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryShadowActivityManager.kt index a959c5dd86..93cb4759e9 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryShadowActivityManager.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryShadowActivityManager.kt @@ -12,11 +12,16 @@ class SentryShadowActivityManager { companion object { private var historicalProcessStartReasons: List = emptyList() private var importance: Int = RunningAppProcessInfo.IMPORTANCE_FOREGROUND + private var historicalProcessStartReasonsException: RuntimeException? = null fun setHistoricalProcessStartReasons(startReasons: List) { historicalProcessStartReasons = startReasons } + fun setHistoricalProcessStartReasonsException(exception: RuntimeException) { + historicalProcessStartReasonsException = exception + } + fun setImportance(importance: Int) { this.importance = importance } @@ -24,6 +29,7 @@ class SentryShadowActivityManager { fun reset() { historicalProcessStartReasons = emptyList() importance = RunningAppProcessInfo.IMPORTANCE_FOREGROUND + historicalProcessStartReasonsException = null } @Implementation @@ -35,6 +41,7 @@ class SentryShadowActivityManager { @Implementation fun getHistoricalProcessStartReasons(maxNum: Int): List { + historicalProcessStartReasonsException?.let { throw it } return historicalProcessStartReasons.take(maxNum) } } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTestApi35.kt b/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTestApi35.kt index b5d87ab77c..fd1474864f 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTestApi35.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTestApi35.kt @@ -249,6 +249,20 @@ class AppStartMetricsTestApi35 { assertNull(metrics.appStartReason) } + @Test + fun `does not crash when getHistoricalProcessStartReasons throws SecurityException`() { + SentryShadowActivityManager.setHistoricalProcessStartReasonsException( + SecurityException("isolated process") + ) + val metrics = AppStartMetrics.getInstance() + + val app = ApplicationProvider.getApplicationContext() + metrics.registerLifecycleCallbacks(app) + + assertEquals(AppStartMetrics.AppStartType.UNKNOWN, metrics.appStartType) + assertNull(metrics.appStartReason) + } + private fun waitForMainLooperIdle() { Handler(Looper.getMainLooper()).post {} Shadows.shadowOf(Looper.getMainLooper()).idle()