1717package com.google.ai.sample
1818
1919import android.app.Activity
20+ import android.content.ComponentName
2021import android.content.Context
2122import android.content.Intent
23+ import android.content.ServiceConnection
2224import android.graphics.Bitmap
23- import android.graphics.PixelFormat
24- import android.hardware.display.DisplayManager
25- import android.hardware.display.VirtualDisplay
26- import android.media.Image
27- import android.media.ImageReader
28- import android.media.projection.MediaProjection
2925import android.media.projection.MediaProjectionManager
30- import android.os.Handler
31- import android.os.Looper
32- import android.util.DisplayMetrics
26+ import android.os.IBinder
3327import android.util.Log
34- import android.view.WindowManager
3528import java.io.File
36- import java.io.FileOutputStream
37- import java.io.IOException
38- import java.nio.ByteBuffer
39- import java.util.UUID
4029
4130/* *
42- * Manager class for handling screenshot functionality using MediaProjection
31+ * Manager class for handling screenshot functionality using MediaProjection via a foreground service
4332 */
4433class ScreenshotManager (private val context : Context ) {
4534 companion object {
@@ -57,23 +46,30 @@ class ScreenshotManager(private val context: Context) {
5746 }
5847 }
5948
60- private var mediaProjection: MediaProjection ? = null
61- private var virtualDisplay: VirtualDisplay ? = null
62- private var imageReader: ImageReader ? = null
63- private var screenDensity: Int = 0
64- private var screenWidth: Int = 0
65- private var screenHeight: Int = 0
66- private var screenshotCallback: ((Bitmap ? ) -> Unit )? = null
67- private val handler = Handler (Looper .getMainLooper())
49+ private var screenshotService: ScreenshotService ? = null
50+ private var isBound = false
51+ private var pendingScreenshotCallback: ((Bitmap ? ) -> Unit )? = null
52+ private var resultCode: Int = Activity .RESULT_CANCELED
53+ private var resultData: Intent ? = null
6854
69- init {
70- // Get screen metrics
71- val windowManager = context.getSystemService(Context .WINDOW_SERVICE ) as WindowManager
72- val metrics = DisplayMetrics ()
73- windowManager.defaultDisplay.getMetrics(metrics)
74- screenDensity = metrics.densityDpi
75- screenWidth = metrics.widthPixels
76- screenHeight = metrics.heightPixels
55+ // Service connection
56+ private val serviceConnection = object : ServiceConnection {
57+ override fun onServiceConnected (name : ComponentName ? , service : IBinder ? ) {
58+ val binder = service as ScreenshotService .LocalBinder
59+ screenshotService = binder.getService()
60+ isBound = true
61+
62+ // If there's a pending screenshot request, execute it now
63+ pendingScreenshotCallback?.let { callback ->
64+ takeScreenshot(callback)
65+ pendingScreenshotCallback = null
66+ }
67+ }
68+
69+ override fun onServiceDisconnected (name : ComponentName ? ) {
70+ screenshotService = null
71+ isBound = false
72+ }
7773 }
7874
7975 /* *
@@ -100,115 +96,59 @@ class ScreenshotManager(private val context: Context) {
10096 return false
10197 }
10298
103- val mediaProjectionManager = context.getSystemService(Context .MEDIA_PROJECTION_SERVICE ) as MediaProjectionManager
104- mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data)
99+ this .resultCode = resultCode
100+ this .resultData = data
101+
102+ // Start the foreground service
103+ val serviceIntent = Intent (context, ScreenshotService ::class .java).apply {
104+ action = ScreenshotService .ACTION_START
105+ putExtra(ScreenshotService .EXTRA_RESULT_CODE , resultCode)
106+ putExtra(ScreenshotService .EXTRA_DATA , data)
107+ }
108+ context.startForegroundService(serviceIntent)
109+
110+ // Bind to the service
111+ bindService()
112+
105113 return true
106114 }
107115
108116 /* *
109- * Take a screenshot
110- * @param callback Callback function that will be called with the screenshot bitmap
117+ * Bind to the screenshot service
111118 */
112- fun takeScreenshot (callback : (Bitmap ? ) -> Unit ) {
113- screenshotCallback = callback
114-
115- if (mediaProjection == null ) {
116- Log .e(TAG , " MediaProjection not initialized" )
117- callback(null )
118- return
119+ private fun bindService () {
120+ if (! isBound) {
121+ val intent = Intent (context, ScreenshotService ::class .java)
122+ context.bindService(intent, serviceConnection, Context .BIND_AUTO_CREATE )
119123 }
120-
121- // Create ImageReader
122- imageReader = ImageReader .newInstance(
123- screenWidth,
124- screenHeight,
125- PixelFormat .RGBA_8888 ,
126- 1
127- )
128-
129- // Create virtual display
130- virtualDisplay = mediaProjection?.createVirtualDisplay(
131- " ScreenCapture" ,
132- screenWidth,
133- screenHeight,
134- screenDensity,
135- DisplayManager .VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR ,
136- imageReader?.surface,
137- null ,
138- handler
139- )
140-
141- // Capture image
142- imageReader?.setOnImageAvailableListener({ reader ->
143- var image: Image ? = null
144- var bitmap: Bitmap ? = null
145-
146- try {
147- image = reader.acquireLatestImage()
148- if (image != null ) {
149- val planes = image.planes
150- val buffer = planes[0 ].buffer
151- val pixelStride = planes[0 ].pixelStride
152- val rowStride = planes[0 ].rowStride
153- val rowPadding = rowStride - pixelStride * screenWidth
154-
155- // Create bitmap
156- bitmap = Bitmap .createBitmap(
157- screenWidth + rowPadding / pixelStride,
158- screenHeight,
159- Bitmap .Config .ARGB_8888
160- )
161- bitmap.copyPixelsFromBuffer(buffer)
162-
163- // Crop bitmap to exact screen size if needed
164- if (bitmap.width > screenWidth || bitmap.height > screenHeight) {
165- bitmap = Bitmap .createBitmap(
166- bitmap,
167- 0 ,
168- 0 ,
169- screenWidth,
170- screenHeight
171- )
172- }
173- }
174- } catch (e: Exception ) {
175- Log .e(TAG , " Error capturing screen: ${e.message} " )
176- bitmap = null
177- } finally {
178- image?.close()
179- tearDown()
180- screenshotCallback?.invoke(bitmap)
181- }
182- }, handler)
183-
184- // Add a delay to ensure the virtual display is set up
185- handler.postDelayed({
186- if (imageReader?.surface == null ) {
187- Log .e(TAG , " Surface is null" )
188- tearDown()
189- callback(null )
190- }
191- }, 100 )
192124 }
193125
194126 /* *
195- * Clean up resources
127+ * Unbind from the screenshot service
196128 */
197- private fun tearDown () {
198- virtualDisplay?.release()
199- virtualDisplay = null
200-
201- imageReader?.close()
202- imageReader = null
129+ private fun unbindService () {
130+ if (isBound) {
131+ context.unbindService(serviceConnection)
132+ isBound = false
133+ }
203134 }
204135
205136 /* *
206- * Release all resources
137+ * Take a screenshot
138+ * @param callback Callback function that will be called with the screenshot bitmap
207139 */
208- fun release () {
209- tearDown()
210- mediaProjection?.stop()
211- mediaProjection = null
140+ fun takeScreenshot (callback : (Bitmap ? ) -> Unit ) {
141+ if (isBound && screenshotService != null ) {
142+ screenshotService?.takeScreenshot(callback)
143+ } else {
144+ // Store the callback and execute it when the service is connected
145+ pendingScreenshotCallback = callback
146+
147+ // Try to bind to the service if not already bound
148+ if (! isBound) {
149+ bindService()
150+ }
151+ }
212152 }
213153
214154 /* *
@@ -217,17 +157,29 @@ class ScreenshotManager(private val context: Context) {
217157 * @return The URI of the saved file, or null if saving failed
218158 */
219159 fun saveBitmapToFile (bitmap : Bitmap ): File ? {
220- val fileName = " screenshot_${UUID .randomUUID()} .png"
221- val file = File (context.cacheDir, fileName)
222-
223- return try {
224- FileOutputStream (file).use { out ->
225- bitmap.compress(Bitmap .CompressFormat .PNG , 100 , out )
226- }
227- file
228- } catch (e: IOException ) {
229- Log .e(TAG , " Error saving bitmap: ${e.message} " )
160+ return if (isBound && screenshotService != null ) {
161+ screenshotService?.saveBitmapToFile(bitmap)
162+ } else {
163+ Log .e(TAG , " Service not bound, cannot save bitmap" )
230164 null
231165 }
232166 }
167+
168+ /* *
169+ * Release all resources
170+ */
171+ fun release () {
172+ // Stop the service
173+ val serviceIntent = Intent (context, ScreenshotService ::class .java).apply {
174+ action = ScreenshotService .ACTION_STOP
175+ }
176+ context.startService(serviceIntent)
177+
178+ // Unbind from the service
179+ unbindService()
180+
181+ // Clear references
182+ screenshotService = null
183+ resultData = null
184+ }
233185}
0 commit comments