fec8aa977b
Bug 1a — Anruf-Pause: Neues PhoneCallModule.kt nutzt TelephonyCallback (API 31+) bzw. PhoneStateListener (Pre-12) um auf RINGING/OFFHOOK/IDLE zu reagieren. Bei Klingeln/Gespraech ruft phoneCall.ts → audioService.haltAllPlayback, ARIA verstummt sofort. READ_PHONE_STATE Permission wird beim ersten Start angefragt; ohne Permission failt der Listener leise. Bug 1b — Spotify-Resume: AudioFocus wird jetzt an den Conversation-Lifecycle gekoppelt statt an einzelne Streams. Solange wakeWordState 'conversing' ist, blockt acquireConversationFocus() jeden per-Stream-Release. Erst beim Wechsel auf 'armed'/'off' darf der Focus tatsaechlich freigegeben werden. Verhindert das "Spotify kommt nach 10s wieder hoch"-Phaenomen auch ueber Render-Pausen + zwischen mehreren ARIA-Antworten hinweg. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
127 lines
5.0 KiB
Kotlin
127 lines
5.0 KiB
Kotlin
package com.ariacockpit
|
|
|
|
import android.Manifest
|
|
import android.content.Context
|
|
import android.content.pm.PackageManager
|
|
import android.os.Build
|
|
import android.telephony.PhoneStateListener
|
|
import android.telephony.TelephonyCallback
|
|
import android.telephony.TelephonyManager
|
|
import android.util.Log
|
|
import androidx.core.content.ContextCompat
|
|
import com.facebook.react.bridge.Arguments
|
|
import com.facebook.react.bridge.Promise
|
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
import com.facebook.react.bridge.ReactMethod
|
|
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
|
|
/**
|
|
* Lauscht auf Anruf-Statusaenderungen — wenn das Telefon klingelt oder ein
|
|
* Anruf laeuft, sendet das Modul ein "PhoneCallStateChanged"-Event an JS.
|
|
*
|
|
* JS-Side stoppt dann die TTS-Wiedergabe damit ARIA nicht mitten ins Gespraech
|
|
* weiterredet. Ohne READ_PHONE_STATE-Permission failt start() leise — der Rest
|
|
* der App funktioniert wie bisher.
|
|
*
|
|
* State-Strings: "idle" | "ringing" | "offhook"
|
|
*/
|
|
class PhoneCallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
|
|
override fun getName() = "PhoneCall"
|
|
|
|
companion object { private const val TAG = "PhoneCall" }
|
|
|
|
private var telephonyManager: TelephonyManager? = null
|
|
private var legacyListener: PhoneStateListener? = null
|
|
private var modernCallback: Any? = null // TelephonyCallback ab API 31
|
|
private var lastState: Int = TelephonyManager.CALL_STATE_IDLE
|
|
|
|
@ReactMethod
|
|
fun start(promise: Promise) {
|
|
try {
|
|
val perm = ContextCompat.checkSelfPermission(reactApplicationContext, Manifest.permission.READ_PHONE_STATE)
|
|
if (perm != PackageManager.PERMISSION_GRANTED) {
|
|
Log.w(TAG, "READ_PHONE_STATE Permission fehlt — Anruf-Erkennung inaktiv")
|
|
promise.resolve(false)
|
|
return
|
|
}
|
|
val tm = reactApplicationContext.getSystemService(Context.TELEPHONY_SERVICE) as? TelephonyManager
|
|
if (tm == null) {
|
|
Log.w(TAG, "TelephonyManager nicht verfuegbar")
|
|
promise.resolve(false)
|
|
return
|
|
}
|
|
telephonyManager = tm
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
val cb = object : TelephonyCallback(), TelephonyCallback.CallStateListener {
|
|
override fun onCallStateChanged(state: Int) {
|
|
handleStateChange(state)
|
|
}
|
|
}
|
|
tm.registerTelephonyCallback(reactApplicationContext.mainExecutor, cb)
|
|
modernCallback = cb
|
|
} else {
|
|
@Suppress("DEPRECATION")
|
|
val l = object : PhoneStateListener() {
|
|
override fun onCallStateChanged(state: Int, phoneNumber: String?) {
|
|
handleStateChange(state)
|
|
}
|
|
}
|
|
@Suppress("DEPRECATION")
|
|
tm.listen(l, PhoneStateListener.LISTEN_CALL_STATE)
|
|
legacyListener = l
|
|
}
|
|
Log.i(TAG, "PhoneCall-Listener aktiv")
|
|
promise.resolve(true)
|
|
} catch (e: Exception) {
|
|
Log.e(TAG, "start fehlgeschlagen", e)
|
|
promise.reject("START_FAILED", e.message ?: "Unbekannter Fehler", e)
|
|
}
|
|
}
|
|
|
|
@ReactMethod
|
|
fun stop(promise: Promise) {
|
|
try {
|
|
val tm = telephonyManager
|
|
if (tm != null) {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
(modernCallback as? TelephonyCallback)?.let { tm.unregisterTelephonyCallback(it) }
|
|
modernCallback = null
|
|
} else {
|
|
@Suppress("DEPRECATION")
|
|
legacyListener?.let { tm.listen(it, PhoneStateListener.LISTEN_NONE) }
|
|
legacyListener = null
|
|
}
|
|
}
|
|
telephonyManager = null
|
|
lastState = TelephonyManager.CALL_STATE_IDLE
|
|
promise.resolve(true)
|
|
} catch (e: Exception) {
|
|
promise.reject("STOP_FAILED", e.message ?: "")
|
|
}
|
|
}
|
|
|
|
private fun handleStateChange(state: Int) {
|
|
if (state == lastState) return
|
|
lastState = state
|
|
val name = when (state) {
|
|
TelephonyManager.CALL_STATE_RINGING -> "ringing"
|
|
TelephonyManager.CALL_STATE_OFFHOOK -> "offhook"
|
|
TelephonyManager.CALL_STATE_IDLE -> "idle"
|
|
else -> return
|
|
}
|
|
Log.i(TAG, "Telefon-State: $name")
|
|
val params = Arguments.createMap().apply { putString("state", name) }
|
|
try {
|
|
reactApplicationContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
.emit("PhoneCallStateChanged", params)
|
|
} catch (e: Exception) {
|
|
Log.w(TAG, "Event-emit fehlgeschlagen: ${e.message}")
|
|
}
|
|
}
|
|
|
|
@ReactMethod fun addListener(eventName: String) {}
|
|
@ReactMethod fun removeListeners(count: Int) {}
|
|
}
|