feat(android-agent): Einblendung geht weg, wenn Stefan etwas tut — statt nach fester Zeit
Eine feste Anzeigedauer ist immer geraten: mal zu kurz zum Lesen, mal steht sie laengst im Weg. `seconds=0` liess die Einblendung bisher einfach stehen. Jetzt heisst seconds=0: bleibt, bis der Nutzer etwas tut. - Fernbedienung: der Accessibility-Dienst bekommt onKeyEvent und raeumt die Einblendung weg. Er gibt die Taste mit `return false` WEITER an den Film — es geht also kein Tastendruck verloren und der Player bleibt bedienbar. Dafuer brauchte der Dienst canRequestFilterKeyEvents + flagRequestFilterKeyEvents. - Touch: FLAG_WATCH_OUTSIDE_TOUCH meldet eine Beruehrung ausserhalb. Die geht dank FLAG_NOT_TOUCHABLE gleichzeitig an die App darunter durch. - Notbremse: nach 10 Minuten verschwindet sie in jedem Fall. Bewusst NICHT der naheliegende Weg (Overlay faengt Tasten selbst ab): dafuer muessten FLAG_NOT_FOCUSABLE und FLAG_NOT_TOUCHABLE weg, und dann nimmt die Einblendung dem laufenden Film JEDE Eingabe, solange sie steht. Genau das soll sie nicht. host_notify beschreibt seconds=0 jetzt als den richtigen Wert fuer alles zum Lesen (Wetter, Listen, Zahlen) und die feste Dauer fuer kurze Hinweise. APK baut durch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+12
-1
@@ -1638,7 +1638,18 @@ META_TOOLS = [
|
|||||||
"host": {"type": "string", "description": "Host-ID/Name (z.B. 'Fire-TV Schlafzimmer')."},
|
"host": {"type": "string", "description": "Host-ID/Name (z.B. 'Fire-TV Schlafzimmer')."},
|
||||||
"text": {"type": "string", "description": "Der anzuzeigende Text."},
|
"text": {"type": "string", "description": "Der anzuzeigende Text."},
|
||||||
"title": {"type": "string", "description": "Ueberschrift (Default 'ARIA')."},
|
"title": {"type": "string", "description": "Ueberschrift (Default 'ARIA')."},
|
||||||
"seconds": {"type": "integer", "description": "Anzeigedauer (Default 12)."},
|
"seconds": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": (
|
||||||
|
"Anzeigedauer in Sekunden (Default 12). "
|
||||||
|
"0 = stehen lassen, bis Stefan selbst wegdrueckt "
|
||||||
|
"(Fernbedienung: beliebige Taste, Handy: daneben "
|
||||||
|
"tippen). Nimm 0 fuer alles zum LESEN — Wetter, "
|
||||||
|
"Listen, Zahlen: eine feste Dauer ist da immer "
|
||||||
|
"geraten und meist zu kurz. Kurze Hinweise waehrend "
|
||||||
|
"eines Films ruhig mit Dauer."
|
||||||
|
),
|
||||||
|
},
|
||||||
"mode": {"type": "string", "description": "overlay | speech | both"},
|
"mode": {"type": "string", "description": "overlay | speech | both"},
|
||||||
"focus": {"type": "string", "description": "Bei Sprachausgabe: 'duck' (laufende Medien werden leiser, Standard) | 'pause' (Film/Musik anhalten) | 'none'."},
|
"focus": {"type": "string", "description": "Bei Sprachausgabe: 'duck' (laufende Medien werden leiser, Standard) | 'pause' (Film/Musik anhalten) | 'none'."},
|
||||||
"image_path": {
|
"image_path": {
|
||||||
|
|||||||
+23
@@ -44,6 +44,29 @@ class AriaAccessibilityService : AccessibilityService() {
|
|||||||
override fun onAccessibilityEvent(event: AccessibilityEvent?) { /* passiv */ }
|
override fun onAccessibilityEvent(event: AccessibilityEvent?) { /* passiv */ }
|
||||||
override fun onInterrupt() { /* passiv */ }
|
override fun onInterrupt() { /* passiv */ }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Steht eine Einblendung offen, raeumt sie der naechste Tastendruck weg.
|
||||||
|
*
|
||||||
|
* Der Trick: wir geben IMMER `false` zurueck. Damit laeuft die Taste ganz
|
||||||
|
* normal weiter an den Film/die App — die Einblendung schluckt nichts. Sie
|
||||||
|
* muss dafuer auch nicht fokussierbar sein, kann also weiterhin ueber einem
|
||||||
|
* laufenden Video stehen, ohne die Fernbedienung zu blockieren.
|
||||||
|
*
|
||||||
|
* Deshalb dieser Weg und nicht "Overlay faengt Tasten selbst ab": ein
|
||||||
|
* fokussierbares Fenster wuerde der App darunter JEDE Eingabe nehmen,
|
||||||
|
* solange es steht.
|
||||||
|
*
|
||||||
|
* Eine feste Anzeigedauer ist naemlich immer falsch — mal zu kurz zum
|
||||||
|
* Lesen, mal steht sie ewig im Bild.
|
||||||
|
*/
|
||||||
|
override fun onKeyEvent(event: android.view.KeyEvent?): Boolean {
|
||||||
|
if (event != null && event.action == android.view.KeyEvent.ACTION_DOWN
|
||||||
|
&& OverlayView.isShowing()) {
|
||||||
|
OverlayView.hide(applicationContext)
|
||||||
|
}
|
||||||
|
return false // Taste NICHT verbrauchen
|
||||||
|
}
|
||||||
|
|
||||||
/** Strukturierter Baum des aktiven Fensters. Shape: {ok, result:{package,count,nodes[]}}. */
|
/** Strukturierter Baum des aktiven Fensters. Shape: {ok, result:{package,count,nodes[]}}. */
|
||||||
fun dump(): JSONObject {
|
fun dump(): JSONObject {
|
||||||
val root = rootInActiveWindow
|
val root = rootInActiveWindow
|
||||||
|
|||||||
@@ -25,13 +25,33 @@ import android.widget.TextView
|
|||||||
*
|
*
|
||||||
* Nicht fokussierbar (FLAG_NOT_FOCUSABLE) — die Fernbedienung steuert weiter die
|
* Nicht fokussierbar (FLAG_NOT_FOCUSABLE) — die Fernbedienung steuert weiter die
|
||||||
* App darunter, die Einblendung schluckt keine Tastendruecke.
|
* App darunter, die Einblendung schluckt keine Tastendruecke.
|
||||||
|
*
|
||||||
|
* WEGBEKOMMEN ohne feste Anzeigedauer (seconds = 0):
|
||||||
|
* - Fernbedienung: der Accessibility-Dienst sieht den naechsten Tastendruck und
|
||||||
|
* raeumt die Einblendung weg — er gibt die Taste trotzdem an den Film weiter
|
||||||
|
* (siehe AriaAccessibilityService.onKeyEvent).
|
||||||
|
* - Touch: FLAG_WATCH_OUTSIDE_TOUCH meldet uns eine Beruehrung AUSSERHALB der
|
||||||
|
* Einblendung. Die Beruehrung geht gleichzeitig an die App darunter — es
|
||||||
|
* geht also kein Tipp verloren.
|
||||||
|
* - Notbremse: nach HARD_CAP_SEC verschwindet sie in jedem Fall. Ohne das
|
||||||
|
* koennte eine vergessene Einblendung dauerhaft im Bild stehen.
|
||||||
|
*
|
||||||
|
* Eine feste Dauer ist naemlich immer falsch geraten — mal zu kurz zum Lesen,
|
||||||
|
* mal steht sie laengst im Weg.
|
||||||
*/
|
*/
|
||||||
object OverlayView {
|
object OverlayView {
|
||||||
|
|
||||||
|
/** Auch eine "bleibt stehen"-Einblendung geht spaetestens danach weg. */
|
||||||
|
private const val HARD_CAP_SEC = 600
|
||||||
|
|
||||||
private val main = Handler(Looper.getMainLooper())
|
private val main = Handler(Looper.getMainLooper())
|
||||||
private var root: LinearLayout? = null
|
private var root: LinearLayout? = null
|
||||||
private var hideRunnable: Runnable? = null
|
private var hideRunnable: Runnable? = null
|
||||||
|
|
||||||
|
/** Steht gerade eine Einblendung? Der Accessibility-Dienst fragt das, bevor
|
||||||
|
* er auf einen Tastendruck hin aufraeumt. */
|
||||||
|
fun isShowing(): Boolean = root != null
|
||||||
|
|
||||||
/** Hat der Nutzer "Ueber anderen Apps anzeigen" erlaubt? */
|
/** Hat der Nutzer "Ueber anderen Apps anzeigen" erlaubt? */
|
||||||
fun canDraw(ctx: Context): Boolean =
|
fun canDraw(ctx: Context): Boolean =
|
||||||
if (Build.VERSION.SDK_INT >= 23) Settings.canDrawOverlays(ctx) else true
|
if (Build.VERSION.SDK_INT >= 23) Settings.canDrawOverlays(ctx) else true
|
||||||
@@ -98,6 +118,10 @@ object OverlayView {
|
|||||||
type,
|
type,
|
||||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
|
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
|
||||||
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or
|
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or
|
||||||
|
// Meldet uns Beruehrungen AUSSERHALB, ohne sie zu
|
||||||
|
// schlucken — damit raeumt ein Tipp aufs Video die
|
||||||
|
// Einblendung weg und steuert trotzdem den Player.
|
||||||
|
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
|
||||||
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
|
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
|
||||||
android.graphics.PixelFormat.TRANSLUCENT,
|
android.graphics.PixelFormat.TRANSLUCENT,
|
||||||
).apply {
|
).apply {
|
||||||
@@ -107,13 +131,23 @@ object OverlayView {
|
|||||||
width = (ctx.resources.displayMetrics.widthPixels * 0.42f).toInt()
|
width = (ctx.resources.displayMetrics.widthPixels * 0.42f).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Beruehrung ausserhalb -> wegraeumen. Die Beruehrung selbst
|
||||||
|
// geht dank FLAG_NOT_TOUCHABLE weiter an die App darunter.
|
||||||
|
box.setOnTouchListener { _, ev ->
|
||||||
|
if (ev.action == android.view.MotionEvent.ACTION_OUTSIDE) hideNow(ctx)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
wm.addView(box, lp)
|
wm.addView(box, lp)
|
||||||
root = box
|
root = box
|
||||||
|
|
||||||
if (seconds > 0) {
|
// seconds > 0 : feste Dauer wie gehabt.
|
||||||
hideRunnable = Runnable { hideNow(ctx) }
|
// seconds == 0: bleibt stehen, bis der Nutzer etwas tut
|
||||||
main.postDelayed(hideRunnable!!, seconds * 1000L)
|
// (Taste via Accessibility-Dienst / Tipp daneben)
|
||||||
}
|
// — mit Notbremse, damit sie nie haengen bleibt.
|
||||||
|
val cap = if (seconds > 0) seconds else HARD_CAP_SEC
|
||||||
|
hideRunnable = Runnable { hideNow(ctx) }
|
||||||
|
main.postDelayed(hideRunnable!!, cap * 1000L)
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
root = null
|
root = null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
|
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged"
|
android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged"
|
||||||
android:accessibilityFeedbackType="feedbackGeneric"
|
android:accessibilityFeedbackType="feedbackGeneric"
|
||||||
android:accessibilityFlags="flagRetrieveInteractiveWindows|flagReportViewIds"
|
android:accessibilityFlags="flagRetrieveInteractiveWindows|flagReportViewIds|flagRequestFilterKeyEvents"
|
||||||
android:canRetrieveWindowContent="true"
|
android:canRetrieveWindowContent="true"
|
||||||
android:canPerformGestures="true"
|
android:canPerformGestures="true"
|
||||||
|
android:canRequestFilterKeyEvents="true"
|
||||||
android:notificationTimeout="100"
|
android:notificationTimeout="100"
|
||||||
android:description="@string/accessibility_desc" />
|
android:description="@string/accessibility_desc" />
|
||||||
|
|||||||
Reference in New Issue
Block a user