diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java
index 03355f4da9..b045492bc4 100644
--- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java
@@ -240,6 +240,11 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
     notifyConversationListeners(getThreadIdForMessage(messageId));
   }
 
+  public void markAsSending(long messageId) {
+    updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_SENDING_TYPE);
+    notifyConversationListeners(getThreadIdForMessage(messageId));
+  }
+
   public void markAsSent(long messageId, byte[] mmsId, long status) {
     SQLiteDatabase database     = databaseHelper.getWritableDatabase();
     ContentValues contentValues = new ContentValues();
diff --git a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java
index 534644f42b..4634bb953f 100644
--- a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java
@@ -349,7 +349,7 @@ public class ThreadDatabase extends Database {
     }
   }
 
-  public Recipients getRecipientsForThreadId(Context context, long threadId) {
+  public Recipients getRecipientsForThreadId(long threadId) {
     SQLiteDatabase db = databaseHelper.getReadableDatabase();
     Cursor cursor     = null;
 
diff --git a/src/org/thoughtcrime/securesms/mms/MmsCommunication.java b/src/org/thoughtcrime/securesms/mms/MmsCommunication.java
index a1cc941feb..83a386e734 100644
--- a/src/org/thoughtcrime/securesms/mms/MmsCommunication.java
+++ b/src/org/thoughtcrime/securesms/mms/MmsCommunication.java
@@ -153,7 +153,7 @@ public class MmsCommunication {
       int ipAddress               = Conversions.byteArrayToIntLittleEndian(ipAddressBytes, 0);
       ConnectivityManager manager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
 
-      if (!manager.requestRouteToHost(MmsDownloader.TYPE_MOBILE_MMS, ipAddress))
+      if (!manager.requestRouteToHost(MmsRadio.TYPE_MOBILE_MMS, ipAddress))
         throw new IOException("Connection manager could not obtain route to host.");
     }
   }
diff --git a/src/org/thoughtcrime/securesms/mms/MmsRadio.java b/src/org/thoughtcrime/securesms/mms/MmsRadio.java
new file mode 100644
index 0000000000..c8e5de8563
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/mms/MmsRadio.java
@@ -0,0 +1,143 @@
+package org.thoughtcrime.securesms.mms;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.PowerManager;
+import android.util.Log;
+
+import org.thoughtcrime.securesms.util.Util;
+
+public class MmsRadio {
+
+  private static MmsRadio instance;
+
+  public static synchronized MmsRadio getInstance(Context context) {
+    if (instance == null)
+      instance = new MmsRadio(context);
+
+    return instance;
+  }
+
+  ///
+
+  private static final String FEATURE_ENABLE_MMS = "enableMMS";
+  private static final int APN_ALREADY_ACTIVE    = 0;
+  public  static final int TYPE_MOBILE_MMS       = 2;
+
+  private final Context context;
+
+  private ConnectivityManager   connectivityManager;
+  private ConnectivityListener  connectivityListener;
+  private PowerManager.WakeLock wakeLock;
+  private int connectedCounter = 0;
+
+  private MmsRadio(Context context) {
+    PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+    this.context             = context;
+    this.connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+    this.wakeLock            = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MMS Connection");
+    this.wakeLock.setReferenceCounted(true);
+  }
+
+  public String getApnInformation() {
+    return connectivityManager.getNetworkInfo(TYPE_MOBILE_MMS).getExtraInfo();
+  }
+
+  public synchronized void disconnect() {
+    Log.w("MmsRadio", "MMS Radio Disconnect Called...");
+    wakeLock.release();
+    connectedCounter--;
+
+    Log.w("MmsRadio", "Reference count: " + connectedCounter);
+
+    if (connectedCounter == 0) {
+      Log.w("MmsRadio", "Turning off MMS radio...");
+      connectivityManager.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, FEATURE_ENABLE_MMS);
+
+      if (connectivityListener != null) {
+        Log.w("MmsRadio", "Unregistering receiver...");
+        context.unregisterReceiver(connectivityListener);
+        connectivityListener = null;
+      }
+    }
+  }
+
+  public synchronized void connect() throws MmsRadioException {
+    int status = connectivityManager.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
+                                                              FEATURE_ENABLE_MMS);
+
+    Log.w("MmsRadio", "startUsingNetworkFeature status: " + status);
+
+    if (status == APN_ALREADY_ACTIVE) {
+      wakeLock.acquire();
+      connectedCounter++;
+      return;
+    } else {
+      wakeLock.acquire();
+      connectedCounter++;
+
+      if (connectivityListener == null) {
+        IntentFilter filter  = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
+        connectivityListener = new ConnectivityListener();
+        context.registerReceiver(connectivityListener, filter);
+      }
+
+      Util.wait(this, 30000);
+
+      if (!isConnected()) {
+        Log.w("MmsRadio", "Got back from connectivity wait, and not connected...");
+        disconnect();
+        throw new MmsRadioException("Unable to successfully enable MMS radio.");
+      }
+    }
+  }
+
+  private boolean isConnected() {
+    NetworkInfo info = connectivityManager.getNetworkInfo(TYPE_MOBILE_MMS);
+
+    if ((info == null) || (info.getType() != TYPE_MOBILE_MMS) || !info.isConnected())
+      return false;
+
+    return true;
+  }
+
+  private boolean isConnectivityPossible() {
+    NetworkInfo networkInfo = connectivityManager.getNetworkInfo(TYPE_MOBILE_MMS);
+
+    return networkInfo != null  && networkInfo.isAvailable();
+  }
+
+  private boolean isConnectivityFailure() {
+    NetworkInfo networkInfo = connectivityManager.getNetworkInfo(TYPE_MOBILE_MMS);
+
+    return networkInfo == null || networkInfo.getDetailedState() == NetworkInfo.DetailedState.FAILED;
+  }
+
+  private synchronized void issueConnectivityChange() {
+    if (isConnected()) {
+      Log.w("MmsRadio", "Notifying connected...");
+      notifyAll();
+      return;
+    }
+
+    if (!isConnected() && (isConnectivityFailure() || !isConnectivityPossible())) {
+      Log.w("MmsRadio", "Notifying not connected...");
+      notifyAll();
+      return;
+    }
+  }
+
+  private class ConnectivityListener extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+      Log.w("MmsRadio", "Got connectivity change...");
+      issueConnectivityChange();
+    }
+  }
+
+
+}
diff --git a/src/org/thoughtcrime/securesms/mms/MmsRadioException.java b/src/org/thoughtcrime/securesms/mms/MmsRadioException.java
new file mode 100644
index 0000000000..9919541b88
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/mms/MmsRadioException.java
@@ -0,0 +1,7 @@
+package org.thoughtcrime.securesms.mms;
+
+public class MmsRadioException extends Throwable {
+  public MmsRadioException(String s) {
+    super(s);
+  }
+}
diff --git a/src/org/thoughtcrime/securesms/mms/MmsSendHelper.java b/src/org/thoughtcrime/securesms/mms/MmsSendHelper.java
index e1f288e67e..4ec0ca0f68 100644
--- a/src/org/thoughtcrime/securesms/mms/MmsSendHelper.java
+++ b/src/org/thoughtcrime/securesms/mms/MmsSendHelper.java
@@ -24,10 +24,8 @@ import android.util.Log;
 import org.apache.http.HttpHost;
 import org.apache.http.HttpResponse;
 import org.apache.http.StatusLine;
-import org.apache.http.client.ClientProtocolException;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.entity.ByteArrayEntity;
-import org.thoughtcrime.securesms.service.MmscProcessor;
 import org.whispersystems.textsecure.util.Util;
 
 import java.io.IOException;
@@ -39,7 +37,9 @@ import ws.com.google.android.mms.pdu.SendConf;
 
 public class MmsSendHelper extends MmsCommunication {
 
-  private static byte[] makePost(Context context, MmsConnectionParameters parameters, byte[] mms) throws ClientProtocolException, IOException {
+  private static byte[] makePost(Context context, MmsConnectionParameters parameters, byte[] mms)
+      throws IOException
+  {
     AndroidHttpClient client = null;
 
     try {
@@ -114,7 +114,7 @@ public class MmsSendHelper extends MmsCommunication {
   public static boolean hasNecessaryApnDetails(Context context) {
     try {
       ConnectivityManager connectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
-      String apn = connectivityManager.getNetworkInfo(MmscProcessor.TYPE_MOBILE_MMS).getExtraInfo();
+      String apn = connectivityManager.getNetworkInfo(MmsRadio.TYPE_MOBILE_MMS).getExtraInfo();
 
       MmsCommunication.getMmsConnectionParameters(context, apn, true);
       return true;
diff --git a/src/org/thoughtcrime/securesms/service/MmsDownloader.java b/src/org/thoughtcrime/securesms/service/MmsDownloader.java
index 687d004617..fff28f7f7f 100644
--- a/src/org/thoughtcrime/securesms/service/MmsDownloader.java
+++ b/src/org/thoughtcrime/securesms/service/MmsDownloader.java
@@ -1,5 +1,5 @@
 /**
- * Copyright (C) 2011 Whisper Systems
+ * Copyright (C) 2013 Open Whisper Systems
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -30,6 +30,8 @@ import org.thoughtcrime.securesms.database.MmsDatabase;
 import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
 import org.thoughtcrime.securesms.mms.ApnUnavailableException;
 import org.thoughtcrime.securesms.mms.MmsDownloadHelper;
+import org.thoughtcrime.securesms.mms.MmsRadio;
+import org.thoughtcrime.securesms.mms.MmsRadioException;
 import org.thoughtcrime.securesms.mms.MmsSendHelper;
 import org.thoughtcrime.securesms.notifications.MessageNotifier;
 import org.thoughtcrime.securesms.protocol.WirePrefix;
@@ -45,48 +47,26 @@ import ws.com.google.android.mms.pdu.PduComposer;
 import ws.com.google.android.mms.pdu.PduHeaders;
 import ws.com.google.android.mms.pdu.RetrieveConf;
 
-public class MmsDownloader extends MmscProcessor {
+public class MmsDownloader {
 
-  private final LinkedList<DownloadItem> pendingMessages = new LinkedList<DownloadItem>();
+  private final Context                         context;
   private final SendReceiveService.ToastHandler toastHandler;
+  private final MmsRadio                        radio;
 
   public MmsDownloader(Context context, SendReceiveService.ToastHandler toastHandler) {
-    super(context);
+    this.context      = context;
     this.toastHandler = toastHandler;
+    this.radio        = MmsRadio.getInstance(context);
   }
 
   public void process(MasterSecret masterSecret, Intent intent) {
     if (intent.getAction().equals(SendReceiveService.DOWNLOAD_MMS_ACTION)) {
-      boolean isCdma    = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA;
-      DownloadItem item = new DownloadItem(masterSecret, !isCdma, false,
-                                           intent.getLongExtra("message_id", -1),
-                                           intent.getLongExtra("thread_id", -1),
-                                           intent.getBooleanExtra("automatic", false),
-                                           intent.getStringExtra("content_location"),
-                                           intent.getByteArrayExtra("transaction_id"));
-
-      handleDownloadMmsAction(item);
-    } else if (intent.getAction().equals(SendReceiveService.DOWNLOAD_MMS_CONNECTIVITY_ACTION)) {
-      handleConnectivityChange();
+      handleDownloadMms(masterSecret, intent);
     } else if (intent.getAction().equals(SendReceiveService.DOWNLOAD_MMS_PENDING_APN_ACTION)) {
       handleMmsPendingApnDownloads(masterSecret);
     }
   }
 
-  private void handleDownloadMmsAction(DownloadItem item) {
-    if (!isConnectivityPossible()) {
-      Log.w("MmsDownloader", "No MMS connectivity available!");
-      handleDownloadError(item, MmsDatabase.Status.DOWNLOAD_NO_CONNECTIVITY,
-                          context.getString(R.string.MmsDownloader_no_connectivity_available_for_mms_download_try_again_later));
-      return;
-    }
-
-    DatabaseFactory.getMmsDatabase(context).markDownloadState(item.getMessageId(), MmsDatabase.Status.DOWNLOAD_CONNECTING);
-
-    if (item.useMmsRadioMode()) downloadMmsWithRadioChange(item);
-    else                        downloadMms(item);
-  }
-
   private void handleMmsPendingApnDownloads(MasterSecret masterSecret) {
     if (!MmsDownloadHelper.isMmsConnectionParametersAvailable(context, null, false))
       return;
@@ -109,86 +89,125 @@ public class MmsDownloader extends MmscProcessor {
     stalledMmsReader.close();
   }
 
-  private void downloadMmsWithRadioChange(DownloadItem item) {
-    Log.w("MmsDownloader", "Handling MMS download with radio change...");
-    pendingMessages.add(item);
-    issueConnectivityRequest();
-  }
+  private void handleDownloadMms(MasterSecret masterSecret, Intent intent) {
+    long        messageId       = intent.getLongExtra("message_id", -1);
+    long        threadId        = intent.getLongExtra("thread_id", -1);
+    byte[]      transactionId   = intent.getByteArrayExtra("transaction_id");
+    String      contentLocation = intent.getStringExtra("content_location");
+    boolean     automatic       = intent.getBooleanExtra("automatic", false);
+    MmsDatabase database        = DatabaseFactory.getMmsDatabase(context);
 
-  private void downloadMms(DownloadItem item) {
-    Log.w("MmsDownloadService", "Handling actual MMS download...");
-    MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
+    database.markDownloadState(messageId, MmsDatabase.Status.DOWNLOAD_CONNECTING);
 
     try {
-      RetrieveConf retrieved = MmsDownloadHelper.retrieveMms(context, item.getContentLocation(),
-                                                             getApnInformation(),
-                                                             item.useMmsRadioMode(),
-                                                             item.proxyRequestIfPossible());
-
-      for (int i=0;i<retrieved.getBody().getPartsNum();i++) {
-        Log.w("MmsDownloader", "Got MMS part of content-type: " +
-              new String(retrieved.getBody().getPart(i).getContentType()));
+      if (isCdmaNetwork()) {
+        Log.w("MmsDownloader", "Connecting directly...");
+        try {
+          retrieveAndStore(masterSecret, messageId, threadId, contentLocation,
+                           transactionId, false, false);
+          return;
+        } catch (IOException e) {
+          Log.w("MmsDownloader", e);
+        }
       }
 
-      storeRetrievedMms(mmsDatabase, item, retrieved);
-      sendRetrievedAcknowledgement(item);
+      Log.w("MmsDownloader", "Changing radio to MMS mode..");
+      radio.connect();
+
+      Log.w("MmsDownloader", "Downloading in MMS mode without proxy...");
+
+      try {
+        retrieveAndStore(masterSecret, messageId, threadId, contentLocation,
+                         transactionId, true, false);
+        radio.disconnect();
+        return;
+      } catch (IOException e) {
+        Log.w("MmsDownloader", e);
+      }
+
+      Log.w("MmsDownloader", "Downloading in MMS mode with proxy...");
+
+      try {
+        retrieveAndStore(masterSecret, messageId, threadId,
+                         contentLocation, transactionId, true, true);
+        radio.disconnect();
+        return;
+      } catch (IOException e) {
+        Log.w("MmsDownloader", e);
+        radio.disconnect();
+        handleDownloadError(masterSecret, messageId, threadId,
+                            MmsDatabase.Status.DOWNLOAD_SOFT_FAILURE,
+                            context.getString(R.string.MmsDownloader_error_connecting_to_mms_provider),
+                            automatic);
+      }
 
     } catch (ApnUnavailableException e) {
       Log.w("MmsDownloader", e);
-      handleDownloadError(item, MmsDatabase.Status.DOWNLOAD_APN_UNAVAILABLE,
-                          context.getString(R.string.MmsDownloader_error_reading_mms_settings));
-    } catch (IOException e) {
-      Log.w("MmsDownloader", e);
-      if (!item.useMmsRadioMode() && !item.proxyRequestIfPossible()) {
-        Log.w("MmsDownloader", "Falling back to just radio mode...");
-        scheduleDownloadWithRadioMode(item);
-      } else if (!item.proxyRequestIfPossible()) {
-        Log.w("MmsDownloadeR", "Falling back to radio mode and proxy...");
-        scheduleDownloadWithRadioModeAndProxy(item);
-      } else {
-        handleDownloadError(item, MmsDatabase.Status.DOWNLOAD_SOFT_FAILURE,
-                            context.getString(R.string.MmsDownloader_error_connecting_to_mms_provider));
-      }
+      handleDownloadError(masterSecret, messageId, threadId, MmsDatabase.Status.DOWNLOAD_APN_UNAVAILABLE,
+                          context.getString(R.string.MmsDownloader_error_reading_mms_settings), automatic);
     } catch (MmsException e) {
       Log.w("MmsDownloader", e);
-      handleDownloadError(item, MmsDatabase.Status.DOWNLOAD_HARD_FAILURE,
-                          context.getString(R.string.MmsDownloader_error_storing_mms));
+      handleDownloadError(masterSecret, messageId, threadId,
+                          MmsDatabase.Status.DOWNLOAD_HARD_FAILURE,
+                          context.getString(R.string.MmsDownloader_error_storing_mms),
+                          automatic);
+    } catch (MmsRadioException e) {
+      Log.w("MmsDownloader", e);
+      handleDownloadError(masterSecret, messageId, threadId,
+                          MmsDatabase.Status.DOWNLOAD_SOFT_FAILURE,
+                          context.getString(R.string.MmsDownloader_error_connecting_to_mms_provider),
+                          automatic);
     }
   }
 
-  private void storeRetrievedMms(MmsDatabase mmsDatabase, DownloadItem item, RetrieveConf retrieved)
+  private void retrieveAndStore(MasterSecret masterSecret, long messageId, long threadId,
+                                String contentLocation, byte[] transactionId,
+                                boolean radioEnabled, boolean useProxy)
+      throws IOException, MmsException
+  {
+    RetrieveConf retrieved = MmsDownloadHelper.retrieveMms(context, contentLocation,
+                                                           radio.getApnInformation(),
+                                                           radioEnabled, useProxy);
+
+    storeRetrievedMms(masterSecret, contentLocation, messageId, threadId, retrieved);
+    sendRetrievedAcknowledgement(transactionId, radioEnabled, useProxy);
+  }
+
+  private void storeRetrievedMms(MasterSecret masterSecret, String contentLocation,
+                                 long messageId, long threadId, RetrieveConf retrieved)
       throws MmsException
   {
+    MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
     Pair<Long, Long> messageAndThreadId;
 
     if (retrieved.getSubject() != null && WirePrefix.isEncryptedMmsSubject(retrieved.getSubject().getString())) {
-      messageAndThreadId = mmsDatabase.insertSecureMessageInbox(item.getMasterSecret(), retrieved,
-                                                                item.getContentLocation(),
-                                                                item.getThreadId());
+      messageAndThreadId = database.insertSecureMessageInbox(masterSecret, retrieved,
+                                                             contentLocation, threadId);
 
-      if (item.getMasterSecret() != null)
-        DecryptingQueue.scheduleDecryption(context, item.getMasterSecret(), messageAndThreadId.first,
+      if (masterSecret != null)
+        DecryptingQueue.scheduleDecryption(context, masterSecret, messageAndThreadId.first,
                                            messageAndThreadId.second, retrieved);
 
     } else {
-      messageAndThreadId = mmsDatabase.insertMessageInbox(item.getMasterSecret(), retrieved,
-                                                          item.getContentLocation(),
-                                                          item.getThreadId());
+      messageAndThreadId = database.insertMessageInbox(masterSecret, retrieved,
+                                                       contentLocation, threadId);
     }
 
-    mmsDatabase.delete(item.getMessageId());
-    MessageNotifier.updateNotification(context, item.getMasterSecret(), messageAndThreadId.second);
+    database.delete(messageId);
+    MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
   }
 
-  private void sendRetrievedAcknowledgement(DownloadItem item) {
+  private void sendRetrievedAcknowledgement(byte[] transactionId,
+                                            boolean usingRadio,
+                                            boolean useProxy)
+  {
     try {
       NotifyRespInd notifyResponse = new NotifyRespInd(PduHeaders.CURRENT_MMS_VERSION,
-                                                       item.getTransactionId(),
+                                                       transactionId,
                                                        PduHeaders.STATUS_RETRIEVED);
 
       MmsSendHelper.sendNotificationReceived(context, new PduComposer(context, notifyResponse).make(),
-                                             getApnInformation(), item.useMmsRadioMode(),
-                                             item.proxyRequestIfPossible());
+                                             radio.getApnInformation(), usingRadio, useProxy);
     } catch (InvalidHeaderValueException e) {
       Log.w("MmsDownloader", e);
     } catch (IOException e) {
@@ -196,116 +215,25 @@ public class MmsDownloader extends MmscProcessor {
     }
   }
 
-  protected void handleConnectivityChange() {
-    LinkedList<DownloadItem> downloadItems = (LinkedList<DownloadItem>)pendingMessages.clone();
-
-    if (isConnected()) {
-      pendingMessages.clear();
-
-      for (DownloadItem item : downloadItems) {
-        downloadMms(item);
-      }
-
-      if (pendingMessages.isEmpty())
-        finishConnectivity();
-
-    } else if (!isConnected() && (!isConnectivityPossible() || isConnectivityFailure())) {
-      pendingMessages.clear();
-      handleDownloadError(downloadItems, MmsDatabase.Status.DOWNLOAD_NO_CONNECTIVITY,
-                          context.getString(R.string.MmsDownloader_no_connectivity_available_for_mms_download_try_again_later));
-      finishConnectivity();
-    }
-  }
 
-  private void handleDownloadError(List<DownloadItem> items, int downloadStatus, String error) {
-    for (DownloadItem item : items) {
-      handleDownloadError(item, downloadStatus, error);
-    }
-  }
-
-  private void handleDownloadError(DownloadItem item, int downloadStatus, String error) {
+  private void handleDownloadError(MasterSecret masterSecret, long messageId, long threadId,
+                                   int downloadStatus, String error, boolean automatic)
+  {
     MmsDatabase db = DatabaseFactory.getMmsDatabase(context);
-    db.markDownloadState(item.getMessageId(), downloadStatus);
-
-    if (item.isAutomatic()) {
-      db.markIncomingNotificationReceived(item.getThreadId());
-      MessageNotifier.updateNotification(context, item.getMasterSecret(), item.getThreadId());
-    }
-
-    toastHandler.makeToast(error);
-  }
-
-  private void scheduleDownloadWithRadioMode(DownloadItem item) {
-    item.mmsRadioMode = true;
-    handleDownloadMmsAction(item);
-  }
-
-  private void scheduleDownloadWithRadioModeAndProxy(DownloadItem item) {
-    item.mmsRadioMode    = true;
-    item.proxyIfPossible = true;
-    handleDownloadMmsAction(item);
-  }
-
-  private static class DownloadItem {
-    private final MasterSecret masterSecret;
-    private boolean            mmsRadioMode;
-    private boolean            proxyIfPossible;
-
-    private long threadId;
-    private long messageId;
-    private byte[] transactionId;
-    private String contentLocation;
-    private boolean automatic;
-
-    public DownloadItem(MasterSecret masterSecret, boolean mmsRadioMode, boolean proxyIfPossible,
-                        long messageId, long threadId, boolean automatic, String contentLocation,
-                        byte[] transactionId)
-    {
-      this.masterSecret    = masterSecret;
-      this.mmsRadioMode    = mmsRadioMode;
-      this.proxyIfPossible = proxyIfPossible;
-      this.threadId        = threadId;
-      this.messageId       = messageId;
-      this.contentLocation = contentLocation;
-      this.transactionId   = transactionId;
-      this.automatic       = automatic;
-    }
-
-    public long getThreadId() {
-      return threadId;
-    }
-
-    public long getMessageId() {
-      return messageId;
-    }
-
-    public String getContentLocation() {
-      return contentLocation;
-    }
 
-    public byte[] getTransactionId() {
-      return transactionId;
-    }
-
-    public MasterSecret getMasterSecret() {
-      return masterSecret;
-    }
+    db.markDownloadState(messageId, downloadStatus);
 
-    public boolean proxyRequestIfPossible() {
-      return proxyIfPossible;
+    if (automatic) {
+      db.markIncomingNotificationReceived(threadId);
+      MessageNotifier.updateNotification(context, masterSecret, threadId);
     }
 
-    public boolean useMmsRadioMode() {
-      return mmsRadioMode;
-    }
-
-    public boolean isAutomatic() {
-      return automatic;
-    }
+    toastHandler.makeToast(error);
   }
 
-  @Override
-  protected String getConnectivityAction() {
-    return SendReceiveService.DOWNLOAD_MMS_CONNECTIVITY_ACTION;
+  private boolean isCdmaNetwork() {
+    return ((TelephonyManager)context
+        .getSystemService(Context.TELEPHONY_SERVICE))
+        .getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA;
   }
 }
diff --git a/src/org/thoughtcrime/securesms/service/MmsSender.java b/src/org/thoughtcrime/securesms/service/MmsSender.java
index a5a8b88277..398efa5553 100644
--- a/src/org/thoughtcrime/securesms/service/MmsSender.java
+++ b/src/org/thoughtcrime/securesms/service/MmsSender.java
@@ -1,5 +1,5 @@
 /**
- * Copyright (C) 2011 Whisper Systems
+ * Copyright (C) 2013 Open Whisper Systems
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -18,250 +18,66 @@ package org.thoughtcrime.securesms.service;
 
 import android.content.Context;
 import android.content.Intent;
-import android.telephony.TelephonyManager;
 import android.util.Log;
+import android.util.Pair;
 
-import org.thoughtcrime.securesms.R;
 import org.thoughtcrime.securesms.crypto.MasterSecret;
-import org.thoughtcrime.securesms.crypto.SessionCipher;
 import org.thoughtcrime.securesms.database.DatabaseFactory;
 import org.thoughtcrime.securesms.database.MmsDatabase;
-import org.thoughtcrime.securesms.mms.MmsSendHelper;
-import org.thoughtcrime.securesms.mms.TextTransport;
+import org.thoughtcrime.securesms.database.ThreadDatabase;
 import org.thoughtcrime.securesms.notifications.MessageNotifier;
-import org.thoughtcrime.securesms.protocol.WirePrefix;
-import org.thoughtcrime.securesms.recipients.Recipient;
 import org.thoughtcrime.securesms.recipients.Recipients;
 import org.thoughtcrime.securesms.service.SendReceiveService.ToastHandler;
-import org.thoughtcrime.securesms.util.Hex;
+import org.thoughtcrime.securesms.transport.MmsTransport;
+import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
 
-import ws.com.google.android.mms.ContentType;
 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;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.List;
+public class MmsSender {
 
-public class MmsSender extends MmscProcessor {
-
-  private final LinkedList<SendItem> pendingMessages = new LinkedList<SendItem>();
+  private final Context      context;
   private final ToastHandler toastHandler;
 
   public MmsSender(Context context, ToastHandler toastHandler) {
-    super(context);
+    this.context      = context;
     this.toastHandler = toastHandler;
   }
 
   public void process(MasterSecret masterSecret, Intent intent) {
+    Log.w("MmsSender", "Got intent action: " + intent.getAction());
     if (intent.getAction().equals(SendReceiveService.SEND_MMS_ACTION)) {
-      long messageId       = intent.getLongExtra("message_id", -1);
-      boolean isCdma       = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA;
-      MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
-
-      try {
-        List<SendReq> sendRequests = getOutgoingMessages(masterSecret, messageId);
-
-        for (SendReq sendRequest : sendRequests) {
-          handleSendMmsAction(new SendItem(masterSecret, sendRequest, messageId != -1, !isCdma, false));
-        }
-
-      } catch (MmsException me) {
-        Log.w("MmsSender", me);
-        if (messageId != -1)
-          database.markAsSentFailed(messageId);
-      }
-    } else if (intent.getAction().equals(SendReceiveService.SEND_MMS_CONNECTIVITY_ACTION)) {
-      handleConnectivityChange();
+      handleSendMms(masterSecret, intent);
     }
   }
 
-  private void handleSendMmsAction(SendItem item) {
-    if (!isConnectivityPossible()) {
-      if (item.targeted) {
-        toastHandler
-          .obtainMessage(0, context.getString(R.string.MmsSender_currently_unable_to_send_your_mms_message))
-          .sendToTarget();
-      }
-
-      return;
-    }
-
-    if (item.useMmsRadio) sendMmsMessageWithRadioChange(item);
-    else                  sendMmsMessage(item);
-  }
-
-  private void sendMmsMessageWithRadioChange(SendItem item) {
-    Log.w("MmsSender", "Sending MMS with radio change..");
-    pendingMessages.add(item);
-    issueConnectivityRequest();
-  }
-
-  private void sendMmsMessage(SendItem item) {
-    Log.w("MmsSender", "Sending MMS SendItem...");
-    MmsDatabase db  = DatabaseFactory.getMmsDatabase(context);
-    String number   = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getLine1Number();
-    long messageId  = item.request.getDatabaseMessageId();
-    long messageBox = item.request.getDatabaseMessageBox();
-    SendReq request = item.request;
-
-
-    if (MmsDatabase.Types.isSecureType(messageBox)) {
-      request = getEncryptedMms(item.masterSecret, request, messageId);
-    }
-
-    if (number != null && number.trim().length() != 0) {
-      request.setFrom(new EncodedStringValue(number));
-    }
+  private void handleSendMms(MasterSecret masterSecret, Intent intent) {
+    long         messageId = intent.getLongExtra("message_id", -1);
+    MmsDatabase  database  = DatabaseFactory.getMmsDatabase(context);
+    ThreadDatabase threads = DatabaseFactory.getThreadDatabase(context);
+    MmsTransport transport = new MmsTransport(context, masterSecret);
 
     try {
-      SendConf conf = MmsSendHelper.sendMms(context, new PduComposer(context, request).make(),
-                                            getApnInformation(), item.useMmsRadio, item.useProxyIfAvailable);
-
-      for (int i=0;i<request.getBody().getPartsNum();i++) {
-        Log.w("MmsSender", "Sent MMS part of content-type: " + new String(request.getBody().getPart(i).getContentType()));
-      }
-
-      long threadId         = DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId);
-      Recipients recipients = DatabaseFactory.getThreadDatabase(context).getRecipientsForThreadId(context, threadId);
-
-      if (conf == null) {
-        db.markAsSentFailed(messageId);
-        MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
-        Log.w("MmsSender", "No M-Send.conf received in response to send.");
-        return;
-      } else if (conf.getResponseStatus() != PduHeaders.RESPONSE_STATUS_OK) {
-        Log.w("MmsSender", "Got bad response: " + conf.getResponseStatus());
-        db.updateResponseStatus(messageId, conf.getResponseStatus());
-        db.markAsSentFailed(messageId);
-        MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
-        return;
-      } else if (isInconsistentResponse(request, conf)) {
-        db.markAsSentFailed(messageId);
-        MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
-        Log.w("MmsSender", "Got a response for the wrong transaction?");
-        return;
-      } else {
-        Log.w("MmsSender", "Successful send! " + messageId);
-        db.markAsSent(messageId, conf.getMessageId(), conf.getResponseStatus());
-      }
-    } catch (IOException ioe) {
-      Log.w("MmsSender", ioe);
-      if      (!item.useMmsRadio)         scheduleSendWithMmsRadio(item);
-      else if (!item.useProxyIfAvailable) scheduleSendWithMmsRadioAndProxy(item);
-      else                                db.markAsSentFailed(messageId);
-    }
-  }
-
-  private List<SendReq> getOutgoingMessages(MasterSecret masterSecret, long messageId)
-      throws MmsException
-  {
-    MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
-    return Arrays.asList(database.getOutgoingMessages(masterSecret, messageId));
-  }
-
-  protected void handleConnectivityChange() {
-    if (!isConnected()) {
-      if ((!isConnectivityPossible() || isConnectivityFailure()) && !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();
+      SendReq[] messages = database.getOutgoingMessages(masterSecret, messageId);
+
+      for (SendReq message : messages) {
+        try {
+          Log.w("MmsSender", "Passing to MMS transport: " + message.getDatabaseMessageId());
+          database.markAsSending(message.getDatabaseMessageId());
+          Pair<byte[], Integer> result = transport.deliver(message);
+          database.markAsSent(message.getDatabaseMessageId(), result.first, result.second);
+        } catch (UndeliverableMessageException e) {
+          Log.w("MmsSender", e);
+          database.markAsSentFailed(message.getDatabaseMessageId());
+          long threadId         = database.getThreadIdForMessage(messageId);
+          Recipients recipients = threads.getRecipientsForThreadId(threadId);
+          MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
+        }
       }
-
-      return;
-    }
-
-    List<SendItem> outgoing = (List<SendItem>)pendingMessages.clone();
-    pendingMessages.clear();
-
-    for (SendItem item : outgoing) {
-      sendMmsMessage(item);
-    }
-
-    if (pendingMessages.isEmpty())
-      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[] encryptedPduBytes              = getEncryptedPdu(masterSecret, recipient, pduBytes);
-
-    PduBody body         = new PduBody();
-    PduPart part         = new PduPart();
-    SendReq encryptedPdu = new SendReq(pdu.getPduHeaders(), body);
-
-    part.setContentId((System.currentTimeMillis()+"").getBytes());
-    part.setContentType(ContentType.TEXT_PLAIN.getBytes());
-    part.setName((System.currentTimeMillis()+"").getBytes());
-    part.setData(encryptedPduBytes);
-    body.addPart(part);
-    encryptedPdu.setSubject(new EncodedStringValue(WirePrefix.calculateEncryptedMmsSubject()));
-    encryptedPdu.setBody(body);
-
-    return encryptedPdu;
-  }
-
-  private void scheduleSendWithMmsRadioAndProxy(SendItem item) {
-    Log.w("MmsSender", "Falling back to sending MMS with radio and proxy...");
-    item.useMmsRadio         = true;
-    item.useProxyIfAvailable = true;
-    handleSendMmsAction(item);
-  }
-
-  private void scheduleSendWithMmsRadio(SendItem item) {
-    Log.w("MmsSender", "Falling back to sending MMS with radio only...");
-    item.useMmsRadio = true;
-    handleSendMmsAction(item);
-  }
-
-  @Override
-  protected String getConnectivityAction() {
-    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;
+    } catch (MmsException e) {
+      Log.w("MmsSender", e);
+      if (messageId != -1)
+        database.markAsSentFailed(messageId);
     }
   }
-
 }
diff --git a/src/org/thoughtcrime/securesms/service/MmscProcessor.java b/src/org/thoughtcrime/securesms/service/MmscProcessor.java
deleted file mode 100644
index 42ccbe3e07..0000000000
--- a/src/org/thoughtcrime/securesms/service/MmscProcessor.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/**
- * Copyright (C) 2011 Whisper Systems
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package org.thoughtcrime.securesms.service;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.util.Log;
-
-public abstract class MmscProcessor {
-
-  private static final String FEATURE_ENABLE_MMS = "enableMMS";
-  private static final int APN_ALREADY_ACTIVE    = 0;
-  public  static final int TYPE_MOBILE_MMS       = 2;
-
-  private ConnectivityManager connectivityManager;
-  private ConnectivityListener connectivityListener;
-  private WakeLock wakeLock;
-
-  protected final Context context;
-
-  public MmscProcessor(Context context) {
-    this.context = context;
-    PowerManager powerManager         = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
-    this.connectivityManager          = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
-    this.wakeLock                     = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MMS Connection");
-    this.wakeLock.setReferenceCounted(false);
-  }
-
-  protected String getApnInformation() {
-    return connectivityManager.getNetworkInfo(TYPE_MOBILE_MMS).getExtraInfo();
-  }
-
-  protected boolean isConnected() {
-    NetworkInfo info = connectivityManager.getNetworkInfo(TYPE_MOBILE_MMS);
-
-    Log.w("MmsService", "NetworkInfo: " + info);
-
-    if ((info == null) || (info.getType() != TYPE_MOBILE_MMS) || !info.isConnected())
-      return false;
-
-    return true;
-  }
-
-  protected abstract String getConnectivityAction();
-
-  protected void issueConnectivityRequest() {
-    int status = connectivityManager.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, FEATURE_ENABLE_MMS);
-
-    Log.w("MmscProcessor", "startUsingNetworkFeature status: " + status);
-
-    if (status == APN_ALREADY_ACTIVE) {
-      issueConnectivityChange();
-    } else if (connectivityListener == null) {
-      IntentFilter filter  = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
-      connectivityListener = new ConnectivityListener();
-      context.registerReceiver(connectivityListener, filter);
-
-      wakeLock.acquire();
-    }
-  }
-
-  protected boolean isConnectivityFailure() {
-    NetworkInfo networkInfo = connectivityManager.getNetworkInfo(TYPE_MOBILE_MMS);
-
-    return networkInfo == null || networkInfo.getDetailedState() == NetworkInfo.DetailedState.FAILED;
-  }
-
-
-  protected boolean isConnectivityPossible() {
-    NetworkInfo networkInfo = connectivityManager.getNetworkInfo(TYPE_MOBILE_MMS);
-    Log.w("MmsService", "Got network info: " + networkInfo);
-
-    return networkInfo != null  && networkInfo.isAvailable();
-  }
-
-  protected void finishConnectivity() {
-    Log.w("MmsService", "Calling stop using network feature!");
-    connectivityManager.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, FEATURE_ENABLE_MMS);
-
-    if (connectivityListener != null) {
-      context.unregisterReceiver(connectivityListener);
-      connectivityListener = null;
-    }
-
-    if (this.wakeLock.isHeld())
-      this.wakeLock.release();
-  }
-
-  private void issueConnectivityChange() {
-    Intent intent = new Intent(context, SendReceiveService.class);
-    intent.setAction(getConnectivityAction());
-    context.startService(intent);
-  }
-
-  private class ConnectivityListener extends BroadcastReceiver {
-    @Override
-    public void onReceive(Context context, Intent intent) {
-      Log.w("MmsService", "Dispatching connectivity change...");
-      issueConnectivityChange();
-      Log.w("MmsService", "Dispatched...");
-    }
-  }
-}
diff --git a/src/org/thoughtcrime/securesms/service/SendReceiveService.java b/src/org/thoughtcrime/securesms/service/SendReceiveService.java
index 0ab4214612..5fb2097d34 100644
--- a/src/org/thoughtcrime/securesms/service/SendReceiveService.java
+++ b/src/org/thoughtcrime/securesms/service/SendReceiveService.java
@@ -50,7 +50,6 @@ public class SendReceiveService extends Service {
   public static final String DELIVERED_SMS_ACTION             = "org.thoughtcrime.securesms.SendReceiveService.DELIVERED_SMS_ACTION";
   public static final String RECEIVE_SMS_ACTION               = "org.thoughtcrime.securesms.SendReceiveService.RECEIVE_SMS_ACTION";
   public static final String SEND_MMS_ACTION                  = "org.thoughtcrime.securesms.SendReceiveService.SEND_MMS_ACTION";
-  public static final String SEND_MMS_CONNECTIVITY_ACTION     = "org.thoughtcrime.securesms.SendReceiveService.SEND_MMS_CONNECTIVITY_ACTION";
   public static final String RECEIVE_MMS_ACTION               = "org.thoughtcrime.securesms.SendReceiveService.RECEIVE_MMS_ACTION";
   public static final String DOWNLOAD_MMS_ACTION              = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_MMS_ACTION";
   public static final String DOWNLOAD_MMS_CONNECTIVITY_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_MMS_CONNECTIVITY_ACTION";
@@ -101,11 +100,11 @@ public class SendReceiveService extends Service {
       scheduleIntent(SEND_SMS, intent);
     else if (intent.getAction().equals(DELIVERED_SMS_ACTION))
       scheduleIntent(SEND_SMS, intent);
-    else if (intent.getAction().equals(SEND_MMS_ACTION) || intent.getAction().equals(SEND_MMS_CONNECTIVITY_ACTION))
+    else if (intent.getAction().equals(SEND_MMS_ACTION))
       scheduleSecretRequiredIntent(SEND_MMS, intent);
     else if (intent.getAction().equals(RECEIVE_MMS_ACTION))
       scheduleIntent(RECEIVE_MMS, intent);
-    else if (intent.getAction().equals(DOWNLOAD_MMS_ACTION) || intent.getAction().equals(DOWNLOAD_MMS_CONNECTIVITY_ACTION))
+    else if (intent.getAction().equals(DOWNLOAD_MMS_ACTION))
       scheduleSecretRequiredIntent(DOWNLOAD_MMS, intent);
     else if (intent.getAction().equals(DOWNLOAD_MMS_PENDING_APN_ACTION))
       scheduleSecretRequiredIntent(DOWNLOAD_MMS_PENDING, intent);
diff --git a/src/org/thoughtcrime/securesms/service/SmsSender.java b/src/org/thoughtcrime/securesms/service/SmsSender.java
index c35cce6dee..5132978606 100644
--- a/src/org/thoughtcrime/securesms/service/SmsSender.java
+++ b/src/org/thoughtcrime/securesms/service/SmsSender.java
@@ -100,7 +100,7 @@ public class SmsSender {
       registerForRadioChanges();
     } else {
       long threadId         = DatabaseFactory.getSmsDatabase(context).getThreadIdForMessage(messageId);
-      Recipients recipients = DatabaseFactory.getThreadDatabase(context).getRecipientsForThreadId(context, threadId);
+      Recipients recipients = DatabaseFactory.getThreadDatabase(context).getRecipientsForThreadId(threadId);
 
       DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageId);
       MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
diff --git a/src/org/thoughtcrime/securesms/service/SystemStateListener.java b/src/org/thoughtcrime/securesms/service/SystemStateListener.java
index ef8cf8bc52..3b165126e9 100644
--- a/src/org/thoughtcrime/securesms/service/SystemStateListener.java
+++ b/src/org/thoughtcrime/securesms/service/SystemStateListener.java
@@ -7,6 +7,8 @@ import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.telephony.ServiceState;
 
+import org.thoughtcrime.securesms.mms.MmsRadio;
+
 public class SystemStateListener extends BroadcastReceiver {
 
   public  static final String ACTION_SERVICE_STATE       = "android.intent.action.SERVICE_STATE";
@@ -42,7 +44,7 @@ public class SystemStateListener extends BroadcastReceiver {
     ConnectivityManager connectivityManager
       = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
 
-    NetworkInfo networkInfo = connectivityManager.getNetworkInfo(MmscProcessor.TYPE_MOBILE_MMS);
+    NetworkInfo networkInfo = connectivityManager.getNetworkInfo(MmsRadio.TYPE_MOBILE_MMS);
 
     if (networkInfo != null  && networkInfo.isAvailable()) {
       sendMmsOutbox(context);
diff --git a/src/org/thoughtcrime/securesms/transport/MmsTransport.java b/src/org/thoughtcrime/securesms/transport/MmsTransport.java
new file mode 100644
index 0000000000..9b150f9fd2
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/transport/MmsTransport.java
@@ -0,0 +1,157 @@
+package org.thoughtcrime.securesms.transport;
+
+import android.content.Context;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.util.Pair;
+
+import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.crypto.SessionCipher;
+import org.thoughtcrime.securesms.database.MmsDatabase;
+import org.thoughtcrime.securesms.mms.MmsRadio;
+import org.thoughtcrime.securesms.mms.MmsRadioException;
+import org.thoughtcrime.securesms.mms.MmsSendHelper;
+import org.thoughtcrime.securesms.mms.TextTransport;
+import org.thoughtcrime.securesms.protocol.WirePrefix;
+import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.util.Hex;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import ws.com.google.android.mms.ContentType;
+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 MmsTransport {
+
+  private final Context      context;
+  private final MasterSecret masterSecret;
+  private final MmsRadio     radio;
+
+  public MmsTransport(Context context, MasterSecret masterSecret) {
+    this.context      = context;
+    this.masterSecret = masterSecret;
+    this.radio        = MmsRadio.getInstance(context);
+  }
+
+  public Pair<byte[], Integer> deliver(SendReq message) throws UndeliverableMessageException {
+    try {
+      if (isCdmaDevice()) {
+        Log.w("MmsTransport", "Sending MMS directly without radio change...");
+        try {
+          return sendMms(message, false, false);
+        } catch (IOException e) {
+          Log.w("MmsTransport", e);
+        }
+      }
+
+      Log.w("MmsTransport", "Sending MMS with radio change...");
+      radio.connect();
+
+      try {
+        Pair<byte[], Integer> result = sendMms(message, true, false);
+        radio.disconnect();
+        return result;
+      } catch (IOException e) {
+        Log.w("MmsTransport", e);
+      }
+
+      Log.w("MmsTransport", "Sending MMS with radio change and proxy...");
+
+      try {
+        Pair<byte[], Integer> result = sendMms(message, true, true);
+        radio.disconnect();
+        return result;
+      } catch (IOException ioe) {
+        Log.w("MmsTransport", ioe);
+        radio.disconnect();
+        throw new UndeliverableMessageException(ioe);
+      }
+
+    } catch (MmsRadioException mre) {
+      Log.w("MmsTransport", mre);
+      throw new UndeliverableMessageException(mre);
+    }
+  }
+
+  private Pair<byte[], Integer> sendMms(SendReq message, boolean usingMmsRadio, boolean useProxy)
+      throws IOException, UndeliverableMessageException
+  {
+    String number = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getLine1Number();
+
+    if (MmsDatabase.Types.isSecureType(message.getDatabaseMessageBox())) {
+      message = getEncryptedMessage(message);
+    }
+
+    if (number != null && number.trim().length() != 0) {
+      message.setFrom(new EncodedStringValue(number));
+    }
+
+    SendConf conf = MmsSendHelper.sendMms(context, new PduComposer(context, message).make(),
+                                          radio.getApnInformation(), usingMmsRadio, useProxy);
+
+    for (int i=0;i<message.getBody().getPartsNum();i++) {
+      Log.w("MmsSender", "Sent MMS part of content-type: " + new String(message.getBody().getPart(i).getContentType()));
+    }
+
+    if (conf == null) {
+      throw new UndeliverableMessageException("No M-Send.conf received in response to send.");
+    } else if (conf.getResponseStatus() != PduHeaders.RESPONSE_STATUS_OK) {
+      throw new UndeliverableMessageException("Got bad response: " + conf.getResponseStatus());
+    } else if (isInconsistentResponse(message, conf)) {
+      throw new UndeliverableMessageException("Mismatched response!");
+    } else {
+      return new Pair<byte[], Integer>(conf.getMessageId(), conf.getResponseStatus());
+    }
+  }
+
+  private SendReq getEncryptedMessage(SendReq pdu) {
+    EncodedStringValue[] encodedRecipient = pdu.getTo();
+    String recipient                      = encodedRecipient[0].getString();
+    byte[] pduBytes                       = new PduComposer(context, pdu).make();
+    byte[] encryptedPduBytes              = getEncryptedPdu(masterSecret, recipient, pduBytes);
+
+    PduBody body         = new PduBody();
+    PduPart part         = new PduPart();
+    SendReq encryptedPdu = new SendReq(pdu.getPduHeaders(), body);
+
+    part.setContentId((System.currentTimeMillis()+"").getBytes());
+    part.setContentType(ContentType.TEXT_PLAIN.getBytes());
+    part.setName((System.currentTimeMillis()+"").getBytes());
+    part.setData(encryptedPduBytes);
+    body.addPart(part);
+    encryptedPdu.setSubject(new EncodedStringValue(WirePrefix.calculateEncryptedMmsSubject()));
+    encryptedPdu.setBody(body);
+
+    return encryptedPdu;
+  }
+
+  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 boolean isInconsistentResponse(SendReq message, SendConf response) {
+    Log.w("MmsTransport", "Comparing: " + Hex.toString(message.getTransactionId()));
+    Log.w("MmsTransport", "With:      " + Hex.toString(response.getTransactionId()));
+    return !Arrays.equals(message.getTransactionId(), response.getTransactionId());
+  }
+
+  private boolean isCdmaDevice() {
+    return ((TelephonyManager)context
+        .getSystemService(Context.TELEPHONY_SERVICE))
+        .getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA;
+  }
+
+}
diff --git a/src/org/thoughtcrime/securesms/util/Util.java b/src/org/thoughtcrime/securesms/util/Util.java
index 1cb9bfab78..b78f56791d 100644
--- a/src/org/thoughtcrime/securesms/util/Util.java
+++ b/src/org/thoughtcrime/securesms/util/Util.java
@@ -25,6 +25,8 @@ import android.widget.EditText;
 import android.os.Build;
 import android.provider.Telephony;
 
+import org.thoughtcrime.securesms.mms.MmsRadio;
+
 import java.io.UnsupportedEncodingException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ThreadPoolExecutor;
@@ -134,6 +136,14 @@ public class Util {
     }
   }
 
+  public static void wait(Object lock, int timeout) {
+    try {
+      lock.wait(timeout);
+    } catch (InterruptedException ie) {
+      throw new AssertionError(ie);
+    }
+  }
+
 
   public static boolean isDefaultSmsProvider(Context context){
     return (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) ||