diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 94b932fa3..7e0d811b6 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -1216,21 +1216,21 @@ uint32_t dc_init_webxdc_integration (dc_context_t* context, uint32_t c /** * Start an outgoing call. - * This sends a message with all relevant information to the callee, + * This sends a message of type #DC_MSG_CALL with all relevant information to the callee, * who will get informed by an #DC_EVENT_INCOMING_CALL event and rings. * * Possible actions during ringing: * * - caller cancels the call using dc_end_call(): - * callee receives #DC_EVENT_CALL_ENDED + * callee receives #DC_EVENT_CALL_ENDED and has a "Missed Call" * * - callee accepts using dc_accept_incoming_call(): * caller receives #DC_EVENT_OUTGOING_CALL_ACCEPTED. * callee's devices receive #DC_EVENT_INCOMING_CALL_ACCEPTED, call starts * - * - callee rejects using dc_end_call(): - * caller receives #DC_EVENT_CALL_ENDED after 1 minute timeout. - * callee's other devices receive #DC_EVENT_CALL_ENDED + * - callee declines using dc_end_call(): + * caller receives #DC_EVENT_CALL_ENDED and has a "Declinced Call". + * callee's other devices receive #DC_EVENT_CALL_ENDED and have a "Cancelled Call", * * - callee is already in a call: * in this case, UI may decide to show a notification instead of ringing. @@ -1240,7 +1240,9 @@ uint32_t dc_init_webxdc_integration (dc_context_t* context, uint32_t c * after 1 minute without action, * caller and callee receive #DC_EVENT_CALL_ENDED * to prevent endless ringing of callee - * in case caller got offline without being able to send cancellation message + * in case caller got offline without being able to send cancellation message. + * for caller, this is a "Cancelled Call"; + * for callee, this is a "Missed Call" * * Actions during the call: * @@ -1276,13 +1278,15 @@ uint32_t dc_place_outgoing_call (dc_context_t* context, uint32_t ch * All affected devices will receive * either #DC_EVENT_OUTGOING_CALL_ACCEPTED or #DC_EVENT_INCOMING_CALL_ACCEPTED. * + * If the call is already accepted or ended, nothing happens. + * * @memberof dc_context_t * @param context The context object. * @param msg_id The ID of the call to accept. * This is the ID reported by #DC_EVENT_INCOMING_CALL * and equals to the ID of the corresponding info message. * @param accept_call_info any data that other devices receive - * in #DC_EVENT_OUTGOING_CALL_ACCEPTED or #DC_EVENT_INCOMING_CALL_ACCEPTED. + * in #DC_EVENT_OUTGOING_CALL_ACCEPTED. * @return 1=success, 0=error */ int dc_accept_incoming_call (dc_context_t* context, uint32_t msg_id, const char* accept_call_info); @@ -1291,17 +1295,13 @@ uint32_t dc_place_outgoing_call (dc_context_t* context, uint32_t ch /** * End incoming or outgoing call. * - * From the view of the caller, a "cancellation", - * from the view of callee, a "rejection". + * For unaccepted calls ended by the caller, this is a "cancellation". + * Unaccepted calls ended by the callee are a "decline". * If the call was accepted, this is a "hangup". * - * For accepted calls, - * all participant devices get informed about the ended call via #DC_EVENT_CALL_ENDED. - * For not accepted calls, only the caller will inform the callee. + * All participant devices get informed about the ended call via #DC_EVENT_CALL_ENDED. * - * If the callee rejects, the caller will get a timeout or give up at some point - - * same as for all other reasons the call cannot be established: Device not in reach, device muted, connectivity etc. - * This is to protect privacy of the callee, avoiding to check if callee is online. + * If the call is already ended, nothing happens. * * @memberof dc_context_t * @param context The context object. @@ -6724,29 +6724,27 @@ void dc_event_unref(dc_event_t* event); * or show a notification if there is already a call in some profile. * * Together with this event, - * an info-message is added to the corresponding chat. - * The info-message, however, is _not_ additionally notified using #DC_EVENT_INCOMING_MSG, - * if needed, this has to be done by the UI explicitly. + * a message of type #DC_MSG_CALL is added to the corresponding chat; + * this message is announced and updated by the usual even as #DC_EVENT_MSGS_CHANGED. * * If user takes action, dc_accept_incoming_call() or dc_end_call() should be called. * * Otherwise, ringing should end on #DC_EVENT_CALL_ENDED * or #DC_EVENT_INCOMING_CALL_ACCEPTED * - * @param data1 (int) msg_id ID of the info-message referring to the call. + * @param data1 (int) msg_id ID of the message referring to the call. * @param data2 (char*) place_call_info, text passed to dc_place_outgoing_call() */ #define DC_EVENT_INCOMING_CALL 2550 /** - * The callee accepted an incoming call on another device using dc_accept_incoming_call(). + * The callee accepted an incoming call on this or another device using dc_accept_incoming_call(). * The caller gets the event #DC_EVENT_OUTGOING_CALL_ACCEPTED at the same time. * * The event is sent unconditionally when the corresponding message is received. * UI should only take action in case call UI was opened before, otherwise the event should be ignored. * - * @param data1 (int) msg_id ID of the info-message referring to the call - * @param data2 (char*) accept_call_info, text passed to dc_place_outgoing_call() + * @param data1 (int) msg_id ID of the message referring to the call */ #define DC_EVENT_INCOMING_CALL_ACCEPTED 2560 @@ -6756,7 +6754,7 @@ void dc_event_unref(dc_event_t* event); * The event is sent unconditionally when the corresponding message is received. * UI should only take action in case call UI was opened before, otherwise the event should be ignored. * - * @param data1 (int) msg_id ID of the info-message referring to the call + * @param data1 (int) msg_id ID of the message referring to the call * @param data2 (char*) accept_call_info, text passed to dc_accept_incoming_call() */ #define DC_EVENT_OUTGOING_CALL_ACCEPTED 2570 @@ -6768,7 +6766,7 @@ void dc_event_unref(dc_event_t* event); * The event is sent unconditionally when the corresponding message is received. * UI should only take action in case call UI was opened before, otherwise the event should be ignored. * - * @param data1 (int) msg_id ID of the info-message referring to the call + * @param data1 (int) msg_id ID of the message referring to the call */ #define DC_EVENT_CALL_ENDED 2580 diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index ddae6de52..9dc2d0680 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -779,6 +779,7 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut | EventType::ChatlistChanged | EventType::AccountsChanged | EventType::AccountsItemChanged + | EventType::IncomingCallAccepted { .. } | EventType::WebxdcRealtimeAdvertisementReceived { .. } => ptr::null_mut(), EventType::IncomingCall { place_call_info, .. @@ -786,10 +787,7 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut let data2 = place_call_info.to_c_string().unwrap_or_default(); data2.into_raw() } - EventType::IncomingCallAccepted { - accept_call_info, .. - } - | EventType::OutgoingCallAccepted { + EventType::OutgoingCallAccepted { accept_call_info, .. } => { let data2 = accept_call_info.to_c_string().unwrap_or_default(); diff --git a/deltachat-jsonrpc/src/api/types/events.rs b/deltachat-jsonrpc/src/api/types/events.rs index 2113a9d7a..746eee72f 100644 --- a/deltachat-jsonrpc/src/api/types/events.rs +++ b/deltachat-jsonrpc/src/api/types/events.rs @@ -430,8 +430,6 @@ pub enum EventType { IncomingCallAccepted { /// ID of the info message referring to the call. msg_id: u32, - /// User-defined info passed to dc_accept_incoming_call() - accept_call_info: String, }, /// Outgoing call accepted. @@ -604,12 +602,8 @@ impl From for EventType { msg_id: msg_id.to_u32(), place_call_info, }, - CoreEventType::IncomingCallAccepted { - msg_id, - accept_call_info, - } => IncomingCallAccepted { + CoreEventType::IncomingCallAccepted { msg_id } => IncomingCallAccepted { msg_id: msg_id.to_u32(), - accept_call_info, }, CoreEventType::OutgoingCallAccepted { msg_id, diff --git a/src/calls.rs b/src/calls.rs index 18155d9d4..5b14fa408 100644 --- a/src/calls.rs +++ b/src/calls.rs @@ -1,17 +1,17 @@ //! # Handle calls. //! -//! Internally, calls are bound to the user-visible info message initializing the call. -//! This means, the "Call ID" is a "Message ID" currently - similar to webxdc. +//! Internally, calls are bound a user-visible message initializing the call. +//! This means, the "Call ID" is a "Message ID" - similar to Webxdc IDs. use crate::chat::{Chat, ChatId, send_msg}; use crate::constants::Chattype; use crate::contact::ContactId; use crate::context::Context; use crate::events::EventType; use crate::headerdef::HeaderDef; -use crate::message::{self, Message, MsgId, Viewtype, rfc724_mid_exists}; +use crate::log::info; +use crate::message::{self, Message, MsgId, Viewtype}; use crate::mimeparser::{MimeMessage, SystemMessage}; use crate::param::Param; -use crate::sync::SyncData; use crate::tools::time; use anyhow::{Result, ensure}; use std::time::Duration; @@ -29,29 +29,30 @@ use tokio::time::sleep; /// as the callee won't start the call afterwards. const RINGING_SECONDS: i64 = 60; +/// For persisting parameters in the call, we use Param::Arg* +const CALL_ACCEPTED_TIMESTAMP: Param = Param::Arg; +const CALL_ENDED_TIMESTAMP: Param = Param::Arg4; + /// Information about the status of a call. #[derive(Debug, Default)] pub struct CallInfo { - /// Incoming or outgoing call? - pub is_incoming: bool, - - /// Was an incoming call accepted on this device? - /// For privacy reasons, only for accepted incoming calls, callee sends a message to caller on `end_call()`. - /// On other devices and for outgoing calls, `is_accepted` is never set. - pub is_accepted: bool, - /// User-defined text as given to place_outgoing_call() pub place_call_info: String, /// User-defined text as given to accept_incoming_call() pub accept_call_info: String, - /// Info message referring to the call. + /// Message referring to the call. + /// Data are persisted along with the message using Param::Arg* pub msg: Message, } impl CallInfo { - fn is_stale_call(&self) -> bool { + fn is_incoming(&self) -> bool { + self.msg.from_id != ContactId::SELF + } + + fn is_stale(&self) -> bool { self.remaining_ring_seconds() <= 0 } @@ -70,6 +71,60 @@ impl CallInfo { .await?; Ok(()) } + + async fn update_text_duration(&self, context: &Context) -> Result<()> { + let minutes = self.get_duration_seconds() / 60; + let duration = match minutes { + 0 => "<1 minute".to_string(), + 1 => "1 minute".to_string(), + n => format!("{} minutes", n), + }; + + if self.is_incoming() { + self.update_text(context, &format!("Incoming call\n{duration}")) + .await?; + } else { + self.update_text(context, &format!("Outgoing call\n{duration}")) + .await?; + } + Ok(()) + } + + /// Mark calls as accepted. + /// This is needed for all devices where a stale-timer runs, to prevent accepted calls being terminated as stale. + async fn mark_as_accepted(&mut self, context: &Context) -> Result<()> { + self.msg.param.set_i64(CALL_ACCEPTED_TIMESTAMP, time()); + self.msg.update_param(context).await?; + Ok(()) + } + + fn is_accepted(&self) -> bool { + self.msg.param.exists(CALL_ACCEPTED_TIMESTAMP) + } + + async fn mark_as_ended(&mut self, context: &Context) -> Result<()> { + self.msg.param.set_i64(CALL_ENDED_TIMESTAMP, time()); + self.msg.update_param(context).await?; + Ok(()) + } + + fn is_ended(&self) -> bool { + self.msg.param.exists(CALL_ENDED_TIMESTAMP) + } + + fn get_duration_seconds(&self) -> i64 { + if let (Some(start), Some(end)) = ( + self.msg.param.get_i64(CALL_ACCEPTED_TIMESTAMP), + self.msg.param.get_i64(CALL_ENDED_TIMESTAMP), + ) { + let seconds = end - start; + if seconds <= 0 { + return 1; + } + return seconds; + } + 0 + } } impl Context { @@ -84,7 +139,7 @@ impl Context { let mut call = Message { viewtype: Viewtype::Call, - text: "Calling...".into(), + text: "Outgoing call".into(), ..Default::default() }; call.param.set(Param::WebrtcRoom, &place_call_info); @@ -107,22 +162,22 @@ impl Context { accept_call_info: String, ) -> Result<()> { let mut call: CallInfo = self.load_call_by_id(call_id).await?; - ensure!(call.is_incoming); + ensure!(call.is_incoming()); + if call.is_accepted() || call.is_ended() { + info!(self, "Call already accepted/ended"); + return Ok(()); + } + call.mark_as_accepted(self).await?; let chat = Chat::load_from_db(self, call.msg.chat_id).await?; if chat.is_contact_request() { chat.id.accept(self).await?; } - call.update_text(self, "Call accepted").await?; - call.msg - .mark_call_as_accepted(self, accept_call_info.to_string()) - .await?; - // send an acceptance message around: to the caller as well as to the other devices of the callee let mut msg = Message { viewtype: Viewtype::Text, - text: "Call accepted".into(), + text: "[Call accepted]".into(), ..Default::default() }; msg.param.set_cmd(SystemMessage::CallAccepted); @@ -133,36 +188,39 @@ impl Context { msg.id = send_msg(self, call.msg.chat_id, &mut msg).await?; self.emit_event(EventType::IncomingCallAccepted { msg_id: call.msg.id, - accept_call_info, }); self.emit_msgs_changed(call.msg.chat_id, call_id); Ok(()) } - /// Cancel, reject or hangup an incoming or outgoing call. + /// Cancel, decline or hangup an incoming or outgoing call. pub async fn end_call(&self, call_id: MsgId) -> Result<()> { - let call: CallInfo = self.load_call_by_id(call_id).await?; - - call.update_text(self, "Call ended").await?; - - if call.is_accepted || !call.is_incoming { - let mut msg = Message { - viewtype: Viewtype::Text, - text: "Call ended".into(), - ..Default::default() - }; - msg.param.set_cmd(SystemMessage::CallEnded); - msg.hidden = true; - msg.set_quote(self, Some(&call.msg)).await?; - msg.id = send_msg(self, call.msg.chat_id, &mut msg).await?; - } else if call.is_incoming { - // to protect privacy, we do not send a message to others from callee for unaccepted calls - self.add_sync_item(SyncData::RejectIncomingCall { - msg: call.msg.rfc724_mid, - }) - .await?; - self.scheduler.interrupt_inbox().await; + let mut call: CallInfo = self.load_call_by_id(call_id).await?; + if call.is_ended() { + info!(self, "Call already ended"); + return Ok(()); } + call.mark_as_ended(self).await?; + + if !call.is_accepted() { + if call.is_incoming() { + call.update_text(self, "Declined call").await?; + } else { + call.update_text(self, "Cancelled call").await?; + } + } else { + call.update_text_duration(self).await?; + } + + let mut msg = Message { + viewtype: Viewtype::Text, + text: "[Call ended]".into(), + ..Default::default() + }; + msg.param.set_cmd(SystemMessage::CallEnded); + msg.hidden = true; + msg.set_quote(self, Some(&call.msg)).await?; + msg.id = send_msg(self, call.msg.chat_id, &mut msg).await?; self.emit_event(EventType::CallEnded { msg_id: call.msg.id, @@ -177,8 +235,15 @@ impl Context { call_id: MsgId, ) -> Result<()> { sleep(Duration::from_secs(wait)).await; - let call = context.load_call_by_id(call_id).await?; - if !call.is_accepted { + let mut call = context.load_call_by_id(call_id).await?; + if !call.is_accepted() && !call.is_ended() { + call.mark_as_ended(&context).await?; + if call.is_incoming() { + call.update_text(&context, "Missed call").await?; + } else { + call.update_text(&context, "Cancelled call").await?; + } + context.emit_msgs_changed(call.msg.chat_id, call_id); context.emit_event(EventType::CallEnded { msg_id: call.msg.id, }); @@ -188,16 +253,18 @@ impl Context { pub(crate) async fn handle_call_msg( &self, - mime_message: &MimeMessage, call_id: MsgId, + mime_message: &MimeMessage, + from_id: ContactId, ) -> Result<()> { if mime_message.is_call() { let call = self.load_call_by_id(call_id).await?; - if call.is_incoming { - if call.is_stale_call() { + if call.is_incoming() { + if call.is_stale() { call.update_text(self, "Missed call").await?; self.emit_incoming_msg(call.msg.chat_id, call_id); // notify missed call } else { + call.update_text(self, "Incoming call").await?; self.emit_msgs_changed(call.msg.chat_id, call_id); // ringing calls are not additionally notified self.emit_event(EventType::IncomingCall { msg_id: call.msg.id, @@ -211,27 +278,28 @@ impl Context { )); } } else { + call.update_text(self, "Outgoing call").await?; self.emit_msgs_changed(call.msg.chat_id, call_id); } } else { match mime_message.is_system_message { SystemMessage::CallAccepted => { - let call = self.load_call_by_id(call_id).await?; - call.update_text(self, "Call accepted").await?; + let mut call = self.load_call_by_id(call_id).await?; + if call.is_ended() || call.is_accepted() { + info!(self, "CallAccepted received for accepted/ended call"); + return Ok(()); + } + + call.mark_as_accepted(self).await?; self.emit_msgs_changed(call.msg.chat_id, call_id); - if call.is_incoming { + if call.is_incoming() { self.emit_event(EventType::IncomingCallAccepted { msg_id: call.msg.id, - accept_call_info: call.accept_call_info, }); } else { let accept_call_info = mime_message .get_header(HeaderDef::ChatWebrtcAccepted) .unwrap_or_default(); - call.msg - .clone() - .mark_call_as_accepted(self, accept_call_info.to_string()) - .await?; self.emit_event(EventType::OutgoingCallAccepted { msg_id: call.msg.id, accept_call_info: accept_call_info.to_string(), @@ -239,8 +307,33 @@ impl Context { } } SystemMessage::CallEnded => { - let call = self.load_call_by_id(call_id).await?; - call.update_text(self, "Call ended").await?; + let mut call = self.load_call_by_id(call_id).await?; + if call.is_ended() { + // may happen eg. if a a message is missed + info!(self, "CallEnded received for ended call"); + return Ok(()); + } + + call.mark_as_ended(self).await?; + if !call.is_accepted() { + if call.is_incoming() { + if from_id == ContactId::SELF { + call.update_text(self, "Declined call").await?; + } else { + call.update_text(self, "Missed call").await?; + } + } else { + // outgoing + if from_id == ContactId::SELF { + call.update_text(self, "Cancelled call").await?; + } else { + call.update_text(self, "Declined call").await?; + } + } + } else { + call.update_text_duration(self).await?; + } + self.emit_msgs_changed(call.msg.chat_id, call_id); self.emit_event(EventType::CallEnded { msg_id: call.msg.id, @@ -252,16 +345,6 @@ impl Context { Ok(()) } - pub(crate) async fn sync_call_rejection(&self, rfc724_mid: &str) -> Result<()> { - if let Some((msg_id, _)) = rfc724_mid_exists(self, rfc724_mid).await? { - let call = self.load_call_by_id(msg_id).await?; - call.update_text(self, "Call ended").await?; - self.emit_event(EventType::CallEnded { msg_id }); - self.emit_msgs_changed(call.msg.chat_id, msg_id); - } - Ok(()) - } - async fn load_call_by_id(&self, call_id: MsgId) -> Result { let call = Message::load_from_db(self, call_id).await?; self.load_call_by_message(call) @@ -271,8 +354,6 @@ impl Context { ensure!(call.viewtype == Viewtype::Call); Ok(CallInfo { - is_incoming: call.get_from_id() != ContactId::SELF, - is_accepted: call.is_call_accepted()?, place_call_info: call .param .get(Param::WebrtcRoom) @@ -288,24 +369,5 @@ impl Context { } } -impl Message { - async fn mark_call_as_accepted( - &mut self, - context: &Context, - accept_call_info: String, - ) -> Result<()> { - ensure!(self.viewtype == Viewtype::Call); - self.param.set_int(Param::Arg, 1); - self.param.set(Param::WebrtcAccepted, accept_call_info); - self.update_param(context).await?; - Ok(()) - } - - fn is_call_accepted(&self) -> Result { - ensure!(self.viewtype == Viewtype::Call); - Ok(self.param.get_int(Param::Arg) == Some(1)) - } -} - #[cfg(test)] mod calls_tests; diff --git a/src/calls/calls_tests.rs b/src/calls/calls_tests.rs index 4c15666b9..d51f2ce28 100644 --- a/src/calls/calls_tests.rs +++ b/src/calls/calls_tests.rs @@ -1,6 +1,6 @@ use super::*; use crate::config::Config; -use crate::test_utils::{TestContext, TestContextManager, sync}; +use crate::test_utils::{TestContext, TestContextManager}; struct CallSetup { pub alice: TestContext, @@ -13,6 +13,11 @@ struct CallSetup { pub bob2_call: Message, } +async fn assert_text(t: &TestContext, call_id: MsgId, text: &str) -> Result<()> { + assert_eq!(Message::load_from_db(t, call_id).await?.text, text); + Ok(()) +} + async fn setup_call() -> Result { let mut tcm = TestContextManager::new(); let alice = tcm.alice().await; @@ -27,7 +32,7 @@ async fn setup_call() -> Result { // Alice's other device sees the same message as an outgoing call. let alice_chat = alice.create_chat(&bob).await; let test_msg_id = alice - .place_outgoing_call(alice_chat.id, "place_info".to_string()) + .place_outgoing_call(alice_chat.id, "place-info-123".to_string()) .await?; let sent1 = alice.pop_sent_msg().await; assert_eq!(sent1.sender_msg_id, test_msg_id); @@ -37,9 +42,10 @@ async fn setup_call() -> Result { assert!(!m.is_info()); assert_eq!(m.viewtype, Viewtype::Call); let info = t.load_call_by_id(m.id).await?; - assert!(!info.is_incoming); - assert!(!info.is_accepted); - assert_eq!(info.place_call_info, "place_info"); + assert!(!info.is_incoming()); + assert!(!info.is_accepted()); + assert_eq!(info.place_call_info, "place-info-123"); + assert_text(t, m.id, "Outgoing call").await?; } // Bob receives the message referring to the call on two devices; @@ -53,9 +59,10 @@ async fn setup_call() -> Result { .get_matching(|evt| matches!(evt, EventType::IncomingCall { .. })) .await; let info = t.load_call_by_id(m.id).await?; - assert!(info.is_incoming); - assert!(!info.is_accepted); - assert_eq!(info.place_call_info, "place_info"); + assert!(info.is_incoming()); + assert!(!info.is_accepted()); + assert_eq!(info.place_call_info, "place-info-123"); + assert_text(t, m.id, "Incoming call").await?; } Ok(CallSetup { @@ -83,48 +90,45 @@ async fn accept_call() -> Result { } = setup_call().await?; // Bob accepts the incoming call - bob.accept_incoming_call(bob_call.id, "accepted_info".to_string()) + bob.accept_incoming_call(bob_call.id, "accept-info-456".to_string()) .await?; + assert_text(&bob, bob_call.id, "Incoming call").await?; bob.evtracker .get_matching(|evt| matches!(evt, EventType::IncomingCallAccepted { .. })) .await; let sent2 = bob.pop_sent_msg().await; let info = bob.load_call_by_id(bob_call.id).await?; - assert!(info.is_accepted); - assert_eq!(info.place_call_info, "place_info"); - assert_eq!(info.accept_call_info, "accepted_info"); + assert!(info.is_accepted()); + assert_eq!(info.place_call_info, "place-info-123"); bob2.recv_msg_trash(&sent2).await; - assert_eq!( - Message::load_from_db(&bob, bob_call.id).await?.text, - "Call accepted" - ); + assert_text(&bob, bob_call.id, "Incoming call").await?; bob2.evtracker .get_matching(|evt| matches!(evt, EventType::IncomingCallAccepted { .. })) .await; let info = bob2.load_call_by_id(bob2_call.id).await?; - assert!(!info.is_accepted); // "accepted" is only true on the device that does the call + assert!(info.is_accepted()); // Alice receives the acceptance message alice.recv_msg_trash(&sent2).await; - assert_eq!( - Message::load_from_db(&alice, alice_call.id).await?.text, - "Call accepted" - ); - alice + assert_text(&alice, alice_call.id, "Outgoing call").await?; + let ev = alice .evtracker .get_matching(|evt| matches!(evt, EventType::OutgoingCallAccepted { .. })) .await; + assert_eq!( + ev, + EventType::OutgoingCallAccepted { + msg_id: alice2_call.id, + accept_call_info: "accept-info-456".to_string() + } + ); let info = alice.load_call_by_id(alice_call.id).await?; - assert!(info.is_accepted); - assert_eq!(info.place_call_info, "place_info"); - assert_eq!(info.accept_call_info, "accepted_info"); + assert!(info.is_accepted()); + assert_eq!(info.place_call_info, "place-info-123"); alice2.recv_msg_trash(&sent2).await; - assert_eq!( - Message::load_from_db(&alice2, alice2_call.id).await?.text, - "Call accepted" - ); + assert_text(&alice2, alice2_call.id, "Outgoing call").await?; alice2 .evtracker .get_matching(|evt| matches!(evt, EventType::OutgoingCallAccepted { .. })) @@ -142,11 +146,6 @@ async fn accept_call() -> Result { }) } -async fn assert_is_call_ended(t: &TestContext, call_id: MsgId) -> Result<()> { - assert_eq!(Message::load_from_db(t, call_id).await?.text, "Call ended"); - Ok(()) -} - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_accept_call_callee_ends() -> Result<()> { // Alice calls Bob, Bob accepts @@ -164,28 +163,28 @@ async fn test_accept_call_callee_ends() -> Result<()> { // Bob has accepted the call and also ends it bob.end_call(bob_call.id).await?; - assert_is_call_ended(&bob, bob_call.id).await?; + assert_text(&bob, bob_call.id, "Incoming call\n<1 minute").await?; bob.evtracker .get_matching(|evt| matches!(evt, EventType::CallEnded { .. })) .await; let sent3 = bob.pop_sent_msg().await; bob2.recv_msg_trash(&sent3).await; - assert_is_call_ended(&bob2, bob2_call.id).await?; + assert_text(&bob2, bob2_call.id, "Incoming call\n<1 minute").await?; bob2.evtracker .get_matching(|evt| matches!(evt, EventType::CallEnded { .. })) .await; // Alice receives the ending message alice.recv_msg_trash(&sent3).await; - assert_is_call_ended(&alice, alice_call.id).await?; + assert_text(&alice, alice_call.id, "Outgoing call\n<1 minute").await?; alice .evtracker .get_matching(|evt| matches!(evt, EventType::CallEnded { .. })) .await; alice2.recv_msg_trash(&sent3).await; - assert_is_call_ended(&alice2, alice2_call.id).await?; + assert_text(&alice2, alice2_call.id, "Outgoing call\n<1 minute").await?; alice2 .evtracker .get_matching(|evt| matches!(evt, EventType::CallEnded { .. })) @@ -199,6 +198,7 @@ async fn test_accept_call_caller_ends() -> Result<()> { // Alice calls Bob, Bob accepts let CallSetup { alice, + alice_call, alice2, alice2_call, bob, @@ -209,7 +209,8 @@ async fn test_accept_call_caller_ends() -> Result<()> { } = accept_call().await?; // Bob has accepted the call but Alice ends it - alice.end_call(bob_call.id).await?; + alice.end_call(alice_call.id).await?; + assert_text(&alice, alice_call.id, "Outgoing call\n<1 minute").await?; alice .evtracker .get_matching(|evt| matches!(evt, EventType::CallEnded { .. })) @@ -217,7 +218,7 @@ async fn test_accept_call_caller_ends() -> Result<()> { let sent3 = alice.pop_sent_msg().await; alice2.recv_msg_trash(&sent3).await; - assert_is_call_ended(&alice2, alice2_call.id).await?; + assert_text(&alice2, alice2_call.id, "Outgoing call\n<1 minute").await?; alice2 .evtracker .get_matching(|evt| matches!(evt, EventType::CallEnded { .. })) @@ -225,13 +226,13 @@ async fn test_accept_call_caller_ends() -> Result<()> { // Bob receives the ending message bob.recv_msg_trash(&sent3).await; - assert_is_call_ended(&bob, bob_call.id).await?; + assert_text(&bob, bob_call.id, "Incoming call\n<1 minute").await?; bob.evtracker .get_matching(|evt| matches!(evt, EventType::CallEnded { .. })) .await; bob2.recv_msg_trash(&sent3).await; - assert_is_call_ended(&bob2, bob2_call.id).await?; + assert_text(&bob2, bob2_call.id, "Incoming call\n<1 minute").await?; bob2.evtracker .get_matching(|evt| matches!(evt, EventType::CallEnded { .. })) .await; @@ -243,6 +244,10 @@ async fn test_accept_call_caller_ends() -> Result<()> { async fn test_callee_rejects_call() -> Result<()> { // Alice calls Bob let CallSetup { + alice, + alice2, + alice_call, + alice2_call, bob, bob2, bob_call, @@ -250,21 +255,36 @@ async fn test_callee_rejects_call() -> Result<()> { .. } = setup_call().await?; - // Bob does not want to talk with Alice. - // To protect Bob's privacy, no message is sent to Alice (who will time out). - // To let Bob close the call window on all devices, a sync message is used instead. + // Bob has accepted Alice before, but does not want to talk with Alice + bob_call.chat_id.accept(&bob).await?; bob.end_call(bob_call.id).await?; - assert_is_call_ended(&bob, bob_call.id).await?; + assert_text(&bob, bob_call.id, "Declined call").await?; bob.evtracker .get_matching(|evt| matches!(evt, EventType::CallEnded { .. })) .await; + let sent3 = bob.pop_sent_msg().await; - sync(&bob, &bob2).await; - assert_is_call_ended(&bob2, bob2_call.id).await?; + bob2.recv_msg_trash(&sent3).await; + assert_text(&bob2, bob2_call.id, "Declined call").await?; bob2.evtracker .get_matching(|evt| matches!(evt, EventType::CallEnded { .. })) .await; + // Alice receives decline message + alice.recv_msg_trash(&sent3).await; + assert_text(&alice, alice_call.id, "Declined call").await?; + alice + .evtracker + .get_matching(|evt| matches!(evt, EventType::CallEnded { .. })) + .await; + + alice2.recv_msg_trash(&sent3).await; + assert_text(&alice2, alice2_call.id, "Declined call").await?; + alice2 + .evtracker + .get_matching(|evt| matches!(evt, EventType::CallEnded { .. })) + .await; + Ok(()) } @@ -285,7 +305,7 @@ async fn test_caller_cancels_call() -> Result<()> { // Alice changes their mind before Bob picks up alice.end_call(alice_call.id).await?; - assert_is_call_ended(&alice, alice_call.id).await?; + assert_text(&alice, alice_call.id, "Cancelled call").await?; alice .evtracker .get_matching(|evt| matches!(evt, EventType::CallEnded { .. })) @@ -293,7 +313,7 @@ async fn test_caller_cancels_call() -> Result<()> { let sent3 = alice.pop_sent_msg().await; alice2.recv_msg_trash(&sent3).await; - assert_is_call_ended(&alice2, alice2_call.id).await?; + assert_text(&alice2, alice2_call.id, "Cancelled call").await?; alice2 .evtracker .get_matching(|evt| matches!(evt, EventType::CallEnded { .. })) @@ -301,13 +321,13 @@ async fn test_caller_cancels_call() -> Result<()> { // Bob receives the ending message bob.recv_msg_trash(&sent3).await; - assert_is_call_ended(&bob, bob_call.id).await?; + assert_text(&bob, bob_call.id, "Missed call").await?; bob.evtracker .get_matching(|evt| matches!(evt, EventType::CallEnded { .. })) .await; bob2.recv_msg_trash(&sent3).await; - assert_is_call_ended(&bob2, bob2_call.id).await?; + assert_text(&bob2, bob2_call.id, "Missed call").await?; bob2.evtracker .get_matching(|evt| matches!(evt, EventType::CallEnded { .. })) .await; @@ -325,7 +345,7 @@ async fn test_is_stale_call() -> Result<()> { }, ..Default::default() }; - assert!(!call_info.is_stale_call()); + assert!(!call_info.is_stale()); let remaining_seconds = call_info.remaining_ring_seconds(); assert!(remaining_seconds == RINGING_SECONDS || remaining_seconds == RINGING_SECONDS - 1); @@ -337,7 +357,7 @@ async fn test_is_stale_call() -> Result<()> { }, ..Default::default() }; - assert!(!call_info.is_stale_call()); + assert!(!call_info.is_stale()); let remaining_seconds = call_info.remaining_ring_seconds(); assert!(remaining_seconds == RINGING_SECONDS - 5 || remaining_seconds == RINGING_SECONDS - 6); @@ -349,28 +369,32 @@ async fn test_is_stale_call() -> Result<()> { }, ..Default::default() }; - assert!(call_info.is_stale_call()); + assert!(call_info.is_stale()); assert_eq!(call_info.remaining_ring_seconds(), 0); Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_mark_call_as_accepted() -> Result<()> { +async fn test_mark_calls() -> Result<()> { let CallSetup { alice, alice_call, .. } = setup_call().await?; - assert!(!alice_call.is_call_accepted()?); - let mut alice_call = Message::load_from_db(&alice, alice_call.id).await?; - assert!(!alice_call.is_call_accepted()?); - alice_call - .mark_call_as_accepted(&alice, "accepted_info".to_string()) - .await?; - assert!(alice_call.is_call_accepted()?); + let mut call_info: CallInfo = alice.load_call_by_id(alice_call.id).await?; + assert!(!call_info.is_accepted()); + assert!(!call_info.is_ended()); + call_info.mark_as_accepted(&alice).await?; + assert!(call_info.is_accepted()); + assert!(!call_info.is_ended()); - let alice_call = Message::load_from_db(&alice, alice_call.id).await?; - assert!(alice_call.is_call_accepted()?); + let mut call_info: CallInfo = alice.load_call_by_id(alice_call.id).await?; + assert!(call_info.is_accepted()); + assert!(!call_info.is_ended()); + + call_info.mark_as_ended(&alice).await?; + assert!(call_info.is_accepted()); + assert!(call_info.is_ended()); Ok(()) } diff --git a/src/events/payload.rs b/src/events/payload.rs index fb0d972a3..6bddadf79 100644 --- a/src/events/payload.rs +++ b/src/events/payload.rs @@ -388,8 +388,6 @@ pub enum EventType { IncomingCallAccepted { /// ID of the message referring to the call. msg_id: MsgId, - /// User-defined info as passed to accept_incoming_call() - accept_call_info: String, }, /// Outgoing call accepted. diff --git a/src/mimefactory.rs b/src/mimefactory.rs index d5c4420ee..5ccbbe9c7 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -1575,6 +1575,9 @@ impl MimeFactory { "Chat-Content", mail_builder::headers::raw::Raw::new("call").into(), )); + placeholdertext = Some( + "[This is a 'Call'. The sender uses an experiment not supported on your version yet]".to_string(), + ); } if msg.param.exists(Param::WebrtcRoom) { diff --git a/src/receive_imf.rs b/src/receive_imf.rs index d247d1123..fac627db5 100644 --- a/src/receive_imf.rs +++ b/src/receive_imf.rs @@ -1001,7 +1001,9 @@ pub(crate) async fn receive_imf_inner( } if mime_parser.is_call() { - context.handle_call_msg(&mime_parser, insert_msg_id).await?; + context + .handle_call_msg(insert_msg_id, &mime_parser, from_id) + .await?; } else if received_msg.hidden { // No need to emit an event about the changed message } else if let Some(replace_chat_id) = replace_chat_id { @@ -1989,7 +1991,9 @@ async fn add_parts( if let Some(call) = message::get_by_rfc724_mids(context, &parse_message_ids(field)).await? { - context.handle_call_msg(mime_parser, call.get_id()).await?; + context + .handle_call_msg(call.get_id(), mime_parser, from_id) + .await?; } else { warn!(context, "Call: Cannot load parent.") } diff --git a/src/sync.rs b/src/sync.rs index b5a42130b..90e302f06 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -71,9 +71,6 @@ pub(crate) enum SyncData { DeleteMessages { msgs: Vec, // RFC724 id (i.e. "Message-Id" header) }, - RejectIncomingCall { - msg: String, // RFC724 id (i.e. "Message-Id" header) - }, } #[derive(Debug, Serialize, Deserialize)] @@ -267,7 +264,6 @@ impl Context { SyncData::Config { key, val } => self.sync_config(key, val).await, SyncData::SaveMessage { src, dest } => self.save_message(src, dest).await, SyncData::DeleteMessages { msgs } => self.sync_message_deletion(msgs).await, - SyncData::RejectIncomingCall { msg } => self.sync_call_rejection(msg).await, }, SyncDataOrUnknown::Unknown(data) => { warn!(self, "Ignored unknown sync item: {data}.");