|
|
|
|
package com.quillstudios.pokegoalshelper.capture
|
|
|
|
|
|
|
|
|
|
import android.app.Activity
|
|
|
|
|
import android.content.Context
|
|
|
|
|
import android.content.Intent
|
|
|
|
|
import android.graphics.PixelFormat
|
|
|
|
|
import android.hardware.display.DisplayManager
|
|
|
|
|
import android.hardware.display.VirtualDisplay
|
|
|
|
|
import android.media.Image
|
|
|
|
|
import android.media.ImageReader
|
|
|
|
|
import android.media.projection.MediaProjection
|
|
|
|
|
import android.media.projection.MediaProjectionManager
|
|
|
|
|
import android.os.Handler
|
|
|
|
|
import android.util.DisplayMetrics
|
|
|
|
|
import android.util.Log
|
|
|
|
|
import android.view.WindowManager
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Implementation of ScreenCaptureManager that handles MediaProjection-based screen capture.
|
|
|
|
|
* Extracted from ScreenCaptureService for better separation of concerns.
|
|
|
|
|
*/
|
|
|
|
|
class ScreenCaptureManagerImpl(
|
|
|
|
|
private val context: Context,
|
|
|
|
|
private val handler: Handler
|
|
|
|
|
) : ScreenCaptureManager
|
|
|
|
|
{
|
|
|
|
|
companion object
|
|
|
|
|
{
|
|
|
|
|
private const val TAG = "ScreenCaptureManager"
|
|
|
|
|
private const val BUFFER_COUNT = 3
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private var mediaProjectionManager: MediaProjectionManager? = null
|
|
|
|
|
private var mediaProjection: MediaProjection? = null
|
|
|
|
|
private var virtualDisplay: VirtualDisplay? = null
|
|
|
|
|
private var imageReader: ImageReader? = null
|
|
|
|
|
|
|
|
|
|
private var screenWidth = 0
|
|
|
|
|
private var screenHeight = 0
|
|
|
|
|
private var screenDensity = 0
|
|
|
|
|
|
|
|
|
|
private var imageCallback: ((Image) -> Unit)? = null
|
|
|
|
|
private var isActive = false
|
|
|
|
|
|
|
|
|
|
init
|
|
|
|
|
{
|
|
|
|
|
mediaProjectionManager = context.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
|
|
|
|
|
initializeScreenMetrics()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun initializeScreenMetrics()
|
|
|
|
|
{
|
|
|
|
|
val display_metrics = DisplayMetrics()
|
|
|
|
|
val window_manager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
|
|
|
|
window_manager.defaultDisplay.getMetrics(display_metrics)
|
|
|
|
|
|
|
|
|
|
screenWidth = display_metrics.widthPixels
|
|
|
|
|
screenHeight = display_metrics.heightPixels
|
|
|
|
|
screenDensity = display_metrics.densityDpi
|
|
|
|
|
|
|
|
|
|
Log.d(TAG, "Screen metrics initialized: ${screenWidth}x${screenHeight}, density: $screenDensity")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private val mediaProjectionCallback = object : MediaProjection.Callback()
|
|
|
|
|
{
|
|
|
|
|
override fun onStop()
|
|
|
|
|
{
|
|
|
|
|
Log.d(TAG, "MediaProjection stopped")
|
|
|
|
|
stopCapture()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onCapturedContentResize(width: Int, height: Int)
|
|
|
|
|
{
|
|
|
|
|
Log.d(TAG, "Screen size changed: ${width}x${height}")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onCapturedContentVisibilityChanged(isVisible: Boolean)
|
|
|
|
|
{
|
|
|
|
|
Log.d(TAG, "Content visibility changed: $isVisible")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private val onImageAvailableListener = ImageReader.OnImageAvailableListener { reader ->
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
val captured_image = reader.acquireLatestImage()
|
|
|
|
|
if (captured_image != null)
|
|
|
|
|
{
|
|
|
|
|
imageCallback?.invoke(captured_image)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (e: Exception)
|
|
|
|
|
{
|
|
|
|
|
Log.e(TAG, "Error in onImageAvailableListener", e)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun startCapture(resultData: Intent): Boolean
|
|
|
|
|
{
|
|
|
|
|
if (isActive)
|
|
|
|
|
{
|
|
|
|
|
Log.w(TAG, "Capture already active")
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
Log.d(TAG, "Starting screen capture")
|
|
|
|
|
|
|
|
|
|
mediaProjection = mediaProjectionManager?.getMediaProjection(Activity.RESULT_OK, resultData)
|
|
|
|
|
if (mediaProjection == null)
|
|
|
|
|
{
|
|
|
|
|
Log.e(TAG, "Failed to get MediaProjection")
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Log.d(TAG, "Registering MediaProjection callback")
|
|
|
|
|
mediaProjection?.registerCallback(mediaProjectionCallback, handler)
|
|
|
|
|
|
|
|
|
|
Log.d(TAG, "Creating ImageReader: ${screenWidth}x${screenHeight}")
|
|
|
|
|
imageReader = ImageReader.newInstance(screenWidth, screenHeight, PixelFormat.RGBA_8888, BUFFER_COUNT)
|
|
|
|
|
imageReader?.setOnImageAvailableListener(onImageAvailableListener, handler)
|
|
|
|
|
|
|
|
|
|
Log.d(TAG, "Creating VirtualDisplay")
|
|
|
|
|
virtualDisplay = mediaProjection?.createVirtualDisplay(
|
|
|
|
|
"ScreenCapture",
|
|
|
|
|
screenWidth,
|
|
|
|
|
screenHeight,
|
|
|
|
|
screenDensity,
|
|
|
|
|
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
|
|
|
|
|
imageReader?.surface,
|
|
|
|
|
null,
|
|
|
|
|
null
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (virtualDisplay == null)
|
|
|
|
|
{
|
|
|
|
|
Log.e(TAG, "Failed to create VirtualDisplay")
|
|
|
|
|
cleanupResources()
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isActive = true
|
|
|
|
|
Log.d(TAG, "Screen capture started successfully")
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
catch (e: Exception)
|
|
|
|
|
{
|
|
|
|
|
Log.e(TAG, "Error starting screen capture", e)
|
|
|
|
|
cleanupResources()
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun stopCapture()
|
|
|
|
|
{
|
|
|
|
|
if (!isActive)
|
|
|
|
|
{
|
|
|
|
|
Log.d(TAG, "Capture not active, nothing to stop")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Log.d(TAG, "Stopping screen capture")
|
|
|
|
|
cleanupResources()
|
|
|
|
|
isActive = false
|
|
|
|
|
Log.d(TAG, "Screen capture stopped")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun cleanupResources()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
virtualDisplay?.release()
|
|
|
|
|
imageReader?.close()
|
|
|
|
|
mediaProjection?.unregisterCallback(mediaProjectionCallback)
|
|
|
|
|
mediaProjection?.stop()
|
|
|
|
|
}
|
|
|
|
|
catch (e: Exception)
|
|
|
|
|
{
|
|
|
|
|
Log.e(TAG, "Error during cleanup", e)
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
virtualDisplay = null
|
|
|
|
|
imageReader = null
|
|
|
|
|
mediaProjection = null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun setImageCallback(callback: (Image) -> Unit)
|
|
|
|
|
{
|
|
|
|
|
this.imageCallback = callback
|
|
|
|
|
Log.d(TAG, "Image callback set")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun isCapturing(): Boolean = isActive
|
|
|
|
|
|
|
|
|
|
override fun getScreenDimensions(): Pair<Int, Int>?
|
|
|
|
|
{
|
|
|
|
|
return if (screenWidth > 0 && screenHeight > 0)
|
|
|
|
|
{
|
|
|
|
|
Pair(screenWidth, screenHeight)
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun getMediaProjection(): MediaProjection?
|
|
|
|
|
{
|
|
|
|
|
return mediaProjection
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun getScreenDensity(): Int
|
|
|
|
|
{
|
|
|
|
|
return screenDensity
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun release()
|
|
|
|
|
{
|
|
|
|
|
Log.d(TAG, "Releasing ScreenCaptureManager")
|
|
|
|
|
stopCapture()
|
|
|
|
|
imageCallback = null
|
|
|
|
|
mediaProjectionManager = null
|
|
|
|
|
}
|
|
|
|
|
}
|