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 {
|
||||
// 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<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
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user