# Задача: телефон не должен засыпать во время просмотра (foreground service) ## Симптом (пользователь, 21.08) «Если я телефон отправлю спать, то звук на телефоне прекращается. А когда на телефоне прекращается звук, очки тоже перестают играть.» ## Корень (диагноз подтверждён) Архитектура: звук играет ТОЛЬКО на телефоне (у очков нет динамиков), видео — на очках, синк по позиции (очки — мастер, 5с-цикл). На телефоне НЕТ foreground service и НЕТ wake/wifi lock — приложение живёт только в Activity (`app-phone/src/main/AndroidManifest.xml`: только MainActivity, пермишенов WAKE_LOCK нет). При выключении экрана HONOR (Android 16, агрессивная фоновая политика) замораживает/убивает процесс: 1. WS-сервер (`GlassesServer`, :8080) умирает → очки теряют связь (ping 5с) → автоматика обрыва на очках («связь потеряна — пауза») останавливает ВИДЕО. 2. `AudioSyncPlayer` (ExoPlayer) заморожен → ЗВУК встаёт. ## Решение Добавить в app-phone **foreground service** типа `mediaPlayback` + WAKE_LOCK + WifiLock, чтобы процесс (WS-сервер + аудио-плеер) жил при выключенном экране телефона. Очки тогда не теряют связь, видео не паузится, звук играет. ### 1. Манифест (`app-phone/src/main/AndroidManifest.xml`) ```xml ``` Внутри `` — сервис: ```xml ``` ### 2. Сервис `app-phone/src/main/kotlin/pw/binom/viewmate/phone/PlaybackService.kt` ```kotlin class PlaybackService : Service() { companion object { const val CHANNEL_ID = "viewmate_playback" const val NOTIFICATION_ID = 1 // аккуратный запуск: ContextCompat.startForegroundService(context, intent) fun start(context: Context) { ContextCompat.startForegroundService( context, Intent(context, PlaybackService::class.java) ) } fun stop(context: Context) { context.stopService(Intent(context, PlaybackService::class.java)) } } private var wakeLock: PowerManager.WakeLock? = null private var wifiLock: WifiManager.WifiLock? = null override fun onCreate() { super.onCreate() // канал уведомлений (Android 8+) val channel = NotificationChannel( CHANNEL_ID, "ViewMate playback", NotificationManager.IMPORTANCE_LOW, ).apply { setShowBadge(false) } getSystemService(NotificationManager::class.java).createNotificationChannel(channel) val notification = NotificationCompat.Builder(this, CHANNEL_ID) .setSmallIcon(android.R.drawable.ic_media_play) .setContentTitle("ViewMate") .setContentText("Звук на телефоне при просмотре на очках") .setOngoing(true) .build() startForeground(NOTIFICATION_ID, notification) // держим CPU и WiFi живыми при выключенном экране wakeLock = (getSystemService(POWER_SERVICE) as PowerManager) .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "viewmate:audio") ?.apply { acquire() } wifiLock = (getApplicationContext().getSystemService(WIFI_SERVICE) as WifiManager) .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "viewmate:wifi") ?.apply { acquire() } } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int = START_NOT_STICKY override fun onDestroy() { wakeLock?.takeIf { it.isHeld }?.release() wifiLock?.takeIf { it.isHeld }?.release() super.onDestroy() } override fun onBind(intent: Intent?): IBinder? = null } ``` Импорты: androidx.core.app.NotificationCompat, androidx.core.content.ContextCompat. Check: в gradle уже есть androidx.core (проверить libs.versions.toml — ktor/compose тянут core-ktx, но для чистоты добавить `androidx.core:core-ktx` если нет). ### 3. Запуск/остановка сервиса в `PhoneApp.kt` - **Старт — при каждом старте аудио** (3 места): - `autostartAudio()` (строка ~173, перед `audioSync.play(source, positionMs)`) - `DetailsScreen.onWatch` — там где вызывается `app.audioSync.play(source, positionMs)` (строка ~127) и второй play (строка ~215) - ОБЩИЙ подход: раз звук — сервис. Проще всего: стартовать в `PhoneApp.onCreate()` сразу (сервис лёгкий, уведомление висит пока приложение живо) — НО только если пользователь не против постоянного уведомления. РЕШЕНИЕ: старт в onCreate, стоп в onTerminate — сервис живёт пока жив телефон-компаньон (WS-сервер тоже живёт всё время), уведомление — постоянный индикатор «ViewMate работает». Это проще и покрывает и watch, и autostartAudio, и STT-сессии. - **Стоп**: в `onTerminate()` — `PlaybackService.stop(this)`. Решение принято: **старт в onCreate, стоп в onTerminate** (сервис = спутник WS-сервера, а не аудио-сессии; уведомление постоянно, но это норма для приложения-компаньона — так работает, например, в паре с очками). ### 4. Проверка 1. Сборка: `./gradlew :app-phone:assembleDebug` — успех. 2. Юнит-тесты: `./gradlew :app-phone:testDebugUnitTest` — зелёные. 3. На железе (через 142): установить APK, запустить, начать просмотр, `adb -s shell input keyevent 26` (экран телефона в сон) → проверить: звук играет, видео на очках играет (позиция растёт в логах), сервис жив: `adb shell dumpsys activity services | grep -A3 PlaybackService`.