feat: adding call messages for incoming/outgoing/missed

This commit is contained in:
jubb
2021-11-23 17:59:02 +11:00
parent c863df2a60
commit f05487f925
15 changed files with 176 additions and 69 deletions

View File

@@ -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

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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<InsertResult> insertCallMessage(IncomingTextMessage message) {
return insertMessageInbox(message, 0, 0);
}
public Optional<InsertResult> insertMessageInbox(IncomingTextMessage message, long serverTimestamp) {
return insertMessageInbox(message, Types.BASE_INBOX_TYPE, serverTimestamp);
}

View File

@@ -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)
}
}

View File

@@ -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();
}
}

View File

@@ -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());

View File

@@ -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<IceCandidate>, 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)
}
}

View File

@@ -1,7 +0,0 @@
package org.thoughtcrime.securesms.webrtc
import org.session.libsignal.protos.SignalServiceProtos
interface CallDataListener {
fun newCallMessage(callMessage: SignalServiceProtos.CallMessage)
}

View File

@@ -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<Unit, Exception> {
@@ -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,

View File

@@ -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)