fix(android-agent): Crash bei Bildschirm-Zugriff (startForeground-Pflicht)
Der Projection-Consent kommt per startForegroundService, obwohl der Dienst schon laeuft. Android verlangt danach binnen ~5s ein startForeground() -> fehlte im Projection-Zweig -> ForegroundServiceDidNotStartInTimeException -> Prozess-Crash (Diagnostic zeigt den Host bis zum Ping-Timeout noch gruen). Fix: onStartCommand ruft IMMER zuerst startForeground(). Zusaetzlich ScreenCapturer.start in try/catch mit lastError, das in der Screenshot-Fehlermeldung und der Notification erscheint. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -47,11 +47,26 @@ class AgentService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
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) {
|
if (intent?.action == ACTION_PROJECTION) {
|
||||||
val code = intent.getIntExtra(EXTRA_RESULT_CODE, 0)
|
val code = intent.getIntExtra(EXTRA_RESULT_CODE, 0)
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
val data = intent.getParcelableExtra<Intent>(EXTRA_RESULT_DATA)
|
val data = intent.getParcelableExtra<Intent>(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
|
if (rvs == null) startRvs() // Dienst war frisch -> Verbindung nachziehen
|
||||||
return START_STICKY
|
return START_STICKY
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,9 +147,11 @@ class RvsClient(
|
|||||||
|
|
||||||
/** Bildschirmfoto — selber Vertrag wie der Desktop-Agent: {format,bytes,base64}. */
|
/** Bildschirmfoto — selber Vertrag wie der Desktop-Agent: {format,bytes,base64}. */
|
||||||
private fun doScreenshot(): JSONObject {
|
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 " +
|
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()
|
val png = ScreenCapturer.capture()
|
||||||
?: return err("Screenshot fehlgeschlagen (kein Frame). Ist der Bildschirm an?")
|
?: return err("Screenshot fehlgeschlagen (kein Frame). Ist der Bildschirm an?")
|
||||||
val b64 = android.util.Base64.encodeToString(png, android.util.Base64.NO_WRAP)
|
val b64 = android.util.Base64.encodeToString(png, android.util.Base64.NO_WRAP)
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ object ScreenCapturer {
|
|||||||
private var h = 0
|
private var h = 0
|
||||||
private var dpi = 0
|
private var dpi = 0
|
||||||
|
|
||||||
|
/** Letzter Init-/Capture-Fehler (fuer die Fehlermeldung an ARIA). */
|
||||||
|
@Volatile
|
||||||
|
var lastError: String? = null
|
||||||
|
|
||||||
val active: Boolean
|
val active: Boolean
|
||||||
@Synchronized get() = projection != null
|
@Synchronized get() = projection != null
|
||||||
|
|
||||||
@@ -43,7 +47,10 @@ object ScreenCapturer {
|
|||||||
fun start(ctx: Context, resultCode: Int, data: Intent) {
|
fun start(ctx: Context, resultCode: Int, data: Intent) {
|
||||||
stop()
|
stop()
|
||||||
val mpm = ctx.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
|
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 metrics = DisplayMetrics()
|
||||||
val wm = ctx.getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
val wm = ctx.getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
||||||
@@ -56,18 +63,22 @@ object ScreenCapturer {
|
|||||||
handlerThread = HandlerThread("aria-capture").also { it.start() }
|
handlerThread = HandlerThread("aria-capture").also { it.start() }
|
||||||
handler = Handler(handlerThread!!.looper)
|
handler = Handler(handlerThread!!.looper)
|
||||||
|
|
||||||
|
// Ab Android 14 Pflicht VOR createVirtualDisplay; frueher unschaedlich.
|
||||||
mp.registerCallback(object : MediaProjection.Callback() {
|
mp.registerCallback(object : MediaProjection.Callback() {
|
||||||
override fun onStop() { stop() }
|
override fun onStop() { stop() }
|
||||||
}, handler)
|
}, handler)
|
||||||
|
|
||||||
val ir = ImageReader.newInstance(w, h, PixelFormat.RGBA_8888, 2)
|
val ir = ImageReader.newInstance(w, h, PixelFormat.RGBA_8888, 2)
|
||||||
reader = ir
|
reader = ir
|
||||||
|
// AUTO_MIRROR = Standard-Flag fuer MediaProjection-Capture (die Projection
|
||||||
|
// selbst autorisiert die Aufnahme, kein Sonderrecht noetig).
|
||||||
vdisplay = mp.createVirtualDisplay(
|
vdisplay = mp.createVirtualDisplay(
|
||||||
"aria-screen", w, h, dpi,
|
"aria-screen", w, h, dpi,
|
||||||
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
|
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
|
||||||
ir.surface, null, handler,
|
ir.surface, null, handler,
|
||||||
)
|
)
|
||||||
projection = mp
|
projection = mp
|
||||||
|
lastError = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
|
|||||||
Reference in New Issue
Block a user