@ -33,127 +33,29 @@ import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase ;
import org.thoughtcrime.securesms.database.ThreadDatabase ;
import org.thoughtcrime.securesms.database.model.MessageRecord ;
import org.thoughtcrime.securesms.database.model.MessageRecord ;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord ;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord ;
import org.thoughtcrime.securesms.jobmanager.Job ;
import org.thoughtcrime.securesms.jobmanager.JobManager ;
import org.thoughtcrime.securesms.jobmanager.JobManager ;
import org.thoughtcrime.securesms.jobs.MmsSendJob ;
import org.thoughtcrime.securesms.jobs.MmsSendJob ;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob ;
import org.thoughtcrime.securesms.jobs.MultiDeviceGroupUpdateJob ;
import org.thoughtcrime.securesms.jobs.PushGroupSendJob ;
import org.thoughtcrime.securesms.jobs.PushGroupSendJob ;
import org.thoughtcrime.securesms.jobs.PushMediaSendJob ;
import org.thoughtcrime.securesms.jobs.PushTextSendJob ;
import org.thoughtcrime.securesms.jobs.SmsSendJob ;
import org.thoughtcrime.securesms.jobs.SmsSendJob ;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository ;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil ;
import org.thoughtcrime.securesms.logging.Log ;
import org.thoughtcrime.securesms.logging.Log ;
import org.thoughtcrime.securesms.loki.BackgroundMessage ;
import org.thoughtcrime.securesms.loki.protocol.FriendRequestProtocol ;
import org.thoughtcrime.securesms.loki.FriendRequestHandler ;
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol ;
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceOpenGroupUpdateJob ;
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities ;
import org.thoughtcrime.securesms.loki.PushBackgroundMessageSendJob ;
import org.thoughtcrime.securesms.loki.PushMessageSyncSendJob ;
import org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt ;
import org.thoughtcrime.securesms.mms.MmsException ;
import org.thoughtcrime.securesms.mms.MmsException ;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage ;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage ;
import org.thoughtcrime.securesms.push.AccountManagerFactory ;
import org.thoughtcrime.securesms.push.AccountManagerFactory ;
import org.thoughtcrime.securesms.recipients.Recipient ;
import org.thoughtcrime.securesms.recipients.Recipient ;
import org.thoughtcrime.securesms.service.ExpiringMessageManager ;
import org.thoughtcrime.securesms.service.ExpiringMessageManager ;
import org.thoughtcrime.securesms.util.TextSecurePreferences ;
import org.thoughtcrime.securesms.util.TextSecurePreferences ;
import org.thoughtcrime.securesms.util.Util ;
import org.whispersystems.libsignal.util.guava.Optional ;
import org.whispersystems.libsignal.util.guava.Optional ;
import org.whispersystems.signalservice.api.SignalServiceAccountManager ;
import org.whispersystems.signalservice.api.SignalServiceAccountManager ;
import org.whispersystems.signalservice.api.push.ContactTokenDetails ;
import org.whispersystems.signalservice.api.push.ContactTokenDetails ;
import org.whispersystems.signalservice.loki.protocol.multidevice.LokiDeviceLinkUtilities ;
import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus ;
import org.whispersystems.signalservice.loki.utilities.PromiseUtil ;
import java.io.IOException ;
import java.io.IOException ;
import java.util.ArrayList ;
import java.util.List ;
import java.util.Map ;
import kotlin.Unit ;
public class MessageSender {
public class MessageSender {
private static final String TAG = MessageSender . class . getSimpleName ( ) ;
private static final String TAG = MessageSender . class . getSimpleName ( ) ;
private enum MessageType { TEXT , MEDIA }
public static void syncAllContacts ( Context context , Address recipient ) {
ApplicationContext . getInstance ( context ) . getJobManager ( ) . add ( new MultiDeviceContactUpdateJob ( context , recipient , true ) ) ;
}
public static void syncAllGroups ( Context context ) {
ApplicationContext . getInstance ( context ) . getJobManager ( ) . add ( new MultiDeviceGroupUpdateJob ( ) ) ;
}
public static void syncAllOpenGroups ( Context context ) {
ApplicationContext . getInstance ( context ) . getJobManager ( ) . add ( new MultiDeviceOpenGroupUpdateJob ( ) ) ;
}
/ * *
* Send a contact sync message to all our devices telling them that we want to sync ` contact `
* /
public static void syncContact ( Context context , Address contact ) {
// Don't bother sending a contact sync message if it's one of our devices that we want to sync across
MultiDeviceUtilities . isOneOfOurDevices ( context , contact ) . success ( isOneOfOurDevice - > {
if ( ! isOneOfOurDevice ) {
ApplicationContext . getInstance ( context ) . getJobManager ( ) . add ( new MultiDeviceContactUpdateJob ( context , contact ) ) ;
}
return Unit . INSTANCE ;
} ) ;
}
public static void sendBackgroundMessageToAllDevices ( Context context , String contactHexEncodedPublicKey ) {
// Send the background message to the original pubkey
sendBackgroundMessage ( context , contactHexEncodedPublicKey ) ;
// Go through the other devices and only send background messages if we're friends or we have received friend request
LokiDeviceLinkUtilities . INSTANCE . getAllLinkedDeviceHexEncodedPublicKeys ( contactHexEncodedPublicKey ) . success ( devices - > {
Util . runOnMain ( ( ) - > {
for ( String device : devices ) {
// Don't send message to the device we already have sent to
if ( device . equals ( contactHexEncodedPublicKey ) ) { continue ; }
Recipient recipient = Recipient . from ( context , Address . fromSerialized ( device ) , false ) ;
long threadID = DatabaseFactory . getThreadDatabase ( context ) . getThreadIdIfExistsFor ( recipient ) ;
if ( threadID < 0 ) { continue ; }
LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory . getLokiThreadDatabase ( context ) . getFriendRequestStatus ( threadID ) ;
if ( friendRequestStatus = = LokiThreadFriendRequestStatus . FRIENDS | | friendRequestStatus = = LokiThreadFriendRequestStatus . REQUEST_RECEIVED ) {
sendBackgroundMessage ( context , device ) ;
} else if ( friendRequestStatus = = LokiThreadFriendRequestStatus . NONE | | friendRequestStatus = = LokiThreadFriendRequestStatus . REQUEST_EXPIRED ) {
sendBackgroundFriendRequest ( context , device , "Please accept to enable messages to be synced across devices" ) ;
}
}
} ) ;
return Unit . INSTANCE ;
} ) ;
}
// region Background message
// We don't call the message sender here directly and instead we just opt to create a specific job for the send
// This is because calling message sender directly would cause the application to freeze in some cases as it was blocking the thread when waiting for a response from the send
public static void sendBackgroundMessage ( Context context , String contactHexEncodedPublicKey ) {
ApplicationContext . getInstance ( context ) . getJobManager ( ) . add ( new PushBackgroundMessageSendJob ( BackgroundMessage . create ( contactHexEncodedPublicKey ) ) ) ;
}
public static void sendBackgroundFriendRequest ( Context context , String contactHexEncodedPublicKey , String messageBody ) {
ApplicationContext . getInstance ( context ) . getJobManager ( ) . add ( new PushBackgroundMessageSendJob ( BackgroundMessage . createFriendRequest ( contactHexEncodedPublicKey , messageBody ) ) ) ;
}
public static void sendUnpairRequest ( Context context , String contactHexEncodedPublicKey ) {
ApplicationContext . getInstance ( context ) . getJobManager ( ) . add ( new PushBackgroundMessageSendJob ( BackgroundMessage . createUnpairingRequest ( contactHexEncodedPublicKey ) ) ) ;
}
public static void sendRestoreSessionMessage ( Context context , String contactHexEncodedPublicKey ) {
ApplicationContext . getInstance ( context ) . getJobManager ( ) . add ( new PushBackgroundMessageSendJob ( BackgroundMessage . createSessionRestore ( contactHexEncodedPublicKey ) ) ) ;
}
public static void sendBackgroundSessionRequest ( Context context , String contactHexEncodedPublicKey ) {
ApplicationContext . getInstance ( context ) . getJobManager ( ) . add ( new PushBackgroundMessageSendJob ( BackgroundMessage . createSessionRequest ( contactHexEncodedPublicKey ) ) ) ;
}
// endregion
public static long send ( final Context context ,
public static long send ( final Context context ,
final OutgoingTextMessage message ,
final OutgoingTextMessage message ,
final long threadId ,
final long threadId ,
@ -174,9 +76,9 @@ public class MessageSender {
long messageId = database . insertMessageOutbox ( allocatedThreadId , message , forceSms , System . currentTimeMillis ( ) , insertListener ) ;
long messageId = database . insertMessageOutbox ( allocatedThreadId , message , forceSms , System . currentTimeMillis ( ) , insertListener ) ;
// Loki - Set the message's friend request status as soon as it h as h it the database
// Loki - Set the message's friend request status as soon as it h its the database
if ( message. isFriendRequest ) {
if ( FriendRequestProtocol. shouldUpdateFriendRequestStatusFromOutgoingTextMessage ( context , message ) ) {
FriendRequest Handler. updateFriendRequestState ( context , FriendRequestHandler . ActionType . Sending , messageId , allocatedThreadId ) ;
FriendRequest Protocol. setFriendRequestStatusToSendingIfNeeded ( context , messageId , allocatedThreadId ) ;
}
}
sendTextMessage ( context , recipient , forceSms , keyExchange , messageId ) ;
sendTextMessage ( context , recipient , forceSms , keyExchange , messageId ) ;
@ -190,6 +92,7 @@ public class MessageSender {
final boolean forceSms ,
final boolean forceSms ,
final SmsDatabase . InsertListener insertListener )
final SmsDatabase . InsertListener insertListener )
{
{
try {
ThreadDatabase threadDatabase = DatabaseFactory . getThreadDatabase ( context ) ;
ThreadDatabase threadDatabase = DatabaseFactory . getThreadDatabase ( context ) ;
MmsDatabase database = DatabaseFactory . getMmsDatabase ( context ) ;
MmsDatabase database = DatabaseFactory . getMmsDatabase ( context ) ;
@ -202,63 +105,21 @@ public class MessageSender {
}
}
Recipient recipient = message . getRecipient ( ) ;
Recipient recipient = message . getRecipient ( ) ;
long messageId = database . insertMessageOutbox ( message , allocatedThreadId , forceSms , insertListener ) ;
// Loki - Turn into a GIF message if possible
// Loki - Set the message's friend request status as soon as it hits the database
if ( message . getLinkPreviews ( ) . isEmpty ( ) & & message . getAttachments ( ) . isEmpty ( ) & & LinkPreviewUtil . isWhitelistedMediaUrl ( message . getBody ( ) ) ) {
if ( FriendRequestProtocol . shouldUpdateFriendRequestStatusFromOutgoingMediaMessage ( context , message ) ) {
new LinkPreviewRepository ( context ) . fetchGIF ( context , message . getBody ( ) , attachmentOrNull - > Util . runOnMain ( ( ) - > {
FriendRequestProtocol . setFriendRequestStatusToSendingIfNeeded ( context , messageId , allocatedThreadId ) ;
Attachment attachment = attachmentOrNull . orNull ( ) ;
try {
if ( attachment ! = null ) { message . getAttachments ( ) . add ( attachment ) ; }
long messageID = database . insertMessageOutbox ( message , allocatedThreadId , forceSms , insertListener ) ;
// Loki - Set the message's friend request status as soon as it has hit the database
if ( message . isFriendRequest & & ! recipient . getAddress ( ) . isGroup ( ) & & ! message . isGroup ( ) ) {
FriendRequestHandler . updateFriendRequestState ( context , FriendRequestHandler . ActionType . Sending , messageID , allocatedThreadId ) ;
}
sendMediaMessage ( context , recipient , forceSms , messageID , message . getExpiresIn ( ) ) ;
} catch ( Exception e ) {
Log . w ( TAG , e ) ;
// TODO: Handle
}
} ) ) ;
} else {
try {
long messageID = database . insertMessageOutbox ( message , allocatedThreadId , forceSms , insertListener ) ;
// Loki - Set the message's friend request status as soon as it has hit the database
if ( message . isFriendRequest & & ! recipient . getAddress ( ) . isGroup ( ) & & ! message . isGroup ( ) ) {
FriendRequestHandler . updateFriendRequestState ( context , FriendRequestHandler . ActionType . Sending , messageID , allocatedThreadId ) ;
}
}
sendMediaMessage ( context , recipient , forceSms , messageID , message . getExpiresIn ( ) ) ;
sendMediaMessage ( context , recipient , forceSms , messageId , message . getExpiresIn ( ) ) ;
return allocatedThreadId ;
} catch ( MmsException e ) {
} catch ( MmsException e ) {
Log . w ( TAG , e ) ;
Log . w ( TAG , e ) ;
return threadId ;
return threadId ;
}
}
}
}
return allocatedThreadId ;
}
public static void sendSyncMessageToOurDevices ( final Context context ,
final long messageID ,
final long timestamp ,
final byte [ ] message ,
final int ttl ) {
String ourPublicKey = TextSecurePreferences . getLocalNumber ( context ) ;
JobManager jobManager = ApplicationContext . getInstance ( context ) . getJobManager ( ) ;
LokiDeviceLinkUtilities . INSTANCE . getAllLinkedDeviceHexEncodedPublicKeys ( ourPublicKey ) . success ( devices - > {
Util . runOnMain ( ( ) - > {
for ( String device : devices ) {
// Don't send to ourselves
if ( device . equals ( ourPublicKey ) ) { continue ; }
// Create a send job for our device
Address address = Address . fromSerialized ( device ) ;
jobManager . add ( new PushMessageSyncSendJob ( messageID , address , timestamp , message , ttl ) ) ;
}
} ) ;
return Unit . INSTANCE ;
} ) ;
}
public static void resendGroupMessage ( Context context , MessageRecord messageRecord , Address filterAddress ) {
public static void resendGroupMessage ( Context context , MessageRecord messageRecord , Address filterAddress ) {
if ( ! messageRecord . isMms ( ) ) throw new AssertionError ( "Not Group" ) ;
if ( ! messageRecord . isMms ( ) ) throw new AssertionError ( "Not Group" ) ;
sendGroupPush ( context , messageRecord . getRecipient ( ) , messageRecord . getId ( ) , filterAddress ) ;
sendGroupPush ( context , messageRecord . getRecipient ( ) , messageRecord . getId ( ) , filterAddress ) ;
@ -301,73 +162,11 @@ public class MessageSender {
}
}
private static void sendTextPush ( Context context , Recipient recipient , long messageId ) {
private static void sendTextPush ( Context context , Recipient recipient , long messageId ) {
sendMessagePush( context , MessageType . TEXT , recipient , messageId ) ;
MultiDeviceProtocol. sendTextPush ( context , recipient , messageId ) ;
}
}
private static void sendMediaPush ( Context context , Recipient recipient , long messageId ) {
private static void sendMediaPush ( Context context , Recipient recipient , long messageId ) {
sendMessagePush ( context , MessageType . MEDIA , recipient , messageId ) ;
MultiDeviceProtocol . sendMediaPush ( context , recipient , messageId ) ;
}
private static void sendMessagePush ( Context context , MessageType type , Recipient recipient , long messageId ) {
JobManager jobManager = ApplicationContext . getInstance ( context ) . getJobManager ( ) ;
// Just send the message normally if it's a group message or we're sending to one of our devices
String recipientHexEncodedPublicKey = recipient . getAddress ( ) . serialize ( ) ;
if ( GeneralUtilitiesKt . isPublicChat ( context , recipientHexEncodedPublicKey ) | | PromiseUtil . get ( MultiDeviceUtilities . isOneOfOurDevices ( context , recipient . getAddress ( ) ) , false ) ) {
if ( type = = MessageType . MEDIA ) {
PushMediaSendJob . enqueue ( context , jobManager , messageId , recipient . getAddress ( ) , false ) ;
} else {
jobManager . add ( new PushTextSendJob ( messageId , recipient . getAddress ( ) ) ) ;
}
return ;
}
// If we get here then we are sending a message to a device that is not ours
boolean [ ] hasSentSyncMessage = { false } ;
MultiDeviceUtilities . getAllDevicePublicKeysWithFriendStatus ( context , recipientHexEncodedPublicKey ) . success ( devices - > {
int friendCount = MultiDeviceUtilities . getFriendCount ( context , devices . keySet ( ) ) ;
Util . runOnMain ( ( ) - > {
ArrayList < Job > jobs = new ArrayList < > ( ) ;
for ( Map . Entry < String , Boolean > entry : devices . entrySet ( ) ) {
String deviceHexEncodedPublicKey = entry . getKey ( ) ;
boolean isFriend = entry . getValue ( ) ;
Address address = Address . fromSerialized ( deviceHexEncodedPublicKey ) ;
long messageIDToUse = recipientHexEncodedPublicKey . equals ( deviceHexEncodedPublicKey ) ? messageId : - 1L ;
if ( isFriend ) {
// Send a normal message if the user is friends with the recipient
// We should also send a sync message if we haven't already sent one
boolean shouldSendSyncMessage = ! hasSentSyncMessage [ 0 ] & & address . isPhone ( ) ;
if ( type = = MessageType . MEDIA ) {
jobs . add ( new PushMediaSendJob ( messageId , messageIDToUse , address , false , null , shouldSendSyncMessage ) ) ;
} else {
jobs . add ( new PushTextSendJob ( messageId , messageIDToUse , address , shouldSendSyncMessage ) ) ;
}
if ( shouldSendSyncMessage ) { hasSentSyncMessage [ 0 ] = true ; }
} else {
// Send friend requests to non-friends. If the user is friends with any
// of the devices then send out a default friend request message.
boolean isFriendsWithAny = ( friendCount > 0 ) ;
String defaultFriendRequestMessage = isFriendsWithAny ? "Please accept to enable messages to be synced across devices" : null ;
if ( type = = MessageType . MEDIA ) {
jobs . add ( new PushMediaSendJob ( messageId , messageIDToUse , address , true , defaultFriendRequestMessage , false ) ) ;
} else {
jobs . add ( new PushTextSendJob ( messageId , messageIDToUse , address , true , defaultFriendRequestMessage , false ) ) ;
}
}
}
// Start the send
if ( type = = MessageType . MEDIA ) {
PushMediaSendJob . enqueue ( context , jobManager , ( List < PushMediaSendJob > ) ( List ) jobs ) ;
} else {
// Schedule text send jobs
jobManager . startChain ( jobs ) . enqueue ( ) ;
}
} ) ;
return Unit . INSTANCE ;
} ) ;
}
}
private static void sendGroupPush ( Context context , Recipient recipient , long messageId , Address filterAddress ) {
private static void sendGroupPush ( Context context , Recipient recipient , long messageId , Address filterAddress ) {