Support for incoming attachments.

1) Refactored MMS layer to use abstracted types.

2) Added support for retrieving attachment IDs.
This commit is contained in:
Moxie Marlinspike 2013-07-18 17:42:45 -07:00
parent 4bb337a3a0
commit 9287d413ac
23 changed files with 501 additions and 193 deletions

View File

@ -33,6 +33,7 @@ import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.List; import java.util.List;
import java.util.zip.GZIPInputStream;
/** /**
* Handles providing lookups, serializing, and deserializing the RedPhone directory. * Handles providing lookups, serializing, and deserializing the RedPhone directory.
@ -102,7 +103,30 @@ public class NumberFilter {
} }
} }
public synchronized void update(File bloomFilter, long capacity, int hashCount, String version) public synchronized void update(DirectoryDescriptor descriptor, File compressedData) {
try {
File uncompressed = File.createTempFile("directory", ".dat", context.getFilesDir());
FileInputStream fin = new FileInputStream (compressedData);
GZIPInputStream gin = new GZIPInputStream(fin);
FileOutputStream out = new FileOutputStream(uncompressed);
byte[] buffer = new byte[4096];
int read;
while ((read = gin.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
out.close();
compressedData.delete();
update(uncompressed, descriptor.getCapacity(), descriptor.getHashCount(), descriptor.getVersion());
} catch (IOException ioe) {
Log.w("NumberFilter", ioe);
}
}
private synchronized void update(File bloomFilter, long capacity, int hashCount, String version)
{ {
if (this.bloomFilter != null) if (this.bloomFilter != null)
this.bloomFilter.delete(); this.bloomFilter.delete();

View File

@ -1,41 +0,0 @@
package org.whispersystems.textsecure.push;
import java.util.List;
public class IncomingGcmMessage {
private String source;
private List<String> destinations;
private String messageText;
private List<String> attachments;
private long timestamp;
public IncomingGcmMessage(String source, List<String> destinations, String messageText, List<String> attachments, long timestamp) {
this.source = source;
this.destinations = destinations;
this.messageText = messageText;
this.attachments = attachments;
this.timestamp = timestamp;
}
public long getTimestampMillis() {
return timestamp;
}
public String getSource() {
return source;
}
public List<String> getAttachments() {
return attachments;
}
public String getMessageText() {
return messageText;
}
public List<String> getDestinations() {
return destinations;
}
}

View File

@ -0,0 +1,87 @@
package org.whispersystems.textsecure.push;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.LinkedList;
import java.util.List;
public class IncomingPushMessage implements Parcelable {
public static final Parcelable.Creator<IncomingPushMessage> CREATOR = new Parcelable.Creator<IncomingPushMessage>() {
@Override
public IncomingPushMessage createFromParcel(Parcel in) {
return new IncomingPushMessage(in);
}
@Override
public IncomingPushMessage[] newArray(int size) {
return new IncomingPushMessage[size];
}
};
private String source;
private List<String> destinations;
private String messageText;
private List<PushAttachmentPointer> attachments;
private long timestamp;
public IncomingPushMessage(String source, List<String> destinations, String messageText,
List<PushAttachmentPointer> attachments, long timestamp)
{
this.source = source;
this.destinations = destinations;
this.messageText = messageText;
this.attachments = attachments;
this.timestamp = timestamp;
}
public IncomingPushMessage(Parcel in) {
this.destinations = new LinkedList<String>();
this.attachments = new LinkedList<PushAttachmentPointer>();
this.source = in.readString();
in.readStringList(destinations);
this.messageText = in.readString();
in.readList(attachments, PushAttachmentPointer.class.getClassLoader());
this.timestamp = in.readLong();
}
public long getTimestampMillis() {
return timestamp;
}
public String getSource() {
return source;
}
public List<PushAttachmentPointer> getAttachments() {
return attachments;
}
public String getMessageText() {
return messageText;
}
public List<String> getDestinations() {
return destinations;
}
public boolean hasAttachments() {
return getAttachments() != null && !getAttachments().isEmpty();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(source);
dest.writeStringList(destinations);
dest.writeString(messageText);
dest.writeList(attachments);
dest.writeLong(timestamp);
}
}

View File

@ -5,13 +5,13 @@ import java.util.List;
public class OutgoingPushMessage { public class OutgoingPushMessage {
private List<String> destinations; private List<String> destinations;
private String messageText; private String messageText;
private List<String> attachments; private List<PushAttachmentPointer> attachments;
public OutgoingPushMessage(String destination, String messageText) { public OutgoingPushMessage(String destination, String messageText) {
this.destinations = new LinkedList<String>(); this.destinations = new LinkedList<String>();
this.attachments = new LinkedList<String>(); this.attachments = new LinkedList<PushAttachmentPointer>();
this.messageText = messageText; this.messageText = messageText;
this.destinations.add(destination); this.destinations.add(destination);
} }
@ -19,11 +19,11 @@ public class OutgoingPushMessage {
public OutgoingPushMessage(List<String> destinations, String messageText) { public OutgoingPushMessage(List<String> destinations, String messageText) {
this.destinations = destinations; this.destinations = destinations;
this.messageText = messageText; this.messageText = messageText;
this.attachments = new LinkedList<String>(); this.attachments = new LinkedList<PushAttachmentPointer>();
} }
public OutgoingPushMessage(List<String> destinations, String messageText, public OutgoingPushMessage(List<String> destinations, String messageText,
List<String> attachments) List<PushAttachmentPointer> attachments)
{ {
this.destinations = destinations; this.destinations = destinations;
this.messageText = messageText; this.messageText = messageText;
@ -38,7 +38,7 @@ public class OutgoingPushMessage {
return messageText; return messageText;
} }
public List<String> getAttachments() { public List<PushAttachmentPointer> getAttachments() {
return attachments; return attachments;
} }

View File

@ -1,13 +1,13 @@
package org.whispersystems.textsecure.push; package org.whispersystems.textsecure.push;
public class PushAttachment { public class PushAttachmentData {
private final String contentType; private final String contentType;
private final byte[] data; private final byte[] data;
public PushAttachment(String contentType, byte[] data) { public PushAttachmentData(String contentType, byte[] data) {
this.contentType = contentType; this.contentType = contentType;
this.data = data; this.data = data;
} }
public String getContentType() { public String getContentType() {

View File

@ -0,0 +1,51 @@
package org.whispersystems.textsecure.push;
import android.os.Parcel;
import android.os.Parcelable;
public class PushAttachmentPointer implements Parcelable {
public static final Parcelable.Creator<PushAttachmentPointer> CREATOR = new Parcelable.Creator<PushAttachmentPointer>() {
@Override
public PushAttachmentPointer createFromParcel(Parcel in) {
return new PushAttachmentPointer(in);
}
@Override
public PushAttachmentPointer[] newArray(int size) {
return new PushAttachmentPointer[size];
}
};
private final String contentType;
private final String key;
public PushAttachmentPointer(String contentType, String key) {
this.contentType = contentType;
this.key = key;
}
public PushAttachmentPointer(Parcel in) {
this.contentType = in.readString();
this.key = in.readString();
}
public String getContentType() {
return contentType;
}
public String getKey() {
return key;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(contentType);
dest.writeString(key);
}
}

View File

@ -2,7 +2,7 @@ package org.whispersystems.textsecure.push;
import java.util.List; import java.util.List;
public class GcmMessageResponse { public class PushMessageResponse {
private List<String> success; private List<String> success;
private List<String> failure; private List<String> failure;

View File

@ -23,8 +23,6 @@ import java.io.OutputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.security.KeyManagementException; import java.security.KeyManagementException;
import java.security.KeyStore; import java.security.KeyStore;
@ -46,11 +44,13 @@ public class PushServiceSocket {
private static final String MESSAGE_PATH = "/v1/messages/"; private static final String MESSAGE_PATH = "/v1/messages/";
private static final String ATTACHMENT_PATH = "/v1/attachments/%s"; private static final String ATTACHMENT_PATH = "/v1/attachments/%s";
private final Context context;
private final String localNumber; private final String localNumber;
private final String password; private final String password;
private final TrustManagerFactory trustManagerFactory; private final TrustManagerFactory trustManagerFactory;
public PushServiceSocket(Context context, String localNumber, String password) { public PushServiceSocket(Context context, String localNumber, String password) {
this.context = context.getApplicationContext();
this.localNumber = localNumber; this.localNumber = localNumber;
this.password = password; this.password = password;
this.trustManagerFactory = initializeTrustManagerFactory(context); this.trustManagerFactory = initializeTrustManagerFactory(context);
@ -70,54 +70,55 @@ public class PushServiceSocket {
makeRequest(REGISTER_GCM_PATH, "PUT", new Gson().toJson(registration)); makeRequest(REGISTER_GCM_PATH, "PUT", new Gson().toJson(registration));
} }
public void unregisterGcmId() throws IOException, RateLimitException { public void unregisterGcmId() throws IOException {
makeRequest(REGISTER_GCM_PATH, "DELETE", null); makeRequest(REGISTER_GCM_PATH, "DELETE", null);
} }
public void sendMessage(String recipient, String messageText) public void sendMessage(String recipient, String messageText)
throws IOException, RateLimitException throws IOException
{ {
OutgoingPushMessage message = new OutgoingPushMessage(recipient, messageText); OutgoingPushMessage message = new OutgoingPushMessage(recipient, messageText);
sendMessage(message); sendMessage(message);
} }
public void sendMessage(List<String> recipients, String messageText) public void sendMessage(List<String> recipients, String messageText)
throws IOException, RateLimitException throws IOException
{ {
OutgoingPushMessage message = new OutgoingPushMessage(recipients, messageText); OutgoingPushMessage message = new OutgoingPushMessage(recipients, messageText);
sendMessage(message); sendMessage(message);
} }
public void sendMessage(List<String> recipients, String messageText, public void sendMessage(List<String> recipients, String messageText,
List<PushAttachment> attachments) List<PushAttachmentData> attachments)
throws IOException, RateLimitException throws IOException
{ {
List<String> attachmentIds = sendAttachments(attachments); List<PushAttachmentPointer> attachmentIds = sendAttachments(attachments);
OutgoingPushMessage message = new OutgoingPushMessage(recipients, messageText, attachmentIds); OutgoingPushMessage message = new OutgoingPushMessage(recipients, messageText, attachmentIds);
sendMessage(message); sendMessage(message);
} }
private void sendMessage(OutgoingPushMessage message) throws IOException, RateLimitException { private void sendMessage(OutgoingPushMessage message) throws IOException {
String responseText = makeRequest(MESSAGE_PATH, "POST", new Gson().toJson(message)); String responseText = makeRequest(MESSAGE_PATH, "POST", new Gson().toJson(message));
GcmMessageResponse response = new Gson().fromJson(responseText, GcmMessageResponse.class); PushMessageResponse response = new Gson().fromJson(responseText, PushMessageResponse.class);
if (response.getFailure().size() != 0) if (response.getFailure().size() != 0)
throw new IOException("Got send failure: " + response.getFailure().get(0)); throw new IOException("Got send failure: " + response.getFailure().get(0));
} }
private List<String> sendAttachments(List<PushAttachment> attachments) private List<PushAttachmentPointer> sendAttachments(List<PushAttachmentData> attachments)
throws IOException, RateLimitException throws IOException
{ {
List<String> attachmentIds = new LinkedList<String>(); List<PushAttachmentPointer> attachmentIds = new LinkedList<PushAttachmentPointer>();
for (PushAttachment attachment : attachments) { for (PushAttachmentData attachment : attachments) {
attachmentIds.add(sendAttachment(attachment)); attachmentIds.add(new PushAttachmentPointer(attachment.getContentType(),
sendAttachment(attachment)));
} }
return attachmentIds; return attachmentIds;
} }
private String sendAttachment(PushAttachment attachment) throws IOException, RateLimitException { private String sendAttachment(PushAttachmentData attachment) throws IOException {
Pair<String, String> response = makeRequestForResponseHeader(String.format(ATTACHMENT_PATH, ""), Pair<String, String> response = makeRequestForResponseHeader(String.format(ATTACHMENT_PATH, ""),
"GET", null, "Content-Location"); "GET", null, "Content-Location");
@ -130,34 +131,52 @@ public class PushServiceSocket {
uploadExternalFile("PUT", contentLocation, attachment.getData()); uploadExternalFile("PUT", contentLocation, attachment.getData());
return new Gson().fromJson(response.second, AttachmentDescriptor.class).getId(); return new Gson().fromJson(response.second, AttachmentKey.class).getId();
} }
public void retrieveDirectory(Context context ) { public List<File> retrieveAttachments(List<PushAttachmentPointer> attachmentIds)
throws IOException
{
List<File> attachments = new LinkedList<File>();
for (PushAttachmentPointer attachmentId : attachmentIds) {
Pair<String, String> response = makeRequestForResponseHeader(String.format(ATTACHMENT_PATH, attachmentId.getKey()),
"GET", null, "Content-Location");
Log.w("PushServiceSocket", "Attachment: " + attachmentId.getKey() + " is at: " + response.first);
File attachment = File.createTempFile("attachment", ".tmp", context.getFilesDir());
downloadExternalFile(response.first, attachment);
attachments.add(attachment);
}
return attachments;
}
public Pair<DirectoryDescriptor, File> retrieveDirectory() {
try { try {
DirectoryDescriptor directoryDescriptor = new Gson().fromJson(makeRequest(DIRECTORY_PATH, "GET", null), DirectoryDescriptor directoryDescriptor = new Gson().fromJson(makeRequest(DIRECTORY_PATH, "GET", null),
DirectoryDescriptor.class); DirectoryDescriptor.class);
File directoryData = File.createTempFile("directory", ".dat", context.getFilesDir()); File directoryData = File.createTempFile("directory", ".dat", context.getFilesDir());
downloadExternalFile(directoryDescriptor.getUrl(), directoryData); downloadExternalFile(directoryDescriptor.getUrl(), directoryData);
NumberFilter.getInstance(context).update(directoryData, return new Pair<DirectoryDescriptor, File>(directoryDescriptor, directoryData);
directoryDescriptor.getCapacity(),
directoryDescriptor.getHashCount(),
directoryDescriptor.getVersion());
} catch (IOException ioe) { } catch (IOException ioe) {
Log.w("PushServiceSocket", ioe); Log.w("PushServiceSocket", ioe);
} catch (RateLimitException e) { return null;
Log.w("PushServiceSocket", e);
} }
} }
private void downloadExternalFile(String url, File localDestination) private void downloadExternalFile(String url, File localDestination)
throws IOException throws IOException
{ {
URL downloadUrl = new URL(url); URL downloadUrl = new URL(url);
HttpsURLConnection connection = (HttpsURLConnection) downloadUrl.openConnection(); HttpURLConnection connection = (HttpURLConnection) downloadUrl.openConnection();
connection.setRequestProperty("Content-Type", "application/octet-stream");
connection.setRequestMethod("GET");
connection.setDoInput(true); connection.setDoInput(true);
try { try {
@ -166,7 +185,7 @@ public class PushServiceSocket {
} }
OutputStream output = new FileOutputStream(localDestination); OutputStream output = new FileOutputStream(localDestination);
InputStream input = new GZIPInputStream(connection.getInputStream()); InputStream input = connection.getInputStream();
byte[] buffer = new byte[4096]; byte[] buffer = new byte[4096];
int read; int read;
@ -175,6 +194,7 @@ public class PushServiceSocket {
} }
output.close(); output.close();
Log.w("PushServiceSocket", "Downloaded: " + url + " to: " + localDestination.getAbsolutePath());
} finally { } finally {
connection.disconnect(); connection.disconnect();
} }
@ -205,7 +225,7 @@ public class PushServiceSocket {
private Pair<String, String> makeRequestForResponseHeader(String urlFragment, String method, private Pair<String, String> makeRequestForResponseHeader(String urlFragment, String method,
String body, String responseHeader) String body, String responseHeader)
throws IOException, RateLimitException throws IOException
{ {
HttpURLConnection connection = makeBaseRequest(urlFragment, method, body); HttpURLConnection connection = makeBaseRequest(urlFragment, method, body);
String response = Util.readFully(connection.getInputStream()); String response = Util.readFully(connection.getInputStream());
@ -216,7 +236,7 @@ public class PushServiceSocket {
} }
private String makeRequest(String urlFragment, String method, String body) private String makeRequest(String urlFragment, String method, String body)
throws IOException, RateLimitException throws IOException
{ {
HttpURLConnection connection = makeBaseRequest(urlFragment, method, body); HttpURLConnection connection = makeBaseRequest(urlFragment, method, body);
String response = Util.readFully(connection.getInputStream()); String response = Util.readFully(connection.getInputStream());
@ -227,7 +247,7 @@ public class PushServiceSocket {
} }
private HttpURLConnection makeBaseRequest(String urlFragment, String method, String body) private HttpURLConnection makeBaseRequest(String urlFragment, String method, String body)
throws IOException, RateLimitException throws IOException
{ {
HttpURLConnection connection = getConnection(urlFragment, method); HttpURLConnection connection = getConnection(urlFragment, method);
@ -314,7 +334,7 @@ public class PushServiceSocket {
} }
} }
private class GcmRegistrationId { private static class GcmRegistrationId {
private String gcmRegistrationId; private String gcmRegistrationId;
public GcmRegistrationId() {} public GcmRegistrationId() {}
@ -324,10 +344,10 @@ public class PushServiceSocket {
} }
} }
private class AttachmentDescriptor { private static class AttachmentKey {
private String id; private String id;
public AttachmentDescriptor(String id) { public AttachmentKey(String id) {
this.id = id; this.id = id;
} }

View File

@ -1,7 +1,9 @@
package org.whispersystems.textsecure.push; package org.whispersystems.textsecure.push;
public class RateLimitException extends Exception { import java.io.IOException;
public class RateLimitException extends IOException {
public RateLimitException(String s) { public RateLimitException(String s) {
super(s); super(s);
} }

View File

@ -9,6 +9,8 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Collection;
import java.util.List;
public class Util { public class Util {
public static boolean isEmpty(String value) { public static boolean isEmpty(String value) {
@ -55,4 +57,18 @@ public class Util {
return new String(bout.toByteArray()); return new String(bout.toByteArray());
} }
public static String join(Collection<String> list, String delimiter) {
StringBuilder result = new StringBuilder();
int i=0;
for (String item : list) {
result.append(item);
if (++i < list.size())
result.append(delimiter);
}
return result.toString();
}
} }

View File

@ -57,7 +57,6 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Trimmer; import org.thoughtcrime.securesms.util.Trimmer;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.push.RateLimitException;
import java.io.IOException; import java.io.IOException;
@ -361,9 +360,6 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
} catch (IOException e) { } catch (IOException e) {
Log.w("ApplicationPreferencesActivity", e); Log.w("ApplicationPreferencesActivity", e);
return NETWORK_ERROR; return NETWORK_ERROR;
} catch (RateLimitException e) {
Log.w("ApplicationPreferencesActivity", e);
return NETWORK_ERROR;
} }
} }
}.execute(); }.execute();

View File

@ -452,12 +452,12 @@ public class RegistrationProgressActivity extends SherlockActivity {
PushServiceSocket socket = new PushServiceSocket(context, e164number, password); PushServiceSocket socket = new PushServiceSocket(context, e164number, password);
socket.verifyAccount(code); socket.verifyAccount(code);
return SUCCESS; return SUCCESS;
} catch (IOException e) {
Log.w("RegistrationProgressActivity", e);
return NETWORK_ERROR;
} catch (RateLimitException e) { } catch (RateLimitException e) {
Log.w("RegistrationProgressActivity", e); Log.w("RegistrationProgressActivity", e);
return RATE_LIMIT_ERROR; return RATE_LIMIT_ERROR;
} catch (IOException e) {
Log.w("RegistrationProgressActivity", e);
return NETWORK_ERROR;
} }
} }
}.execute(); }.execute();
@ -539,12 +539,12 @@ public class RegistrationProgressActivity extends SherlockActivity {
socket.createAccount(true); socket.createAccount(true);
return SUCCESS; return SUCCESS;
} catch (IOException e) {
Log.w("RegistrationProgressActivity", e);
return NETWORK_ERROR;
} catch (RateLimitException e) { } catch (RateLimitException e) {
Log.w("RegistrationProgressActivity", e); Log.w("RegistrationProgressActivity", e);
return RATE_LIMIT_EXCEEDED; return RATE_LIMIT_EXCEEDED;
} catch (IOException e) {
Log.w("RegistrationProgressActivity", e);
return NETWORK_ERROR;
} }
} }
}.execute(); }.execute();

View File

@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
import org.thoughtcrime.securesms.mms.TextTransport; import org.thoughtcrime.securesms.mms.TextTransport;
import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
@ -212,7 +213,7 @@ public class DecryptingQueue {
RetrieveConf plaintextPdu = new RetrieveConf(plaintextGenericPdu.getPduHeaders(), RetrieveConf plaintextPdu = new RetrieveConf(plaintextGenericPdu.getPduHeaders(),
plaintextGenericPdu.getBody()); plaintextGenericPdu.getBody());
Log.w("DecryptingQueue", "Successfully decrypted MMS!"); Log.w("DecryptingQueue", "Successfully decrypted MMS!");
database.insertSecureDecryptedMessageInbox(masterSecret, plaintextPdu, threadId); database.insertSecureDecryptedMessageInbox(masterSecret, new IncomingMediaMessage(plaintextPdu), threadId);
database.delete(messageId); database.delete(messageId);
} catch (RecipientFormattingException rfe) { } catch (RecipientFormattingException rfe) {
Log.w("DecryptingQueue", rfe); Log.w("DecryptingQueue", rfe);

View File

@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.database.model.DisplayRecord;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord; import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
import org.thoughtcrime.securesms.mms.PartParser; import org.thoughtcrime.securesms.mms.PartParser;
import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.mms.TextSlide; import org.thoughtcrime.securesms.mms.TextSlide;
@ -59,11 +60,9 @@ import ws.com.google.android.mms.InvalidHeaderValueException;
import ws.com.google.android.mms.MmsException; import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.CharacterSets; import ws.com.google.android.mms.pdu.CharacterSets;
import ws.com.google.android.mms.pdu.EncodedStringValue; import ws.com.google.android.mms.pdu.EncodedStringValue;
import ws.com.google.android.mms.pdu.MultimediaMessagePdu;
import ws.com.google.android.mms.pdu.NotificationInd; import ws.com.google.android.mms.pdu.NotificationInd;
import ws.com.google.android.mms.pdu.PduBody; import ws.com.google.android.mms.pdu.PduBody;
import ws.com.google.android.mms.pdu.PduHeaders; import ws.com.google.android.mms.pdu.PduHeaders;
import ws.com.google.android.mms.pdu.RetrieveConf;
import ws.com.google.android.mms.pdu.SendReq; import ws.com.google.android.mms.pdu.SendReq;
// XXXX Clean up MMS efficiency: // XXXX Clean up MMS efficiency:
@ -177,27 +176,23 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
} }
} }
private long getThreadIdFor(RetrieveConf retrieved) throws RecipientFormattingException { private long getThreadIdFor(IncomingMediaMessage retrieved) throws RecipientFormattingException {
try { try {
PduHeaders headers = retrieved.getPduHeaders();
Set<String> group = new HashSet<String>(); Set<String> group = new HashSet<String>();
EncodedStringValue encodedFrom = retrieved.getFrom(); EncodedStringValue encodedFrom = headers.getEncodedStringValue(PduHeaders.FROM);
group.add(new String(encodedFrom.getTextString(), CharacterSets.MIMENAME_ISO_8859_1)); group.add(new String(encodedFrom.getTextString(), CharacterSets.MIMENAME_ISO_8859_1));
EncodedStringValue[] encodedCcList = retrieved.getCc(); EncodedStringValue[] encodedCcList = headers.getEncodedStringValues(PduHeaders.CC);
if (encodedCcList != null) { if (encodedCcList != null) {
for (EncodedStringValue encodedCc : encodedCcList) { for (EncodedStringValue encodedCc : encodedCcList) {
group.add(new String(encodedCc.getTextString(), CharacterSets.MIMENAME_ISO_8859_1)); group.add(new String(encodedCc.getTextString(), CharacterSets.MIMENAME_ISO_8859_1));
} }
} }
StringBuilder sb = new StringBuilder(); String recipientsList = Util.join(group, ",");
for (String recipient : group) { Recipients recipients = RecipientFactory.getRecipientsFromString(context, recipientsList, false);
sb.append(recipient);
sb.append(",");
}
Recipients recipients = RecipientFactory.getRecipientsFromString(context, sb.toString(), false);
return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw new AssertionError(e); throw new AssertionError(e);
@ -280,7 +275,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
ContentValues contentValues = new ContentValues(); ContentValues contentValues = new ContentValues();
contentValues.put(READ, 1); contentValues.put(READ, 1);
database.update(TABLE_NAME, contentValues, THREAD_ID + " = ?", new String[] {threadId+""}); database.update(TABLE_NAME, contentValues, THREAD_ID + " = ?", new String[] {threadId + ""});
} }
public void setAllMessagesRead() { public void setAllMessagesRead() {
@ -359,7 +354,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
return new Reader(masterSecret, cursor); return new Reader(masterSecret, cursor);
} }
private Pair<Long, Long> insertMessageInbox(MasterSecret masterSecret, RetrieveConf retrieved, private Pair<Long, Long> insertMessageInbox(MasterSecret masterSecret, IncomingMediaMessage retrieved,
String contentLocation, long threadId, long mailbox) String contentLocation, long threadId, long mailbox)
throws MmsException throws MmsException
{ {
@ -367,11 +362,13 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
ContentValues contentValues = getContentValuesFromHeader(headers); ContentValues contentValues = getContentValuesFromHeader(headers);
boolean unread = Util.isDefaultSmsProvider(context) || ((mailbox & Types.SECURE_MESSAGE_BIT) != 0); boolean unread = Util.isDefaultSmsProvider(context) || ((mailbox & Types.SECURE_MESSAGE_BIT) != 0);
if (!org.thoughtcrime.securesms.util.Util.isEmpty(retrieved.getCc())) { if (threadId == -1 || retrieved.isGroupMessage()) {
try { try {
threadId = getThreadIdFor(retrieved); threadId = getThreadIdFor(retrieved);
} catch (RecipientFormattingException e) { } catch (RecipientFormattingException e) {
Log.w("MmsDatabase", e); Log.w("MmsDatabase", e);
if (threadId == -1)
throw new MmsException(e);
} }
} }
@ -382,10 +379,12 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
contentValues.put(DATE_RECEIVED, System.currentTimeMillis() / 1000); contentValues.put(DATE_RECEIVED, System.currentTimeMillis() / 1000);
contentValues.put(READ, unread ? 0 : 1); contentValues.put(READ, unread ? 0 : 1);
if (!contentValues.containsKey(DATE_SENT)) if (!contentValues.containsKey(DATE_SENT)) {
contentValues.put(DATE_SENT, contentValues.getAsLong(DATE_RECEIVED)); contentValues.put(DATE_SENT, contentValues.getAsLong(DATE_RECEIVED));
}
long messageId = insertMediaMessage(masterSecret, retrieved, contentValues); long messageId = insertMediaMessage(masterSecret, retrieved.getPduHeaders(),
retrieved.getBody(), contentValues);
if (unread) { if (unread) {
DatabaseFactory.getThreadDatabase(context).setUnread(threadId); DatabaseFactory.getThreadDatabase(context).setUnread(threadId);
@ -398,7 +397,8 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
return new Pair<Long, Long>(messageId, threadId); return new Pair<Long, Long>(messageId, threadId);
} }
public Pair<Long, Long> insertMessageInbox(MasterSecret masterSecret, RetrieveConf retrieved, public Pair<Long, Long> insertMessageInbox(MasterSecret masterSecret,
IncomingMediaMessage retrieved,
String contentLocation, long threadId) String contentLocation, long threadId)
throws MmsException throws MmsException
{ {
@ -406,7 +406,8 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
Types.BASE_INBOX_TYPE | Types.ENCRYPTION_SYMMETRIC_BIT); Types.BASE_INBOX_TYPE | Types.ENCRYPTION_SYMMETRIC_BIT);
} }
public Pair<Long, Long> insertSecureMessageInbox(MasterSecret masterSecret, RetrieveConf retrieved, public Pair<Long, Long> insertSecureMessageInbox(MasterSecret masterSecret,
IncomingMediaMessage retrieved,
String contentLocation, long threadId) String contentLocation, long threadId)
throws MmsException throws MmsException
{ {
@ -415,7 +416,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
} }
public Pair<Long, Long> insertSecureDecryptedMessageInbox(MasterSecret masterSecret, public Pair<Long, Long> insertSecureDecryptedMessageInbox(MasterSecret masterSecret,
RetrieveConf retrieved, IncomingMediaMessage retrieved,
long threadId) long threadId)
throws MmsException throws MmsException
{ {
@ -425,11 +426,11 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
public Pair<Long, Long> insertMessageInbox(NotificationInd notification) { public Pair<Long, Long> insertMessageInbox(NotificationInd notification) {
try { try {
SQLiteDatabase db = databaseHelper.getWritableDatabase(); SQLiteDatabase db = databaseHelper.getWritableDatabase();
PduHeaders headers = notification.getPduHeaders();
ContentValues contentValues = getContentValuesFromHeader(headers);
long threadId = getThreadIdFor(notification);
MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context); MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context);
long threadId = getThreadIdFor(notification);
PduHeaders headers = notification.getPduHeaders();
ContentValues contentValues = getContentValuesFromHeader(headers);
Log.w("MmsDatabse", "Message received type: " + headers.getOctet(PduHeaders.MESSAGE_TYPE)); Log.w("MmsDatabse", "Message received type: " + headers.getOctet(PduHeaders.MESSAGE_TYPE));
@ -486,21 +487,22 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
contentValues.put(DATE_RECEIVED, contentValues.getAsLong(DATE_SENT)); contentValues.put(DATE_RECEIVED, contentValues.getAsLong(DATE_SENT));
contentValues.remove(ADDRESS); contentValues.remove(ADDRESS);
long messageId = insertMediaMessage(masterSecret, sendRequest, contentValues); long messageId = insertMediaMessage(masterSecret, sendRequest.getPduHeaders(),
sendRequest.getBody(), contentValues);
Trimmer.trimThread(context, threadId); Trimmer.trimThread(context, threadId);
return messageId; return messageId;
} }
private long insertMediaMessage(MasterSecret masterSecret, private long insertMediaMessage(MasterSecret masterSecret,
MultimediaMessagePdu message, PduHeaders headers,
PduBody body,
ContentValues contentValues) ContentValues contentValues)
throws MmsException throws MmsException
{ {
SQLiteDatabase db = databaseHelper.getWritableDatabase(); SQLiteDatabase db = databaseHelper.getWritableDatabase();
PartDatabase partsDatabase = getPartDatabase(masterSecret); PartDatabase partsDatabase = getPartDatabase(masterSecret);
MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context); MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context);
PduBody body = message.getBody();
if (Types.isSymmetricEncryption(contentValues.getAsLong(MESSAGE_BOX))) { if (Types.isSymmetricEncryption(contentValues.getAsLong(MESSAGE_BOX))) {
String messageText = PartParser.getMessageText(body); String messageText = PartParser.getMessageText(body);
@ -515,7 +517,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
long messageId = db.insert(TABLE_NAME, null, contentValues); long messageId = db.insert(TABLE_NAME, null, contentValues);
addressDatabase.insertAddressesForId(messageId, message.getPduHeaders()); addressDatabase.insertAddressesForId(messageId, headers);
partsDatabase.insertParts(messageId, body); partsDatabase.insertParts(messageId, body);
notifyConversationListeners(contentValues.getAsLong(THREAD_ID)); notifyConversationListeners(contentValues.getAsLong(THREAD_ID));

View File

@ -10,7 +10,7 @@ import org.thoughtcrime.securesms.service.RegistrationService;
import org.thoughtcrime.securesms.service.SendReceiveService; import org.thoughtcrime.securesms.service.SendReceiveService;
import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.push.IncomingGcmMessage; import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.push.RateLimitException; import org.whispersystems.textsecure.push.RateLimitException;
import org.whispersystems.textsecure.util.Util; import org.whispersystems.textsecure.util.Util;
@ -33,8 +33,6 @@ public class GcmIntentService extends GCMBaseIntentService {
getGcmSocket(context).registerGcmId(registrationId); getGcmSocket(context).registerGcmId(registrationId);
} catch (IOException e) { } catch (IOException e) {
Log.w("GcmIntentService", e); Log.w("GcmIntentService", e);
} catch (RateLimitException e) {
Log.w("GcmIntentService", e);
} }
} }
} }
@ -45,22 +43,30 @@ public class GcmIntentService extends GCMBaseIntentService {
getGcmSocket(context).unregisterGcmId(); getGcmSocket(context).unregisterGcmId();
} catch (IOException ioe) { } catch (IOException ioe) {
Log.w("GcmIntentService", ioe); Log.w("GcmIntentService", ioe);
} catch (RateLimitException e) {
Log.w("GcmIntentService", e);
} }
} }
@Override @Override
protected void onMessage(Context context, Intent intent) { protected void onMessage(Context context, Intent intent) {
Log.w("GcmIntentService", "Got GCM message!");
String data = intent.getStringExtra("message"); String data = intent.getStringExtra("message");
Log.w("GcmIntentService", "GCM message: " + data); Log.w("GcmIntentService", "GCM message: " + data);
if (Util.isEmpty(data)) if (Util.isEmpty(data))
return; return;
IncomingGcmMessage message = new Gson().fromJson(data, IncomingGcmMessage.class); IncomingPushMessage message = new Gson().fromJson(data, IncomingPushMessage.class);
if (!message.hasAttachments()) handleIncomingTextMessage(context, message);
else handleIncomingMediaMessage(context, message);
}
@Override
protected void onError(Context context, String s) {
Log.w("GcmIntentService", "GCM Error: " + s);
}
private void handleIncomingTextMessage(Context context, IncomingPushMessage message) {
ArrayList<IncomingTextMessage> messages = new ArrayList<IncomingTextMessage>(); ArrayList<IncomingTextMessage> messages = new ArrayList<IncomingTextMessage>();
messages.add(new IncomingTextMessage(message)); messages.add(new IncomingTextMessage(message));
@ -70,9 +76,11 @@ public class GcmIntentService extends GCMBaseIntentService {
context.startService(receivedIntent); context.startService(receivedIntent);
} }
@Override private void handleIncomingMediaMessage(Context context, IncomingPushMessage message) {
protected void onError(Context context, String s) { Intent receivedIntent = new Intent(context, SendReceiveService.class);
Log.w("GcmIntentService", "GCM Error: " + s); receivedIntent.setAction(SendReceiveService.RECEIVE_PUSH_MMS_ACTION);
receivedIntent.putExtra("media_message", message);
context.startService(receivedIntent);
} }
private PushServiceSocket getGcmSocket(Context context) { private PushServiceSocket getGcmSocket(Context context) {

View File

@ -0,0 +1,86 @@
package org.thoughtcrime.securesms.mms;
import android.util.Log;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.push.PushAttachmentPointer;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
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.PduHeaders;
import ws.com.google.android.mms.pdu.PduPart;
import ws.com.google.android.mms.pdu.RetrieveConf;
public class IncomingMediaMessage {
private final PduHeaders headers;
private final PduBody body;
public IncomingMediaMessage(RetrieveConf retreived) {
this.headers = retreived.getPduHeaders();
this.body = retreived.getBody();
}
public IncomingMediaMessage(String localNumber, IncomingPushMessage message, List<File> attachments)
throws IOException
{
this.headers = new PduHeaders();
this.body = new PduBody();
this.headers.setEncodedStringValue(new EncodedStringValue(message.getSource()), PduHeaders.FROM);
this.headers.appendEncodedStringValue(new EncodedStringValue(localNumber), PduHeaders.TO);
for (String destination : message.getDestinations()) {
if (!destination.equals(localNumber)) {
this.headers.appendEncodedStringValue(new EncodedStringValue(destination), PduHeaders.CC);
}
}
this.headers.setLongInteger(message.getTimestampMillis() / 1000, PduHeaders.DATE);
if (message.getMessageText() != null && message.getMessageText().length() > 0) {
PduPart text = new PduPart();
text.setData(message.getMessageText().getBytes());
text.setContentType("text/plain".getBytes(CharacterSets.MIMENAME_ISO_8859_1));
body.addPart(text);
}
Iterator<PushAttachmentPointer> descriptors = message.getAttachments().iterator();
if (attachments != null) {
for (File attachment : attachments) {
PduPart media = new PduPart();
FileInputStream fin = new FileInputStream(attachment);
byte[] data = Util.readFully(fin);
PushAttachmentPointer descriptor = descriptors.next();
Log.w("IncomingMediaMessage", "Adding part: " + descriptor.getContentType() + " with length: " + data.length);
media.setContentType(descriptor.getContentType().getBytes(CharacterSets.MIMENAME_ISO_8859_1));
media.setData(data);
body.addPart(media);
}
}
}
public PduHeaders getPduHeaders() {
return headers;
}
public PduBody getBody() {
return body;
}
public boolean isGroupMessage() {
return !Util.isEmpty(headers.getEncodedStringValues(PduHeaders.CC));
}
}

View File

@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord; import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
import org.thoughtcrime.securesms.mms.ApnUnavailableException; import org.thoughtcrime.securesms.mms.ApnUnavailableException;
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
import org.thoughtcrime.securesms.mms.MmsDownloadHelper; import org.thoughtcrime.securesms.mms.MmsDownloadHelper;
import org.thoughtcrime.securesms.mms.MmsRadio; import org.thoughtcrime.securesms.mms.MmsRadio;
import org.thoughtcrime.securesms.mms.MmsRadioException; import org.thoughtcrime.securesms.mms.MmsRadioException;
@ -177,11 +178,13 @@ public class MmsDownloader {
long messageId, long threadId, RetrieveConf retrieved) long messageId, long threadId, RetrieveConf retrieved)
throws MmsException throws MmsException
{ {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context); MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
IncomingMediaMessage message = new IncomingMediaMessage(retrieved);
Pair<Long, Long> messageAndThreadId; Pair<Long, Long> messageAndThreadId;
if (retrieved.getSubject() != null && WirePrefix.isEncryptedMmsSubject(retrieved.getSubject().getString())) { if (retrieved.getSubject() != null && WirePrefix.isEncryptedMmsSubject(retrieved.getSubject().getString())) {
messageAndThreadId = database.insertSecureMessageInbox(masterSecret, retrieved, messageAndThreadId = database.insertSecureMessageInbox(masterSecret, message,
contentLocation, threadId); contentLocation, threadId);
if (masterSecret != null) if (masterSecret != null)
@ -189,7 +192,7 @@ public class MmsDownloader {
messageAndThreadId.second, retrieved); messageAndThreadId.second, retrieved);
} else { } else {
messageAndThreadId = database.insertMessageInbox(masterSecret, retrieved, messageAndThreadId = database.insertMessageInbox(masterSecret, message,
contentLocation, threadId); contentLocation, threadId);
} }

View File

@ -24,8 +24,17 @@ import android.util.Pair;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.push.RateLimitException;
import java.io.File;
import java.io.IOException;
import java.util.List;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.GenericPdu; import ws.com.google.android.mms.pdu.GenericPdu;
import ws.com.google.android.mms.pdu.NotificationInd; import ws.com.google.android.mms.pdu.NotificationInd;
import ws.com.google.android.mms.pdu.PduHeaders; import ws.com.google.android.mms.pdu.PduHeaders;
@ -39,6 +48,54 @@ public class MmsReceiver {
this.context = context; this.context = context;
} }
public void process(MasterSecret masterSecret, Intent intent) {
try {
if (intent.getAction().equals(SendReceiveService.RECEIVE_MMS_ACTION)) {
handleMmsNotification(intent);
} else if (intent.getAction().equals(SendReceiveService.RECEIVE_PUSH_MMS_ACTION)) {
handlePushMedia(masterSecret, intent);
}
} catch (MmsException e) {
Log.w("MmsReceiver", e);
}
}
private void handleMmsNotification(Intent intent) {
byte[] mmsData = intent.getByteArrayExtra("data");
PduParser parser = new PduParser(mmsData);
GenericPdu pdu = parser.parse();
if (pdu.getMessageType() == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND) {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Pair<Long, Long> messageAndThreadId = database.insertMessageInbox((NotificationInd)pdu);
Log.w("MmsReceiver", "Inserted received MMS notification...");
scheduleDownload((NotificationInd)pdu, messageAndThreadId.first, messageAndThreadId.second);
}
}
private void handlePushMedia(MasterSecret masterSecret, Intent intent) throws MmsException {
IncomingPushMessage pushMessage = intent.getParcelableExtra("media_message");
String localNumber = TextSecurePreferences.getLocalNumber(context);
String password = TextSecurePreferences.getPushServerPassword(context);
PushServiceSocket socket = new PushServiceSocket(context, localNumber, password);
try {
List<File> attachments = socket.retrieveAttachments(pushMessage.getAttachments());
IncomingMediaMessage message = new IncomingMediaMessage(localNumber, pushMessage, attachments);
DatabaseFactory.getMmsDatabase(context).insertMessageInbox(masterSecret, message, "", -1);
} catch (IOException e) {
Log.w("MmsReceiver", e);
try {
IncomingMediaMessage message = new IncomingMediaMessage(localNumber, pushMessage, null);
DatabaseFactory.getMmsDatabase(context).insertMessageInbox(masterSecret, message, "", -1);
} catch (IOException e1) {
throw new MmsException(e1);
}
}
}
private void scheduleDownload(NotificationInd pdu, long messageId, long threadId) { private void scheduleDownload(NotificationInd pdu, long messageId, long threadId) {
Intent intent = new Intent(SendReceiveService.DOWNLOAD_MMS_ACTION, null, context, SendReceiveService.class); Intent intent = new Intent(SendReceiveService.DOWNLOAD_MMS_ACTION, null, context, SendReceiveService.class);
intent.putExtra("content_location", new String(pdu.getContentLocation())); intent.putExtra("content_location", new String(pdu.getContentLocation()));
@ -50,21 +107,4 @@ public class MmsReceiver {
context.startService(intent); context.startService(intent);
} }
public void process(MasterSecret masterSecret, Intent intent) {
byte[] mmsData = intent.getByteArrayExtra("data");
PduParser parser = new PduParser(mmsData);
GenericPdu pdu = parser.parse();
if (pdu.getMessageType() == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND) {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Pair<Long, Long> messageAndThreadId = database.insertMessageInbox((NotificationInd)pdu);
// long threadId = database.getThreadIdForMessage(messageId);
// MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
scheduleDownload((NotificationInd)pdu, messageAndThreadId.first, messageAndThreadId.second);
Log.w("MmsReceiverService", "Inserted received notification...");
}
}
} }

View File

@ -5,24 +5,23 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Binder; import android.os.Binder;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import com.google.android.gcm.GCMRegistrar; import com.google.android.gcm.GCMRegistrar;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.gcm.GcmIntentService; import org.thoughtcrime.securesms.gcm.GcmIntentService;
import org.thoughtcrime.securesms.gcm.GcmRegistrationTimeoutException; import org.thoughtcrime.securesms.gcm.GcmRegistrationTimeoutException;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.directory.DirectoryDescriptor;
import org.whispersystems.textsecure.directory.NumberFilter;
import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.push.RateLimitException;
import org.whispersystems.textsecure.util.Util; import org.whispersystems.textsecure.util.Util;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -172,7 +171,8 @@ public class RegistrationService extends Service {
String gcmRegistrationId = waitForGcmRegistrationId(); String gcmRegistrationId = waitForGcmRegistrationId();
socket.registerGcmId(gcmRegistrationId); socket.registerGcmId(gcmRegistrationId);
socket.retrieveDirectory(this); Pair<DirectoryDescriptor, File> directory = socket.retrieveDirectory();
NumberFilter.getInstance(this).update(directory.first, directory.second);
markAsVerified(number, password); markAsVerified(number, password);
@ -190,10 +190,6 @@ public class RegistrationService extends Service {
Log.w("RegistrationService", e); Log.w("RegistrationService", e);
setState(new RegistrationState(RegistrationState.STATE_GCM_TIMEOUT)); setState(new RegistrationState(RegistrationState.STATE_GCM_TIMEOUT));
broadcastComplete(false); broadcastComplete(false);
} catch (RateLimitException e) {
Log.w("RegistrationService", e);
setState(new RegistrationState(RegistrationState.STATE_NETWORK_ERROR));
broadcastComplete(false);
} finally { } finally {
shutdownGcmRegistrationListener(); shutdownGcmRegistrationListener();
} }
@ -222,7 +218,8 @@ public class RegistrationService extends Service {
String gcmRegistrationId = waitForGcmRegistrationId(); String gcmRegistrationId = waitForGcmRegistrationId();
socket.registerGcmId(gcmRegistrationId); socket.registerGcmId(gcmRegistrationId);
socket.retrieveDirectory(this); Pair<DirectoryDescriptor, File> directory = socket.retrieveDirectory();
NumberFilter.getInstance(this).update(directory.first, directory.second);
markAsVerified(number, password); markAsVerified(number, password);
@ -244,10 +241,6 @@ public class RegistrationService extends Service {
Log.w("RegistrationService", e); Log.w("RegistrationService", e);
setState(new RegistrationState(RegistrationState.STATE_GCM_TIMEOUT)); setState(new RegistrationState(RegistrationState.STATE_GCM_TIMEOUT));
broadcastComplete(false); broadcastComplete(false);
} catch (RateLimitException e) {
Log.w("RegistrationService", e);
setState(new RegistrationState(RegistrationState.STATE_NETWORK_ERROR));
broadcastComplete(false);
} finally { } finally {
shutdownChallengeListener(); shutdownChallengeListener();
shutdownGcmRegistrationListener(); shutdownGcmRegistrationListener();

View File

@ -51,6 +51,7 @@ public class SendReceiveService extends Service {
public static final String RECEIVE_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.RECEIVE_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_ACTION = "org.thoughtcrime.securesms.SendReceiveService.SEND_MMS_ACTION";
public static final String RECEIVE_MMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.RECEIVE_MMS_ACTION"; public static final String RECEIVE_MMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.RECEIVE_MMS_ACTION";
public static final String RECEIVE_PUSH_MMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.RECEIVE_PUSH_MMS_ACTION";
public static final String DOWNLOAD_MMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_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"; public static final String DOWNLOAD_MMS_CONNECTIVITY_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_MMS_CONNECTIVITY_ACTION";
public static final String DOWNLOAD_MMS_PENDING_APN_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_MMS_PENDING_APN_ACTION"; public static final String DOWNLOAD_MMS_PENDING_APN_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_MMS_PENDING_APN_ACTION";
@ -92,19 +93,21 @@ public class SendReceiveService extends Service {
public void onStart(Intent intent, int startId) { public void onStart(Intent intent, int startId) {
if (intent == null) return; if (intent == null) return;
if (intent.getAction().equals(SEND_SMS_ACTION)) String action = intent.getAction();
if (action.equals(SEND_SMS_ACTION))
scheduleSecretRequiredIntent(SEND_SMS, intent); scheduleSecretRequiredIntent(SEND_SMS, intent);
else if (intent.getAction().equals(RECEIVE_SMS_ACTION)) else if (action.equals(RECEIVE_SMS_ACTION))
scheduleIntent(RECEIVE_SMS, intent); scheduleIntent(RECEIVE_SMS, intent);
else if (intent.getAction().equals(SENT_SMS_ACTION)) else if (action.equals(SENT_SMS_ACTION))
scheduleIntent(SEND_SMS, intent); scheduleIntent(SEND_SMS, intent);
else if (intent.getAction().equals(DELIVERED_SMS_ACTION)) else if (action.equals(DELIVERED_SMS_ACTION))
scheduleIntent(SEND_SMS, intent); scheduleIntent(SEND_SMS, intent);
else if (intent.getAction().equals(SEND_MMS_ACTION)) else if (action.equals(SEND_MMS_ACTION))
scheduleSecretRequiredIntent(SEND_MMS, intent); scheduleSecretRequiredIntent(SEND_MMS, intent);
else if (intent.getAction().equals(RECEIVE_MMS_ACTION)) else if (action.equals(RECEIVE_MMS_ACTION) || action.equals(RECEIVE_PUSH_MMS_ACTION))
scheduleIntent(RECEIVE_MMS, intent); scheduleIntent(RECEIVE_MMS, intent);
else if (intent.getAction().equals(DOWNLOAD_MMS_ACTION)) else if (action.equals(DOWNLOAD_MMS_ACTION))
scheduleSecretRequiredIntent(DOWNLOAD_MMS, intent); scheduleSecretRequiredIntent(DOWNLOAD_MMS, intent);
else if (intent.getAction().equals(DOWNLOAD_MMS_PENDING_APN_ACTION)) else if (intent.getAction().equals(DOWNLOAD_MMS_PENDING_APN_ACTION))
scheduleSecretRequiredIntent(DOWNLOAD_MMS_PENDING, intent); scheduleSecretRequiredIntent(DOWNLOAD_MMS_PENDING, intent);

View File

@ -4,7 +4,7 @@ import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.telephony.SmsMessage; import android.telephony.SmsMessage;
import org.whispersystems.textsecure.push.IncomingGcmMessage; import org.whispersystems.textsecure.push.IncomingPushMessage;
import java.util.List; import java.util.List;
@ -40,7 +40,7 @@ public class IncomingTextMessage implements Parcelable {
this.sentTimestampMillis = message.getTimestampMillis(); this.sentTimestampMillis = message.getTimestampMillis();
} }
public IncomingTextMessage(IncomingGcmMessage message) { public IncomingTextMessage(IncomingPushMessage message) {
this.message = message.getMessageText(); this.message = message.getMessageText();
this.sender = message.getSource(); this.sender = message.getSource();
this.protocol = 31337; this.protocol = 31337;

View File

@ -8,7 +8,7 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.mms.PartParser; import org.thoughtcrime.securesms.mms.PartParser;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.textsecure.push.PushAttachment; import org.whispersystems.textsecure.push.PushAttachmentData;
import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.push.RateLimitException; import org.whispersystems.textsecure.push.RateLimitException;
import org.whispersystems.textsecure.util.PhoneNumberFormatter; import org.whispersystems.textsecure.util.PhoneNumberFormatter;
@ -52,11 +52,11 @@ public class PushTransport extends BaseTransport {
public void deliver(SendReq message, List<String> destinations) throws IOException { public void deliver(SendReq message, List<String> destinations) throws IOException {
try { try {
String localNumber = TextSecurePreferences.getLocalNumber(context); String localNumber = TextSecurePreferences.getLocalNumber(context);
String password = TextSecurePreferences.getPushServerPassword(context); String password = TextSecurePreferences.getPushServerPassword(context);
PushServiceSocket socket = new PushServiceSocket(context, localNumber, password); PushServiceSocket socket = new PushServiceSocket(context, localNumber, password);
String messageText = PartParser.getMessageText(message.getBody()); String messageText = PartParser.getMessageText(message.getBody());
List<PushAttachment> attachments = getAttachmentsFromBody(message.getBody()); List<PushAttachmentData> attachments = getAttachmentsFromBody(message.getBody());
if (attachments.isEmpty()) socket.sendMessage(destinations, messageText); if (attachments.isEmpty()) socket.sendMessage(destinations, messageText);
else socket.sendMessage(destinations, messageText, attachments); else socket.sendMessage(destinations, messageText, attachments);
@ -66,8 +66,8 @@ public class PushTransport extends BaseTransport {
} }
} }
private List<PushAttachment> getAttachmentsFromBody(PduBody body) { private List<PushAttachmentData> getAttachmentsFromBody(PduBody body) {
List<PushAttachment> attachments = new LinkedList<PushAttachment>(); List<PushAttachmentData> attachments = new LinkedList<PushAttachmentData>();
for (int i=0;i<body.getPartsNum();i++) { for (int i=0;i<body.getPartsNum();i++) {
String contentType = Util.toIsoString(body.getPart(i).getContentType()); String contentType = Util.toIsoString(body.getPart(i).getContentType());
@ -76,7 +76,7 @@ public class PushTransport extends BaseTransport {
ContentType.isAudioType(contentType) || ContentType.isAudioType(contentType) ||
ContentType.isVideoType(contentType)) ContentType.isVideoType(contentType))
{ {
attachments.add(new PushAttachment(contentType, body.getPart(i).getData())); attachments.add(new PushAttachmentData(contentType, body.getPart(i).getData()));
} }
} }

View File

@ -29,7 +29,11 @@ import android.provider.Telephony;
import org.thoughtcrime.securesms.mms.MmsRadio; import org.thoughtcrime.securesms.mms.MmsRadio;
import org.whispersystems.textsecure.util.PhoneNumberFormatter; import org.whispersystems.textsecure.util.PhoneNumberFormatter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -151,6 +155,19 @@ public class Util {
return PhoneNumberFormatter.formatNumber(number, localNumber); return PhoneNumberFormatter.formatNumber(number, localNumber);
} }
public static byte[] readFully(InputStream in) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[4069];
int read;
while ((read = in.read(buffer)) != -1) {
baos.write(buffer, 0, read);
}
in.close();
return baos.toByteArray();
}
public static boolean isDefaultSmsProvider(Context context){ public static boolean isDefaultSmsProvider(Context context){
return (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) || return (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) ||