@@ -29,12 +29,16 @@ import android.view.View
2929import androidx.annotation.RequiresApi
3030import io.sentry.SentryLevel
3131import io.sentry.SentryOptions
32+ import io.sentry.SentryReplayOptions
33+ import io.sentry.SentryReplayOptions.IMAGE_VIEW_CLASS_NAME
34+ import io.sentry.SentryReplayOptions.TEXT_VIEW_CLASS_NAME
3235import io.sentry.android.replay.ExecutorProvider
3336import io.sentry.android.replay.ScreenshotRecorderCallback
3437import io.sentry.android.replay.ScreenshotRecorderConfig
3538import io.sentry.android.replay.util.submitSafely
3639import io.sentry.util.AutoClosableReentrantLock
3740import io.sentry.util.IntegrationUtils
41+ import java.io.Closeable
3842import java.util.WeakHashMap
3943import java.util.concurrent.atomic.AtomicBoolean
4044import java.util.concurrent.atomic.AtomicReference
@@ -55,13 +59,14 @@ internal class CanvasStrategy(
5559 private val prescaledMatrix by
5660 lazy(NONE ) { Matrix ().apply { preScale(config.scaleFactorX, config.scaleFactorY) } }
5761 private val lastCaptureSuccessful = AtomicBoolean (false )
58- private val textIgnoringCanvas = TextIgnoringDelegateCanvas ()
62+ private val textIgnoringCanvas = TextIgnoringDelegateCanvas (options.sessionReplay )
5963
6064 private val isClosed = AtomicBoolean (false )
6165
6266 private val onImageAvailableListener: (holder: PictureReaderHolder ) -> Unit = { holder ->
6367 if (isClosed.get()) {
6468 options.logger.log(SentryLevel .ERROR , " CanvasStrategy already closed, skipping image" )
69+ holder.close()
6570 } else {
6671 try {
6772 val image = holder.reader.acquireLatestImage()
@@ -86,12 +91,20 @@ internal class CanvasStrategy(
8691 }
8792 }
8893 } finally {
89- image.close()
94+ try {
95+ image.close()
96+ } catch (_: Throwable ) {
97+ // ignored
98+ }
9099 }
91100 } catch (e: Throwable ) {
92101 options.logger.log(SentryLevel .ERROR , " CanvasStrategy: image processing failed" , e)
93102 } finally {
94- freePictureRef.set(holder)
103+ if (isClosed.get()) {
104+ holder.close()
105+ } else {
106+ freePictureRef.set(holder)
107+ }
95108 }
96109 }
97110 }
@@ -116,10 +129,7 @@ internal class CanvasStrategy(
116129 )
117130 return @Runnable
118131 }
119- val holder = unprocessedPictureRef.getAndSet(null )
120- if (holder == null ) {
121- return @Runnable
122- }
132+ val holder = unprocessedPictureRef.getAndSet(null ) ? : return @Runnable
123133
124134 try {
125135 if (! holder.setup.getAndSet(true )) {
@@ -135,14 +145,20 @@ internal class CanvasStrategy(
135145 surface.unlockCanvasAndPost(canvas)
136146 }
137147 } catch (t: Throwable ) {
138- freePictureRef.set(holder)
148+ if (isClosed.get()) {
149+ holder.close()
150+ } else {
151+ freePictureRef.set(holder)
152+ }
139153 options.logger.log(SentryLevel .ERROR , " Canvas Strategy: picture render failed" , t)
140154 }
141155 }
142156
143157 @SuppressLint(" UnclosedTrace" )
144158 override fun capture (root : View ) {
145-
159+ if (isClosed.get()) {
160+ return
161+ }
146162 val holder = freePictureRef.getAndSet(null )
147163 if (holder == null ) {
148164 options.logger.log(SentryLevel .DEBUG , " No free Picture available, skipping capture" )
@@ -156,9 +172,12 @@ internal class CanvasStrategy(
156172 root.draw(textIgnoringCanvas)
157173 holder.picture.endRecording()
158174
159- unprocessedPictureRef.set(holder)
160-
161- executor.getExecutor().submitSafely(options, " screenshot_recorder.canvas" , pictureRenderTask)
175+ if (isClosed.get()) {
176+ holder.close()
177+ } else {
178+ unprocessedPictureRef.set(holder)
179+ executor.getExecutor().submitSafely(options, " screenshot_recorder.canvas" , pictureRenderTask)
180+ }
162181 }
163182
164183 override fun onContentChanged () {
@@ -173,7 +192,11 @@ internal class CanvasStrategy(
173192 recycle()
174193 }
175194 }
195+ screenshot = null
176196 }
197+ // the image can be free, unprocessed or in transit
198+ freePictureRef.getAndSet(null )?.reader?.close()
199+ unprocessedPictureRef.getAndSet(null )?.reader?.close()
177200 }
178201
179202 override fun lastCaptureSuccessful (): Boolean {
@@ -191,7 +214,7 @@ internal class CanvasStrategy(
191214}
192215
193216@SuppressLint(" UseKtx" )
194- private class TextIgnoringDelegateCanvas : Canvas () {
217+ private class TextIgnoringDelegateCanvas ( sessionReplay : SentryReplayOptions ) : Canvas() {
195218
196219 lateinit var delegate: Canvas
197220 private val solidPaint = Paint ()
@@ -205,6 +228,13 @@ private class TextIgnoringDelegateCanvas : Canvas() {
205228
206229 private val bitmapColorCache = WeakHashMap <Bitmap , Pair <Int , Int >>()
207230
231+ private val maskAllText =
232+ sessionReplay.maskViewClasses.contains(TEXT_VIEW_CLASS_NAME ) ||
233+ sessionReplay.maskViewClasses.size > 1
234+ private val maskAllImages =
235+ sessionReplay.maskViewClasses.contains(IMAGE_VIEW_CLASS_NAME ) ||
236+ sessionReplay.maskViewClasses.size > 1
237+
208238 override fun isHardwareAccelerated (): Boolean {
209239 return false
210240 }
@@ -992,7 +1022,7 @@ private class PictureReaderHolder(
9921022 val width : Int ,
9931023 val height : Int ,
9941024 val listener : (holder: PictureReaderHolder ) -> Unit ,
995- ) : ImageReader.OnImageAvailableListener {
1025+ ) : ImageReader.OnImageAvailableListener, Closeable {
9961026 val picture = Picture ()
9971027
9981028 @SuppressLint(" InlinedApi" )
@@ -1005,4 +1035,12 @@ private class PictureReaderHolder(
10051035 listener(this )
10061036 }
10071037 }
1038+
1039+ override fun close () {
1040+ try {
1041+ reader.close()
1042+ } catch (_: Throwable ) {
1043+ // ignored
1044+ }
1045+ }
10081046}
0 commit comments