Retain overlay windows through partial IPC failures and guarded service startup
This commit is contained in:
1 parent
fa14305349
commit
b3d70ea92e
1 file changed
+55
-36
@@ -279,80 +279,99 @@ class RadioOverlayService : Service() {
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
|
||||
override fun onCreate() {
|
||||
checkMain()
|
||||
super.onCreate()
|
||||
LogManager.addLog("DEBUG", "RADIO_DBG", "RadioOverlayService.onCreate thread=${Thread.currentThread().name} t=${System.currentTimeMillis()}")
|
||||
createChannel()
|
||||
var operation = "channel"
|
||||
try {
|
||||
createChannel()
|
||||
operation = "foreground"
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
ServiceCompat.startForeground(this, NOTIF_ID, buildNotification(),
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE)
|
||||
} else {
|
||||
startForeground(NOTIF_ID, buildNotification())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LogManager.addLog("ERROR", "RadioOverlay", "Foreground microphone start failed: $e")
|
||||
starting = false
|
||||
setStatus(R.string.microphone_service_failed)
|
||||
stopSelf()
|
||||
return
|
||||
}
|
||||
|
||||
running = true
|
||||
currentService = this
|
||||
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
|
||||
serviceScope.launch {
|
||||
RadioAudioEngine.instance().talkingFlow.collect { updateButtonColor() }
|
||||
}
|
||||
starting = false
|
||||
LogManager.addLog("INFO", "RadioOverlay", "Foreground microphone service ready")
|
||||
RadioAudioEngine.instance().reconfigure()
|
||||
serviceScope.launch {
|
||||
while (isActive) {
|
||||
delay(1000)
|
||||
if (!MicrophonePermission.granted() ||
|
||||
(overlayView != null && !canDrawOverlays(this@RadioOverlayService))) sync(this@RadioOverlayService)
|
||||
operation = "previous owner"
|
||||
currentService?.takeIf { it !== this }?.let { previous ->
|
||||
check(previous.destroyed && previous.removeOverlay()) { "previous overlay owner still retained" }
|
||||
}
|
||||
}
|
||||
if (pendingStop) {
|
||||
currentService = this
|
||||
running = true
|
||||
starting = false
|
||||
if (pendingStop) {
|
||||
LogManager.addLog("INFO", "RadioOverlay", "Cancelled service start completed foreground handshake")
|
||||
stopSelf(); return
|
||||
}
|
||||
operation = "window service"
|
||||
windowManager = getSystemService(WINDOW_SERVICE) as? WindowManager ?: error("window manager unavailable")
|
||||
serviceScope.launch { RadioAudioEngine.instance().talkingFlow.collect { updateButtonColor() } }
|
||||
RadioAudioEngine.instance().reconfigure()
|
||||
serviceScope.launch {
|
||||
while (isActive) {
|
||||
delay(1000)
|
||||
if (!MicrophonePermission.granted() ||
|
||||
(overlayView != null && !canDrawOverlays(this@RadioOverlayService))) sync(this@RadioOverlayService)
|
||||
}
|
||||
}
|
||||
LogManager.addLog("INFO", "RadioOverlay", "Foreground microphone service ready notification=$NOTIF_ID")
|
||||
} catch (e: Exception) {
|
||||
LogManager.addLog("ERROR", "RadioOverlay", "service setup failed operation=$operation: $e")
|
||||
if (currentService == null) currentService = this
|
||||
if (currentService === this) { starting = false; running = false; pendingStop = true }
|
||||
setStatus(R.string.microphone_service_failed)
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
checkMain()
|
||||
refresh()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
private fun refresh() {
|
||||
if (!running || pendingStop || !RadioAudioEngine.instance().isActive() || !MicrophonePermission.granted()) {
|
||||
if (destroyed || currentService !== this || !running || pendingStop ||
|
||||
!RadioAudioEngine.instance().isActive() || !MicrophonePermission.granted()) {
|
||||
removeOverlay()
|
||||
if (currentService === this) pendingStop = true
|
||||
stopSelf()
|
||||
return
|
||||
}
|
||||
val provider = ChatApplication.instance.configProvider
|
||||
val wantOverlay = provider.getRadioOverlay() && canDrawOverlays(this)
|
||||
if (overlayView != null && !overlayReady && !removeOverlay()) return
|
||||
if (wantOverlay && overlayView == null) {
|
||||
try { addOverlay() } catch (e: Exception) {
|
||||
removeOverlay()
|
||||
setStatus(R.string.overlay_show_failed)
|
||||
LogManager.addLog("ERROR", "RadioOverlay", "Overlay creation failed: $e")
|
||||
if (provider.getInt("radio.auto_enabled") == 0) { pendingStop = true; stopSelf() }
|
||||
return
|
||||
}
|
||||
}
|
||||
if (!wantOverlay) removeOverlay()
|
||||
if (overlayView != null) setStatus(R.string.overlay_active)
|
||||
if (!wantOverlay && provider.getInt("radio.auto_enabled") == 0) stopSelf()
|
||||
if (overlayReady) setStatus(R.string.overlay_active)
|
||||
if (!wantOverlay && provider.getInt("radio.auto_enabled") == 0) { pendingStop = true; stopSelf() }
|
||||
}
|
||||
|
||||
private fun removeOverlay() {
|
||||
val view = overlayView ?: return
|
||||
/** Снять только свой PTT. Ссылка на окно сохраняется до успешного remove либо подтверждённого отсутствия. */
|
||||
private fun removeOverlay(): Boolean {
|
||||
talking = false
|
||||
RadioAudioEngine.instance().cancelTalk(RadioPttSource.OVERLAY)
|
||||
try { windowManager?.removeView(view) } catch (e: Exception) {
|
||||
LogManager.addLog("WARN", "RadioOverlay", "Overlay removal failed: $e")
|
||||
overlayReady = false
|
||||
positioningPending = false
|
||||
if (currentService === this) RadioAudioEngine.instance().cancelTalk(RadioPttSource.OVERLAY)
|
||||
val view = overlayView ?: return true
|
||||
try { requireNotNull(windowManager) { "owned window without manager" }.removeView(view) } catch (e: IllegalArgumentException) {
|
||||
LogManager.addLog("WARN", "RadioOverlay", "Overlay already absent view=${System.identityHashCode(view)}: $e")
|
||||
} catch (e: Exception) {
|
||||
LogManager.addLog("ERROR", "RadioOverlay", "Overlay removal failed view=${System.identityHashCode(view)}; retained for retry: $e")
|
||||
return false
|
||||
}
|
||||
overlayView = null
|
||||
params = null
|
||||
_isShown.value = false
|
||||
LogManager.addLog("INFO", "RadioOverlay", "Overlay removed; transmission cancelled")
|
||||
if (currentService === this) _isShown.value = false
|
||||
LogManager.addLog("INFO", "RadioOverlay", "Overlay removed view=${System.identityHashCode(view)}; own transmission cancelled")
|
||||
return true
|
||||
}
|
||||
|
||||
private fun createChannel() {
|
||||
|
||||
Reference in new issue
Block a user