Skip to content

Commit d64012c

Browse files
Add files via upload
1 parent b42d3a5 commit d64012c

3 files changed

Lines changed: 382 additions & 136 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ class MainActivity : ComponentActivity() {
7676
screenshotManager.requestScreenshotPermission(this)
7777
}
7878

79+
@Deprecated("Deprecated in Java")
7980
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
8081
super.onActivityResult(requestCode, resultCode, data)
8182

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

Lines changed: 88 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,18 @@
1717
package com.google.ai.sample
1818

1919
import android.app.Activity
20+
import android.content.ComponentName
2021
import android.content.Context
2122
import android.content.Intent
23+
import android.content.ServiceConnection
2224
import 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
2925
import android.media.projection.MediaProjectionManager
30-
import android.os.Handler
31-
import android.os.Looper
32-
import android.util.DisplayMetrics
26+
import android.os.IBinder
3327
import android.util.Log
34-
import android.view.WindowManager
3528
import 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
*/
4433
class 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

Comments
 (0)