diff --git a/app/build.gradle b/app/build.gradle index ac9e90038a..43f79a8b53 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -155,7 +155,7 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.4' } -def canonicalVersionCode = 235 +def canonicalVersionCode = 236 def canonicalVersionName = "1.12.0-ALPHA3" def postFixSize = 10 diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 47cf6470b8..5a2a6a1468 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -161,7 +161,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO public void onCreate() { DatabaseModule.init(this); super.onCreate(); - callMessageProcessor = new CallMessageProcessor(this, ProcessLifecycleOwner.get().getLifecycle()); + callMessageProcessor = new CallMessageProcessor(this, ProcessLifecycleOwner.get().getLifecycle(), storage); Log.i(TAG, "onCreate()"); startKovenant(); initializeSecurityProvider(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java index 52642b5d1f..1ae872623d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -204,7 +204,8 @@ public interface MmsSmsColumns { } public static boolean isCallLog(long type) { - return type == INCOMING_CALL_TYPE || type == OUTGOING_CALL_TYPE || type == MISSED_CALL_TYPE; + long baseType = type & BASE_TYPE_MASK; + return baseType == INCOMING_CALL_TYPE || baseType == OUTGOING_CALL_TYPE || baseType == MISSED_CALL_TYPE; } public static boolean isExpirationTimerUpdate(long type) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 558957515e..12705be0c2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -28,6 +28,7 @@ import com.annimon.stream.Stream; import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteStatement; +import org.session.libsession.messaging.calls.CallMessageType; import org.session.libsession.messaging.messages.signal.IncomingGroupMessage; import org.session.libsession.messaging.messages.signal.IncomingTextMessage; import org.session.libsession.messaging.messages.signal.OutgoingTextMessage; @@ -373,6 +374,21 @@ public class SmsDatabase extends MessagingDatabase { if (message.isOpenGroupInvitation()) type |= Types.OPEN_GROUP_INVITATION_BIT; + CallMessageType callMessageType = message.getCallType(); + if (callMessageType != null) { + switch (callMessageType) { + case CALL_OUTGOING: + type |= Types.OUTGOING_CALL_TYPE; + break; + case CALL_INCOMING: + type |= Types.INCOMING_CALL_TYPE; + break; + case CALL_MISSED: + type |= Types.MISSED_CALL_TYPE; + break; + } + } + Recipient recipient = Recipient.from(context, message.getSender(), true); Recipient groupRecipient; @@ -384,7 +400,7 @@ public class SmsDatabase extends MessagingDatabase { } boolean unread = (Util.isDefaultSmsProvider(context) || - message.isSecureMessage() || message.isGroup()); + message.isSecureMessage() || message.isGroup() || message.isCallInfo()); long threadId; @@ -441,6 +457,10 @@ public class SmsDatabase extends MessagingDatabase { return insertMessageInbox(message, Types.BASE_INBOX_TYPE, 0); } + public Optional insertCallMessage(IncomingTextMessage message) { + return insertMessageInbox(message, 0, 0); + } + public Optional insertMessageInbox(IncomingTextMessage message, long serverTimestamp) { return insertMessageInbox(message, Types.BASE_INBOX_TYPE, serverTimestamp); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 196bffcdb6..bdcdc26816 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.database import android.content.Context import android.net.Uri import org.session.libsession.database.StorageProtocol +import org.session.libsession.messaging.calls.CallMessageType import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.jobs.* import org.session.libsession.messaging.messages.control.ConfigurationMessage @@ -617,4 +618,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, database.insertSecureDecryptedMessageInbox(mediaMessage, -1) } + + override fun insertCallMessage(senderPublicKey: String, callMessageType: CallMessageType, sentTimestamp: Long) { + val database = DatabaseComponent.get(context).smsDatabase() + val address = fromSerialized(senderPublicKey) + val callMessage = IncomingTextMessage.fromCallInfo(callMessageType, address, Optional.absent(), sentTimestamp) + database.insertCallMessage(callMessage) + } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java index 10e4cb753e..db3408afe3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java @@ -120,6 +120,6 @@ public abstract class DisplayRecord { public boolean isDeleted() { return MmsSmsColumns.Types.isDeletedMessage(type); } public boolean isControlMessage() { - return isGroupUpdateMessage() || isExpirationTimerUpdate() || isDataExtractionNotification(); + return isGroupUpdateMessage() || isExpirationTimerUpdate() || isDataExtractionNotification() || isCallLog(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index 31db5b4514..7c7bcb6fd8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -24,6 +24,7 @@ import android.text.style.StyleSpan; import androidx.annotation.NonNull; +import org.session.libsession.messaging.calls.CallMessageType; import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage; import org.session.libsession.messaging.utilities.UpdateMessageBuilder; import org.session.libsession.messaging.utilities.UpdateMessageData; @@ -112,6 +113,9 @@ public abstract class MessageRecord extends DisplayRecord { } else if (isDataExtractionNotification()) { if (isScreenshotNotification()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.SCREENSHOT, getIndividualRecipient().getAddress().serialize()))); else if (isMediaSavedNotification()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED, getIndividualRecipient().getAddress().serialize()))); + } else if (isCallLog()) { + CallMessageType callType = isIncomingCall() ? CallMessageType.CALL_INCOMING : isOutgoingCall() ? CallMessageType.CALL_OUTGOING : CallMessageType.CALL_MISSED; + return new SpannableString(UpdateMessageBuilder.INSTANCE.buildCallMessage(context, callType, getIndividualRecipient().getAddress().serialize())); } return new SpannableString(getBody()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt index 8b766530f0..edeca39233 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt @@ -12,6 +12,7 @@ import android.telephony.PhoneStateListener import android.telephony.TelephonyManager import androidx.localbroadcastmanager.content.LocalBroadcastManager import dagger.hilt.android.AndroidEntryPoint +import org.session.libsession.messaging.calls.CallMessageType import org.session.libsession.utilities.Address import org.session.libsession.utilities.FutureTaskListener import org.session.libsession.utilities.Util @@ -113,11 +114,12 @@ class WebRtcCallService: Service(), PeerConnection.Observer { .putExtra(EXTRA_CALL_ID, callId) .putExtra(EXTRA_REMOTE_DESCRIPTION, sdp) - fun preOffer(context: Context, address: Address, callId: UUID) = - Intent(context, WebRtcCallService::class.java) - .setAction(ACTION_PRE_OFFER) - .putExtra(EXTRA_RECIPIENT_ADDRESS, address) - .putExtra(EXTRA_CALL_ID, callId) + fun preOffer(context: Context, address: Address, callId: UUID, sentTimestamp: Long) = + Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_PRE_OFFER) + .putExtra(EXTRA_RECIPIENT_ADDRESS, address) + .putExtra(EXTRA_CALL_ID, callId) + .putExtra(EXTRA_TIMESTAMP, sentTimestamp) fun iceCandidates(context: Context, address: Address, iceCandidates: List, callId: UUID) = Intent(context, WebRtcCallService::class.java) @@ -205,7 +207,7 @@ class WebRtcCallService: Service(), PeerConnection.Observer { action == ACTION_OUTGOING_CALL && isIdle() -> handleOutgoingCall(intent) action == ACTION_ANSWER_CALL -> handleAnswerCall(intent) action == ACTION_DENY_CALL -> handleDenyCall(intent) - action == ACTION_LOCAL_HANGUP -> handleLocalHangup(intent, true) + action == ACTION_LOCAL_HANGUP -> handleLocalHangup(intent) action == ACTION_REMOTE_HANGUP -> handleRemoteHangup(intent) action == ACTION_SET_MUTE_AUDIO -> handleSetMuteAudio(intent) action == ACTION_SET_MUTE_VIDEO -> handleSetMuteVideo(intent) @@ -311,8 +313,10 @@ class WebRtcCallService: Service(), PeerConnection.Observer { } val callId = getCallId(intent) val recipient = getRemoteRecipient(intent) + val sentTimestamp = intent.getLongExtra(EXTRA_TIMESTAMP, -1) + // TODO: check stale call info and don't proceed setCallInProgressNotification(TYPE_INCOMING_PRE_OFFER, recipient) - callManager.onPreOffer(callId, recipient) + callManager.onPreOffer(callId, recipient, sentTimestamp) callManager.postViewModelState(CallViewModel.State.CALL_PRE_INIT) callManager.initializeAudioForCall() callManager.startIncomingRinger() @@ -346,7 +350,8 @@ class WebRtcCallService: Service(), PeerConnection.Observer { if (callManager.currentConnectionState != STATE_IDLE) throw IllegalStateException("Dialing from non-idle") callManager.postConnectionEvent(STATE_DIALING) - callManager.recipient = getRemoteRecipient(intent) + val recipient = getRemoteRecipient(intent) + callManager.recipient = recipient val callId = UUID.randomUUID() callManager.callId = callId @@ -357,7 +362,7 @@ class WebRtcCallService: Service(), PeerConnection.Observer { callManager.initializeAudioForCall() callManager.startOutgoingRinger(OutgoingRinger.Type.RINGING) setCallInProgressNotification(TYPE_OUTGOING_RINGING, callManager.recipient) - // TODO: DatabaseComponent.get(this).insertOutgoingCall(callManager.recipient!!.address) + callManager.insertCallMessage(recipient.address.serialize(), CallMessageType.CALL_OUTGOING) timeoutExecutor.schedule(TimeoutRunnable(callId, this), 2, TimeUnit.MINUTES) val expectedState = callManager.currentConnectionState @@ -438,13 +443,13 @@ class WebRtcCallService: Service(), PeerConnection.Observer { } callManager.handleDenyCall() - // DatabaseComponent.get(this).smsDatabase().insertMissedCall(recipient) terminate() } - private fun handleLocalHangup(intent: Intent, sendHangup: Boolean) { - // TODO: check current call ID and recipient == expected - callManager.handleLocalHangup(sendHangup) + private fun handleLocalHangup(intent: Intent) { + val intentRecipient = getRemoteRecipient(intent) + val callId = getCallId(intent) + callManager.handleLocalHangup(intentRecipient, callId) terminate() } @@ -496,7 +501,7 @@ class WebRtcCallService: Service(), PeerConnection.Observer { try { val recipient = getRemoteRecipient(intent) if (callManager.isCurrentUser(recipient) && callManager.currentConnectionState == STATE_LOCAL_RINGING) { - handleLocalHangup(intent, false) + handleLocalHangup(intent) return } val callId = getCallId(intent) @@ -558,7 +563,7 @@ class WebRtcCallService: Service(), PeerConnection.Observer { if (callId == getCallId(intent) && callState !in arrayOf(STATE_CONNECTED)) { Log.w(TAG, "Timing out call: $callId") - handleLocalHangup(intent, true) + handleLocalHangup(intent) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallDataListener.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallDataListener.kt deleted file mode 100644 index 989cb104c4..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallDataListener.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.thoughtcrime.securesms.webrtc - -import org.session.libsignal.protos.SignalServiceProtos - -interface CallDataListener { - fun newCallMessage(callMessage: SignalServiceProtos.CallMessage) -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt index 2ea2fbb244..21e88299ad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt @@ -12,6 +12,7 @@ import nl.komponents.kovenant.Promise import nl.komponents.kovenant.combine.and import nl.komponents.kovenant.functional.bind import org.session.libsession.database.StorageProtocol +import org.session.libsession.messaging.calls.CallMessageType import org.session.libsession.messaging.messages.control.CallMessage import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.utilities.Address @@ -36,8 +37,7 @@ import java.util.* import java.util.concurrent.Executors class CallManager(context: Context, audioManager: AudioManagerCompat, private val storage: StorageProtocol): PeerConnection.Observer, - SignalAudioManager.EventListener, - CallDataListener, CameraEventListener, DataChannel.Observer { + SignalAudioManager.EventListener, CameraEventListener, DataChannel.Observer { enum class CallState { STATE_IDLE, STATE_PRE_OFFER, STATE_DIALING, STATE_ANSWERING, STATE_REMOTE_RINGING, STATE_LOCAL_RINGING, STATE_CONNECTED @@ -163,10 +163,6 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va _callStateEvents.value = newState } - override fun newCallMessage(callMessage: SignalServiceProtos.CallMessage) { - - } - fun isBusy(context: Context, callId: UUID) = callId != this.callId && (currentConnectionState != CallState.STATE_IDLE || context.getSystemService(TelephonyManager::class.java).callState != TelephonyManager.CALL_STATE_IDLE) @@ -355,7 +351,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va localCameraState = newCameraState } - fun onPreOffer(callId: UUID, recipient: Recipient) { + fun onPreOffer(callId: UUID, recipient: Recipient, sentTimestamp: Long) { if (preOfferCallData != null) { Log.d(TAG, "Received new pre-offer when we are already expecting one") } @@ -363,6 +359,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va this.callId = callId preOfferCallData = PreOffer(callId, recipient) postConnectionEvent(CallState.STATE_PRE_OFFER) + insertCallMessage(recipient.address.serialize(), CallMessageType.CALL_INCOMING, sentTimestamp) } fun onNewOffer(offer: String, callId: UUID, recipient: Recipient): Promise { @@ -482,9 +479,16 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va MessageSender.sendNonDurably(CallMessage.endCall(callId), recipient.address) } - fun handleLocalHangup(sendHangup: Boolean) { + fun handleLocalHangup(intentRecipient: Recipient, intentCallId: UUID) { val recipient = recipient ?: return val callId = callId ?: return + if (intentCallId != callId) { + Log.w(TAG,"Processing local hangup for non-active ring call ID") + return + } + + val currentUserPublicKey = storage.getUserPublicKey() + val sendHangup = intentRecipient == recipient && recipient.address.serialize() != currentUserPublicKey postViewModelState(CallViewModel.State.CALL_DISCONNECTED) if (sendHangup) { @@ -492,6 +496,10 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va } } + fun insertCallMessage(threadPublicKey: String, callMessageType: CallMessageType, sentTimestamp: Long = System.currentTimeMillis()) { + storage.insertCallMessage(threadPublicKey, callMessageType, sentTimestamp) + } + fun handleRemoteHangup() { when (currentConnectionState) { CallState.STATE_DIALING, diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt index fd3b6839af..58a57f6b84 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt @@ -7,6 +7,8 @@ import androidx.lifecycle.coroutineScope import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import org.session.libsession.database.StorageProtocol +import org.session.libsession.messaging.calls.CallMessageType import org.session.libsession.messaging.messages.control.CallMessage import org.session.libsession.messaging.utilities.WebRtcUtils import org.session.libsession.utilities.Address @@ -17,7 +19,7 @@ import org.thoughtcrime.securesms.service.WebRtcCallService import org.webrtc.IceCandidate -class CallMessageProcessor(private val context: Context, lifecycle: Lifecycle) { +class CallMessageProcessor(private val context: Context, lifecycle: Lifecycle, private val storage: StorageProtocol) { init { lifecycle.coroutineScope.launch(IO) { @@ -26,7 +28,10 @@ class CallMessageProcessor(private val context: Context, lifecycle: Lifecycle) { Log.d("Loki", nextMessage.type?.name ?: "CALL MESSAGE RECEIVED") if (!TextSecurePreferences.isCallNotificationsEnabled(context)) { Log.d("Loki","Dropping call message if call notifications disabled") - // TODO: maybe insert a message here saying you missed a call due to permissions + if (nextMessage.type != PRE_OFFER) continue + val sentTimestamp = nextMessage.sentTimestamp ?: continue + val sender = nextMessage.sender ?: continue + insertMissedCall(sender, sentTimestamp) continue } when (nextMessage.type) { @@ -41,6 +46,12 @@ class CallMessageProcessor(private val context: Context, lifecycle: Lifecycle) { } } + private fun insertMissedCall(sender: String, sentTimestamp: Long) { + val currentUserPublicKey = storage.getUserPublicKey() + if (sender == currentUserPublicKey) return // don't insert a "missed" due to call notifications disabled if it's our own sender + storage.insertCallMessage(sender, CallMessageType.CALL_MISSED, sentTimestamp) + } + private fun incomingHangup(callMessage: CallMessage) { val callId = callMessage.callId ?: return val hangupIntent = WebRtcCallService.remoteHangupIntent(context, callId) @@ -81,9 +92,10 @@ class CallMessageProcessor(private val context: Context, lifecycle: Lifecycle) { val recipientAddress = callMessage.sender ?: return val callId = callMessage.callId ?: return val incomingIntent = WebRtcCallService.preOffer( - context = context, - address = Address.fromSerialized(recipientAddress), - callId = callId, + context = context, + address = Address.fromSerialized(recipientAddress), + callId = callId, + sentTimestamp = callMessage.sentTimestamp!! ) ContextCompat.startForegroundService(context, incomingIntent) } @@ -97,7 +109,7 @@ class CallMessageProcessor(private val context: Context, lifecycle: Lifecycle) { address = Address.fromSerialized(recipientAddress), sdp = sdp, callId = callId, - callTime = callMessage.sentTimestamp ?: -1L + callTime = callMessage.sentTimestamp!! ) ContextCompat.startForegroundService(context, incomingIntent) diff --git a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt index 09a3ba5e4c..ec6d2f3cc7 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -2,6 +2,7 @@ package org.session.libsession.database import android.content.Context import android.net.Uri +import org.session.libsession.messaging.calls.CallMessageType import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.Job @@ -153,4 +154,5 @@ interface StorageProtocol { */ fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List, groupPublicKey: String?, openGroupID: String?, attachments: List): Long? fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long) + fun insertCallMessage(senderPublicKey: String, callMessageType: CallMessageType, sentTimestamp: Long) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/calls/CallMessageType.kt b/libsession/src/main/java/org/session/libsession/messaging/calls/CallMessageType.kt new file mode 100644 index 0000000000..c3c954a3ad --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/calls/CallMessageType.kt @@ -0,0 +1,7 @@ +package org.session.libsession.messaging.calls + +enum class CallMessageType { + CALL_MISSED, + CALL_INCOMING, + CALL_OUTGOING +} \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingTextMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingTextMessage.java index 5ef9179d0e..789d04201c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingTextMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingTextMessage.java @@ -5,6 +5,7 @@ import android.os.Parcelable; import androidx.annotation.Nullable; +import org.session.libsession.messaging.calls.CallMessageType; import org.session.libsession.messaging.messages.visible.OpenGroupInvitation; import org.session.libsession.messaging.messages.visible.VisibleMessage; import org.session.libsession.utilities.Address; @@ -41,12 +42,19 @@ public class IncomingTextMessage implements Parcelable { private final int subscriptionId; private final long expiresInMillis; private final boolean unidentified; + private final int callType; private boolean isOpenGroupInvitation = false; + public IncomingTextMessage(Address sender, int senderDeviceId, long sentTimestampMillis, + String encodedBody, Optional group, + long expiresInMillis, boolean unidentified) { + this(sender, senderDeviceId, sentTimestampMillis, encodedBody, group, expiresInMillis, unidentified, -1); + } + public IncomingTextMessage(Address sender, int senderDeviceId, long sentTimestampMillis, String encodedBody, Optional group, - long expiresInMillis, boolean unidentified) + long expiresInMillis, boolean unidentified, int callType) { this.message = encodedBody; this.sender = sender; @@ -60,6 +68,7 @@ public class IncomingTextMessage implements Parcelable { this.subscriptionId = -1; this.expiresInMillis = expiresInMillis; this.unidentified = unidentified; + this.callType = callType; if (group.isPresent()) { this.groupId = Address.fromSerialized(GroupUtil.getEncodedId(group.get())); @@ -69,36 +78,39 @@ public class IncomingTextMessage implements Parcelable { } public IncomingTextMessage(Parcel in) { - this.message = in.readString(); - this.sender = in.readParcelable(IncomingTextMessage.class.getClassLoader()); - this.senderDeviceId = in.readInt(); - this.protocol = in.readInt(); - this.serviceCenterAddress = in.readString(); - this.replyPathPresent = (in.readInt() == 1); - this.pseudoSubject = in.readString(); - this.sentTimestampMillis = in.readLong(); - this.groupId = in.readParcelable(IncomingTextMessage.class.getClassLoader()); - this.push = (in.readInt() == 1); - this.subscriptionId = in.readInt(); - this.expiresInMillis = in.readLong(); - this.unidentified = in.readInt() == 1; + this.message = in.readString(); + this.sender = in.readParcelable(IncomingTextMessage.class.getClassLoader()); + this.senderDeviceId = in.readInt(); + this.protocol = in.readInt(); + this.serviceCenterAddress = in.readString(); + this.replyPathPresent = (in.readInt() == 1); + this.pseudoSubject = in.readString(); + this.sentTimestampMillis = in.readLong(); + this.groupId = in.readParcelable(IncomingTextMessage.class.getClassLoader()); + this.push = (in.readInt() == 1); + this.subscriptionId = in.readInt(); + this.expiresInMillis = in.readLong(); + this.unidentified = in.readInt() == 1; + this.isOpenGroupInvitation = in.readInt() == 1; + this.callType = in.readInt(); } public IncomingTextMessage(IncomingTextMessage base, String newBody) { - this.message = newBody; - this.sender = base.getSender(); - this.senderDeviceId = base.getSenderDeviceId(); - this.protocol = base.getProtocol(); - this.serviceCenterAddress = base.getServiceCenterAddress(); - this.replyPathPresent = base.isReplyPathPresent(); - this.pseudoSubject = base.getPseudoSubject(); - this.sentTimestampMillis = base.getSentTimestampMillis(); - this.groupId = base.getGroupId(); - this.push = base.isPush(); - this.subscriptionId = base.getSubscriptionId(); - this.expiresInMillis = base.getExpiresIn(); - this.unidentified = base.isUnidentified(); - this.isOpenGroupInvitation= base.isOpenGroupInvitation(); + this.message = newBody; + this.sender = base.getSender(); + this.senderDeviceId = base.getSenderDeviceId(); + this.protocol = base.getProtocol(); + this.serviceCenterAddress = base.getServiceCenterAddress(); + this.replyPathPresent = base.isReplyPathPresent(); + this.pseudoSubject = base.getPseudoSubject(); + this.sentTimestampMillis = base.getSentTimestampMillis(); + this.groupId = base.getGroupId(); + this.push = base.isPush(); + this.subscriptionId = base.getSubscriptionId(); + this.expiresInMillis = base.getExpiresIn(); + this.unidentified = base.isUnidentified(); + this.isOpenGroupInvitation = base.isOpenGroupInvitation(); + this.callType = base.callType; } public static IncomingTextMessage from(VisibleMessage message, @@ -121,6 +133,13 @@ public class IncomingTextMessage implements Parcelable { return incomingTextMessage; } + public static IncomingTextMessage fromCallInfo(CallMessageType callMessageType, + Address sender, + Optional group, + long sentTimestamp) { + return new IncomingTextMessage(sender, 1, sentTimestamp, null, group, 0, false, callMessageType.ordinal()); + } + public int getSubscriptionId() { return subscriptionId; } @@ -183,6 +202,18 @@ public class IncomingTextMessage implements Parcelable { public boolean isOpenGroupInvitation() { return isOpenGroupInvitation; } + public boolean isCallInfo() { + int callMessageTypeLength = CallMessageType.values().length; + return callType >= 0 && callType < callMessageTypeLength; + } + + @Nullable + public CallMessageType getCallType() { + int callTypeLength = CallMessageType.values().length; + if (callType < 0 || callType >= callTypeLength) return null; + return CallMessageType.values()[callType]; + } + @Override public int describeContents() { return 0; @@ -202,5 +233,7 @@ public class IncomingTextMessage implements Parcelable { out.writeInt(push ? 1 : 0); out.writeInt(subscriptionId); out.writeInt(unidentified ? 1 : 0); + out.writeInt(isOpenGroupInvitation ? 1 : 0); + out.writeInt(callType); } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt b/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt index 90b71f578c..4c396b7c69 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt @@ -3,6 +3,7 @@ package org.session.libsession.messaging.utilities import android.content.Context import org.session.libsession.R import org.session.libsession.messaging.MessagingModuleConfiguration +import org.session.libsession.messaging.calls.CallMessageType import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage import org.session.libsession.utilities.ExpirationUtil @@ -102,4 +103,17 @@ object UpdateMessageBuilder { context.getString(R.string.MessageRecord_media_saved_by_s, senderName) } } + + fun buildCallMessage(context: Context, type: CallMessageType, sender: String): String { + val storage = MessagingModuleConfiguration.shared.storage + val senderName = storage.getContactWithSessionID(sender)?.displayName(Contact.ContactContext.REGULAR) ?: sender + return when (type) { + CallMessageType.CALL_MISSED -> + context.getString(R.string.MessageRecord_missed_call_from, senderName) + CallMessageType.CALL_INCOMING -> + context.getString(R.string.MessageRecord_called_s, senderName) + CallMessageType.CALL_OUTGOING -> + context.getString(R.string.MessageRecord_s_called_you, senderName) + } + } }