аудио UDP+Opus (очки→телефон), жесты дужки → гость, «убить всё лишнее» на очках
- shared/audio: вендорнут Concentus (libs/concentus.jar), OpusCodec/AudioDatagram/AudioConfig; захват на очках 48кГц стерео → Opus → UDP-датаграммы вместо сырого PCM по WebSocket - glass.audio: AudioRecord 48кГц с минимальным буфером; UDP-сервер запоминает адрес пира из датаграммы - glasses: жесты тач-сенсора дужки (cyttsp5_mt) через dispatchTouchEvent → DPAD/BACK гостю (TempleGesture*) - glasses: forceStopAll — force-stop сторонних + xr.runtime, kill scrcpy-сирот через Shizuku UserService - phone: AudioPlaybackManager на UDP+Opus+low-latency AudioTrack; кнопка «Убить всё лишнее» на вкладке очков - deps: mercury transport 0.1.2-SNAPSHOT, shizuku-vd 0.1.1-SNAPSHOT
This commit is contained in:
@@ -7,6 +7,8 @@ import android.media.projection.MediaProjectionManager
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Display
|
||||
import android.view.InputDevice
|
||||
import android.view.MotionEvent
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
@@ -29,6 +31,7 @@ import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import kotlinx.coroutines.launch
|
||||
import pw.binom.rayneovm.glasses.audio.AudioBridge
|
||||
import pw.binom.rayneovm.glasses.input.TempleGestureDetector
|
||||
import pw.binom.rayneovm.glasses.protocol.VmGlassesController
|
||||
import pw.binom.rayneovm.glasses.ui.GlassesScreen
|
||||
import pw.binom.rayneovm.glasses.vm.GlassesScreenState
|
||||
@@ -38,6 +41,26 @@ class MainActivity : ComponentActivity() {
|
||||
/** Последний обработанный tick запроса проекции (см. onCreate). */
|
||||
private var handledTick = 0L
|
||||
|
||||
/** Контроллер очков — нужен и Compose, и распознавателю жестов дужки. */
|
||||
private lateinit var controller: VmGlassesController
|
||||
|
||||
/**
|
||||
* Распознаватель жестов тач-сенсора дужки. Дужка на X3 Pro — отдельное input-устройство
|
||||
* `cyttsp5_mt` без ассоциированного дисплея, поэтому события не доходят до Compose и
|
||||
* перехватываются в [dispatchTouchEvent]. Жесты уходят гостю через
|
||||
* [VmGlassesController.sendTempleGesture].
|
||||
*/
|
||||
private val templeGestureDetector by lazy {
|
||||
TempleGestureDetector { gesture -> controller.sendTempleGesture(gesture) }
|
||||
}
|
||||
|
||||
/** id input-устройства дужки (`cyttsp5_mt`); -1 — не найдено. */
|
||||
private val templeDeviceId: Int by lazy {
|
||||
InputDevice.getDeviceIds().firstOrNull { id ->
|
||||
InputDevice.getDevice(id)?.name?.contains(TEMPLE_DEVICE_HINT) == true
|
||||
} ?: -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Пользовательский диалог MediaProjection. При выставленном appop `PROJECT_MEDIA = allow`
|
||||
* он не показывается — интент сразу возвращает RESULT_OK (нужно для задачи #2).
|
||||
@@ -71,6 +94,13 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
enableEdgeToEdge()
|
||||
ShizukuBootstrap.attach(this)
|
||||
// Диагностика: печатаем input-устройства и id дужки, чтобы в логе было видно,
|
||||
// куда приходят жесты (см. dispatchTouchEvent).
|
||||
InputDevice.getDeviceIds().forEach { id ->
|
||||
val d = InputDevice.getDevice(id)
|
||||
Log.i(TAG, "input device id=$id name=${d?.name} sources=0x${d?.sources?.toString(16)}")
|
||||
}
|
||||
Log.i(TAG, "temple device id=$templeDeviceId (hint='$TEMPLE_DEVICE_HINT')")
|
||||
// Запросы MediaProjection приходят из VmService через AudioBridge (задача #2).
|
||||
// Запускаем интент только когда Activity в RESUMED (реально видима): если её запускать
|
||||
// из не-foreground состояния, системная grant-активити отменяется и возвращает CANCELED.
|
||||
@@ -89,6 +119,7 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
val controller = VmGlassesController.fromContext(this)
|
||||
this.controller = controller
|
||||
|
||||
setContent {
|
||||
CompositionLocalProvider(VmGlassesController.Local provides controller) {
|
||||
@@ -116,6 +147,29 @@ class MainActivity : ComponentActivity() {
|
||||
ShizukuBootstrap.refresh()
|
||||
}
|
||||
|
||||
/**
|
||||
* Перехватываем тач-события окна ДО Compose/View-иерархии. События с устройства дужки
|
||||
* (`cyttsp6_mt`) не ассоциированы с дисплеем и иначе теряются: `onTouchEvent` Activity
|
||||
* при этом может не вызываться вовсе. Всё, что пришло с дужки, отдаём распознавателю и
|
||||
* потребляем, чтобы не утекло в UI.
|
||||
*/
|
||||
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
|
||||
// Диагностика: на ACTION_DOWN печатаем устройство — видно, доходят ли касания дужки
|
||||
// (deviceId тача дужки = 4, `cyttsp5_mt`) до окна Activity вообще.
|
||||
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||
Log.d(
|
||||
TAG,
|
||||
"touch down: deviceId=${event.deviceId} source=0x${event.source.toString(16)} " +
|
||||
"x=${event.x} y=${event.y}",
|
||||
)
|
||||
}
|
||||
val isTemple = event.deviceId == templeDeviceId && templeDeviceId != -1
|
||||
if (!isTemple) return super.dispatchTouchEvent(event)
|
||||
Log.d(TAG, "temple: action=${event.actionMasked} x=${event.x} y=${event.y}")
|
||||
templeGestureDetector.onTouchEvent(event)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
AudioBridge.activityResumed = false
|
||||
super.onPause()
|
||||
@@ -142,6 +196,13 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "MainActivity"
|
||||
|
||||
/**
|
||||
* Имя input-устройства тач-сенсора дужки RayNeo X3 Pro (проверено через `getevent`:
|
||||
* все касания дужки приходят на `/dev/input/event2`, устройство `cyttsp5_mt`).
|
||||
* Второй контроллер `cyttsp6_mt` не генерирует событий.
|
||||
*/
|
||||
private const val TEMPLE_DEVICE_HINT = "cyttsp5_mt"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user