package org.thoughtcrime.securesms.jobs; import android.content.Context; import android.telephony.TelephonyManager; import android.util.Log; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MmsCipher; import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.NoSuchMessageException; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.mms.ApnUnavailableException; import org.thoughtcrime.securesms.mms.MmsRadio; import org.thoughtcrime.securesms.mms.MmsRadioException; import org.thoughtcrime.securesms.mms.MmsSendResult; import org.thoughtcrime.securesms.mms.OutgoingMmsConnection; import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException; import org.thoughtcrime.securesms.transport.UndeliverableMessageException; import org.thoughtcrime.securesms.util.Hex; import org.thoughtcrime.securesms.util.NumberUtil; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.requirements.NetworkRequirement; import org.whispersystems.libaxolotl.NoSessionException; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; import ws.com.google.android.mms.MmsException; import ws.com.google.android.mms.pdu.EncodedStringValue; import ws.com.google.android.mms.pdu.PduBody; import ws.com.google.android.mms.pdu.PduComposer; import ws.com.google.android.mms.pdu.PduHeaders; import ws.com.google.android.mms.pdu.PduPart; import ws.com.google.android.mms.pdu.SendConf; import ws.com.google.android.mms.pdu.SendReq; public class MmsSendJob extends MasterSecretJob { private static final String TAG = MmsSendJob.class.getSimpleName(); private final long messageId; public MmsSendJob(Context context, long messageId) { super(context, JobParameters.newBuilder() .withGroupId("mms-operation") .withRequirement(new NetworkRequirement(context)) .withRequirement(new MasterSecretRequirement(context)) .withPersistence() .create()); this.messageId = messageId; } @Override public void onAdded() { } @Override public void onRun(MasterSecret masterSecret) throws MmsException, NoSuchMessageException, IOException { MmsDatabase database = DatabaseFactory.getMmsDatabase(context); SendReq message = database.getOutgoingMessage(masterSecret, messageId); populatePartData(message.getBody(), masterSecret); try { MmsSendResult result = deliver(masterSecret, message); if (result.isUpgradedSecure()) { database.markAsSecure(messageId); } database.markAsSent(messageId, result.getMessageId(), result.getResponseStatus()); } catch (UndeliverableMessageException e) { Log.w(TAG, e); database.markAsSentFailed(messageId); notifyMediaMessageDeliveryFailed(context, messageId); } catch (InsecureFallbackApprovalException e) { Log.w(TAG, e); database.markAsPendingInsecureSmsFallback(messageId); notifyMediaMessageDeliveryFailed(context, messageId); } } @Override public boolean onShouldRetryThrowable(Exception exception) { return false; } @Override public void onCanceled() { DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageId); notifyMediaMessageDeliveryFailed(context, messageId); } private void populatePartData(PduPart part, MasterSecret masterSecret) throws IOException { ByteArrayOutputStream os = part.getDataSize() > 0 && part.getDataSize() < Integer.MAX_VALUE ? new ByteArrayOutputStream((int)part.getDataSize()) : new ByteArrayOutputStream(); Util.copy(PartAuthority.getPartStream(context, masterSecret, part.getDataUri()), os); part.setData(os.toByteArray()); } private void populatePartData(PduBody body, MasterSecret masterSecret) throws IOException { for (int i=body.getPartsNum()-1; i>=0; i--) { populatePartData(body.getPart(i), masterSecret); } } public MmsSendResult deliver(MasterSecret masterSecret, SendReq message) throws UndeliverableMessageException, InsecureFallbackApprovalException { validateDestinations(message); MmsRadio radio = MmsRadio.getInstance(context); try { if (isCdmaDevice()) { Log.w(TAG, "Sending MMS directly without radio change..."); try { return sendMms(masterSecret, radio, message, false, false); } catch (IOException e) { Log.w(TAG, e); } } Log.w(TAG, "Sending MMS with radio change and proxy..."); radio.connect(); try { MmsSendResult result = sendMms(masterSecret, radio, message, true, true); radio.disconnect(); return result; } catch (IOException e) { Log.w(TAG, e); } Log.w(TAG, "Sending MMS with radio change and without proxy..."); try { MmsSendResult result = sendMms(masterSecret, radio, message, true, false); radio.disconnect(); return result; } catch (IOException ioe) { Log.w(TAG, ioe); radio.disconnect(); throw new UndeliverableMessageException(ioe); } } catch (MmsRadioException mre) { Log.w(TAG, mre); throw new UndeliverableMessageException(mre); } } private MmsSendResult sendMms(MasterSecret masterSecret, MmsRadio radio, SendReq message, boolean usingMmsRadio, boolean useProxy) throws IOException, UndeliverableMessageException, InsecureFallbackApprovalException { String number = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getLine1Number(); boolean upgradedSecure = false; if (MmsDatabase.Types.isSecureType(message.getDatabaseMessageBox())) { message = getEncryptedMessage(masterSecret, message); upgradedSecure = true; } if (number != null && number.trim().length() != 0) { message.setFrom(new EncodedStringValue(number)); } try { OutgoingMmsConnection connection = new OutgoingMmsConnection(context, radio.getApnInformation(), new PduComposer(context, message).make()); SendConf conf = connection.send(usingMmsRadio, useProxy); for (int i=0;i