59069a741e
- 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.
167 lines
11 KiB
Markdown
167 lines
11 KiB
Markdown
# 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)
|
||
|
||
## Лицензия
|
||
|
||
Внутренний проект.
|