Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ import javax.inject.Singleton

interface ComposableBitmapRenderer {

suspend fun renderComposableToBitmap(canvasSize: Size, composableContent: @Composable () -> Unit): Bitmap?
suspend fun renderComposableToBitmap(
canvasSize: Size,
composableContent: @Composable () -> Unit,
): Bitmap?
}

/**
Expand All @@ -74,20 +77,22 @@ interface ComposableBitmapRenderer {
* }
*/
@Singleton
class ComposableBitmapRendererImpl @Inject constructor(private val application: Application) : ComposableBitmapRenderer {
class ComposableBitmapRendererImpl @Inject constructor(private val application: Application) :
ComposableBitmapRenderer {

private suspend fun <T> useVirtualDisplay(callback: suspend (display: Display) -> T): T? {
val texture = SurfaceTexture(false)
val surface = Surface(texture)
val virtualDisplay: VirtualDisplay? =
(application.getSystemService(DISPLAY_SERVICE) as DisplayManager).createVirtualDisplay(
"virtualDisplay",
1,
1,
72,
surface,
DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY,
)
val outerContext = application.resources.displayMetrics
val virtualDisplay =
application.getSystemService(DisplayManager::class.java).createVirtualDisplay(
"virtualDisplay",
outerContext.widthPixels,
outerContext.heightPixels,
outerContext.densityDpi,
surface,
DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY,
)
Comment on lines +86 to +95
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

While these changes correctly fix the StrictMode violation, the surrounding useVirtualDisplay function has a critical flaw regarding resource management. If createVirtualDisplay or the callback on line 97 throws an exception, the release() methods for virtualDisplay, surface, and texture will not be called, leading to resource leaks.

It is crucial to wrap the resource allocation and usage in a try...finally block to guarantee that release() is always called, regardless of exceptions.

try {
    // resource allocation and usage
} finally {
    // resource release
}

Additionally, getSystemService can return null, which should be handled to prevent crashes. The non-null assertion (!!) on virtualDisplay (line 97) is also redundant as createVirtualDisplay returns a non-null type.


val result = callback(virtualDisplay!!.display)
virtualDisplay.release()
Expand All @@ -96,7 +101,10 @@ class ComposableBitmapRendererImpl @Inject constructor(private val application:
return result
}

override suspend fun renderComposableToBitmap(canvasSize: Size, composableContent: @Composable () -> Unit): Bitmap? {
override suspend fun renderComposableToBitmap(
canvasSize: Size,
composableContent: @Composable () -> Unit,
): Bitmap? {
val bitmap = useVirtualDisplay { display ->
val outputDensity = Density(1f)

Expand All @@ -119,6 +127,7 @@ class ComposableBitmapRendererImpl @Inject constructor(private val application:
}
return bitmap
}

private data class CaptureComposableScope(val capture: () -> Unit)

private fun Size.roundedToIntSize(): IntSize =
Expand Down Expand Up @@ -172,7 +181,7 @@ class ComposableBitmapRendererImpl @Inject constructor(private val application:
}
}

val composeView = ComposeView(context).apply {
val composeView = ComposeView(presentation.context).apply {
val intSize = with(density) { size.toSize().roundedToIntSize() }
require(intSize.width > 0 && intSize.height > 0) { "pixel size must not have zero dimension" }

Expand Down