fix: implement TODOs for call duration, contact lookup, and event queue
- VoiceCallService: Track call start time and calculate actual duration on disconnect - CallScreener: Implement contact lookup via ContactsContract.PhoneLookup - GatewayClient: Add event queue (max 100) for offline buffering, flush on reconnect - ClawdNodeApp: Route call handlers through CallManager instead of TODOs
This commit is contained in:
parent
b7e213ff32
commit
0cd111343f
|
|
@ -9,6 +9,7 @@ import com.inou.clawdnode.gateway.DirectGateway
|
|||
import com.inou.clawdnode.security.AuditLog
|
||||
import com.inou.clawdnode.security.DeviceIdentity
|
||||
import com.inou.clawdnode.security.TokenStore
|
||||
import com.inou.clawdnode.service.CallManager
|
||||
import com.inou.clawdnode.service.NotificationManager as AppNotificationManager
|
||||
|
||||
/**
|
||||
|
|
@ -58,17 +59,17 @@ class ClawdNodeApp : Application() {
|
|||
|
||||
DirectGateway.onCallAnswer = { callId ->
|
||||
auditLog.log("COMMAND_RECEIVED", "call.answer: $callId")
|
||||
// TODO: Implement call answering via TelecomManager
|
||||
CallManager.answer(callId, null)
|
||||
}
|
||||
|
||||
DirectGateway.onCallReject = { callId ->
|
||||
auditLog.log("COMMAND_RECEIVED", "call.reject: $callId")
|
||||
// TODO: Implement call rejection
|
||||
CallManager.reject(callId, null)
|
||||
}
|
||||
|
||||
DirectGateway.onCallHangup = { callId ->
|
||||
auditLog.log("COMMAND_RECEIVED", "call.hangup: $callId")
|
||||
// TODO: Implement call hangup
|
||||
CallManager.hangup(callId)
|
||||
}
|
||||
|
||||
// Connect
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@ import android.content.ComponentName
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.os.IBinder
|
||||
import android.provider.ContactsContract
|
||||
import android.telecom.Call
|
||||
import android.telecom.CallScreeningService
|
||||
import android.util.Log
|
||||
|
|
@ -104,9 +107,26 @@ class CallScreener : CallScreeningService() {
|
|||
}
|
||||
|
||||
private fun lookupContact(number: String): String? {
|
||||
// TODO: Look up in contacts
|
||||
// For now, return null (unknown caller)
|
||||
return null
|
||||
return try {
|
||||
val uri = Uri.withAppendedPath(
|
||||
ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
|
||||
Uri.encode(number)
|
||||
)
|
||||
val projection = arrayOf(ContactsContract.PhoneLookup.DISPLAY_NAME)
|
||||
|
||||
contentResolver.query(uri, projection, null, null, null)?.use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
cursor.getString(
|
||||
cursor.getColumnIndexOrThrow(ContactsContract.PhoneLookup.DISPLAY_NAME)
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(tag, "Contact lookup failed for $number: ${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ class VoiceCallService : InCallService(), TextToSpeech.OnInitListener {
|
|||
private var audioManager: AudioManager? = null
|
||||
|
||||
private val activeCalls = mutableMapOf<String, Call>()
|
||||
private val callStartTimes = mutableMapOf<String, Long>()
|
||||
private var currentCallId: String? = null
|
||||
private var isListening = false
|
||||
|
||||
|
|
@ -135,25 +136,32 @@ class VoiceCallService : InCallService(), TextToSpeech.OnInitListener {
|
|||
|
||||
when (state) {
|
||||
Call.STATE_ACTIVE -> {
|
||||
// Call is active, start listening
|
||||
// Call is active, record start time and start listening
|
||||
callStartTimes[callId] = System.currentTimeMillis()
|
||||
currentCallId = callId
|
||||
startListening()
|
||||
}
|
||||
Call.STATE_DISCONNECTED -> {
|
||||
// Call ended
|
||||
val duration = 0 // TODO: Calculate actual duration
|
||||
DirectGateway.sendCall(
|
||||
callId = callId,
|
||||
number = call.details.handle?.schemeSpecificPart,
|
||||
contact = null,
|
||||
state = "ended"
|
||||
)
|
||||
// Call ended - calculate duration
|
||||
val startTime = callStartTimes.remove(callId)
|
||||
val duration = if (startTime != null) {
|
||||
((System.currentTimeMillis() - startTime) / 1000).toInt()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
||||
DirectGateway.sendLog("call.ended", mapOf(
|
||||
"callId" to callId,
|
||||
"number" to (call.details.handle?.schemeSpecificPart ?: "unknown"),
|
||||
"duration" to duration,
|
||||
"outcome" to "completed"
|
||||
))
|
||||
|
||||
ClawdNodeApp.instance.auditLog.logCall(
|
||||
"CALL_ENDED",
|
||||
call.details.handle?.schemeSpecificPart,
|
||||
null,
|
||||
"completed"
|
||||
"completed (${duration}s)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,10 @@ class GatewayClient(
|
|||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
private val requestIdCounter = AtomicInteger(0)
|
||||
|
||||
// Queue for events when disconnected (max 100 to prevent memory issues)
|
||||
private val eventQueue = mutableListOf<NodeEvent>()
|
||||
private val maxQueueSize = 100
|
||||
|
||||
private val auditLog get() = ClawdNodeApp.instance.auditLog
|
||||
private val tokenStore get() = ClawdNodeApp.instance.tokenStore
|
||||
|
||||
|
|
@ -146,8 +150,33 @@ class GatewayClient(
|
|||
if (isConnected && isHandshakeComplete) {
|
||||
webSocket?.send(json)
|
||||
} else {
|
||||
log("Not connected or handshake incomplete, cannot send event")
|
||||
// TODO: Queue for retry
|
||||
// Queue for retry when reconnected
|
||||
synchronized(eventQueue) {
|
||||
if (eventQueue.size >= maxQueueSize) {
|
||||
// Drop oldest event to make room
|
||||
val dropped = eventQueue.removeAt(0)
|
||||
log("Queue full, dropped oldest event: ${dropped.type}")
|
||||
}
|
||||
eventQueue.add(event)
|
||||
log("Queued event for retry (queue size: ${eventQueue.size})")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun flushEventQueue() {
|
||||
val eventsToSend: List<NodeEvent>
|
||||
synchronized(eventQueue) {
|
||||
eventsToSend = eventQueue.toList()
|
||||
eventQueue.clear()
|
||||
}
|
||||
|
||||
if (eventsToSend.isNotEmpty()) {
|
||||
log("Flushing ${eventsToSend.size} queued events")
|
||||
eventsToSend.forEach { event ->
|
||||
val requestId = generateRequestId()
|
||||
val json = event.toProtocolFrame(requestId)
|
||||
webSocket?.send(json)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -365,6 +394,9 @@ class GatewayClient(
|
|||
isHandshakeComplete = true
|
||||
onConnectionChange(true)
|
||||
auditLog.log("GATEWAY_CONNECTED", "Protocol v$protocol handshake complete")
|
||||
|
||||
// Send any queued events
|
||||
flushEventQueue()
|
||||
}
|
||||
|
||||
private fun handleDisconnect() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue