Files
subochev 59069a741e phone: keep-alive foreground service + notification perm; glasses: clear NoConnection on reconnect
- ConnectionKeepAliveService (foreground, PARTIAL_WAKE_LOCK + WIFI_MODE_FULL_HIGH_PERF,
  started in PhoneVmApp.onCreate): without it HONOR deep-sleeps the phone process on
  screen-off and the glasses lose the WebSocket link. Same mechanic as view-mate.
- MainActivity: request POST_NOTIFICATIONS so the FGS notification is visible.
- VmGlassesController.onPhoneConnected(): drop a stuck NoConnection state when the
  phone (re)connects; returns to Running if a guest is active, else NoAppSelected.
- VmService: call it from onConnected.
- stopGuest/switch: force-stop the previous guest before releasing its display
  (releasing first migrates the guest task to display 0 full-screen); VirtualDisplay
  frame source now destroys its UserService on release.
- Mercury status: retry the first pullGeneralStatus until it lands, refresh every 15s
  and on tab open; GlassesStatusScreen shows 'Ждём статус от очков…' instead of fake zeros.
- MainActivity(glasses): HOME candidate flag + finish() on a non-default display (recursion guard).
- README: Home-launcher setup/rollback section.
- BOOTSTRAP_TEST.md: post-reboot Shizuku bootstrap investigation log.
2026-09-26 13:59:54 +03:00

167 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RayNeo VM
Приложение для запуска Android-приложений (например, Zona) на очках **RayNeo X3 Pro** через виртуальный дисплей.
## Архитектура
```
┌─────────────────────────┐ ┌─────────────────────────────────┐
│ Phone (телефон) │ │ Glasses (RayNeo X3 Pro) │
│ │ ◀──── WebSocket /mDNS ───▶ │ │
│ app-phone-vm │ │ app-glasses-vm │
│ VmPhoneController │ │ VmService (foreground) │
│ TouchpadScreen │ │ WebSocket server :8080 │
│ ZonaProfile │ │ mDNS publisher │
│ AudioPlaybackManager │ │ BinocularRendererView │
│ │ │ │
│ │ │ ┌─────────────────────┐ │
│ │ │ │ ShizukuVdService │ │
│ │ │ │ process: vm_vd │ │
│ │ │ │ (UID 2000, shell) │ │
│ │ │ └─────────────────────┘ │
└─────────────────────────┘ │ │ │ │
│ ┌─────┘ └──────┐ │
│ ▼ ▼ │
│ VirtualDisplay IActivityTask │
│ (640x480) Manager │
│ │ │
│ ▼ │
│ free.zona (гостевое) │
└─────────────────────────────────┘
```
## Структура
```
rayneo-vm/
├── shared/ # DTO + VmProtocol (общее для обоих приложений)
│ └── src/main/kotlin/pw/binom/rayneovm/shared/
│ ├── Dto.kt # VmMessage sealed, TouchEvent, KeyEvent, ...
│ ├── VmProtocol.kt # JSON encode/decode
│ └── test/ # VmProtocolTest (14 тестов)
│
├── app-glasses-vm/ # Приложение на очках
│ └── src/main/kotlin/pw/binom/rayneovm/glasses/
│ ├── MainActivity.kt # Compose host
│ ├── VmService.kt # Foreground service, WebSocket server
│ ├── VmShizukuService.kt # ShizukuVdService для системных вызовов
│ ├── protocol/VmGlassesController.kt
│ ├── vm/GlassesScreenState.kt
│ ├── shizuku/VirtualDisplayFrameSource.kt # SurfaceTexture ↔ VirtualDisplay
│ ├── audio/AudioCaptureService.kt # Режим 2: захват системного аудио
│ └── audio/AudioStreamer.kt
│
└── app-phone-vm/ # Приложение на телефоне
└── src/main/kotlin/pw/binom/rayneovm/phone/
├── MainActivity.kt # Compose host + NavGraph
├── PhoneVmApp.kt # Application
├── protocol/VmPhoneController.kt # Auto-reconnect + mDNS
├── ui/HomeScreen.kt
├── ui/GlassesStatusScreen.kt
├── ui/AppsListScreen.kt
├── ui/ZonaProfile.kt # Touchpad + media keys + keyboard
├── ui/TouchpadScreen.kt
└── audio/AudioPlaybackManager.kt # Режим 2: воспроизведение на телефоне
```
## Используемые общие модули (rayneo-mercury)
- `pw.binom.mercury:rayneo-mercury-transport:0.1.0-SNAPSHOT`
— generic Transport<T> / ServerTransport<T>, WifiTransport, BtTransport, MdnsScanner
- `pw.binom.mercury:rayneo-mercury-binocular-renderer:0.1.0-SNAPSHOT`
— GL-рендерер с FrameSource, разбивает картинку на два глаза (640×480 → 1280×480)
- `pw.binom.mercury:rayneo-mercury-shizuku-vd:0.1.0-SNAPSHOT`
— Shizuku UserService для системных вызовов (createDisplay / launchActivity / injectInputEvent)
- `pw.binom.mercury:library:0.1.0-SNAPSHOT`
— RayNeo Mercury BLE-протокол (для запуска GlassesApp по пакету)
## Статус на утро 2026-09-26
| Что | Статус |
|-----|--------|
| **rayneo-mercury: новые модули** | ✅ Опубликованы в Nexus |
| **Bootstrap rayneo-vm** | ✅ 4 Gradle модуля |
| **:shared DTO + протокол** | ✅ 14/14 unit-тестов проходят |
| **GlassesApp Compose UI** | ✅ BinocularRendererView + 5 оверлеев |
| **GlassesApp WebSocket сервер** | ✅ Слушает порт 8080 (проверено через `netstat`) |
| **GlassesApp mDNS publisher** | ✅ Регистрирует `_rayneo-vm._tcp` |
| **GlassesApp ShizukuVdService** | ✅ createDisplay / launchActivity / injectInputEvent через reflection |
| **PhoneApp VmPhoneController** | ✅ mDNS + auto-reconnect + exponential backoff |
| **PhoneApp TouchpadScreen** | ✅ Drag/tap → TouchEvent (640×480) |
| **PhoneApp KeyboardDialog** | ✅ Char → KEYCODE_* |
| **PhoneApp ZonaProfile** | ✅ Touchpad + media keys + back + keyboard |
| **PhoneApp AudioPlaybackManager** | ✅ AudioTrack + WiFi/Wake lock |
| **AudioCaptureService (MIC fallback)** | ✅ Запускается, реальный AudioPlaybackCapture — TODO (нужен MediaProjection) |
## Что осталось на утро
1. **Реальный проброс SurfaceTexture клиент → сервис.** Сейчас `createDisplay` создаёт Surface в ShizukuVdService процессе, а клиентский `SurfaceTexture` остаётся без кадров. Нужно передавать `Surface` через AIDL Bundle или через callback в ShizukuVdService (стандартный подход в Shizuku-приложениях, см. `IIdProvider`-style).
2. **AudioPlaybackCapture (Android 12+).** Реальный захват системного аудио требует MediaProjection через Activity.onActivityResult. Сейчас сервис стартует в MIC-режиме.
3. **InjectInputEvent — verify on device.** Код написан, но не тестировался вживую на устройстве.
4. **TaskStackListener для обнаружения краша гостя.** Каркас в SPEC, реализация не доделана.
## Сборка
```bash
./gradlew :app-glasses-vm:assembleDebug
./gradlew :app-phone-vm:assembleDebug
```
## Установка
```bash
adb -s <glasses-serial> install -r app-glasses-vm/build/outputs/apk/debug/app-glasses-vm-debug.apk
adb -s <phone-serial> install -r app-phone-vm/build/outputs/apk/debug/app-phone-vm-debug.apk
```
## GlassesApp как Home (лаунчер)
На RayNeo X3 Pro `BOOT_COMPLETED` ненадёжен, поэтому штатный автозапуск — сделать GlassesApp домашним экраном: система поднимает HOME при каждой загрузке. В манифесте у `MainActivity` уже есть intent-filter `MAIN` + `HOME` + `DEFAULT`, но **дефолтным он не назначается автоматически** — это отдельная команда, ставится вручную.
Назначить нашим приложением (выполнять осознанно; пока не сделано):
```bash
adb -s <glasses-serial> shell cmd package set-home-activity pw.binom.rayneovm.glasses/.MainActivity
```
Вернуть штатный лаунчер RayNeo обратно:
```bash
adb -s <glasses-serial> shell cmd package set-home-activity com.ffalconxr.mercury.launcher/home.MainActivity
```
> Компонент записывается через `/` (package/class), а не через `/.` — именно так его принимает `set-home-activity` на этой прошивке.
Проверить, кто сейчас дефолтный HOME:
```bash
adb -s <glasses-serial> shell cmd package resolve-activity -a android.intent.action.MAIN -c android.intent.category.HOME
```
Показать всех кандидатов в HOME:
```bash
adb -s <glasses-serial> shell cmd package query-activities -a android.intent.action.MAIN -c android.intent.category.HOME
```
Аварийный выход, если домашний экран вообще перестал открываться (например, после `pm disable-user` не того пакета): переключиться на другой зарегистрированный HOME временно тем же `set-home-activity` (у нас в системе есть `com.teslacoilsw.launcher`, Nova) либо снять отключение через `cmd package enable-user`.
## Smoke-test (проверено)
1. Запустить `GlassesVmApp` на очках → VmService стартует, `netstat -tlnp` показывает `*:8080`.
2. Запустить `PhoneVmApp` на телефоне → mDNS ищет `_rayneo-vm._tcp`, ловит сервис очков.
3. На телефоне: тап на Zona → `LaunchAppCommand("free.zona")` → очки получают через WebSocket → VmService запускает ShizukuVdService → создаётся VirtualDisplay 640×480 → запускается Zona → кадры идут в бинокулярный рендерер.
## Конфигурация
Версии в `gradle/libs.versions.toml`:
- Kotlin 2.4.20, AGP 9.4.1, Gradle 9.x
- Compose BOM 2026.09.00
- Ktor 3.6.0
- Shizuku API 13.1.5
- compileSdk 37, minSdk 32 (glasses), 32 (phone)
## Лицензия
Внутренний проект.