79a500150d
- MercuryBridge.pairClassicAuto: классический discovery → найти RayNeo по
имени → createBond (без QR и без камеры очков) для случая «бонд слетел,
очки в first-pairing». discoverGlasses — список найденных.
- MercuryPanel: кнопка «🔗 паринг (вручную)» рядом с QR-парингом
- DebugHttpServer: /mercury/pair, /mercury/pair/stop, /mercury/pair/classic,
/mercury/unpair, /mercury/bonded, /mercury/discover, поля pairing в /status
- keep-screen-on, чтобы QR/состояние реально были на экране телефона
99 lines
3.7 KiB
Kotlin
99 lines
3.7 KiB
Kotlin
package pw.binom.viewmate.phone
|
|
|
|
import android.Manifest
|
|
import android.content.pm.PackageManager
|
|
import android.graphics.Color
|
|
import android.os.Bundle
|
|
import android.view.Gravity
|
|
import android.view.WindowManager
|
|
import android.widget.FrameLayout
|
|
import android.widget.TextView
|
|
import androidx.activity.ComponentActivity
|
|
import androidx.activity.compose.setContent
|
|
import androidx.core.content.ContextCompat
|
|
import pw.binom.viewmate.phone.stt.SttDebug
|
|
import pw.binom.viewmate.phone.agent.LlmDebug
|
|
import pw.binom.viewmate.phone.ui.ViewMateApp
|
|
import java.io.File
|
|
|
|
class MainActivity : ComponentActivity() {
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
super.onCreate(savedInstanceState)
|
|
requestBtPermissions()
|
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
setContent {
|
|
ViewMateApp()
|
|
}
|
|
if (intent?.getBooleanExtra("stt_debug", false) == true) {
|
|
runSttDebug()
|
|
}
|
|
if (intent?.getBooleanExtra("llm_debug", false) == true) {
|
|
runLlmDebug()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Runtime-запрос Bluetooth-пермишенов (TASK-transport.md п.5): только при
|
|
* вкл. SPP ([PhoneConfig.USE_BLUETOOTH]). На 31+ — BLUETOOTH_CONNECT/SCAN,
|
|
* на ≤30 — BLUETOOTH/BLUETOOTH_ADMIN. Если уже выданы — молча пропускаем.
|
|
*/
|
|
private fun requestBtPermissions() {
|
|
if (!PhoneConfig.USE_BLUETOOTH) return
|
|
val needed = BtServerTransport.runtimePermissions()
|
|
val missing = needed.filter {
|
|
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
|
|
}
|
|
if (missing.isNotEmpty()) {
|
|
requestPermissions(missing.toTypedArray(), 1009)
|
|
log("bt", "запрошены Bluetooth-пермишены (SPP): ${missing.joinToString()}")
|
|
}
|
|
}
|
|
|
|
/** Smoke Koog-клиента: один вызов llm.binom.pw, ответ в лог [llm]. */
|
|
private fun runLlmDebug() {
|
|
showOverlay("[llm] вызываю llm.binom.pw...")
|
|
Thread {
|
|
LlmDebug.run(BuildConfig.LLM_API_KEY, BuildConfig.LLM_MODEL)
|
|
runOnUiThread { showOverlay("[llm] готово — смотри лог") }
|
|
}.start()
|
|
}
|
|
|
|
/** Отладочный прогон распознавания: берёт первый wav из filesDir/samples. */
|
|
private fun runSttDebug() {
|
|
val samplesDir = File(filesDir, "samples")
|
|
val wav = samplesDir.listFiles()
|
|
?.filter { it.isFile && it.extension.equals("wav", true) }
|
|
?.firstOrNull()
|
|
if (wav == null) {
|
|
val msg = "[stt] нет wav-файлов в ${samplesDir.absolutePath}"
|
|
log("stt", msg)
|
|
showOverlay(msg)
|
|
return
|
|
}
|
|
showOverlay("[stt] распознаю ${wav.name}...")
|
|
Thread {
|
|
val text = SttDebug.run(this, wav.absolutePath)
|
|
runOnUiThread { showOverlay(text ?: "[stt] не удалось распознать") }
|
|
}.start()
|
|
}
|
|
|
|
/** TextView поверх экрана — чтобы видеть результат на эмуляторе. */
|
|
private fun showOverlay(text: String) {
|
|
val tv = TextView(this).apply {
|
|
this.text = text
|
|
setTextColor(Color.WHITE)
|
|
setBackgroundColor(Color.argb(200, 0, 0, 0))
|
|
textSize = 16f
|
|
setPadding(24, 24, 24, 24)
|
|
}
|
|
addContentView(
|
|
tv,
|
|
FrameLayout.LayoutParams(
|
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
FrameLayout.LayoutParams.WRAP_CONTENT,
|
|
Gravity.TOP,
|
|
),
|
|
)
|
|
}
|
|
}
|