"use client"; import { Copy, Layers, Loader2, Package, ShieldCheck, Terminal, } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; import { LiveAndroidStreamConsole } from "@/components/live-product/LiveAndroidStreamConsole"; type DeviceRow = { serial: string; model?: string; state: string; transport?: string; kind?: string; }; type TFn = (zh: string, en: string) => string; type AndroidDiagnostics = { kind?: string; transport?: string; boot_completed?: string; hardware?: string; hardware_boot?: string; fingerprint?: string; abi?: string; device_provisioned?: string; user_setup_complete?: string; google_play?: boolean; google_services?: boolean; setupwizard?: boolean; magisk?: boolean; root?: boolean; ready?: boolean; }; type AndroidOverview = { serial?: string; model?: string; brand?: string; android_version?: string; sdk?: string; battery?: string; focus?: string; resolution?: string; rotation?: string; screen_state?: string; transport?: string; kind?: string; diagnostics?: AndroidDiagnostics; }; type PkgRow = { package: string; label?: string }; type UiNode = { label: string; text?: string; content_desc?: string; resource_id?: string; center_x: number; center_y: number; clickable?: boolean; enabled?: boolean; }; type RedroidInstance = { name: string; image?: string; status_text?: string; port?: number; running?: boolean; data_dir?: string; adb_serial?: string; diagnostics?: AndroidDiagnostics & { model?: string; android_version?: string; }; }; type RedroidSummary = { enabled?: boolean; arch?: string; image?: string; device_profile?: string; gpu_mode?: string; default_port?: number; instance_prefix?: string; running?: boolean; ready_count?: number; requirements?: { docker_available?: boolean; adb_available?: boolean; kvm?: boolean; binderfs?: boolean; binder_nodes?: string[]; ashmem?: boolean; }; instances?: RedroidInstance[]; }; const SHELL_SNIPPETS: { label: string; cmd: string }[] = [ { label: "getprop", cmd: "getprop ro.build.version.release" }, { label: "top pkg", cmd: "dumpsys activity activities | grep -E 'mResumedActivity' | tail -n 1" }, { label: "usb", cmd: "dumpsys usb | head -n 60" }, { label: "surface", cmd: "dumpsys SurfaceFlinger | head -n 40" }, ]; type Props = { t: TFn; notify: (msg: string) => void; fetchJson: (path: string, init?: RequestInit) => Promise; apiBase: string; busy: string | null; androidSerial: string; setAndroidSerial: (s: string) => void; devices: DeviceRow[]; adbAvailable: boolean; apkPath: string; setApkPath: (v: string) => void; onAndroidAction: (action: string, payload?: Record) => Promise; onCopy: (text: string) => void; onRefreshDevices: () => void; }; function boolBadge(v: boolean | undefined, ok: string, bad: string) { if (v) return {ok}; return {bad}; } export function LiveAndroidWorkstation({ t, notify, fetchJson, apiBase, busy, androidSerial, setAndroidSerial, devices, adbAvailable, apkPath, setApkPath, onAndroidAction, onCopy, onRefreshDevices, }: Props) { const [overview, setOverview] = useState(null); const [overviewErr, setOverviewErr] = useState(null); const [redroid, setRedroid] = useState(null); const [redroidErr, setRedroidErr] = useState(null); const [pkgQuery, setPkgQuery] = useState(""); const [pkgList, setPkgList] = useState([]); const [shellCmd, setShellCmd] = useState("getprop ro.product.model"); const [shellOut, setShellOut] = useState(""); const [logcatLines, setLogcatLines] = useState("300"); const [logcatText, setLogcatText] = useState(""); const [logcatErr, setLogcatErr] = useState(null); const [uiNodes, setUiNodes] = useState([]); const [uiErr, setUiErr] = useState(null); const [openUrl, setOpenUrl] = useState("https://"); const [adbInputText, setAdbInputText] = useState(""); const [logcatLive, setLogcatLive] = useState(false); const [pending, setPending] = useState(null); const lock = !!(busy || pending); const currentDevice = devices.find((d) => d.serial === androidSerial) || null; const loadOverview = useCallback(async () => { if (!androidSerial || !adbAvailable) { setOverview(null); setOverviewErr(null); return; } setPending("overview"); setOverviewErr(null); try { const data = await fetchJson(`/android/overview?serial=${encodeURIComponent(androidSerial)}`); setOverview(data); } catch (e) { setOverview(null); setOverviewErr((e as Error).message); } finally { setPending(null); } }, [adbAvailable, androidSerial, fetchJson]); const loadRedroid = useCallback(async () => { setRedroidErr(null); try { const data = await fetchJson("/redroid/summary"); setRedroid(data); } catch (e) { setRedroid(null); setRedroidErr((e as Error).message); } }, [fetchJson]); useEffect(() => { void loadOverview(); }, [loadOverview]); useEffect(() => { void loadRedroid(); }, [loadRedroid]); const searchPackages = async () => { if (!androidSerial) { notify(t("请选择设备", "Pick a device")); return; } setPending("packages"); try { const q = encodeURIComponent(pkgQuery.trim()); const data = await fetchJson<{ packages: PkgRow[] }>( `/android/packages?serial=${encodeURIComponent(androidSerial)}&query=${q}&limit=80`, ); setPkgList(data.packages || []); } catch (e) { notify((e as Error).message); setPkgList([]); } finally { setPending(null); } }; const runShell = async () => { if (!androidSerial) { notify(t("请选择设备", "Pick a device")); return; } const cmd = shellCmd.trim(); if (!cmd) return; setPending("shell"); try { const data = await fetchJson<{ output?: string; message?: string }>("/android/action", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: "shell", serial: androidSerial, payload: { command: cmd } }), }); setShellOut(data.output ?? data.message ?? ""); } catch (e) { setShellOut((e as Error).message); } finally { setPending(null); } }; const refreshLogcat = useCallback( async (opts?: { silent?: boolean }) => { if (!androidSerial) return; const n = Math.min(3000, Math.max(1, parseInt(logcatLines, 10) || 200)); const silent = !!opts?.silent; if (!silent) setPending("logcat"); setLogcatErr(null); try { const data = await fetchJson<{ text?: string }>( `/android/logcat?serial=${encodeURIComponent(androidSerial)}&lines=${n}`, ); setLogcatText(data.text ?? ""); } catch (e) { setLogcatText(""); setLogcatErr((e as Error).message); } finally { if (!silent) setPending(null); } }, [androidSerial, fetchJson, logcatLines], ); useEffect(() => { if (!logcatLive || !androidSerial || !adbAvailable) return; const id = window.setInterval(() => void refreshLogcat({ silent: true }), 3000); void refreshLogcat({ silent: true }); return () => clearInterval(id); }, [adbAvailable, androidSerial, logcatLive, refreshLogcat]); const loadUiNodes = async () => { if (!androidSerial) { notify(t("请选择设备", "Pick a device")); return; } setPending("ui"); setUiErr(null); try { const data = await fetchJson<{ nodes: UiNode[] }>( `/android/ui?serial=${encodeURIComponent(androidSerial)}&limit=100`, ); setUiNodes(data.nodes || []); } catch (e) { setUiNodes([]); setUiErr((e as Error).message); } finally { setPending(null); } }; const runRedroidAction = async (action: string, name?: string) => { setPending(`redroid:${action}`); try { const res = await fetchJson<{ message?: string; summary?: RedroidSummary }>("/redroid/action", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action, name: name || "" }), }); if (res.summary) setRedroid(res.summary); notify(res.message || "OK"); await loadRedroid(); onRefreshDevices(); } catch (e) { notify((e as Error).message); } finally { setPending(null); } }; const sendKey = async (keycode: string) => { await onAndroidAction("key", { keycode }); }; const sendAdbText = async () => { const raw = adbInputText.trim(); if (!raw) { notify(t("请输入文字", "Enter text")); return; } await onAndroidAction("text", { text: raw }); }; const openUrlGo = async () => { const u = openUrl.trim(); if (!u || u === "https://") { notify(t("请输入 URL", "Enter URL")); return; } await onAndroidAction("open_url", { url: u }); }; return (

{t("安卓设备工作台", "Android workstation")}

{t("USB 真机与 Redroid 共用一套控制面,优先保证 USB 真机稳定。", "USB phone and Redroid share one control surface.")}

{!adbAvailable ? ( {t("宿主机未检测到 adb", "adb not found on host")} ) : (
)}
{devices.map((d) => ( ))}
{adbAvailable && devices.length === 0 ? (

{t( "当前没有已授权设备。USB 真机请检查调试授权;Redroid 请先启动实例,再 adb connect 127.0.0.1:5555。", "No authorized devices. For USB phones, confirm USB debugging. For Redroid, start an instance then adb connect 127.0.0.1:5555.", )}

) : null}

{t("真机诊断", "Phone diagnostics")}

{overviewErr ?

{overviewErr}

: null} {overview ? (
model
{" "}
{overview.brand} {overview.model}
android
{" "}
{overview.android_version} (sdk {overview.sdk})
kind
{" "}
{overview.kind || currentDevice?.kind || "unknown"} / {overview.transport || currentDevice?.transport || "unknown"}
display
{" "}
{overview.resolution}
rotation
{" "}
{overview.rotation}
{overview.battery}
{overview.focus}
{boolBadge(overview.diagnostics?.ready, t("已启动", "Boot ready"), t("未完成启动", "Boot pending"))} {boolBadge(overview.diagnostics?.google_play, "Google Play", "No Play")} {boolBadge(overview.diagnostics?.google_services, "GMS", "No GMS")} {boolBadge(overview.diagnostics?.magisk, "Magisk", "No Magisk")} {boolBadge(overview.diagnostics?.root, "Root", "No root")}
boot_completed: {overview.diagnostics?.boot_completed || "-"}
provisioned: {overview.diagnostics?.device_provisioned || "-"}
setup_complete: {overview.diagnostics?.user_setup_complete || "-"}
abi: {overview.diagnostics?.abi || "-"}
hardware: {overview.diagnostics?.hardware || "-"}
) : !overviewErr ? (

{t("加载中", "Loading")}

) : null}

{t("Redroid 板块", "Redroid panel")}

{redroidErr ?

{redroidErr}

: null} {redroid ? (
arch: {redroid.arch || "-"}
image: {redroid.image || "-"}
profile: {redroid.device_profile || "-"}
gpu: {redroid.gpu_mode || "-"}
adb: 127.0.0.1:{redroid.default_port || 5555}
{boolBadge(redroid.requirements?.docker_available, "Docker", "No Docker")} {boolBadge(redroid.requirements?.adb_available, "ADB", "No ADB")} {boolBadge(redroid.requirements?.kvm, "KVM", "No KVM")} {boolBadge(redroid.requirements?.binderfs, "binderfs", "No binderfs")}
{(redroid.instances || []).map((item) => (
{item.name}
{item.port ? `127.0.0.1:${item.port}` : "-"} / {item.status_text || "-"}
{boolBadge(item.diagnostics?.ready, t("系统就绪", "System ready"), t("启动中", "Booting"))} {boolBadge(item.diagnostics?.google_play, "Play", "No Play")} {boolBadge(item.diagnostics?.magisk, "Magisk", "No Magisk")} {boolBadge(item.diagnostics?.root, "Root", "No root")}
{item.data_dir ? (
{item.data_dir}
) : null}
))} {!redroid.instances?.length ? (

{t("当前没有 Redroid 实例。默认动作会创建 redroid13-1。", "No Redroid instances yet. Default actions create redroid13-1.")}

) : null}
) : (

{t("读取 Redroid 状态中", "Loading Redroid status")}

)}
{adbAvailable && androidSerial ? (

{t("快捷控制", "Quick controls")}

{t("滑动手势", "Swipe gestures")}

{(["up", "down", "left", "right"] as const).map((dir) => ( ))}

{t("ADB 文本输入", "ADB text input")}

setAdbInputText(e.target.value)} placeholder="hello / 123" onKeyDown={(e) => { if (e.key === "Enter") void sendAdbText(); }} />
setOpenUrl(e.target.value)} placeholder="https://" />

{t("USB 真机优化建议", "USB optimization")}

  • {t("优先使用主板直连 USB,不要经过不稳的 HUB。", "Prefer direct motherboard USB, not unstable hubs.")}
  • {t("开发板端固定 adb server,避免频繁 kill-server。", "Keep adb server stable and avoid frequent kill-server.")}
  • {t("镜像页默认支持 scrcpy H.264 真流,截图模式作为回退。", "Use scrcpy H.264 live stream first, screenshot mode as fallback.")}
  • {t("如果 TikTok / YouTube App 直播,要先确认屏幕常亮、分辨率稳定、USB 供电足够。", "For TikTok / YouTube app live, keep screen on, resolution stable, and USB power sufficient.")}
) : null} {adbAvailable && androidSerial ? (

{t("包名搜索与启动", "Packages")}

setPkgQuery(e.target.value)} placeholder="tiktok / musically / youtube" onKeyDown={(e) => { if (e.key === "Enter") void searchPackages(); }} />
    {pkgList.map((p) => (
  • {p.package}
  • ))} {!pkgList.length ?
  • {t("暂无结果", "No results")}
  • : null}
) : null} {adbAvailable && androidSerial ? (

ADB shell

{SHELL_SNIPPETS.map((s) => ( ))}