@@ -52,7 +52,6 @@ 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
5655
5756 // Callback for MediaProjection
5857 private val mediaProjectionCallback = object : MediaProjection .Callback () {
@@ -188,133 +187,124 @@ class ScreenCaptureService : Service() {
188187 }
189188 }
190189
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- }
217- } else {
218- @Suppress(" DEPRECATION" )
219- windowManager.defaultDisplay.getMetrics(displayMetrics)
220- }
221-
222- val width = displayMetrics.widthPixels
223- val height = displayMetrics.heightPixels
224- val density = displayMetrics.densityDpi
225-
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 " )
232-
233- imageReader?.close() // Close previous reader if any
234- virtualDisplay?.release() // Release previous display if any
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- }
242-
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." )
280- }
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
302- }
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." )
307- }
308-
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- }
317- }
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+ val windowManager = getSystemService(Context .WINDOW_SERVICE ) as WindowManager
199+ val displayMetrics = DisplayMetrics ()
200+
201+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .R ) {
202+ val defaultDisplay = windowManager.defaultDisplay
203+ if (defaultDisplay != null ) {
204+ defaultDisplay.getRealMetrics(displayMetrics)
205+ } else {
206+ val bounds = windowManager.currentWindowMetrics.bounds
207+ displayMetrics.widthPixels = bounds.width()
208+ displayMetrics.heightPixels = bounds.height()
209+ displayMetrics.densityDpi = resources.displayMetrics.densityDpi
210+ }
211+ } else {
212+ @Suppress(" DEPRECATION" )
213+ windowManager.defaultDisplay.getMetrics(displayMetrics)
214+ }
215+
216+ val width = displayMetrics.widthPixels
217+ val height = displayMetrics.heightPixels
218+ val density = displayMetrics.densityDpi
219+
220+ if (width <= 0 || height <= 0 ) {
221+ Log .e(TAG , " Invalid display dimensions: ${width} x${height} . Cannot create ImageReader." )
222+ return
223+ }
224+ Log .d(TAG , " Display dimensions: ${width} x${height} , density: $density " )
225+
226+ // Always close previous resources before creating new ones
227+ virtualDisplay?.release()
228+ virtualDisplay = null
229+ imageReader?.close()
230+ imageReader = null
231+
232+ // Create new ImageReader for this screenshot
233+ imageReader = ImageReader .newInstance(width, height, PixelFormat .RGBA_8888 , 1 )
234+ val localImageReader = imageReader ? : run {
235+ Log .e(TAG , " ImageReader is null after creation attempt." )
236+ return
237+ }
238+
239+ // Create VirtualDisplay for this screenshot
240+ virtualDisplay = mediaProjection?.createVirtualDisplay(
241+ " ScreenCapture" ,
242+ width, height, density,
243+ DisplayManager .VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR ,
244+ localImageReader.surface,
245+ object : VirtualDisplay .Callback () {
246+ override fun onPaused () { Log .d(TAG , " VirtualDisplay paused" ) }
247+ override fun onResumed () { Log .d(TAG , " VirtualDisplay resumed" ) }
248+ override fun onStopped () { Log .d(TAG , " VirtualDisplay stopped" ) }
249+ },
250+ Handler (Looper .getMainLooper())
251+ )
252+
253+ if (virtualDisplay == null ) {
254+ Log .e(TAG , " Failed to create VirtualDisplay." )
255+ localImageReader.close()
256+ this .imageReader = null
257+ return
258+ }
259+
260+ // Set up one-shot image capture
261+ localImageReader.setOnImageAvailableListener({ reader ->
262+ var image: android.media.Image ? = null
263+ try {
264+ image = reader.acquireLatestImage()
265+ if (image != null ) {
266+ val planes = image.planes
267+ val buffer = planes[0 ].buffer
268+ val pixelStride = planes[0 ].pixelStride
269+ val rowStride = planes[0 ].rowStride
270+ val rowPadding = rowStride - pixelStride * width
271+
272+ val bitmap = Bitmap .createBitmap(
273+ width + rowPadding / pixelStride,
274+ height,
275+ Bitmap .Config .ARGB_8888
276+ )
277+ bitmap.copyPixelsFromBuffer(buffer)
278+ Log .d(TAG , " Bitmap created, proceeding to save." )
279+ saveScreenshot(bitmap)
280+ } else {
281+ Log .w(TAG , " acquireLatestImage returned null." )
282+ }
283+ } catch (e: Exception ) {
284+ Log .e(TAG , " Error processing image" , e)
285+ } finally {
286+ image?.close()
287+
288+ // Clean up VirtualDisplay and ImageReader after screenshot
289+ // BUT keep MediaProjection active
290+ Log .d(TAG , " Cleaning up VirtualDisplay and ImageReader (MediaProjection remains active)" )
291+ this .virtualDisplay?.release()
292+ this .virtualDisplay = null
293+ reader.close()
294+ if (this .imageReader == reader) {
295+ this .imageReader = null
296+ }
297+ }
298+ }, Handler (Looper .getMainLooper()))
299+
300+ } catch (e: Exception ) {
301+ Log .e(TAG , " Error in takeScreenshot setup" , e)
302+ virtualDisplay?.release()
303+ virtualDisplay = null
304+ imageReader?.close()
305+ imageReader = null
306+ }
307+ }
318308
319309 private fun saveScreenshot (bitmap : Bitmap ) {
320310 try {
0 commit comments