Панель перенесена из view-mate (StatusPanel) как часть активити, без виджета:
StatusHud + rememberStatusHudData в :app-glasses-vm/ui. Слой добавляется в
GlassesScreen поверх when(state) внутри Binocular, поэтому виден в любом
состоянии — ожидание/нет связи/запущенный гость — и дублируется в обе
половины (левый и правый глаз).
Экран залипал в «Нет связи», хотя телефон был подключён и команды
доходили. Причина: currentConn — одна ссылка на все сессии, и ЛЮБОЙ
onDisconnected переводил экран в NoConnection. Лишние сессии создаёт
в т.ч. mDNS-фоллбек телефона (probeWebSocket делает полный WS-апгрейд
для проверки кандидата и сразу рвёт соединение).
Теперь очки ведут набор живых сессий и гасят экран только когда
закрылась последняя.
Прогрев 30 кадров тишины на старте переводил Concentus в патологическое
состояние: enc_us вырастал с ~2.6мс до ~88мс на кадр (113% CPU), кодер не
успевал, терялось ~85% кадров -> «полная жопа» в звуке. Подтверждено A/B:
без прогрева enc_us~2.6мс, dropped единицы, пробник принимает ровно 100/с.
- shared: новое сообщение ScreenState(on) от очков телефону
- glasses: слушаем ACTION_SCREEN_ON/OFF (PowerManager.isInteractive), шлём состояние
при подключении телефона и на каждое изменение; после ScreenControl — актуализация
- phone: VmPhoneController.screenOn (StateFlow<Boolean?>), оптимистичное обновление в setScreen
- phone: на экране «Пульт» одна кнопка-тумблер «Погасить/Разбудить экран очков»,
подпись отражает реальное состояние, disabled без Канала 1
- glasses: очередь capture→Opus 4→12 кадров (джиттер планировщика больше не роняет кадры
до присвоения seq, отчего телефон не видел потерь); буфер AudioRecord 2→4 кадра
- glasses: диагностика captured/encoded/dropped в логе (видно потери на источнике)
- phone: AudioTrack-буфер 1→4 кадра (редкие underrun'ы давали щелчки); на пропуске seq
вставляем PLC-кадры (было — сразу склейка); в лог добавлен underruns
Как штатное приложение (OnConnected.startSyncTask): сразу после подъёма
BLE-сессии шлём очкам время телефона. Нужно очкам, когда они без сети —
иначе у них своё время и валятся TLS-проверки в гостевых приложениях.
Вызов идемпотентный, ставится и в connectInternal, и в Event.Connected
(внутренние переподключения библиотеки).
Phone now self-provisions after a glasses reboot: BLE enableWirelessAdb ->
glasses start wireless adb -> address back over BLE -> adb connect ->
Shizuku started + accessibility/media grants applied. Verified on device
16:22 (glasses rebooted, adb_wifi_enabled=0, nothing activated over USB).
Обе строки назывались «Связь» (Канал 1 WebSocket и Канал 2 Mercury BLE), из-за чего
статус BLE читался как статус TCP: при выключенном Wi-Fi на очках телефон показывал
«подключено». Теперь вверху вкладки «Очки» сводка с отдельными строками BLE (Mercury)
и TCP/IP (Wi-Fi), в секциях — «BLE» и «TCP/IP».
mercuryTransport -> 0.1.1-SNAPSHOT (клиентский WS-пинг, см. BLOCKERS B-5).
- MainActivity: requestTick собирается в repeatOnLifecycle(RESUMED) + флаг AudioBridge.activityResumed
- requestProjection(): не дёргает startActivity(MainActivity), пока Activity RESUMED (гонка с grant)
- ensureAudioCaptureLoop(): запрос проекции один раз, повтор не чаще 20 c (было 2.5 c)
- AudioBridge/GlassesAudioCapture: проекция одна на процесс и не гасится, переиспользуется
- ShizukuProvisioner: PROJECT_MEDIA per-UID (на Android 12 package-level = no-op)
- отладочные логи phone-side audio; BLOCKERS.md B-3 закрыт (E2E проверен)
- AdbController: shell/push/ensureConnected/invalidate; перед каждой командой
проверяет соединение (echo ok) и сам поднимает wireless-adb (Mercury -> mDNS ->
connect). Мьютекс только на подъём, длинный бутстрап не блокирует команды.
- ShizukuProvisioner переведён на AdbController (adb-код вынесен).
- Убрана неработающая UI-консоль adb (нужен только программный API).
- ScreenControl(sleep) в shared: гашение/пробуждение экрана очков их же
Shizuku-UserService (инжект 223/224 как shell), не зависит от Mercury/adb.
- VmPhoneController.setScreen + кнопки «Экран очков» на Канале 1.
- DebugReceiver: DEBUG_ADB_SHELL (+ SET_DEEP_SUSPEND) как отладочный инструмент.
- MercuryController/AdbClient: сопутствующие правки.
AdbClient wraps the bundled libadb.so (useLegacyPackaging -> exec from nativeLibraryDir, HOME/TMPDIR overridden, adb key injected into BuildConfig from ~/.android at build time and never committed). ShizukuProvisioner: on each BLE+WiFi connect it enables ADB-over-WiFi via Mercury, connects with mDNS fallback, pushes the Shizuku starter and runs it; runs once per BLE session, does not relaunch GlassesApp when the WS channel is already up, and exposes runShell() for arbitrary commands. DebugReceiver (DUMP) + am start --ez provision for diagnostics.
Protocol: ShizukuStatus(running/granted) + RequestShizukuStatus; glasses push it on connect and on Shizuku state change, using a passive probe() that never shows a permission dialog. Glasses kill-guard now recreates its UserService when the Shizuku binder dies instead of spamming DeadObjectException. Mercury: brightness disables auto-brightness first (was silently ignored), BLE auto-connect loop retries every 6s (fixes 'searches then stops'). New phone section 'Экран очков' with wake/sleep via adb (input keyevent 224/223).
- 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.
- shared: VmDisplay (guest 640x480 coordinate space), CursorMove and
CursorClick messages (CursorClick replaces the DOWN/UP pair — see below).
- glasses: cursor state + Canvas overlay drawn in both eyes, clickCursor
injects one atomic tap at the cursor, sendKey for Back/Play/Pause.
- phone: RemoteScreen — touchpad (relative drag moves the cursor, tap
clicks), «Назад»/«Плей»/«Пауза» buttons, «Клавиатура» stub (TODO).
- The old CursorButton(DOWN)/CursorButton(UP) pair was removed: the two
messages were injected concurrently and could arrive out of order,
breaking the guest's touch stream so clicks worked only once.
- tests: CursorMove/CursorClick roundtrips.
- VmShizukuService is a Shizuku UserService only; remove its <service> manifest entry (ServiceStarter instantiates it reflectively, an Android service entry causes ClassCastException)
- VirtualDisplayFrameSource: await binder via ShizukuVdClient.bindForResult(5s), expose virtualDisplayId + shizukuBinder, stop the UserService on release
- VmGlassesController: serialize acquire via Mutex, use double-buffered SurfaceTexture (DisplayManagerService rejects single-buffer), launch the guest on the VD with ShizukuVdBinder.launchActivity
- GlassesScreen: render controller.frameSource (Running) directly; drop the LaunchedEffect acquire
- DebugTestReceiver: register at runtime from MainActivity (background manifest receivers are blocked on Android 12+)
Rewrite the Shizuku integration to follow https://github.com/RikkaApps/Shizuku-API:
- attach(activity) instead of attach(): requestPermission() must run from
an Activity context (Manager's RequestPermissionActivity is a normal
Activity started from ours).
- addBinderReceivedListenerSticky: ensures our listener fires even if the
binder was already received before we attached (race against the
ShizukuProvider init).
- addRequestPermissionResultListener: routes the dialog's allow/deny
back into the StateFlow.
- onRequestPermissionResult(requestCode, grant): helper for the Activity
to forward the system callback.
- pingBinder / checkSelfPermission / requestPermission gates follow the
state machine from the docs (Waiting → NeedsPermission → Granted).
Move attach() out of GlassesVmApp.onCreate (no Activity context there)
into MainActivity.onCreate after super.onCreate.
Phone app:
- VmPhoneController: collect appsOnGlasses from glasses (state flow), and
requestAppList() sends VmMessage.GetAppList over WebSocket.
- AppsListScreen: rename to dynamic list from glasses, verticalScroll,
tap → LaunchAppCommand(packageName). Tap calls onAppClick for parent
nav (Glasses tab on the bottom NavigationBar).
- Shared VM message types (VmMessage.GetAppList / AppListResponse /
AppEntry) move into :shared module so both phone and glasses can
serialize/deserialize them through the existing VmMessage.Json
Glasses app:
- VmService handles GetAppList by listing installed launchable apps
via PackageManager.queryIntentActivities (combined with Android 11+
<queries> fix in the previous commit so 3rd-party apps appear) and
replies with AppListResponse.
- GlassesScreen drives state from controller.state; no longer hardcodes
any apps in the UI.
Tested end-to-end on A06B4AB933C4103: phone taps app on
AppsListScreen → glasses' VmService receives LaunchAppCommand →
controller.launchGuest(pkg) launches the activity via
PackageManager.getLaunchIntentForPackage + FLAG_ACTIVITY_NEW_TASK.
- Add <intent MAIN+LAUNCHER> to <queries> so queryIntentActivities on the
glasses side returns third-party apps (free.zona etc), not only system /
RayNeo system apps. Without this entry Android 11+ package visibility
filter strips apps whose package is not explicitly named.
- Pass MATCH_ALL | GET_RESOLVED_FILTER to queryIntentActivities as a
belt-and-suspenders measure (no visible effect with queries above, but
safe on Android 12+ where the platform may still filter).
- Default VmGlassesController state is NoAppSelected instead of Starting,
so we don't show 'Подготавливаем очки...' on first launch when no
LaunchAppCommand has been received yet.
- DriveShizukuState leaves Running state alone on re-check (don't drop
a running guest back to ShizukuMissing just because Shizuku was
re-polled).
- Replace obsolete moe.shizuku.permission.SHIZUKU (v11) with the v12+
moe.shizuku.manager.permission.API_V23 in the GlassesApp permission list.
- Document the rikka.shizuku.ShizukuProvider provider block required by
the Shizuku-API guide; without it the binder never arrives.
- Add §5.3.1 'Интеграция с Shizuku' explaining:
* why dev.rikka.shizuku:provider is required (ContentProvider.onCreate
pulls the binder via IPC);
* the activity-side listener pattern (addBinderReceivedListenerSticky +
addRequestPermissionResultListener);
* the RayNeo system launcher race condition (PID 24482
com.ffalconxr.mercury.launcher, UID system, forceStopPackage on
RequestPermissionActivity within ~14ms).
- Add dev.rikka.shizuku:provider:13.1.5 dependency.
- Replace custom ShizukuInitProvider with rikka.shizuku.ShizukuProvider
from the upstream library; configured per Shizuku v12+ docs with
android:permission='android.permission.INTERACT_ACROSS_USERS_FULL'
to protect the provider from ordinary apps.
- Move ShizukuBootstrap.attach() from GlassesVmApp.onCreate to
MainActivity.onCreate (requestPermission needs an Activity context).
- MainActivity now overrides onRequestPermissionsResult and forwards
the grant to ShizukuBootstrap.
- Rewrite ShizukuBootstrap strictly per the Shizuku-API guide:
addBinderReceivedListenerSticky + addRequestPermissionResultListener
in attach(); pingBinder / checkSelfPermission / requestPermission
in refresh().
- Drop ShizukuInitProvider.kt — its job is now done by
rikka.shizuku.ShizukuProvider.onCreate (verified in logcat:
'ShizukuProvider: binder received').
Verified on device ARGF20 (A06B4AB933C4103, API 32): Shizuku dialog
'Allow rayneo-vm to use Shizuku?' opens correctly; binder received
via ShizukuProvider; launcher icon now visible (meta-data fix).