Initial fixes for MMS retrieval.

1) Parse the APN information based on what the ConnectionManager
tells us.

2) Accept email addresses as a valid Recipient format.
This commit is contained in:
Moxie Marlinspike
2012-09-30 11:46:45 -07:00
parent 59e7226183
commit cf9dc51f31
9 changed files with 224 additions and 217 deletions

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,14 +10,15 @@
* 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 java.io.IOException;
import java.util.LinkedList;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
import org.thoughtcrime.securesms.crypto.MasterSecret;
@@ -29,20 +30,20 @@ import org.thoughtcrime.securesms.protocol.WirePrefix;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.PduParser;
import ws.com.google.android.mms.pdu.RetrieveConf;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import java.io.IOException;
import java.util.LinkedList;
public class MmsDownloader extends MmscProcessor {
private final LinkedList<DownloadItem> pendingMessages = new LinkedList<DownloadItem>();
private final SendReceiveService.ToastHandler toastHandler;
public MmsDownloader(Context context, SendReceiveService.ToastHandler toastHandler) {
super(context);
this.toastHandler = toastHandler;
}
private void handleDownloadMms(DownloadItem item) {
if (!isConnectivityPossible()) {
DatabaseFactory.getMmsDatabase(context).markDownloadState(item.getMessageId(), MmsDatabase.Types.DOWNLOAD_NO_CONNECTIVITY);
@@ -54,37 +55,38 @@ public class MmsDownloader extends MmscProcessor {
issueConnectivityRequest();
}
}
private void handleDownloadMmsContinued(DownloadItem item) {
Log.w("MmsDownloadService", "Handling MMS download continuation...");
MmsDatabase mmsDatabase;
if (item.getMasterSecret() == null)
mmsDatabase = DatabaseFactory.getMmsDatabase(context);
else
mmsDatabase = DatabaseFactory.getEncryptingMmsDatabase(context, item.getMasterSecret());
try {
byte[] pdu = MmsDownloadHelper.retrieveMms(context, item.getContentLocation());
byte[] pdu = MmsDownloadHelper.retrieveMms(context, item.getContentLocation(),
getApnInformation());
RetrieveConf retrieved = (RetrieveConf)new PduParser(pdu).parse();
for (int i=0;i<retrieved.getBody().getPartsNum();i++) {
Log.w("MmsDownloader", "Sent MMS part of content-type: " + new String(retrieved.getBody().getPart(i).getContentType()));
Log.w("MmsDownloader", "Sent MMS part of content-type: " + new String(retrieved.getBody().getPart(i).getContentType()));
}
if (retrieved == null)
throw new IOException("Bad retrieved PDU");
if (retrieved.getSubject() != null && WirePrefix.isEncryptedMmsSubject(retrieved.getSubject().getString())) {
long messageId = mmsDatabase.insertSecureMessageReceived(retrieved, item.getContentLocation(), item.getThreadId());
if (item.getMasterSecret() != null)
DecryptingQueue.scheduleDecryption(context, item.getMasterSecret(), messageId, item.getThreadId(), retrieved);
} else {
mmsDatabase.insertMessageReceived(retrieved, item.getContentLocation(), item.getThreadId());
mmsDatabase.insertMessageReceived(retrieved, item.getContentLocation(), item.getThreadId());
}
mmsDatabase.delete(item.getMessageId());
// NotifyRespInd notifyResponse = new NotifyRespInd(PduHeaders.CURRENT_MMS_VERSION, item.getTransactionId(), PduHeaders.STATUS_RETRIEVED);
@@ -92,7 +94,7 @@ public class MmsDownloader extends MmscProcessor {
if (this.pendingMessages.isEmpty())
finishConnectivity();
} catch (IOException e) {
DatabaseFactory.getMmsDatabase(context).markDownloadState(item.getMessageId(), MmsDatabase.Types.DOWNLOAD_SOFT_FAILURE);
toastHandler.makeToast("Error connecting to MMS provider...");
@@ -103,7 +105,7 @@ public class MmsDownloader extends MmscProcessor {
Log.w("MmsDownloader", e);
}
}
protected void handleConnectivityChange() {
if (!isConnected()) {
if (!isConnectivityPossible() && !pendingMessages.isEmpty()) {
@@ -112,36 +114,36 @@ public class MmsDownloader extends MmscProcessor {
Log.w("MmsDownloadService", "Unable to download MMS, please try again later.");
finishConnectivity();
}
return;
}
if (!pendingMessages.isEmpty()) handleDownloadMmsContinued(pendingMessages.remove());
else finishConnectivity();
}
public void process(MasterSecret masterSecret, Intent intent) {
if (intent.getAction().equals(SendReceiveService.DOWNLOAD_MMS_ACTION)) {
DownloadItem item = new DownloadItem(intent.getLongExtra("message_id", -1),
DownloadItem item = new DownloadItem(intent.getLongExtra("message_id", -1),
intent.getLongExtra("thread_id", -1),
intent.getStringExtra("content_location"),
intent.getByteArrayExtra("transaction_id"),
masterSecret);
handleDownloadMms(item);
handleDownloadMms(item);
} else if (intent.getAction().equals(SendReceiveService.DOWNLOAD_MMS_CONNECTIVITY_ACTION)) {
handleConnectivityChange();
}
}
private class DownloadItem {
private long threadId;
private long messageId;
private byte[] transactionId;
private String contentLocation;
private MasterSecret masterSecret;
public DownloadItem(long messageId, long threadId, String contentLocation, byte[] transactionId, MasterSecret masterSecret) {
this.threadId = threadId;
this.messageId = messageId;
@@ -149,23 +151,23 @@ public class MmsDownloader extends MmscProcessor {
this.masterSecret = masterSecret;
this.transactionId = transactionId;
}
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;
}

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,12 +10,16 @@
* 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.Context;
import android.content.Intent;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
@@ -24,18 +28,15 @@ import ws.com.google.android.mms.pdu.GenericPdu;
import ws.com.google.android.mms.pdu.NotificationInd;
import ws.com.google.android.mms.pdu.PduHeaders;
import ws.com.google.android.mms.pdu.PduParser;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class MmsReceiver {
private final Context context;
public MmsReceiver(Context context) {
this.context = context;
}
private void scheduleDownload(NotificationInd pdu, long messageId) {
Intent intent = new Intent(SendReceiveService.DOWNLOAD_MMS_ACTION, null, context, SendReceiveService.class);
intent.putExtra("content_location", new String(pdu.getContentLocation()));
@@ -45,7 +46,7 @@ public class MmsReceiver {
context.startService(intent);
}
public void process(MasterSecret masterSecret, Intent intent) {
byte[] mmsData = intent.getByteArrayExtra("data");
PduParser parser = new PduParser(mmsData);
@@ -53,17 +54,17 @@ public class MmsReceiver {
if (pdu.getMessageType() == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND) {
MmsDatabase database;
if (masterSecret != null)
database = DatabaseFactory.getEncryptingMmsDatabase(context, masterSecret);
database = DatabaseFactory.getEncryptingMmsDatabase(context, masterSecret);
else
database = DatabaseFactory.getMmsDatabase(context);
database = DatabaseFactory.getMmsDatabase(context);
long messageId = database.insertMessageReceived((NotificationInd)pdu);
MessageNotifier.updateNotification(context, true);
scheduleDownload((NotificationInd)pdu, messageId);
Log.w("MmsReceiverService", "Inserted received notification...");
}
}
}
}

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,22 +10,22 @@
* 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 java.io.IOException;
import java.util.Arrays;
import java.util.LinkedList;
import android.content.Context;
import android.content.Intent;
import android.telephony.TelephonyManager;
import android.util.Log;
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.PngTransport;
import org.thoughtcrime.securesms.mms.TextTransport;
import org.thoughtcrime.securesms.protocol.WirePrefix;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -33,7 +33,6 @@ import org.thoughtcrime.securesms.util.Hex;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.CharacterSets;
import ws.com.google.android.mms.pdu.EncodedStringValue;
import ws.com.google.android.mms.pdu.PduBody;
import ws.com.google.android.mms.pdu.PduComposer;
@@ -42,51 +41,51 @@ import ws.com.google.android.mms.pdu.PduParser;
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 android.content.Context;
import android.content.Intent;
import android.telephony.TelephonyManager;
import android.util.Log;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedList;
public class MmsSender extends MmscProcessor {
private final LinkedList<SendReq[]> pendingMessages = new LinkedList<SendReq[]>();
public MmsSender(Context context) {
super(context);
}
public void process(MasterSecret masterSecret, Intent intent) {
if (intent.getAction().equals(SendReceiveService.SEND_MMS_ACTION)) {
long messageId = intent.getLongExtra("message_id", -1);
long messageId = intent.getLongExtra("message_id", -1);
MmsDatabase database = DatabaseFactory.getEncryptingMmsDatabase(context, masterSecret);
try {
SendReq[] sendRequests;
if (messageId == -1) {
sendRequests = database.getOutgoingMessages();
} else {
sendRequests = new SendReq[1];
sendRequests[0] = database.getSendRequest(messageId);
}
if (sendRequests.length > 0)
handleSendMms(sendRequests);
SendReq[] sendRequests;
if (messageId == -1) {
sendRequests = database.getOutgoingMessages();
} else {
sendRequests = new SendReq[1];
sendRequests[0] = database.getSendRequest(messageId);
}
if (sendRequests.length > 0)
handleSendMms(sendRequests);
} catch (MmsException me) {
Log.w("MmsSender", me);
if (messageId != -1)
database.markAsSentFailed(messageId);
}
Log.w("MmsSender", me);
if (messageId != -1)
database.markAsSentFailed(messageId);
}
} else if (intent.getAction().equals(SendReceiveService.SEND_MMS_CONNECTIVITY_ACTION)) {
handleConnectivityChange(masterSecret);
}
}
protected void handleConnectivityChange(MasterSecret masterSecret) {
if (!isConnected())
return;
if (!pendingMessages.isEmpty()) handleSendMmsContinued(masterSecret, pendingMessages.remove());
else finishConnectivity();
}
@@ -94,13 +93,13 @@ public class MmsSender extends MmscProcessor {
private void handleSendMms(SendReq[] sendRequests) {
if (!isConnectivityPossible()) {
for (int i=0;i<sendRequests.length;i++)
DatabaseFactory.getMmsDatabase(context).markAsSentFailed(sendRequests[i].getDatabaseMessageId());
DatabaseFactory.getMmsDatabase(context).markAsSentFailed(sendRequests[i].getDatabaseMessageId());
} else {
pendingMessages.add(sendRequests);
issueConnectivityRequest();
}
}
}
private boolean isInconsistentResponse(SendReq send, SendConf response) {
Log.w("MmsSenderService", "Comparing: " + Hex.toString(send.getTransactionId()));
Log.w("MmsSenderService", "With: " + Hex.toString(response.getTransactionId()));
@@ -113,7 +112,7 @@ public class MmsSender extends MmscProcessor {
return cipher.encryptMessage(pduBytes);
}
}
private SendReq getEncryptedMms(MasterSecret masterSecret, SendReq pdu, long messageId) {
Log.w("MmsSender", "Sending Secure MMS.");
EncodedStringValue[] encodedRecipient = pdu.getTo();
@@ -131,51 +130,51 @@ public class MmsSender extends MmscProcessor {
body.addPart(part);
pdu.setSubject(new EncodedStringValue(WirePrefix.calculateEncryptedMmsSubject()));
pdu.setBody(body);
return pdu;
}
private void sendMms(MmsDatabase db, SendReq pdu, String number, long messageId, boolean secure) {
try {
if (number != null && number.trim().length() != 0)
pdu.setFrom(new EncodedStringValue(number));
byte[] response = MmsSendHelper.sendMms(context, new PduComposer(context, pdu).make());
pdu.setFrom(new EncodedStringValue(number));
byte[] response = MmsSendHelper.sendMms(context, new PduComposer(context, pdu).make(), getApnInformation());
SendConf conf = (SendConf) new PduParser(response).parse();
for (int i=0;i<pdu.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(pdu.getBody().getPart(i).getContentType()));
}
if (conf == null) {
db.markAsSentFailed(messageId);
Log.w("MmsSender", "No M-Send.conf received in response to send.");
return;
db.markAsSentFailed(messageId);
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);
return;
Log.w("MmsSender", "Got bad response: " + conf.getResponseStatus());
db.updateResponseStatus(messageId, conf.getResponseStatus());
db.markAsSentFailed(messageId);
return;
} else if (isInconsistentResponse(pdu, conf)) {
db.markAsSentFailed(messageId);
Log.w("MmsSender", "Got a response for the wrong transaction?");
return;
} else {
Log.w("MmsSender", "Successful send! " + messageId);
if (!secure)
db.markAsSent(messageId, conf.getMessageId(), conf.getResponseStatus());
else
db.markAsSecureSent(messageId, conf.getMessageId(), conf.getResponseStatus());
}
db.markAsSentFailed(messageId);
Log.w("MmsSender", "Got a response for the wrong transaction?");
return;
} else {
Log.w("MmsSender", "Successful send! " + messageId);
if (!secure)
db.markAsSent(messageId, conf.getMessageId(), conf.getResponseStatus());
else
db.markAsSecureSent(messageId, conf.getMessageId(), conf.getResponseStatus());
}
} catch (IOException ioe) {
Log.w("MmsSender", ioe);
db.markAsSentFailed(messageId);
}
}
}
private void handleSendMmsContinued(MasterSecret masterSecret, SendReq[] requests) {
Log.w("MmsSenderService", "Handling MMS send continuation...");
MmsDatabase db = DatabaseFactory.getEncryptingMmsDatabase(context, masterSecret);
String number = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getLine1Number();
@@ -183,19 +182,19 @@ public class MmsSender extends MmscProcessor {
SendReq request = requests[i];
long messageId = request.getDatabaseMessageId();
long messageBox = request.getDatabaseMessageBox();
if (MmsDatabase.Types.isSecureMmsBox(messageBox))
request = getEncryptedMms(masterSecret, request, messageId);
request = getEncryptedMms(masterSecret, request, messageId);
sendMms(db, request, number, messageId, MmsDatabase.Types.isSecureMmsBox(messageBox));
}
if (this.pendingMessages.isEmpty())
finishConnectivity();
}
@Override
protected String getConnectivityAction() {
protected String getConnectivityAction() {
return SendReceiveService.SEND_MMS_CONNECTIVITY_ACTION;
}

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,14 +10,12 @@
* 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 java.util.LinkedList;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -36,34 +34,38 @@ public abstract class MmscProcessor {
private ConnectivityManager connectivityManager;
private ConnectivityListener connectivityListener;
private WakeLock wakeLock;
private WakeLock wakeLock;
protected final Context context;
public MmscProcessor(Context context) {
this.context = context;
PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
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);
if (status == APN_ALREADY_ACTIVE) {
issueConnectivityChange();
} else if (connectivityListener == null) {
@@ -74,7 +76,7 @@ public abstract class MmscProcessor {
wakeLock.acquire();
}
}
protected boolean isConnectivityPossible() {
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(TYPE_MOBILE_MMS);
Log.w("MmsService", "Got network info: " + networkInfo);
@@ -84,25 +86,25 @@ public abstract class MmscProcessor {
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) {
public void onReceive(Context context, Intent intent) {
Log.w("MmsService", "Dispatching connectivity change...");
issueConnectivityChange();
Log.w("MmsService", "Dispatched...");