# QuizGateTV **Repository Path**: sievy/quiz-gate-tv ## Basic Information - **Project Name**: QuizGateTV - **Description**: AndroidTV电视答题服务 - **Primary Language**: Kotlin - **License**: Not specified - **Default Branch**: dev - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-11-04 - **Last Updated**: 2026-11-04 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 答题门禁(QuizGateTV) Android TV / 电视盒子上的**看片门禁**:每隔 N 分钟,画面会被一道题全屏接管, 答对才能继续看;答错罚冷却,超时换题。 定位是"提高摩擦力的家长控制工具",不是不可绕过的技术牢笼——物理层的绕过路径(换 HDMI 源、恢复出厂)永远存在。 ## 架构 ``` WatchService(前台服务,5s 一跳) ├─ Watching.now() → 前台是视频应用?&& 有音频在播? │ └─ ForegroundApp ← 无障碍事件(主)/ UsageStats(兜底) ├─ 累计 watchedMs(SharedPreferences,拔电不清零) └─ 到阈值 → gatePending=true → GateLauncher ├─ GateActivity(Compose 全屏答题 + 锁任务) └─ GateOverlay(悬浮窗,Activity 拉不起来时兜底) ``` 关键设计取舍: | 问题 | 做法 | 原因 | |---|---|---| | 怎么判断"真的在看" | 前台包名 + `isMusicActive()` 双条件 | 单看前台会把暂停、看设置页都算进去 | | 计时怎么不丢 | 存 `watchedMs` 而不是存"到期时间戳" | 改系统时间无效,拔电重启也不清零 | | 怎么防 HOME 逃逸 | Device Owner + `startLockTask()` | 普通应用在框架层就拿不到 HOME 键 | | 为什么还要悬浮窗 | Android 10+ 后台起 Activity 会**静默失败** | `startActivity` 不抛异常,只能延时回查 `GateState` 再降级 | | 题目从哪来 | 口算本地生成 + 语文/英语读 assets JSON | 无限题量、零联网、零延迟 | ## 目录 ``` app/src/main/java/com/family/quizgatetv/ ├── App.kt 通知渠道 ├── MainActivity.kt 家长控制台(状态 / 体检 / 启停) ├── watch/ │ ├── Watching.kt 前台应用探测 + 播放状态判定 │ ├── WatchService.kt 计时前台服务(唯一时间真相源) │ └── BootReceiver.kt 开机自启 / 覆盖安装自启 ├── gate/ │ ├── GateActivity.kt 全屏门禁页(吞返回键 / 常亮 / 锁任务) │ ├── GateScreen.kt 答题 UI(Compose,D-pad 焦点) │ ├── GateOverlay.kt 悬浮窗降级方案 │ └── GateLauncher.kt 拉起 + 降级判定 ├── a11y/GateA11yService.kt 前台监测 + 逃逸拉回 ├── admin/LockTask.kt DeviceAdmin / Device Owner / 锁任务封装 ├── data/ │ ├── Prefs.kt 配置与统计(SharedPreferences) │ └── QuizEngine.kt 出题引擎(口算生成 + 题库) └── parent/ParentWebServer.kt 局域网家长配置页(手写 HTTP,零依赖) app/src/main/assets/quizzes/bank.json 语文/英语题库 docs/QA-CHECKLIST.md 真机验证清单(先看这个) docs/checklist-security.md 安全与合规清单 docs/checklist-a11y.md 无障碍清单 ``` ## 构建 需要 JDK 17 + Android SDK 34。 ```bash gradle :app:assembleDebug # 或 Android Studio 直接打开 ``` 仓库里只有 `gradle-wrapper.properties`,没有 `gradlew` 脚本本身。首次使用: ```bash gradle wrapper --gradle-version 8.7 # 生成 gradlew / gradlew.bat / gradle-wrapper.jar ``` 签名走环境变量,仓库内不留任何密钥: ```bash export QUIZGATE_KEYSTORE=/path/to/quizgate.jks export QUIZGATE_STORE_PASSWORD=... export QUIZGATE_KEY_ALIAS=quizgate export QUIZGATE_KEY_PASSWORD=... gradle :app:assembleRelease ``` ## 部署到盒子 ```bash PKG=com.family.quizgatetv adb connect <盒子IP>:5555 adb install -r app/build/outputs/apk/release/app-release.apk # 权限(TV 设置里往往没有这些入口,只能 adb) adb shell appops set $PKG GET_USAGE_STATS allow adb shell appops set $PKG SYSTEM_ALERT_WINDOW allow adb shell settings put secure enabled_accessibility_services $PKG/.a11y.GateA11yService adb shell settings put secure accessibility_enabled 1 # 可选但强烈建议:拿 Device Owner,HOME 键才会失效 adb shell dpm set-device-owner $PKG/.admin.AdminRx ``` 详细步骤与"每步失败意味着什么"见 [`docs/QA-CHECKLIST.md`](docs/QA-CHECKLIST.md)。 ## 参数 家长端:手机连同一 WiFi,浏览器打开控制台显示的 `http://<盒子IP>:8080`。 改配置需要 4 位 PIN(默认 `1234`,**第一次用必须改**)。 | 参数 | 默认 | 说明 | |---|---|---| | 间隔 | 20 分钟 | 每看这么久弹一道题;最小 1 分钟(调试用) | | 答题限时 | 45 秒 | 超时按答错处理 | | 答错冷却 | 60 秒 | 冷却结束自动换题 | | 难度 | 2(1–5) | 连对 3 题升一级,答错降一级 | | 科目 | 口算 | 口算 / 语文 / 英语,可多选 | | 免打扰 | 关闭 | 例如 22:00–07:00 不打扰 | | 视频包名 | 常见 8 个 | **必须按真机实际包名核对**,否则计时不会启动 | ## 已知边界 1. **未在本机编译验证**。本环境无 Android SDK / Xcode,所有代码是文件级产物, 编译、签名、安装必须在你本地或 CI 上执行。 2. **Device Owner 可能设不上**(盒子出厂带账号)。设不上就只能靠无障碍拉回门禁,体验会闪一下。 3. **无法防物理绕过**:恢复出厂、换 HDMI 源、手机投屏。 4. **不能上架应用商店**:覆盖其他应用、读前台包名、禁用 HOME 都属于审核红线,只能侧载自用。 5. 只用于自己家庭设备;不采集儿童个人信息,全部数据留在盒子本地。