Skip to content

Commit 4e7e8a9

Browse files
Refactor: Introduce flag to control screenshot capture
This commit introduces a `shouldTakeScreenshot` flag in `ScreenCaptureService.kt` to control when a screenshot is actually captured. The `takeScreenshot()` method has been updated to: - Set this flag when a screenshot is requested. - The `OnImageAvailableListener` now checks this flag and only processes the image if the flag is true. - Other frames are acquired and immediately closed to free up resources. - The flag is reset after a successful capture. - The VirtualDisplay and ImageReader are now reused if they have been previously initialized, optimizing resource usage.
1 parent 5794d8c commit 4e7e8a9

1 file changed

Lines changed: 116 additions & 102 deletions

File tree

app/src/main/kotlin/com/google/ai/sample/ScreenCaptureService.kt

Lines changed: 116 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class ScreenCaptureService : Service() {
5252
private var virtualDisplay: VirtualDisplay? = null
5353
private var imageReader: ImageReader? = null
5454
private var isReady = false // Flag to indicate if MediaProjection is set up and active
55+
private var shouldTakeScreenshot = false // Flag to control when to actually capture
5556

5657
// Callback for MediaProjection
5758
private val mediaProjectionCallback = object : MediaProjection.Callback() {
@@ -187,120 +188,133 @@ class ScreenCaptureService : Service() {
187188
}
188189
}
189190

190-
private fun takeScreenshot() {
191-
if (!isReady || mediaProjection == null) {
192-
Log.e(TAG, "Cannot take screenshot - service not ready or mediaProjection is null. isReady=$isReady, mediaProjectionIsNull=${mediaProjection == null}")
193-
return
194-
}
195-
Log.d(TAG, "takeScreenshot: Preparing to capture.")
196-
197-
try {
198-
// Check if we need to initialize VirtualDisplay and ImageReader
199-
if (virtualDisplay == null || imageReader == null) {
200-
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
201-
val displayMetrics = DisplayMetrics()
202-
203-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
204-
val defaultDisplay = windowManager.defaultDisplay
205-
if (defaultDisplay != null) {
206-
defaultDisplay.getRealMetrics(displayMetrics)
191+
private fun takeScreenshot() {
192+
if (!isReady || mediaProjection == null) {
193+
Log.e(TAG, "Cannot take screenshot - service not ready or mediaProjection is null. isReady=$isReady, mediaProjectionIsNull=${mediaProjection == null}")
194+
return
195+
}
196+
Log.d(TAG, "takeScreenshot: Preparing to capture.")
197+
198+
// Set flag to capture on next frame
199+
shouldTakeScreenshot = true
200+
201+
try {
202+
// Check if we need to initialize VirtualDisplay and ImageReader
203+
if (virtualDisplay == null || imageReader == null) {
204+
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
205+
val displayMetrics = DisplayMetrics()
206+
207+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
208+
val defaultDisplay = windowManager.defaultDisplay
209+
if (defaultDisplay != null) {
210+
defaultDisplay.getRealMetrics(displayMetrics)
211+
} else {
212+
val bounds = windowManager.currentWindowMetrics.bounds
213+
displayMetrics.widthPixels = bounds.width()
214+
displayMetrics.heightPixels = bounds.height()
215+
displayMetrics.densityDpi = resources.displayMetrics.densityDpi
216+
}
207217
} else {
208-
val bounds = windowManager.currentWindowMetrics.bounds
209-
displayMetrics.widthPixels = bounds.width()
210-
displayMetrics.heightPixels = bounds.height()
211-
displayMetrics.densityDpi = resources.displayMetrics.densityDpi
218+
@Suppress("DEPRECATION")
219+
windowManager.defaultDisplay.getMetrics(displayMetrics)
212220
}
213-
} else {
214-
@Suppress("DEPRECATION")
215-
windowManager.defaultDisplay.getMetrics(displayMetrics)
216-
}
217221

218-
val width = displayMetrics.widthPixels
219-
val height = displayMetrics.heightPixels
220-
val density = displayMetrics.densityDpi
222+
val width = displayMetrics.widthPixels
223+
val height = displayMetrics.heightPixels
224+
val density = displayMetrics.densityDpi
221225

222-
if (width <= 0 || height <= 0) {
223-
Log.e(TAG, "Invalid display dimensions: ${width}x${height}. Cannot create ImageReader.")
224-
return
225-
}
226-
Log.d(TAG, "Display dimensions: ${width}x${height}, density: $density")
226+
if (width <= 0 || height <= 0) {
227+
Log.e(TAG, "Invalid display dimensions: ${width}x${height}. Cannot create ImageReader.")
228+
shouldTakeScreenshot = false
229+
return
230+
}
231+
Log.d(TAG, "Display dimensions: ${width}x${height}, density: $density")
227232

228-
imageReader?.close() // Close previous reader if any
229-
virtualDisplay?.release() // Release previous display if any
233+
imageReader?.close() // Close previous reader if any
234+
virtualDisplay?.release() // Release previous display if any
230235

231-
imageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 1)
232-
val localImageReader = imageReader ?: run {
233-
Log.e(TAG, "ImageReader is null after creation attempt.")
234-
return
235-
}
236+
imageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 1)
237+
val localImageReader = imageReader ?: run {
238+
Log.e(TAG, "ImageReader is null after creation attempt.")
239+
shouldTakeScreenshot = false
240+
return
241+
}
236242

237-
localImageReader.setOnImageAvailableListener({ reader ->
238-
var image: android.media.Image? = null
239-
try {
240-
image = reader.acquireLatestImage()
241-
if (image != null) {
242-
val planes = image.planes
243-
val buffer = planes[0].buffer
244-
val pixelStride = planes[0].pixelStride
245-
val rowStride = planes[0].rowStride
246-
val rowPadding = rowStride - pixelStride * width
247-
248-
val bitmap = Bitmap.createBitmap(
249-
width + rowPadding / pixelStride,
250-
height,
251-
Bitmap.Config.ARGB_8888
252-
)
253-
bitmap.copyPixelsFromBuffer(buffer)
254-
Log.d(TAG, "Bitmap created, proceeding to save.")
255-
saveScreenshot(bitmap)
256-
} else {
257-
Log.w(TAG, "acquireLatestImage returned null.")
243+
localImageReader.setOnImageAvailableListener({ reader ->
244+
// Only process image if screenshot was requested
245+
if (!shouldTakeScreenshot) {
246+
reader.acquireLatestImage()?.close()
247+
return@setOnImageAvailableListener
248+
}
249+
250+
var image: android.media.Image? = null
251+
try {
252+
image = reader.acquireLatestImage()
253+
if (image != null) {
254+
val planes = image.planes
255+
val buffer = planes[0].buffer
256+
val pixelStride = planes[0].pixelStride
257+
val rowStride = planes[0].rowStride
258+
val rowPadding = rowStride - pixelStride * width
259+
260+
val bitmap = Bitmap.createBitmap(
261+
width + rowPadding / pixelStride,
262+
height,
263+
Bitmap.Config.ARGB_8888
264+
)
265+
bitmap.copyPixelsFromBuffer(buffer)
266+
Log.d(TAG, "Bitmap created, proceeding to save.")
267+
saveScreenshot(bitmap)
268+
269+
// Reset flag after capturing
270+
shouldTakeScreenshot = false
271+
} else {
272+
Log.w(TAG, "acquireLatestImage returned null.")
273+
}
274+
} catch (e: Exception) {
275+
Log.e(TAG, "Error processing image", e)
276+
shouldTakeScreenshot = false
277+
} finally {
278+
image?.close()
279+
Log.d(TAG, "Screenshot captured, keeping resources for reuse.")
258280
}
259-
} catch (e: Exception) {
260-
Log.e(TAG, "Error processing image", e)
261-
} finally {
262-
image?.close()
263-
// Do NOT release VirtualDisplay or ImageReader here
264-
// They will be reused for the next screenshot
265-
Log.d(TAG, "Screenshot captured, keeping resources for reuse.")
281+
}, Handler(Looper.getMainLooper()))
282+
283+
virtualDisplay = mediaProjection?.createVirtualDisplay(
284+
"ScreenCapture",
285+
width, height, density,
286+
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
287+
localImageReader.surface,
288+
object : VirtualDisplay.Callback() {
289+
override fun onPaused() { Log.d(TAG, "VirtualDisplay paused") }
290+
override fun onResumed() { Log.d(TAG, "VirtualDisplay resumed") }
291+
override fun onStopped() { Log.d(TAG, "VirtualDisplay stopped") }
292+
},
293+
Handler(Looper.getMainLooper())
294+
)
295+
296+
if (virtualDisplay == null) {
297+
Log.e(TAG, "Failed to create VirtualDisplay.")
298+
localImageReader.close()
299+
this.imageReader = null
300+
shouldTakeScreenshot = false
301+
return
266302
}
267-
}, Handler(Looper.getMainLooper()))
268-
269-
virtualDisplay = mediaProjection?.createVirtualDisplay(
270-
"ScreenCapture",
271-
width, height, density,
272-
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
273-
localImageReader.surface,
274-
object : VirtualDisplay.Callback() {
275-
override fun onPaused() { Log.d(TAG, "VirtualDisplay paused") }
276-
override fun onResumed() { Log.d(TAG, "VirtualDisplay resumed") }
277-
override fun onStopped() { Log.d(TAG, "VirtualDisplay stopped") }
278-
},
279-
Handler(Looper.getMainLooper())
280-
)
281-
282-
if (virtualDisplay == null) {
283-
Log.e(TAG, "Failed to create VirtualDisplay.")
284-
localImageReader.close() // Clean up the reader we just created
285-
this.imageReader = null
286-
return
303+
Log.d(TAG, "VirtualDisplay and ImageReader initialized for reuse.")
304+
} else {
305+
// Resources already exist, flag is set, listener will capture on next frame
306+
Log.d(TAG, "Using existing VirtualDisplay and ImageReader, screenshot will be captured on next frame.")
287307
}
288-
Log.d(TAG, "VirtualDisplay and ImageReader initialized for reuse.")
289-
} else {
290-
// Resources already exist, just trigger a new capture
291-
Log.d(TAG, "Using existing VirtualDisplay and ImageReader.")
292-
// Force the ImageReader to capture a new frame
293-
// The listener is already set up and will handle the new image
294-
}
295308

296-
} catch (e: Exception) {
297-
Log.e(TAG, "Error in takeScreenshot setup", e)
298-
virtualDisplay?.release()
299-
virtualDisplay = null
300-
imageReader?.close()
301-
imageReader = null
309+
} catch (e: Exception) {
310+
Log.e(TAG, "Error in takeScreenshot setup", e)
311+
shouldTakeScreenshot = false
312+
virtualDisplay?.release()
313+
virtualDisplay = null
314+
imageReader?.close()
315+
imageReader = null
316+
}
302317
}
303-
}
304318

305319
private fun saveScreenshot(bitmap: Bitmap) {
306320
try {

0 commit comments

Comments
 (0)