package com.parentalmonitor.child.services import android.app.Activity import android.app.Notification import android.app.Service import android.content.Context import android.content.Intent import android.graphics.Bitmap import android.graphics.PixelFormat import android.hardware.display.DisplayManager import android.hardware.display.VirtualDisplay import android.media.ImageReader import android.media.projection.MediaProjection import android.media.projection.MediaProjectionManager import android.os.IBinder import android.util.DisplayMetrics import android.util.Log import android.view.WindowManager import androidx.core.app.NotificationCompat import com.parentalmonitor.child.ParentalMonitorApp import kotlinx.coroutines.* import java.io.File import java.io.FileOutputStream class ScreenCaptureService : Service() { private var mediaProjection: MediaProjection? = null private var virtualDisplay: VirtualDisplay? = null private var imageReader: ImageReader? = null private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) companion object { const val TAG = "ScreenCaptureService" const val NOTIFICATION_ID = 1002 const val EXTRA_RESULT_CODE = "result_code" const val EXTRA_DATA = "data" var resultCode: Int = Activity.RESULT_CANCELED var resultData: Intent? = null } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { startForeground(NOTIFICATION_ID, createNotification()) val code = intent?.getIntExtra(EXTRA_RESULT_CODE, Activity.RESULT_CANCELED) ?: resultCode val data = intent?.getParcelableExtra(EXTRA_DATA) ?: resultData if (code == Activity.RESULT_OK && data != null) { val projectionManager = getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager mediaProjection = projectionManager.getMediaProjection(code, data) captureScreen() } else { stopSelf() } return START_NOT_STICKY } override fun onBind(intent: Intent?): IBinder? = null private fun createNotification(): Notification { return NotificationCompat.Builder(this, ParentalMonitorApp.CHANNEL_MONITORING) .setContentTitle("Screen Capture") .setContentText("Capturing screenshot...") .setSmallIcon(android.R.drawable.ic_menu_camera) .setSilent(true) .build() } private fun captureScreen() { serviceScope.launch { try { val wm = getSystemService(Context.WINDOW_SERVICE) as WindowManager val metrics = DisplayMetrics() @Suppress("DEPRECATION") wm.defaultDisplay.getMetrics(metrics) val width = metrics.widthPixels val height = metrics.heightPixels val density = metrics.densityDpi imageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 2) virtualDisplay = mediaProjection?.createVirtualDisplay( "ScreenCapture", width, height, density, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, imageReader!!.surface, null, null ) delay(500) // Wait for capture val image = imageReader?.acquireLatestImage() if (image != null) { val planes = image.planes val buffer = planes[0].buffer val pixelStride = planes[0].pixelStride val rowStride = planes[0].rowStride val rowPadding = rowStride - pixelStride * width val bitmap = Bitmap.createBitmap( width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888 ) bitmap.copyPixelsFromBuffer(buffer) image.close() val croppedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height) if (croppedBitmap != bitmap) bitmap.recycle() // Save and upload val file = File(cacheDir, "screenshot_${System.currentTimeMillis()}.png") FileOutputStream(file).use { fos -> croppedBitmap.compress(Bitmap.CompressFormat.PNG, 90, fos) } croppedBitmap.recycle() val apiClient = (application as ParentalMonitorApp).apiClient apiClient.uploadScreenshot(file) file.delete() } } catch (e: Exception) { Log.e(TAG, "Screen capture error", e) } finally { cleanup() stopSelf() } } } private fun cleanup() { virtualDisplay?.release() imageReader?.close() mediaProjection?.stop() } override fun onDestroy() { super.onDestroy() cleanup() serviceScope.cancel() } }