1c1bbe2551
Make the existing Application automation rule (foreground app -> activate scene) work on the Android-TV build. A Kotlin ForegroundAppBridge reads the foreground app via UsageStatsManager and lists launchable apps via LauncherApps; PlatformDetector bridges it in (ahead of the Windows-only ctypes guard) so the existing AutomationEngine / ApplicationRule / storage / deactivation modes are unchanged. New /system/installed-apps + /system/info endpoints feed an app picker that stores package names (vs process names on desktop); on Android the editor hides the match-type selector since the foreground app is the only obtainable signal. PACKAGE_USAGE_STATS is granted via an on-device button + a web-UI banner (no blanket prompt at capture start); detection degrades gracefully until granted. Zero new Python/Gradle deps (UsageStatsManager + LauncherApps are in-platform; matching only string-compares the package name, so no QUERY_ALL_PACKAGES). assembleDebug + 1897 pytest + ruff + tsc + npm build all green; independent final review (0 blockers) + security review (no critical issues).
134 lines
5.3 KiB
XML
134 lines
5.3 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<FrameLayout
|
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
xmlns:tools="http://schemas.android.com/tools"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="match_parent"
|
|
android:background="@drawable/bg_main">
|
|
|
|
<!-- STOPPED STATE -->
|
|
<LinearLayout
|
|
android:id="@+id/stopped_panel"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="match_parent"
|
|
android:orientation="vertical"
|
|
android:gravity="center"
|
|
android:paddingStart="160dp"
|
|
android:paddingEnd="160dp">
|
|
|
|
<ImageView
|
|
android:layout_width="72dp"
|
|
android:layout_height="72dp"
|
|
android:src="@drawable/ic_launcher"
|
|
android:contentDescription="@null"
|
|
android:layout_marginBottom="24dp" />
|
|
|
|
<TextView
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:text="@string/app_name"
|
|
android:textColor="@color/teal_accent"
|
|
android:textSize="64sp"
|
|
android:textStyle="bold"
|
|
android:letterSpacing="0.08"
|
|
android:layout_marginBottom="12dp"
|
|
android:fontFamily="sans-serif" />
|
|
|
|
<TextView
|
|
android:id="@+id/tagline_text"
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:text="@string/tagline"
|
|
android:textColor="@color/text_secondary"
|
|
android:textSize="28sp"
|
|
android:layout_marginBottom="24dp" />
|
|
|
|
<!-- Transient status (root probing / permission denial). Always
|
|
present so the layout doesn't reflow when text appears. -->
|
|
<TextView
|
|
android:id="@+id/status_text"
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="32dp"
|
|
android:gravity="center"
|
|
android:textColor="@color/text_secondary"
|
|
android:textSize="20sp"
|
|
android:layout_marginBottom="32dp"
|
|
tools:text="Checking root access…" />
|
|
|
|
<Button
|
|
android:id="@+id/toggle_button"
|
|
style="@style/Widget.LedGrab.Button.Primary"
|
|
android:layout_width="320dp"
|
|
android:layout_height="72dp"
|
|
android:text="@string/btn_start"
|
|
android:textSize="22sp"
|
|
android:focusable="true"
|
|
android:focusableInTouchMode="true"
|
|
android:nextFocusDown="@+id/autostart_check" />
|
|
|
|
<!-- Shown only while notification-listener access is missing. The D-pad
|
|
focus chain is wired at runtime (wireStoppedFocusChain) because this
|
|
button and the autostart checkbox are both conditionally visible. -->
|
|
<Button
|
|
android:id="@+id/grant_notification_button"
|
|
style="@style/Widget.LedGrab.Button.Secondary"
|
|
android:layout_width="320dp"
|
|
android:layout_height="56dp"
|
|
android:layout_marginTop="20dp"
|
|
android:text="@string/btn_grant_notification_access"
|
|
android:textSize="18sp"
|
|
android:focusable="true"
|
|
android:focusableInTouchMode="true"
|
|
android:visibility="gone" />
|
|
|
|
<!-- Shown only while Usage Access is missing (needed by the foreground-app
|
|
automation rule). Like the grant-notification button, its D-pad focus
|
|
chain is wired at runtime (wireStoppedFocusChain). -->
|
|
<Button
|
|
android:id="@+id/grant_usage_access_button"
|
|
style="@style/Widget.LedGrab.Button.Secondary"
|
|
android:layout_width="320dp"
|
|
android:layout_height="56dp"
|
|
android:layout_marginTop="20dp"
|
|
android:text="@string/btn_grant_usage_access"
|
|
android:textSize="18sp"
|
|
android:focusable="true"
|
|
android:focusableInTouchMode="true"
|
|
android:visibility="gone" />
|
|
|
|
<CheckBox
|
|
android:id="@+id/autostart_check"
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:layout_marginTop="32dp"
|
|
android:text="@string/autostart_label"
|
|
android:textColor="@color/text_secondary"
|
|
android:textSize="20sp"
|
|
android:buttonTint="@color/teal_accent"
|
|
android:focusable="true"
|
|
android:focusableInTouchMode="true"
|
|
android:nextFocusUp="@id/toggle_button" />
|
|
</LinearLayout>
|
|
|
|
<!-- Version at bottom (always visible — looks polished on TV idle). -->
|
|
<TextView
|
|
android:id="@+id/version_text"
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:layout_gravity="bottom|center_horizontal"
|
|
android:layout_marginBottom="32dp"
|
|
android:textColor="@color/text_hint"
|
|
android:textSize="18sp"
|
|
tools:text="v0.1.0" />
|
|
|
|
<!-- RUNNING STATE — deferred-inflate via ViewStub so first paint is
|
|
cheaper and the inflater doesn't measure two competing layouts. -->
|
|
<ViewStub
|
|
android:id="@+id/running_panel_stub"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="match_parent"
|
|
android:inflatedId="@+id/running_panel"
|
|
android:layout="@layout/panel_running"
|
|
android:visibility="gone" />
|
|
</FrameLayout>
|