I'm writing a screenshot app using Android MediaProjection
Api in which an overlay button is shown on top of everything and user can click it to capture a screenshot anywhere. Since MediaProjection records screen content, overlay button itself is in captured screenshots. To hide the button when capturing screenshot, I tried to set view visibility
to INVISIBLE
, take screenshot and revert it back to VISIBLE
but since changing visibility is an async operation in Android, sometimes overlay button is still present in recorded shots.
I Changed to below snippet and it worked in my experiments:
floatingButton?.setOnClickListener { view ->
view.visibility = View.INVISIBLE
view.postDelayed(100) {
takeShot()
view.post {view.visibility = View.VISIBLE}
}
}
But it's basically saying I feeling good that in 100ms, button would be invisible. It's not a good solution and in the case of videos, in 100ms content could be very different from what user actually saw at that moment.
Android doesn't provide a onVisibiltyChangedListener kind of thing, so how could I perform a task after ensuring that a view visibility has changed?
Edit 1
Here's the takeShot()
method:
private fun takeShot() {
val image = imageReader.acquireLatestImage()
val bitmap = image?.run {
val planes = image.planes
val buffer: ByteBuffer = planes[0].buffer
val pixelStride = planes[0].pixelStride
val rowStride = planes[0].rowStride
val rowPadding = rowStride - pixelStride * width
val bitmap = Bitmap.createBitmap(
width + rowPadding / pixelStride,
height,
Bitmap.Config.ARGB_8888
)
bitmap.copyPixelsFromBuffer(buffer)
image.close()
bitmap
}
bitmap?.let{
serviceScope.launch {
gallery.store(it)
}
}
}
The codes are inside of a foreground service and when user accepts media projection, I create ImageReader
and VirtualDisplay
:
imageReader = ImageReader.newInstance(size.width, size.height, PixelFormat.RGBA_8888, 2)
virtualDisplay = mediaProjection.createVirtualDisplay(
"screen-mirror",
size.width,
size.height,
Resources.getSystem().displayMetrics.densityDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, // TODO: DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC ??
imageReader.surface, null, null
)
mediaProjection.registerCallback(object : MediaProjection.Callback() {
override fun onStop() {
virtualDisplay.release()
mediaProjection.unregisterCallback(this)
}
}, null)
I've tried without suspension and coroutine stuff and result was the same, so they most likely are irrelevant to problem.