mirror of
https://github.com/oxen-io/session-android.git
synced 2025-10-27 12:39:04 +00:00
Support for incoming attachments.
1) Refactored MMS layer to use abstracted types. 2) Added support for retrieving attachment IDs.
This commit is contained in:
@@ -33,6 +33,7 @@ import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
this.bloomFilter.delete();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,13 @@ import java.util.List;
|
||||
|
||||
public class OutgoingPushMessage {
|
||||
|
||||
private List<String> destinations;
|
||||
private String messageText;
|
||||
private List<String> attachments;
|
||||
private List<String> destinations;
|
||||
private String messageText;
|
||||
private List<PushAttachmentPointer> attachments;
|
||||
|
||||
public OutgoingPushMessage(String destination, String messageText) {
|
||||
this.destinations = new LinkedList<String>();
|
||||
this.attachments = new LinkedList<String>();
|
||||
this.attachments = new LinkedList<PushAttachmentPointer>();
|
||||
this.messageText = messageText;
|
||||
this.destinations.add(destination);
|
||||
}
|
||||
@@ -19,11 +19,11 @@ public class OutgoingPushMessage {
|
||||
public OutgoingPushMessage(List<String> destinations, String messageText) {
|
||||
this.destinations = destinations;
|
||||
this.messageText = messageText;
|
||||
this.attachments = new LinkedList<String>();
|
||||
this.attachments = new LinkedList<PushAttachmentPointer>();
|
||||
}
|
||||
|
||||
public OutgoingPushMessage(List<String> destinations, String messageText,
|
||||
List<String> attachments)
|
||||
List<PushAttachmentPointer> attachments)
|
||||
{
|
||||
this.destinations = destinations;
|
||||
this.messageText = messageText;
|
||||
@@ -38,7 +38,7 @@ public class OutgoingPushMessage {
|
||||
return messageText;
|
||||
}
|
||||
|
||||
public List<String> getAttachments() {
|
||||
public List<PushAttachmentPointer> getAttachments() {
|
||||
return attachments;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
public class PushAttachment {
|
||||
public class PushAttachmentData {
|
||||
|
||||
private final String contentType;
|
||||
private final byte[] data;
|
||||
|
||||
public PushAttachment(String contentType, byte[] data) {
|
||||
public PushAttachmentData(String contentType, byte[] data) {
|
||||
this.contentType = contentType;
|
||||
this.data = data;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String getContentType() {
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package org.whispersystems.textsecure.push;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GcmMessageResponse {
|
||||
public class PushMessageResponse {
|
||||
private List<String> success;
|
||||
private List<String> failure;
|
||||
|
||||
@@ -23,8 +23,6 @@ import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
@@ -46,11 +44,13 @@ public class PushServiceSocket {
|
||||
private static final String MESSAGE_PATH = "/v1/messages/";
|
||||
private static final String ATTACHMENT_PATH = "/v1/attachments/%s";
|
||||
|
||||
private final Context context;
|
||||
private final String localNumber;
|
||||
private final String password;
|
||||
private final TrustManagerFactory trustManagerFactory;
|
||||
|
||||
public PushServiceSocket(Context context, String localNumber, String password) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.localNumber = localNumber;
|
||||
this.password = password;
|
||||
this.trustManagerFactory = initializeTrustManagerFactory(context);
|
||||
@@ -70,54 +70,55 @@ public class PushServiceSocket {
|
||||
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);
|
||||
}
|
||||
|
||||
public void sendMessage(String recipient, String messageText)
|
||||
throws IOException, RateLimitException
|
||||
throws IOException
|
||||
{
|
||||
OutgoingPushMessage message = new OutgoingPushMessage(recipient, messageText);
|
||||
sendMessage(message);
|
||||
}
|
||||
|
||||
public void sendMessage(List<String> recipients, String messageText)
|
||||
throws IOException, RateLimitException
|
||||
throws IOException
|
||||
{
|
||||
OutgoingPushMessage message = new OutgoingPushMessage(recipients, messageText);
|
||||
sendMessage(message);
|
||||
}
|
||||
|
||||
public void sendMessage(List<String> recipients, String messageText,
|
||||
List<PushAttachment> attachments)
|
||||
throws IOException, RateLimitException
|
||||
List<PushAttachmentData> attachments)
|
||||
throws IOException
|
||||
{
|
||||
List<String> attachmentIds = sendAttachments(attachments);
|
||||
OutgoingPushMessage message = new OutgoingPushMessage(recipients, messageText, attachmentIds);
|
||||
List<PushAttachmentPointer> attachmentIds = sendAttachments(attachments);
|
||||
OutgoingPushMessage message = new OutgoingPushMessage(recipients, messageText, attachmentIds);
|
||||
sendMessage(message);
|
||||
}
|
||||
|
||||
private void sendMessage(OutgoingPushMessage message) throws IOException, RateLimitException {
|
||||
String responseText = makeRequest(MESSAGE_PATH, "POST", new Gson().toJson(message));
|
||||
GcmMessageResponse response = new Gson().fromJson(responseText, GcmMessageResponse.class);
|
||||
private void sendMessage(OutgoingPushMessage message) throws IOException {
|
||||
String responseText = makeRequest(MESSAGE_PATH, "POST", new Gson().toJson(message));
|
||||
PushMessageResponse response = new Gson().fromJson(responseText, PushMessageResponse.class);
|
||||
|
||||
if (response.getFailure().size() != 0)
|
||||
throw new IOException("Got send failure: " + response.getFailure().get(0));
|
||||
}
|
||||
|
||||
private List<String> sendAttachments(List<PushAttachment> attachments)
|
||||
throws IOException, RateLimitException
|
||||
private List<PushAttachmentPointer> sendAttachments(List<PushAttachmentData> attachments)
|
||||
throws IOException
|
||||
{
|
||||
List<String> attachmentIds = new LinkedList<String>();
|
||||
List<PushAttachmentPointer> attachmentIds = new LinkedList<PushAttachmentPointer>();
|
||||
|
||||
for (PushAttachment attachment : attachments) {
|
||||
attachmentIds.add(sendAttachment(attachment));
|
||||
for (PushAttachmentData attachment : attachments) {
|
||||
attachmentIds.add(new PushAttachmentPointer(attachment.getContentType(),
|
||||
sendAttachment(attachment)));
|
||||
}
|
||||
|
||||
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, ""),
|
||||
"GET", null, "Content-Location");
|
||||
|
||||
@@ -130,34 +131,52 @@ public class PushServiceSocket {
|
||||
|
||||
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 {
|
||||
DirectoryDescriptor directoryDescriptor = new Gson().fromJson(makeRequest(DIRECTORY_PATH, "GET", null),
|
||||
DirectoryDescriptor.class);
|
||||
|
||||
File directoryData = File.createTempFile("directory", ".dat", context.getFilesDir());
|
||||
|
||||
downloadExternalFile(directoryDescriptor.getUrl(), directoryData);
|
||||
|
||||
NumberFilter.getInstance(context).update(directoryData,
|
||||
directoryDescriptor.getCapacity(),
|
||||
directoryDescriptor.getHashCount(),
|
||||
directoryDescriptor.getVersion());
|
||||
|
||||
return new Pair<DirectoryDescriptor, File>(directoryDescriptor, directoryData);
|
||||
} catch (IOException ioe) {
|
||||
Log.w("PushServiceSocket", ioe);
|
||||
} catch (RateLimitException e) {
|
||||
Log.w("PushServiceSocket", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void downloadExternalFile(String url, File localDestination)
|
||||
throws IOException
|
||||
{
|
||||
URL downloadUrl = new URL(url);
|
||||
HttpsURLConnection connection = (HttpsURLConnection) downloadUrl.openConnection();
|
||||
URL downloadUrl = new URL(url);
|
||||
HttpURLConnection connection = (HttpURLConnection) downloadUrl.openConnection();
|
||||
connection.setRequestProperty("Content-Type", "application/octet-stream");
|
||||
connection.setRequestMethod("GET");
|
||||
connection.setDoInput(true);
|
||||
|
||||
try {
|
||||
@@ -166,7 +185,7 @@ public class PushServiceSocket {
|
||||
}
|
||||
|
||||
OutputStream output = new FileOutputStream(localDestination);
|
||||
InputStream input = new GZIPInputStream(connection.getInputStream());
|
||||
InputStream input = connection.getInputStream();
|
||||
byte[] buffer = new byte[4096];
|
||||
int read;
|
||||
|
||||
@@ -175,6 +194,7 @@ public class PushServiceSocket {
|
||||
}
|
||||
|
||||
output.close();
|
||||
Log.w("PushServiceSocket", "Downloaded: " + url + " to: " + localDestination.getAbsolutePath());
|
||||
} finally {
|
||||
connection.disconnect();
|
||||
}
|
||||
@@ -205,7 +225,7 @@ public class PushServiceSocket {
|
||||
|
||||
private Pair<String, String> makeRequestForResponseHeader(String urlFragment, String method,
|
||||
String body, String responseHeader)
|
||||
throws IOException, RateLimitException
|
||||
throws IOException
|
||||
{
|
||||
HttpURLConnection connection = makeBaseRequest(urlFragment, method, body);
|
||||
String response = Util.readFully(connection.getInputStream());
|
||||
@@ -216,7 +236,7 @@ public class PushServiceSocket {
|
||||
}
|
||||
|
||||
private String makeRequest(String urlFragment, String method, String body)
|
||||
throws IOException, RateLimitException
|
||||
throws IOException
|
||||
{
|
||||
HttpURLConnection connection = makeBaseRequest(urlFragment, method, body);
|
||||
String response = Util.readFully(connection.getInputStream());
|
||||
@@ -227,7 +247,7 @@ public class PushServiceSocket {
|
||||
}
|
||||
|
||||
private HttpURLConnection makeBaseRequest(String urlFragment, String method, String body)
|
||||
throws IOException, RateLimitException
|
||||
throws IOException
|
||||
{
|
||||
HttpURLConnection connection = getConnection(urlFragment, method);
|
||||
|
||||
@@ -314,7 +334,7 @@ public class PushServiceSocket {
|
||||
}
|
||||
}
|
||||
|
||||
private class GcmRegistrationId {
|
||||
private static class GcmRegistrationId {
|
||||
private String gcmRegistrationId;
|
||||
|
||||
public GcmRegistrationId() {}
|
||||
@@ -324,10 +344,10 @@ public class PushServiceSocket {
|
||||
}
|
||||
}
|
||||
|
||||
private class AttachmentDescriptor {
|
||||
private static class AttachmentKey {
|
||||
private String id;
|
||||
|
||||
public AttachmentDescriptor(String id) {
|
||||
public AttachmentKey(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
|
||||
public class RateLimitException extends Exception {
|
||||
import java.io.IOException;
|
||||
|
||||
public class RateLimitException extends IOException {
|
||||
public RateLimitException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class Util {
|
||||
public static boolean isEmpty(String value) {
|
||||
@@ -55,4 +57,18 @@ public class Util {
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user