fix: reduce event expiration to 20s and fix foreground service crash

- Change EXPIRATION_SECONDS from 300 to 20 for all call signaling
  events (offer, answer, ICE, hangup, reject, renegotiate)
- Change MAX_EVENT_AGE_SECONDS from 30 to 20 to match
- Update NIP-AC doc accordingly
- Fix SecurityException crash on SDK 36: phoneCall foreground service
  type requires MANAGE_OWN_CALLS permission. Switch to microphone
  type which only needs RECORD_AUDIO (already granted).

https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
This commit is contained in:
Claude
2026-04-02 01:28:00 +00:00
parent d93b384f72
commit 1016e46d50
10 changed files with 11 additions and 11 deletions
+2 -2
View File
@@ -39,7 +39,7 @@
<!-- Audio/Video Playback --> <!-- Audio/Video Playback -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" /> <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<!-- Keeps screen on while playing videos --> <!-- Keeps screen on while playing videos -->
@@ -225,7 +225,7 @@
<service <service
android:name=".service.call.CallForegroundService" android:name=".service.call.CallForegroundService"
android:foregroundServiceType="phoneCall" android:foregroundServiceType="microphone"
android:stopWithTask="true" android:stopWithTask="true"
android:exported="false" /> android:exported="false" />
@@ -62,7 +62,7 @@ class CallForegroundService : Service() {
NOTIFICATION_ID, NOTIFICATION_ID,
notification, notification,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
} else { } else {
0 0
}, },
@@ -61,7 +61,7 @@ class CallManager(
companion object { companion object {
const val CALL_TIMEOUT_MS = 60_000L // 60 seconds ringing timeout const val CALL_TIMEOUT_MS = 60_000L // 60 seconds ringing timeout
const val ENDED_DISPLAY_MS = 2_000L // show "call ended" briefly before resetting const val ENDED_DISPLAY_MS = 2_000L // show "call ended" briefly before resetting
const val MAX_EVENT_AGE_SECONDS = 30L // discard signaling events older than this const val MAX_EVENT_AGE_SECONDS = 20L // discard signaling events older than this
} }
private fun isEventTooOld(event: Event): Boolean = TimeUtils.now() - event.createdAt > MAX_EVENT_AGE_SECONDS private fun isEventTooOld(event: Event): Boolean = TimeUtils.now() - event.createdAt > MAX_EVENT_AGE_SECONDS
@@ -215,7 +215,7 @@ This NIP does not mandate specific STUN or TURN servers. Clients SHOULD:
## Implementation Notes ## Implementation Notes
- The `call-id` tag MUST be a UUID that is unique per call session. All signaling events for the same call share the same `call-id`. - The `call-id` tag MUST be a UUID that is unique per call session. All signaling events for the same call share the same `call-id`.
- Events SHOULD have short expiration times (~5 minutes) since signaling data is ephemeral and has no long-term value. - Events SHOULD have short expiration times (~20 seconds) since signaling data is ephemeral and has no long-term value.
- Clients SHOULD implement a ringing timeout (e.g., 60 seconds). If no answer is received, the call transitions to a "timed out" state. - Clients SHOULD implement a ringing timeout (e.g., 60 seconds). If no answer is received, the call transitions to a "timed out" state.
- Clients SHOULD use a foreground service or equivalent mechanism to keep calls active when the app is backgrounded. - Clients SHOULD use a foreground service or equivalent mechanism to keep calls active when the app is backgrounded.
- The WebRTC `PeerConnection` SHOULD use Unified Plan SDP semantics. - The WebRTC `PeerConnection` SHOULD use Unified Plan SDP semantics.
@@ -48,7 +48,7 @@ class CallAnswerEvent(
companion object { companion object {
const val KIND = 25051 const val KIND = 25051
const val ALT_DESCRIPTION = "WebRTC call answer" const val ALT_DESCRIPTION = "WebRTC call answer"
const val EXPIRATION_SECONDS = 300L const val EXPIRATION_SECONDS = 20L
fun build( fun build(
sdpAnswer: String, sdpAnswer: String,
@@ -48,7 +48,7 @@ class CallHangupEvent(
companion object { companion object {
const val KIND = 25053 const val KIND = 25053
const val ALT_DESCRIPTION = "WebRTC call hangup" const val ALT_DESCRIPTION = "WebRTC call hangup"
const val EXPIRATION_SECONDS = 300L const val EXPIRATION_SECONDS = 20L
fun build( fun build(
peerPubKey: HexKey, peerPubKey: HexKey,
@@ -48,7 +48,7 @@ class CallIceCandidateEvent(
companion object { companion object {
const val KIND = 25052 const val KIND = 25052
const val ALT_DESCRIPTION = "WebRTC ICE candidate" const val ALT_DESCRIPTION = "WebRTC ICE candidate"
const val EXPIRATION_SECONDS = 300L const val EXPIRATION_SECONDS = 20L
fun build( fun build(
candidateJson: String, candidateJson: String,
@@ -53,7 +53,7 @@ class CallOfferEvent(
companion object { companion object {
const val KIND = 25050 const val KIND = 25050
const val ALT_DESCRIPTION = "WebRTC call offer" const val ALT_DESCRIPTION = "WebRTC call offer"
const val EXPIRATION_SECONDS = 300L // 5 minutes const val EXPIRATION_SECONDS = 20L
fun build( fun build(
sdpOffer: String, sdpOffer: String,
@@ -48,7 +48,7 @@ class CallRejectEvent(
companion object { companion object {
const val KIND = 25054 const val KIND = 25054
const val ALT_DESCRIPTION = "WebRTC call rejection" const val ALT_DESCRIPTION = "WebRTC call rejection"
const val EXPIRATION_SECONDS = 300L const val EXPIRATION_SECONDS = 20L
fun build( fun build(
callerPubKey: HexKey, callerPubKey: HexKey,
@@ -48,7 +48,7 @@ class CallRenegotiateEvent(
companion object { companion object {
const val KIND = 25055 const val KIND = 25055
const val ALT_DESCRIPTION = "WebRTC call renegotiation" const val ALT_DESCRIPTION = "WebRTC call renegotiation"
const val EXPIRATION_SECONDS = 300L const val EXPIRATION_SECONDS = 20L
fun build( fun build(
sdpOffer: String, sdpOffer: String,