package pw.binom.rayneovm.phone import android.app.Application import android.util.Log import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import pw.binom.rayneovm.phone.adb.AdbController import pw.binom.rayneovm.phone.bootstrap.ShizukuProvisioner import pw.binom.rayneovm.phone.mercury.MercuryController import pw.binom.rayneovm.phone.mercury.MercuryControllerProvider import pw.binom.rayneovm.phone.mercury.MercuryState import pw.binom.rayneovm.phone.protocol.ConnectionState import pw.binom.rayneovm.phone.protocol.ControllerProvider import pw.binom.rayneovm.phone.protocol.VmPhoneController /** * Application-класс PhoneApp. * * При старте подготавливает контроллер [VmPhoneController] (auto-reconnect к очкам через mDNS) * и [MercuryController] (второй канал — RayNeo Mercury BLE). */ class PhoneVmApp : Application(), ControllerProvider, MercuryControllerProvider, ShizukuProvisionerProvider, AdbControllerProvider { private val appScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) private val controller: VmPhoneController by lazy { VmPhoneController(this) } private val mercuryController: MercuryController by lazy { MercuryController(this) } private val adbController: AdbController by lazy { AdbController(this, mercuryController) } private val provisioner: ShizukuProvisioner by lazy { ShizukuProvisioner(this, mercuryController, controller, adbController) } override fun onCreate() { super.onCreate() Log.i(TAG, "PhoneVmApp onCreate, uid=${android.os.Process.myUid()}") // Хранитель связи: держит процесс живым при выключенном экране, иначе HONOR усыпляет // его и очки теряют WebSocket-связь. Может быть запрещён системой при старте из фона — // тогда связь просто отвалится по экрану, как раньше; приложение не падает. try { ConnectionKeepAliveService.start(this) } catch (t: Throwable) { Log.w(TAG, "ConnectionKeepAliveService start failed", t) } // Запуск отложен до первого обращения — в lazy. controller.start() mercuryController.refresh() // Автономный подъём Shizuku. Условие: есть и WiFi-канал (наша WebSocket-связь // с очками), и BLE (Канал 2, через который очки включают ADB over WiFi). // Проверяем при каждом появлении связи и периодически, пока BLE жив, — // если Shizuku не запущен, поднимаем его. appScope.launch { controller.state.collect { st -> if (st is ConnectionState.Connected && mercuryController.state.value is MercuryState.Connected) { Log.i(TAG, "WiFi подключение к очкам — проверяю Shizuku") provisioner.ensure() } } } // Периодический дешёвый дозвон, пока BLE жив. Полный adb-цикл гоняется не чаще // одного раза за BLE-сессию (см. ShizukuProvisioner.provisionedInSession), поэтому // это не дёргает очки; зато так мы подхватываем случай, когда Shizuku упал и очки // прислали явный статус running=false. appScope.launch { while (true) { delay(PROVISION_RETRY_MS) if (mercuryController.state.value is MercuryState.Connected) { provisioner.ensure() } } } // Разрыв BLE — новая сессия: при следующем подключении проверяем Shizuku заново. appScope.launch { mercuryController.state.collect { st -> if (st !is MercuryState.Connected) provisioner.onBleDisconnected() } } } override fun onTerminate() { super.onTerminate() ConnectionKeepAliveService.stop(this) provisioner.close() controller.stop() mercuryController.close() } override fun provideController(): VmPhoneController = controller override fun provideMercuryController(): MercuryController = mercuryController override fun provideShizukuProvisioner(): ShizukuProvisioner = provisioner override fun provideAdbController(): AdbController = adbController companion object { private const val TAG = "PhoneVmApp" private const val PROVISION_RETRY_MS = 30_000L } } /** Провайдер автономного бутстрапа Shizuku для UI. */ interface ShizukuProvisionerProvider { fun provideShizukuProvisioner(): ShizukuProvisioner } /** Провайдер adb-контроллера (единая точка входа для adb-команд) для UI. */ interface AdbControllerProvider { fun provideAdbController(): AdbController }