@ -18,7 +18,6 @@ package org.thoughtcrime.securesms.service;
import android.content.Context ;
import android.content.Context ;
import android.content.Intent ;
import android.content.Intent ;
import android.os.Handler ;
import android.telephony.TelephonyManager ;
import android.telephony.TelephonyManager ;
import android.util.Log ;
import android.util.Log ;
@ -33,6 +32,7 @@ import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.protocol.WirePrefix ;
import org.thoughtcrime.securesms.protocol.WirePrefix ;
import org.thoughtcrime.securesms.recipients.Recipient ;
import org.thoughtcrime.securesms.recipients.Recipient ;
import org.thoughtcrime.securesms.recipients.Recipients ;
import org.thoughtcrime.securesms.recipients.Recipients ;
import org.thoughtcrime.securesms.service.SendReceiveService.ToastHandler ;
import org.thoughtcrime.securesms.util.Hex ;
import org.thoughtcrime.securesms.util.Hex ;
import ws.com.google.android.mms.ContentType ;
import ws.com.google.android.mms.ContentType ;
@ -41,21 +41,22 @@ import ws.com.google.android.mms.pdu.EncodedStringValue;
import ws.com.google.android.mms.pdu.PduBody ;
import ws.com.google.android.mms.pdu.PduBody ;
import ws.com.google.android.mms.pdu.PduComposer ;
import ws.com.google.android.mms.pdu.PduComposer ;
import ws.com.google.android.mms.pdu.PduHeaders ;
import ws.com.google.android.mms.pdu.PduHeaders ;
import ws.com.google.android.mms.pdu.PduParser ;
import ws.com.google.android.mms.pdu.PduPart ;
import ws.com.google.android.mms.pdu.PduPart ;
import ws.com.google.android.mms.pdu.SendConf ;
import ws.com.google.android.mms.pdu.SendConf ;
import ws.com.google.android.mms.pdu.SendReq ;
import ws.com.google.android.mms.pdu.SendReq ;
import java.io.IOException ;
import java.io.IOException ;
import java.util.ArrayList ;
import java.util.Arrays ;
import java.util.Arrays ;
import java.util.LinkedList ;
import java.util.LinkedList ;
import java.util.List ;
public class MmsSender extends MmscProcessor {
public class MmsSender extends MmscProcessor {
private final LinkedList < Send Req[ ] > pendingMessages = new LinkedList < Send Req[ ] > ( ) ;
private final LinkedList < Send Item > pendingMessages = new LinkedList < Send Item > ( ) ;
private final Handler toastHandler ;
private final Toast Handler toastHandler ;
public MmsSender ( Context context , Handler toastHandler ) {
public MmsSender ( Context context , Toast Handler toastHandler ) {
super ( context ) ;
super ( context ) ;
this . toastHandler = toastHandler ;
this . toastHandler = toastHandler ;
}
}
@ -66,95 +67,66 @@ public class MmsSender extends MmscProcessor {
MmsDatabase database = DatabaseFactory . getEncryptingMmsDatabase ( context , masterSecret ) ;
MmsDatabase database = DatabaseFactory . getEncryptingMmsDatabase ( context , masterSecret ) ;
try {
try {
SendReq[ ] sendRequests ;
List< SendReq > sendRequests = getOutgoingMessages ( masterSecret , messageId ) ;
if ( messageId = = - 1 ) {
for ( SendReq sendRequest : sendRequests ) {
sendRequests = database . getOutgoingMessages ( ) ;
handleSendMmsAction ( new SendItem ( masterSecret , sendRequest , messageId ! = - 1 , false , false ) ) ;
} else {
sendRequests = new SendReq [ 1 ] ;
sendRequests [ 0 ] = database . getSendRequest ( messageId ) ;
}
}
if ( sendRequests ! = null & & sendRequests . length > 0 )
handleSendMms ( sendRequests , messageId ! = - 1 ) ;
} catch ( MmsException me ) {
} catch ( MmsException me ) {
Log . w ( "MmsSender" , me ) ;
Log . w ( "MmsSender" , me ) ;
if ( messageId ! = - 1 )
if ( messageId ! = - 1 )
database . markAsSentFailed ( messageId ) ;
database . markAsSentFailed ( messageId ) ;
}
}
} else if ( intent . getAction ( ) . equals ( SendReceiveService . SEND_MMS_CONNECTIVITY_ACTION ) ) {
} else if ( intent . getAction ( ) . equals ( SendReceiveService . SEND_MMS_CONNECTIVITY_ACTION ) ) {
handleConnectivityChange ( masterSecret ) ;
handleConnectivityChange ( ) ;
}
}
}
}
protected void handleConnectivityChange ( MasterSecret masterSecret ) {
private void handleSendMmsAction ( SendItem item ) {
if ( ! isConnected ( ) )
return ;
if ( ! pendingMessages . isEmpty ( ) ) handleSendMmsContinued ( masterSecret , pendingMessages . remove ( ) ) ;
else finishConnectivity ( ) ;
}
private void handleSendMms ( SendReq [ ] sendRequests , boolean targeted ) {
if ( ! isConnectivityPossible ( ) ) {
if ( ! isConnectivityPossible ( ) ) {
if ( targeted) {
if ( item . targeted ) {
toastHandler
toastHandler
. obtainMessage ( 0 , context . getString ( R . string . MmsSender_currently_unable_to_send_your_mms_message ) )
. obtainMessage ( 0 , context . getString ( R . string . MmsSender_currently_unable_to_send_your_mms_message ) )
. sendToTarget ( ) ;
. sendToTarget ( ) ;
}
}
// for (int i=0;i<sendRequests.length;i++)
// DatabaseFactory.getMmsDatabase(context).markAsSentFailed(sendRequests[i].getDatabaseMessageId());
return ;
} else {
pendingMessages . add ( sendRequests ) ;
issueConnectivityRequest ( ) ;
}
}
}
private boolean isInconsistentResponse ( SendReq send , SendConf response ) {
if ( item . useMmsRadio ) sendMmsMessageWithRadioChange ( item ) ;
Log . w ( "MmsSenderService" , "Comparing: " + Hex . toString ( send . getTransactionId ( ) ) ) ;
else sendMmsMessage ( item ) ;
Log . w ( "MmsSenderService" , "With: " + Hex . toString ( response . getTransactionId ( ) ) ) ;
return ! Arrays . equals ( send . getTransactionId ( ) , response . getTransactionId ( ) ) ;
}
}
private byte [ ] getEncryptedPdu ( MasterSecret masterSecret , String recipient , byte [ ] pduBytes ) {
private void sendMmsMessageWithRadioChange ( SendItem item ) {
synchronized ( SessionCipher . CIPHER_LOCK ) {
Log . w ( "MmsSender" , "Sending MMS with radio change.." ) ;
SessionCipher cipher = new SessionCipher ( context , masterSecret , new Recipient ( null , recipient , null , null ) , new TextTransport ( ) ) ;
pendingMessages . add ( item ) ;
return cipher . encryptMessage ( pduBytes ) ;
issueConnectivityRequest ( ) ;
}
}
}
private SendReq getEncryptedMms ( MasterSecret masterSecret , SendReq pdu , long messageId ) {
private void sendMmsMessage ( SendItem item ) {
Log . w ( "MmsSender" , "Sending Secure MMS." ) ;
Log . w ( "MmsSender" , "Sending MMS SendItem..." ) ;
EncodedStringValue [ ] encodedRecipient = pdu . getTo ( ) ;
MmsDatabase db = DatabaseFactory . getEncryptingMmsDatabase ( context , item . masterSecret ) ;
String recipient = encodedRecipient [ 0 ] . getString ( ) ;
String number = ( ( TelephonyManager ) context . getSystemService ( Context . TELEPHONY_SERVICE ) ) . getLine1Number ( ) ;
byte [ ] pduBytes = new PduComposer ( context , pdu ) . make ( ) ;
long messageId = item . request . getDatabaseMessageId ( ) ;
byte [ ] encryptedPdu = getEncryptedPdu ( masterSecret , recipient , pduBytes ) ;
long messageBox = item . request . getDatabaseMessageBox ( ) ;
Log . w ( "MmsSendeR" , "Got encrypted bytes: " + encryptedPdu . length ) ;
SendReq request = item . request ;
PduBody body = new PduBody ( ) ;
PduPart part = new PduPart ( ) ;
part . setContentId ( ( System . currentTimeMillis ( ) + "" ) . getBytes ( ) ) ;
part . setContentType ( ContentType . TEXT_PLAIN . getBytes ( ) ) ;
part . setName ( ( System . currentTimeMillis ( ) + "" ) . getBytes ( ) ) ;
part . setData ( encryptedPdu ) ;
body . addPart ( part ) ;
pdu . setSubject ( new EncodedStringValue ( WirePrefix . calculateEncryptedMmsSubject ( ) ) ) ;
pdu . setBody ( body ) ;
return pdu ;
if ( MmsDatabase . Types . isSecureMmsBox ( messageBox ) ) {
}
request = getEncryptedMms ( item . masterSecret , request , messageId ) ;
}
private void sendMms ( MmsDatabase db , SendReq pdu , String number , long messageId , boolean secure ) {
if ( number ! = null & & number . trim ( ) . length ( ) ! = 0 ) {
try {
request . setFrom ( new EncodedStringValue ( number ) ) ;
if ( number ! = null & & number . trim ( ) . length ( ) ! = 0 )
}
pdu . setFrom ( new EncodedStringValue ( number ) ) ;
byte [ ] response = MmsSendHelper . sendMms ( context , new PduComposer ( context , pdu ) . make ( ) , getApnInformation ( ) ) ;
try {
SendConf conf = ( SendConf ) new PduParser ( response ) . parse ( ) ;
SendConf conf = MmsSendHelper . sendMms ( context , new PduComposer ( context , request ) . make ( ) ,
getApnInformation ( ) , item . useMmsRadio , item . useProxyIfAvailable ) ;
for ( int i = 0 ; i < pdu . getBody ( ) . getPartsNum ( ) ; i + + ) {
for ( int i = 0 ; i < request . getBody ( ) . getPartsNum ( ) ; i + + ) {
Log . w ( "MmsSender" , "Sent MMS part of content-type: " + new String ( pdu . getBody ( ) . getPart ( i ) . getContentType ( ) ) ) ;
Log . w ( "MmsSender" , "Sent MMS part of content-type: " + new String ( request . getBody ( ) . getPart ( i ) . getContentType ( ) ) ) ;
}
}
long threadId = DatabaseFactory . getMmsDatabase ( context ) . getThreadIdForMessage ( messageId ) ;
long threadId = DatabaseFactory . getMmsDatabase ( context ) . getThreadIdForMessage ( messageId ) ;
@ -171,43 +143,106 @@ public class MmsSender extends MmscProcessor {
db . markAsSentFailed ( messageId ) ;
db . markAsSentFailed ( messageId ) ;
MessageNotifier . notifyMessageDeliveryFailed ( context , recipients , threadId ) ;
MessageNotifier . notifyMessageDeliveryFailed ( context , recipients , threadId ) ;
return ;
return ;
} else if ( isInconsistentResponse ( pdu , conf ) ) {
} else if ( isInconsistentResponse ( request , conf ) ) {
db . markAsSentFailed ( messageId ) ;
db . markAsSentFailed ( messageId ) ;
MessageNotifier . notifyMessageDeliveryFailed ( context , recipients , threadId ) ;
MessageNotifier . notifyMessageDeliveryFailed ( context , recipients , threadId ) ;
Log . w ( "MmsSender" , "Got a response for the wrong transaction?" ) ;
Log . w ( "MmsSender" , "Got a response for the wrong transaction?" ) ;
return ;
return ;
} else {
} else {
Log . w ( "MmsSender" , "Successful send! " + messageId ) ;
Log . w ( "MmsSender" , "Successful send! " + messageId ) ;
if ( ! secure)
if ( ! Mm sDatabase. Types . isS ecureMmsBox( messageBox ) ) {
db . markAsSent ( messageId , conf . getMessageId ( ) , conf . getResponseStatus ( ) ) ;
db . markAsSent ( messageId , conf . getMessageId ( ) , conf . getResponseStatus ( ) ) ;
else
} else {
db . markAsSecureSent ( messageId , conf . getMessageId ( ) , conf . getResponseStatus ( ) ) ;
db . markAsSecureSent ( messageId , conf . getMessageId ( ) , conf . getResponseStatus ( ) ) ;
}
}
}
} catch ( IOException ioe ) {
} catch ( IOException ioe ) {
Log . w ( "MmsSender" , ioe ) ;
Log . w ( "MmsSender" , ioe ) ;
db . markAsSentFailed ( messageId ) ;
if ( ! item . useMmsRadio ) scheduleSendWithMmsRadio ( item ) ;
else if ( ! item . useProxyIfAvailable ) scheduleSendWithMmsRadioAndProxy ( item ) ;
else db . markAsSentFailed ( messageId ) ;
}
}
}
}
private void handleSendMmsContinued ( MasterSecret masterSecret , SendReq [ ] requests ) {
private List < SendReq > getOutgoingMessages ( MasterSecret masterSecret , long messageId )
Log . w ( "MmsSenderService" , "Handling MMS send continuation..." ) ;
throws MmsException
{
MmsDatabase database = DatabaseFactory . getEncryptingMmsDatabase ( context , masterSecret ) ;
List < SendReq > sendRequests ;
MmsDatabase db = DatabaseFactory . getEncryptingMmsDatabase ( context , masterSecret ) ;
if ( messageId = = - 1 ) {
String number = ( ( TelephonyManager ) context . getSystemService ( Context . TELEPHONY_SERVICE ) ) . getLine1Number ( ) ;
sendRequests = Arrays . asList ( database . getOutgoingMessages ( ) ) ;
} else {
sendRequests = new ArrayList < SendReq > ( 1 ) ;
sendRequests . add ( database . getSendRequest ( messageId ) ) ;
}
for ( int i = 0 ; i < requests . length ; i + + ) {
return sendRequests ;
SendReq request = requests [ i ] ;
}
long messageId = request . getDatabaseMessageId ( ) ;
long messageBox = request . getDatabaseMessageBox ( ) ;
if ( MmsDatabase . Types . isSecureMmsBox ( messageBox ) )
protected void handleConnectivityChange ( ) {
request = getEncryptedMms ( masterSecret , request , messageId ) ;
if ( ! isConnected ( ) ) {
if ( ! isConnectivityPossible ( ) & & ! pendingMessages . isEmpty ( ) ) {
DatabaseFactory . getMmsDatabase ( context ) . markAsSentFailed ( pendingMessages . remove ( ) . request . getDatabaseMessageId ( ) ) ;
toastHandler . makeToast ( context . getString ( R . string . MmsSender_currently_unable_to_send_your_mms_message ) ) ;
Log . w ( "MmsSender" , "Unable to send MMS." ) ;
finishConnectivity ( ) ;
}
return ;
}
sendMms ( db , request , number , messageId , MmsDatabase . Types . isSecureMmsBox ( messageBox ) ) ;
for ( SendItem item : pendingMessages ) {
sendMmsMessage ( item ) ;
}
}
if ( this . pendingMessages . isEmpty ( ) )
pendingMessages . clear ( ) ;
finishConnectivity ( ) ;
finishConnectivity ( ) ;
}
private boolean isInconsistentResponse ( SendReq send , SendConf response ) {
Log . w ( "MmsSenderService" , "Comparing: " + Hex . toString ( send . getTransactionId ( ) ) ) ;
Log . w ( "MmsSenderService" , "With: " + Hex . toString ( response . getTransactionId ( ) ) ) ;
return ! Arrays . equals ( send . getTransactionId ( ) , response . getTransactionId ( ) ) ;
}
private byte [ ] getEncryptedPdu ( MasterSecret masterSecret , String recipient , byte [ ] pduBytes ) {
synchronized ( SessionCipher . CIPHER_LOCK ) {
SessionCipher cipher = new SessionCipher ( context , masterSecret , new Recipient ( null , recipient , null , null ) , new TextTransport ( ) ) ;
return cipher . encryptMessage ( pduBytes ) ;
}
}
private SendReq getEncryptedMms ( MasterSecret masterSecret , SendReq pdu , long messageId ) {
Log . w ( "MmsSender" , "Sending Secure MMS." ) ;
EncodedStringValue [ ] encodedRecipient = pdu . getTo ( ) ;
String recipient = encodedRecipient [ 0 ] . getString ( ) ;
byte [ ] pduBytes = new PduComposer ( context , pdu ) . make ( ) ;
byte [ ] encryptedPdu = getEncryptedPdu ( masterSecret , recipient , pduBytes ) ;
Log . w ( "MmsSendeR" , "Got encrypted bytes: " + encryptedPdu . length ) ;
PduBody body = new PduBody ( ) ;
PduPart part = new PduPart ( ) ;
part . setContentId ( ( System . currentTimeMillis ( ) + "" ) . getBytes ( ) ) ;
part . setContentType ( ContentType . TEXT_PLAIN . getBytes ( ) ) ;
part . setName ( ( System . currentTimeMillis ( ) + "" ) . getBytes ( ) ) ;
part . setData ( encryptedPdu ) ;
body . addPart ( part ) ;
pdu . setSubject ( new EncodedStringValue ( WirePrefix . calculateEncryptedMmsSubject ( ) ) ) ;
pdu . setBody ( body ) ;
return pdu ;
}
private void scheduleSendWithMmsRadioAndProxy ( SendItem item ) {
item . useMmsRadio = true ;
handleSendMmsAction ( item ) ;
}
private void scheduleSendWithMmsRadio ( SendItem item ) {
item . useMmsRadio = true ;
item . useProxyIfAvailable = true ;
handleSendMmsAction ( item ) ;
}
}
@Override
@Override
@ -215,4 +250,24 @@ public class MmsSender extends MmscProcessor {
return SendReceiveService . SEND_MMS_CONNECTIVITY_ACTION ;
return SendReceiveService . SEND_MMS_CONNECTIVITY_ACTION ;
}
}
private static class SendItem {
private final MasterSecret masterSecret ;
private boolean useMmsRadio ;
private boolean useProxyIfAvailable ;
private SendReq request ;
private boolean targeted ;
public SendItem ( MasterSecret masterSecret , SendReq request ,
boolean targeted , boolean useMmsRadio ,
boolean useProxyIfAvailable )
{
this . masterSecret = masterSecret ;
this . request = request ;
this . targeted = targeted ;
this . useMmsRadio = useMmsRadio ;
this . useProxyIfAvailable = useProxyIfAvailable ;
}
}
}
}