diff --git a/CHANGELOG.md b/CHANGELOG.md index a6bd32c9e3b..dda091cc0b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixes - Measure Session Replay's 1h recording cap and its touch-move debounce on a monotonic clock, so a device time change no longer stops a healthy recording early, keeps a finished one alive, or suppresses gesture events ([#6090](https://github.com/getsentry/sentry-java/pull/6090)) +- Session Replay: Use a software canvas for video encoding on the Android Emulator to avoid a native crash in `eglDestroySurface` ([#6282](https://github.com/getsentry/sentry-java/pull/6282)) ## 8.61.0 diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/video/SimpleVideoEncoder.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/video/SimpleVideoEncoder.kt index dd9af1c24d6..b1e3cd20513 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/video/SimpleVideoEncoder.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/video/SimpleVideoEncoder.kt @@ -177,25 +177,8 @@ internal class SimpleVideoEncoder( } fun encode(image: Bitmap) { - /** - * it seems that Xiaomi devices have problems with hardware canvas, so we have to use lockCanvas - * instead https://stackoverflow.com/a/73520742 - * --- - * Same for Motorola devices. - * --- - * As for the Spreadtrum/Unisoc chipset, it can be spread across various devices, so we have to - * check the SOC_MANUFACTURER property, as the manufacturer name might have changed. - * https://github.com/getsentry/sentry-android-gradle-plugin/issues/861#issuecomment-2867021256 - */ val canvas = - if ( - Build.MANUFACTURER.contains("xiaomi", ignoreCase = true) || - Build.MANUFACTURER.contains("motorola", ignoreCase = true) || - SystemProperties.get(SystemProperties.Property.SOC_MANUFACTURER) - .equals("spreadtrum", ignoreCase = true) || - SystemProperties.get(SystemProperties.Property.SOC_MANUFACTURER) - .equals("unisoc", ignoreCase = true) - ) { + if (shouldUseSoftwareCanvas()) { surface?.lockCanvas(null) } else { surface?.lockHardwareCanvas() @@ -338,6 +321,34 @@ internal class SimpleVideoEncoder( } } +/** + * Whether frames should be drawn onto the encoder's input surface with a software canvas + * ([Surface.lockCanvas]) instead of a hardware one ([Surface.lockHardwareCanvas]). + * + * It seems that Xiaomi devices have problems with hardware canvas, so we have to use lockCanvas + * instead https://stackoverflow.com/a/73520742 + * --- + * Same for Motorola devices. + * --- + * As for the Spreadtrum/Unisoc chipset, it can be spread across various devices, so we have to + * check the SOC_MANUFACTURER property, as the manufacturer name might have changed. + * https://github.com/getsentry/sentry-android-gradle-plugin/issues/861#issuecomment-2867021256 + * --- + * On the Android Emulator (goldfish/ranchu), a hardware canvas makes hwui own an EGL window surface + * on the input surface. The emulator's EGL driver keeps a dequeued buffer on it and cancels that + * buffer when the surface is destroyed, which can crash natively in eglDestroySurface once the + * encoder has been torn down. https://github.com/getsentry/sentry-java/issues/6279 + */ +internal fun shouldUseSoftwareCanvas(): Boolean = + Build.MANUFACTURER.contains("xiaomi", ignoreCase = true) || + Build.MANUFACTURER.contains("motorola", ignoreCase = true) || + SystemProperties.get(SystemProperties.Property.SOC_MANUFACTURER) + .equals("spreadtrum", ignoreCase = true) || + SystemProperties.get(SystemProperties.Property.SOC_MANUFACTURER) + .equals("unisoc", ignoreCase = true) || + Build.HARDWARE.contains("goldfish", ignoreCase = true) || + Build.HARDWARE.contains("ranchu", ignoreCase = true) + @SuppressLint("UseRequiresApi") @TargetApi(24) internal data class MuxerConfig( diff --git a/sentry-android-replay/src/test/java/io/sentry/android/replay/video/SimpleVideoEncoderCanvasTest.kt b/sentry-android-replay/src/test/java/io/sentry/android/replay/video/SimpleVideoEncoderCanvasTest.kt new file mode 100644 index 00000000000..01598df8ef8 --- /dev/null +++ b/sentry-android-replay/src/test/java/io/sentry/android/replay/video/SimpleVideoEncoderCanvasTest.kt @@ -0,0 +1,51 @@ +package io.sentry.android.replay.video + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import org.junit.runner.RunWith +import org.robolectric.annotation.Config +import org.robolectric.shadows.ShadowBuild + +@RunWith(AndroidJUnit4::class) +@Config(sdk = [30]) +class SimpleVideoEncoderCanvasTest { + + @Test + fun `uses hardware canvas on regular devices`() { + ShadowBuild.setManufacturer("Google") + ShadowBuild.setHardware("tensor") + + assertThat(shouldUseSoftwareCanvas()).isFalse() + } + + @Test + fun `uses software canvas on Xiaomi devices`() { + ShadowBuild.setManufacturer("Xiaomi") + + assertThat(shouldUseSoftwareCanvas()).isTrue() + } + + @Test + fun `uses software canvas on Motorola devices`() { + ShadowBuild.setManufacturer("motorola") + + assertThat(shouldUseSoftwareCanvas()).isTrue() + } + + @Test + fun `uses software canvas on ranchu emulators`() { + ShadowBuild.setManufacturer("Google") + ShadowBuild.setHardware("ranchu") + + assertThat(shouldUseSoftwareCanvas()).isTrue() + } + + @Test + fun `uses software canvas on goldfish emulators`() { + ShadowBuild.setManufacturer("unknown") + ShadowBuild.setHardware("goldfish") + + assertThat(shouldUseSoftwareCanvas()).isTrue() + } +}