diff --git a/host-agent/android/app/src/main/java/de/hackersoft/ariaagent/AgentService.kt b/host-agent/android/app/src/main/java/de/hackersoft/ariaagent/AgentService.kt index 96fcdbd..da5af61 100644 --- a/host-agent/android/app/src/main/java/de/hackersoft/ariaagent/AgentService.kt +++ b/host-agent/android/app/src/main/java/de/hackersoft/ariaagent/AgentService.kt @@ -47,11 +47,26 @@ class AgentService : Service() { } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + // WICHTIG: Jeder startForegroundService()-Aufruf MUSS binnen ~5s mit + // startForeground() beantwortet werden — sonst crasht Android den Prozess + // (ForegroundServiceDidNotStartInTimeException). Der Projection-Intent kommt + // per startForegroundService, obwohl der Dienst schon laeuft -> hier IMMER + // zuerst startForeground aufrufen (idempotent). + startForeground(NOTIF_ID, buildNotification(status)) + if (intent?.action == ACTION_PROJECTION) { val code = intent.getIntExtra(EXTRA_RESULT_CODE, 0) @Suppress("DEPRECATION") val data = intent.getParcelableExtra(EXTRA_RESULT_DATA) - if (code != 0 && data != null) ScreenCapturer.start(applicationContext, code, data) + if (code != 0 && data != null) { + try { + ScreenCapturer.start(applicationContext, code, data) + updateNotification("$status · Bildschirm-Zugriff aktiv") + } catch (e: Throwable) { + ScreenCapturer.lastError = e.message ?: e.javaClass.simpleName + updateNotification("Bildschirm-Fehler: ${ScreenCapturer.lastError}") + } + } if (rvs == null) startRvs() // Dienst war frisch -> Verbindung nachziehen return START_STICKY } diff --git a/host-agent/android/app/src/main/java/de/hackersoft/ariaagent/RvsClient.kt b/host-agent/android/app/src/main/java/de/hackersoft/ariaagent/RvsClient.kt index 4597d7a..b1cd70d 100644 --- a/host-agent/android/app/src/main/java/de/hackersoft/ariaagent/RvsClient.kt +++ b/host-agent/android/app/src/main/java/de/hackersoft/ariaagent/RvsClient.kt @@ -147,9 +147,11 @@ class RvsClient( /** Bildschirmfoto — selber Vertrag wie der Desktop-Agent: {format,bytes,base64}. */ private fun doScreenshot(): JSONObject { - if (!ScreenCapturer.active) + if (!ScreenCapturer.active) { + val why = ScreenCapturer.lastError?.let { " (letzter Fehler: $it)" } ?: "" return err("Bildschirm-Zugriff nicht erlaubt. In der Agent-App auf dem Handy " + - "einmalig 'Bildschirm-Zugriff erlauben' antippen.") + "einmalig 'Bildschirm-Zugriff erlauben' antippen.$why") + } val png = ScreenCapturer.capture() ?: return err("Screenshot fehlgeschlagen (kein Frame). Ist der Bildschirm an?") val b64 = android.util.Base64.encodeToString(png, android.util.Base64.NO_WRAP) diff --git a/host-agent/android/app/src/main/java/de/hackersoft/ariaagent/ScreenCapturer.kt b/host-agent/android/app/src/main/java/de/hackersoft/ariaagent/ScreenCapturer.kt index b4830d6..3cbaeee 100644 --- a/host-agent/android/app/src/main/java/de/hackersoft/ariaagent/ScreenCapturer.kt +++ b/host-agent/android/app/src/main/java/de/hackersoft/ariaagent/ScreenCapturer.kt @@ -36,6 +36,10 @@ object ScreenCapturer { private var h = 0 private var dpi = 0 + /** Letzter Init-/Capture-Fehler (fuer die Fehlermeldung an ARIA). */ + @Volatile + var lastError: String? = null + val active: Boolean @Synchronized get() = projection != null @@ -43,7 +47,10 @@ object ScreenCapturer { fun start(ctx: Context, resultCode: Int, data: Intent) { stop() val mpm = ctx.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager - val mp = mpm.getMediaProjection(resultCode, data) ?: return + val mp = mpm.getMediaProjection(resultCode, data) ?: run { + lastError = "getMediaProjection lieferte null" + return + } val metrics = DisplayMetrics() val wm = ctx.getSystemService(Context.WINDOW_SERVICE) as WindowManager @@ -56,18 +63,22 @@ object ScreenCapturer { handlerThread = HandlerThread("aria-capture").also { it.start() } handler = Handler(handlerThread!!.looper) + // Ab Android 14 Pflicht VOR createVirtualDisplay; frueher unschaedlich. mp.registerCallback(object : MediaProjection.Callback() { override fun onStop() { stop() } }, handler) val ir = ImageReader.newInstance(w, h, PixelFormat.RGBA_8888, 2) reader = ir + // AUTO_MIRROR = Standard-Flag fuer MediaProjection-Capture (die Projection + // selbst autorisiert die Aufnahme, kein Sonderrecht noetig). vdisplay = mp.createVirtualDisplay( "aria-screen", w, h, dpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, ir.surface, null, handler, ) projection = mp + lastError = null } @Synchronized