diff --git a/library/protobuf/IncomingPushMessageSignal.proto b/library/protobuf/IncomingPushMessageSignal.proto index 1e82872705..90465ae279 100644 --- a/library/protobuf/IncomingPushMessageSignal.proto +++ b/library/protobuf/IncomingPushMessageSignal.proto @@ -7,13 +7,18 @@ message IncomingPushMessageSignal { optional uint32 type = 1; optional string source = 2; repeated string destinations = 3; - optional bytes message = 4; + optional uint64 timestamp = 4; + optional bytes message = 5; // Contains an encrypted IncomingPushMessageContent +} + +message PushMessageContent { + optional string body = 1; message AttachmentPointer { - optional string contentType = 1; - optional string key = 2; + optional fixed64 id = 1; + optional string contentType = 2; + optional bytes key = 3; } - repeated AttachmentPointer attachments = 5; - optional uint64 timestamp = 6; + repeated AttachmentPointer attachments = 2; } \ No newline at end of file diff --git a/library/src/org/whispersystems/textsecure/crypto/AttachmentCipher.java b/library/src/org/whispersystems/textsecure/crypto/AttachmentCipher.java new file mode 100644 index 0000000000..4496f8653a --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/AttachmentCipher.java @@ -0,0 +1,156 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.whispersystems.textsecure.crypto; + +import android.util.Log; + +import org.whispersystems.textsecure.util.Hex; +import org.whispersystems.textsecure.util.Util; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.Mac; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +/** + * Encrypts push attachments. + * + * @author Moxie Marlinspike + */ +public class AttachmentCipher { + + static final int CIPHER_KEY_SIZE = 32; + static final int MAC_KEY_SIZE = 20; + + private final SecretKeySpec cipherKey; + private final SecretKeySpec macKey; + private final Cipher cipher; + private final Mac mac; + + public AttachmentCipher() { + this.cipherKey = initializeRandomCipherKey(); + this.macKey = initializeRandomMacKey(); + this.cipher = initializeCipher(); + this.mac = initializeMac(); + } + + public AttachmentCipher(byte[] combinedKeyMaterial) { + byte[][] parts = Util.split(combinedKeyMaterial, CIPHER_KEY_SIZE, MAC_KEY_SIZE); + this.cipherKey = new SecretKeySpec(parts[0], "AES"); + this.macKey = new SecretKeySpec(parts[1], "HmacSHA1"); + this.cipher = initializeCipher(); + this.mac = initializeMac(); + } + + public byte[] getCombinedKeyMaterial() { + return Util.combine(this.cipherKey.getEncoded(), this.macKey.getEncoded()); + } + + public byte[] encrypt(byte[] plaintext) { + try { + this.cipher.init(Cipher.ENCRYPT_MODE, this.cipherKey); + this.mac.init(this.macKey); + + byte[] ciphertext = this.cipher.doFinal(plaintext); + byte[] iv = this.cipher.getIV(); + byte[] mac = this.mac.doFinal(Util.combine(iv, ciphertext)); + + return Util.combine(iv, ciphertext, mac); + } catch (IllegalBlockSizeException e) { + throw new AssertionError(e); + } catch (BadPaddingException e) { + throw new AssertionError(e); + } catch (InvalidKeyException e) { + throw new AssertionError(e); + } + } + + public byte[] decrypt(byte[] ciphertext) + throws InvalidMacException, InvalidMessageException + { + try { + if (ciphertext.length <= cipher.getBlockSize() + mac.getMacLength()) { + throw new InvalidMessageException("Message too short!"); + } + + byte[][] ciphertextParts = Util.split(ciphertext, + this.cipher.getBlockSize(), + ciphertext.length - this.cipher.getBlockSize() - this.mac.getMacLength(), + this.mac.getMacLength()); + + this.mac.update(ciphertext, 0, ciphertext.length - mac.getMacLength()); + byte[] ourMac = this.mac.doFinal(); + + if (!Arrays.equals(ourMac, ciphertextParts[2])) { + throw new InvalidMacException("Mac doesn't match!"); + } + + this.cipher.init(Cipher.DECRYPT_MODE, this.cipherKey, + new IvParameterSpec(ciphertextParts[0])); + + return cipher.doFinal(ciphertextParts[1]); + } catch (InvalidKeyException e) { + throw new AssertionError(e); + } catch (InvalidAlgorithmParameterException e) { + throw new AssertionError(e); + } catch (IllegalBlockSizeException e) { + throw new AssertionError(e); + } catch (BadPaddingException e) { + throw new InvalidMessageException(e); + } + } + + private Mac initializeMac() { + try { + Mac mac = Mac.getInstance("HmacSHA1"); + return mac; + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + } + + private Cipher initializeCipher() { + try { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + return cipher; + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } catch (NoSuchPaddingException e) { + throw new AssertionError(e); + } + } + + private SecretKeySpec initializeRandomCipherKey() { + byte[] key = new byte[CIPHER_KEY_SIZE]; + Util.getSecureRandom().nextBytes(key); + return new SecretKeySpec(key, "AES"); + } + + private SecretKeySpec initializeRandomMacKey() { + byte[] key = new byte[MAC_KEY_SIZE]; + Util.getSecureRandom().nextBytes(key); + return new SecretKeySpec(key, "HmacSHA1"); + } + +} diff --git a/library/src/org/whispersystems/textsecure/crypto/AttachmentCipherInputStream.java b/library/src/org/whispersystems/textsecure/crypto/AttachmentCipherInputStream.java new file mode 100644 index 0000000000..753e4c80fd --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/AttachmentCipherInputStream.java @@ -0,0 +1,178 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.whispersystems.textsecure.crypto; + +import android.util.Log; + +import org.whispersystems.textsecure.util.Hex; +import org.whispersystems.textsecure.util.Util; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.Mac; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +/** + * Class for streaming an encrypted push attachment off disk. + * + * @author Moxie Marlinspike + */ + +public class AttachmentCipherInputStream extends FileInputStream { + + private static final int BLOCK_SIZE = 16; + + private Cipher cipher; + private boolean done; + private long totalDataSize; + private long totalRead; + + public AttachmentCipherInputStream(File file, byte[] combinedKeyMaterial) + throws IOException, InvalidMessageException + { + super(file); + + try { + byte[][] parts = Util.split(combinedKeyMaterial, + AttachmentCipher.CIPHER_KEY_SIZE, + AttachmentCipher.MAC_KEY_SIZE); + + Mac mac = Mac.getInstance("HmacSHA1"); + mac.init(new SecretKeySpec(parts[1], "HmacSHA1")); + + if (file.length() <= BLOCK_SIZE + mac.getMacLength()) { + throw new InvalidMessageException("Message shorter than crypto overhead!"); + } + + verifyMac(file, mac); + + byte[] iv = new byte[BLOCK_SIZE]; + readFully(iv); + + this.cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + this.cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(parts[0], "AES"), new IvParameterSpec(iv)); + + this.done = false; + this.totalRead = 0; + this.totalDataSize = file.length() - cipher.getBlockSize() - mac.getMacLength(); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } catch (InvalidKeyException e) { + throw new AssertionError(e); + } catch (InvalidMacException e) { + throw new InvalidMessageException(e); + } catch (NoSuchPaddingException e) { + throw new AssertionError(e); + } catch (InvalidAlgorithmParameterException e) { + throw new AssertionError(e); + } + } + + @Override + public int read(byte[] buffer) throws IOException { + return read(buffer, 0, buffer.length); + } + + @Override + public int read(byte[] buffer, int offset, int length) throws IOException { + if (totalRead != totalDataSize) return readIncremental(buffer, offset, length); + else if (!done) return readFinal(buffer, offset, length); + else return -1; + } + + private int readFinal(byte[] buffer, int offset, int length) throws IOException { + try { + int flourish = cipher.doFinal(buffer, offset); + + done = true; + return flourish; + } catch (IllegalBlockSizeException e) { + Log.w("EncryptingPartInputStream", e); + throw new IOException("Illegal block size exception!"); + } catch (ShortBufferException e) { + Log.w("EncryptingPartInputStream", e); + throw new IOException("Short buffer exception!"); + } catch (BadPaddingException e) { + Log.w("EncryptingPartInputStream", e); + throw new IOException("Bad padding exception!"); + } + } + + private int readIncremental(byte[] buffer, int offset, int length) throws IOException { + if (length + totalRead > totalDataSize) + length = (int)(totalDataSize - totalRead); + + byte[] internalBuffer = new byte[length]; + int read = super.read(internalBuffer, 0, internalBuffer.length <= cipher.getBlockSize() ? internalBuffer.length : internalBuffer.length - cipher.getBlockSize()); + totalRead += read; + + try { + return cipher.update(internalBuffer, 0, read, buffer, offset); + } catch (ShortBufferException e) { + throw new AssertionError(e); + } + } + + private void verifyMac(File file, Mac mac) throws FileNotFoundException, InvalidMacException { + try { + FileInputStream fin = new FileInputStream(file); + int remainingData = (int) file.length() - mac.getMacLength(); + byte[] buffer = new byte[4096]; + + while (remainingData > 0) { + int read = fin.read(buffer, 0, Math.min(buffer.length, remainingData)); + mac.update(buffer, 0, read); + remainingData -= read; + } + + byte[] ourMac = mac.doFinal(); + byte[] theirMac = new byte[mac.getMacLength()]; + Util.readFully(fin, theirMac); + + if (!Arrays.equals(ourMac, theirMac)) { + throw new InvalidMacException("MAC doesn't match!"); + } + } catch (IOException e1) { + throw new InvalidMacException(e1); + } + } + + private void readFully(byte[] buffer) throws IOException { + int offset = 0; + + for (;;) { + int read = super.read(buffer, offset, buffer.length - offset); + + if (read + offset < buffer.length) offset += read; + else return; + } + } + + +} diff --git a/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java b/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java index 57b2fd1427..8de52cd294 100644 --- a/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java +++ b/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java @@ -1,12 +1,26 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package org.whispersystems.textsecure.push; -import org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal; -import org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer; -import org.whispersystems.textsecure.util.Base64; - import android.os.Parcel; import android.os.Parcelable; +import org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal; + import java.util.LinkedList; import java.util.List; @@ -24,12 +38,20 @@ public class IncomingPushMessage implements PushMessage, Parcelable { } }; - private int type; - private String source; - private List destinations; - private byte[] message; - private List attachments; - private long timestamp; + private int type; + private String source; + private List destinations; + private byte[] message; + private long timestamp; + + private IncomingPushMessage(IncomingPushMessage message, byte[] body) { + this.type = message.type; + this.source = message.source; + this.destinations = new LinkedList(); + this.destinations.addAll(message.destinations); + this.message = body; + this.timestamp = message.timestamp; + } public IncomingPushMessage(IncomingPushMessageSignal signal) { this.type = signal.getType(); @@ -37,25 +59,15 @@ public class IncomingPushMessage implements PushMessage, Parcelable { this.destinations = signal.getDestinationsList(); this.message = signal.getMessage().toByteArray(); this.timestamp = signal.getTimestamp(); - this.attachments = new LinkedList(); - - List attachmentPointers = signal.getAttachmentsList(); - - for (AttachmentPointer pointer : attachmentPointers) { - this.attachments.add(new PushAttachmentPointer(pointer.getContentType(), pointer.getKey())); - } } public IncomingPushMessage(Parcel in) { this.destinations = new LinkedList(); - this.attachments = new LinkedList(); - this.type = in.readInt(); this.source = in.readString(); in.readStringList(destinations); this.message = new byte[in.readInt()]; in.readByteArray(this.message); - in.readList(attachments, PushAttachmentPointer.class.getClassLoader()); this.timestamp = in.readLong(); } @@ -67,10 +79,6 @@ public class IncomingPushMessage implements PushMessage, Parcelable { return source; } - public List getAttachments() { - return attachments; - } - public byte[] getBody() { return message; } @@ -79,10 +87,6 @@ public class IncomingPushMessage implements PushMessage, Parcelable { return destinations; } - public boolean hasAttachments() { - return getAttachments() != null && !getAttachments().isEmpty(); - } - @Override public int describeContents() { return 0; @@ -95,11 +99,22 @@ public class IncomingPushMessage implements PushMessage, Parcelable { dest.writeStringList(destinations); dest.writeInt(message.length); dest.writeByteArray(message); - dest.writeList(attachments); dest.writeLong(timestamp); } + public IncomingPushMessage withBody(byte[] body) { + return new IncomingPushMessage(this, body); + } + public int getType() { return type; } + + public boolean isSecureMessage() { + return getType() == PushMessage.TYPE_MESSAGE_CIPHERTEXT; + } + + public boolean isPreKeyBundle() { + return getType() == PushMessage.TYPE_MESSAGE_PREKEY_BUNDLE; + } } diff --git a/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java b/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java index 487a0da07d..1cfe139895 100644 --- a/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java +++ b/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java @@ -1,34 +1,35 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package org.whispersystems.textsecure.push; import org.whispersystems.textsecure.util.Base64; -import java.util.LinkedList; -import java.util.List; - public class OutgoingPushMessage implements PushMessage { - private int type; - private String destination; - private String body; - private List attachments; + private int type; + private String destination; + private String body; public OutgoingPushMessage(String destination, byte[] body, int type) { - this.attachments = new LinkedList(); this.destination = destination; this.body = Base64.encodeBytes(body); this.type = type; } - public OutgoingPushMessage(String destination, byte[] body, - List attachments, - int type) - { - this.destination = destination; - this.body = Base64.encodeBytes(body); - this.attachments = attachments; - this.type = type; - } - public String getDestination() { return destination; } @@ -37,10 +38,6 @@ public class OutgoingPushMessage implements PushMessage { return body; } - public List getAttachments() { - return attachments; - } - public int getType() { return type; } diff --git a/library/src/org/whispersystems/textsecure/push/PushAttachmentPointer.java b/library/src/org/whispersystems/textsecure/push/PushAttachmentPointer.java index 8a3a81286e..30e893861b 100644 --- a/library/src/org/whispersystems/textsecure/push/PushAttachmentPointer.java +++ b/library/src/org/whispersystems/textsecure/push/PushAttachmentPointer.java @@ -18,23 +18,33 @@ public class PushAttachmentPointer implements Parcelable { }; private final String contentType; - private final String key; + private final long id; + private final byte[] key; - public PushAttachmentPointer(String contentType, String key) { + public PushAttachmentPointer(String contentType, long id, byte[] key) { this.contentType = contentType; + this.id = id; this.key = key; } public PushAttachmentPointer(Parcel in) { this.contentType = in.readString(); - this.key = in.readString(); + this.id = in.readLong(); + + int keyLength = in.readInt(); + this.key = new byte[keyLength]; + in.readByteArray(this.key); } public String getContentType() { return contentType; } - public String getKey() { + public long getId() { + return id; + } + + public byte[] getKey() { return key; } @@ -46,6 +56,8 @@ public class PushAttachmentPointer implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(contentType); - dest.writeString(key); + dest.writeLong(id); + dest.writeInt(this.key.length); + dest.writeByteArray(this.key); } } diff --git a/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java b/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java index ff79ffa74e..ca0179643d 100644 --- a/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java +++ b/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java @@ -24,23 +24,13 @@ public final class PushMessageProtos { int getDestinationsCount(); String getDestinations(int index); - // optional bytes message = 4; - boolean hasMessage(); - com.google.protobuf.ByteString getMessage(); - - // repeated .textsecure.IncomingPushMessageSignal.AttachmentPointer attachments = 5; - java.util.List - getAttachmentsList(); - org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer getAttachments(int index); - int getAttachmentsCount(); - java.util.List - getAttachmentsOrBuilderList(); - org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointerOrBuilder getAttachmentsOrBuilder( - int index); - - // optional uint64 timestamp = 6; + // optional uint64 timestamp = 4; boolean hasTimestamp(); long getTimestamp(); + + // optional bytes message = 5; + boolean hasMessage(); + com.google.protobuf.ByteString getMessage(); } public static final class IncomingPushMessageSignal extends com.google.protobuf.GeneratedMessage @@ -70,475 +60,6 @@ public final class PushMessageProtos { return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_IncomingPushMessageSignal_fieldAccessorTable; } - public interface AttachmentPointerOrBuilder - extends com.google.protobuf.MessageOrBuilder { - - // optional string contentType = 1; - boolean hasContentType(); - String getContentType(); - - // optional string key = 2; - boolean hasKey(); - String getKey(); - } - public static final class AttachmentPointer extends - com.google.protobuf.GeneratedMessage - implements AttachmentPointerOrBuilder { - // Use AttachmentPointer.newBuilder() to construct. - private AttachmentPointer(Builder builder) { - super(builder); - } - private AttachmentPointer(boolean noInit) {} - - private static final AttachmentPointer defaultInstance; - public static AttachmentPointer getDefaultInstance() { - return defaultInstance; - } - - public AttachmentPointer getDefaultInstanceForType() { - return defaultInstance; - } - - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_IncomingPushMessageSignal_AttachmentPointer_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_IncomingPushMessageSignal_AttachmentPointer_fieldAccessorTable; - } - - private int bitField0_; - // optional string contentType = 1; - public static final int CONTENTTYPE_FIELD_NUMBER = 1; - private java.lang.Object contentType_; - public boolean hasContentType() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - public String getContentType() { - java.lang.Object ref = contentType_; - if (ref instanceof String) { - return (String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); - if (com.google.protobuf.Internal.isValidUtf8(bs)) { - contentType_ = s; - } - return s; - } - } - private com.google.protobuf.ByteString getContentTypeBytes() { - java.lang.Object ref = contentType_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8((String) ref); - contentType_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - // optional string key = 2; - public static final int KEY_FIELD_NUMBER = 2; - private java.lang.Object key_; - public boolean hasKey() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - public String getKey() { - java.lang.Object ref = key_; - if (ref instanceof String) { - return (String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); - if (com.google.protobuf.Internal.isValidUtf8(bs)) { - key_ = s; - } - return s; - } - } - private com.google.protobuf.ByteString getKeyBytes() { - java.lang.Object ref = key_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8((String) ref); - key_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - private void initFields() { - contentType_ = ""; - key_ = ""; - } - private byte memoizedIsInitialized = -1; - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized != -1) return isInitialized == 1; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, getContentTypeBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, getKeyBytes()); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(1, getContentTypeBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(2, getKeyBytes()); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - @java.lang.Override - protected java.lang.Object writeReplace() - throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return newBuilder().mergeFrom(data).buildParsed(); - } - public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return newBuilder().mergeFrom(data, extensionRegistry) - .buildParsed(); - } - public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return newBuilder().mergeFrom(data).buildParsed(); - } - public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return newBuilder().mergeFrom(data, extensionRegistry) - .buildParsed(); - } - public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer parseFrom(java.io.InputStream input) - throws java.io.IOException { - return newBuilder().mergeFrom(input).buildParsed(); - } - public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return newBuilder().mergeFrom(input, extensionRegistry) - .buildParsed(); - } - public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - Builder builder = newBuilder(); - if (builder.mergeDelimitedFrom(input)) { - return builder.buildParsed(); - } else { - return null; - } - } - public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - Builder builder = newBuilder(); - if (builder.mergeDelimitedFrom(input, extensionRegistry)) { - return builder.buildParsed(); - } else { - return null; - } - } - public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return newBuilder().mergeFrom(input).buildParsed(); - } - public static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return newBuilder().mergeFrom(input, extensionRegistry) - .buildParsed(); - } - - public static Builder newBuilder() { return Builder.create(); } - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder(org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer prototype) { - return newBuilder().mergeFrom(prototype); - } - public Builder toBuilder() { return newBuilder(this); } - - @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder - implements org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointerOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_IncomingPushMessageSignal_AttachmentPointer_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_IncomingPushMessageSignal_AttachmentPointer_fieldAccessorTable; - } - - // Construct using org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder(BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - } - } - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - contentType_ = ""; - bitField0_ = (bitField0_ & ~0x00000001); - key_ = ""; - bitField0_ = (bitField0_ & ~0x00000002); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.getDescriptor(); - } - - public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer getDefaultInstanceForType() { - return org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.getDefaultInstance(); - } - - public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer build() { - org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - private org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer buildParsed() - throws com.google.protobuf.InvalidProtocolBufferException { - org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException( - result).asInvalidProtocolBufferException(); - } - return result; - } - - public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer buildPartial() { - org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer result = new org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.contentType_ = contentType_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.key_ = key_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer) { - return mergeFrom((org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer other) { - if (other == org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.getDefaultInstance()) return this; - if (other.hasContentType()) { - setContentType(other.getContentType()); - } - if (other.hasKey()) { - setKey(other.getKey()); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - com.google.protobuf.UnknownFieldSet.Builder unknownFields = - com.google.protobuf.UnknownFieldSet.newBuilder( - this.getUnknownFields()); - while (true) { - int tag = input.readTag(); - switch (tag) { - case 0: - this.setUnknownFields(unknownFields.build()); - onChanged(); - return this; - default: { - if (!parseUnknownField(input, unknownFields, - extensionRegistry, tag)) { - this.setUnknownFields(unknownFields.build()); - onChanged(); - return this; - } - break; - } - case 10: { - bitField0_ |= 0x00000001; - contentType_ = input.readBytes(); - break; - } - case 18: { - bitField0_ |= 0x00000002; - key_ = input.readBytes(); - break; - } - } - } - } - - private int bitField0_; - - // optional string contentType = 1; - private java.lang.Object contentType_ = ""; - public boolean hasContentType() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - public String getContentType() { - java.lang.Object ref = contentType_; - if (!(ref instanceof String)) { - String s = ((com.google.protobuf.ByteString) ref).toStringUtf8(); - contentType_ = s; - return s; - } else { - return (String) ref; - } - } - public Builder setContentType(String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - contentType_ = value; - onChanged(); - return this; - } - public Builder clearContentType() { - bitField0_ = (bitField0_ & ~0x00000001); - contentType_ = getDefaultInstance().getContentType(); - onChanged(); - return this; - } - void setContentType(com.google.protobuf.ByteString value) { - bitField0_ |= 0x00000001; - contentType_ = value; - onChanged(); - } - - // optional string key = 2; - private java.lang.Object key_ = ""; - public boolean hasKey() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - public String getKey() { - java.lang.Object ref = key_; - if (!(ref instanceof String)) { - String s = ((com.google.protobuf.ByteString) ref).toStringUtf8(); - key_ = s; - return s; - } else { - return (String) ref; - } - } - public Builder setKey(String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - key_ = value; - onChanged(); - return this; - } - public Builder clearKey() { - bitField0_ = (bitField0_ & ~0x00000002); - key_ = getDefaultInstance().getKey(); - onChanged(); - return this; - } - void setKey(com.google.protobuf.ByteString value) { - bitField0_ |= 0x00000002; - key_ = value; - onChanged(); - } - - // @@protoc_insertion_point(builder_scope:textsecure.IncomingPushMessageSignal.AttachmentPointer) - } - - static { - defaultInstance = new AttachmentPointer(true); - defaultInstance.initFields(); - } - - // @@protoc_insertion_point(class_scope:textsecure.IncomingPushMessageSignal.AttachmentPointer) - } - private int bitField0_; // optional uint32 type = 1; public static final int TYPE_FIELD_NUMBER = 1; @@ -596,54 +117,32 @@ public final class PushMessageProtos { return destinations_.get(index); } - // optional bytes message = 4; - public static final int MESSAGE_FIELD_NUMBER = 4; - private com.google.protobuf.ByteString message_; - public boolean hasMessage() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - public com.google.protobuf.ByteString getMessage() { - return message_; - } - - // repeated .textsecure.IncomingPushMessageSignal.AttachmentPointer attachments = 5; - public static final int ATTACHMENTS_FIELD_NUMBER = 5; - private java.util.List attachments_; - public java.util.List getAttachmentsList() { - return attachments_; - } - public java.util.List - getAttachmentsOrBuilderList() { - return attachments_; - } - public int getAttachmentsCount() { - return attachments_.size(); - } - public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer getAttachments(int index) { - return attachments_.get(index); - } - public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointerOrBuilder getAttachmentsOrBuilder( - int index) { - return attachments_.get(index); - } - - // optional uint64 timestamp = 6; - public static final int TIMESTAMP_FIELD_NUMBER = 6; + // optional uint64 timestamp = 4; + public static final int TIMESTAMP_FIELD_NUMBER = 4; private long timestamp_; public boolean hasTimestamp() { - return ((bitField0_ & 0x00000008) == 0x00000008); + return ((bitField0_ & 0x00000004) == 0x00000004); } public long getTimestamp() { return timestamp_; } + // optional bytes message = 5; + public static final int MESSAGE_FIELD_NUMBER = 5; + private com.google.protobuf.ByteString message_; + public boolean hasMessage() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + public com.google.protobuf.ByteString getMessage() { + return message_; + } + private void initFields() { type_ = 0; source_ = ""; destinations_ = com.google.protobuf.LazyStringArrayList.EMPTY; - message_ = com.google.protobuf.ByteString.EMPTY; - attachments_ = java.util.Collections.emptyList(); timestamp_ = 0L; + message_ = com.google.protobuf.ByteString.EMPTY; } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -667,13 +166,10 @@ public final class PushMessageProtos { output.writeBytes(3, destinations_.getByteString(i)); } if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeBytes(4, message_); - } - for (int i = 0; i < attachments_.size(); i++) { - output.writeMessage(5, attachments_.get(i)); + output.writeUInt64(4, timestamp_); } if (((bitField0_ & 0x00000008) == 0x00000008)) { - output.writeUInt64(6, timestamp_); + output.writeBytes(5, message_); } getUnknownFields().writeTo(output); } @@ -703,15 +199,11 @@ public final class PushMessageProtos { } if (((bitField0_ & 0x00000004) == 0x00000004)) { size += com.google.protobuf.CodedOutputStream - .computeBytesSize(4, message_); - } - for (int i = 0; i < attachments_.size(); i++) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(5, attachments_.get(i)); + .computeUInt64Size(4, timestamp_); } if (((bitField0_ & 0x00000008) == 0x00000008)) { size += com.google.protobuf.CodedOutputStream - .computeUInt64Size(6, timestamp_); + .computeBytesSize(5, message_); } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; @@ -829,7 +321,6 @@ public final class PushMessageProtos { } private void maybeForceBuilderInitialization() { if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - getAttachmentsFieldBuilder(); } } private static Builder create() { @@ -844,16 +335,10 @@ public final class PushMessageProtos { bitField0_ = (bitField0_ & ~0x00000002); destinations_ = com.google.protobuf.LazyStringArrayList.EMPTY; bitField0_ = (bitField0_ & ~0x00000004); - message_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000008); - if (attachmentsBuilder_ == null) { - attachments_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000010); - } else { - attachmentsBuilder_.clear(); - } timestamp_ = 0L; - bitField0_ = (bitField0_ & ~0x00000020); + bitField0_ = (bitField0_ & ~0x00000008); + message_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000010); return this; } @@ -909,20 +394,11 @@ public final class PushMessageProtos { if (((from_bitField0_ & 0x00000008) == 0x00000008)) { to_bitField0_ |= 0x00000004; } - result.message_ = message_; - if (attachmentsBuilder_ == null) { - if (((bitField0_ & 0x00000010) == 0x00000010)) { - attachments_ = java.util.Collections.unmodifiableList(attachments_); - bitField0_ = (bitField0_ & ~0x00000010); - } - result.attachments_ = attachments_; - } else { - result.attachments_ = attachmentsBuilder_.build(); - } - if (((from_bitField0_ & 0x00000020) == 0x00000020)) { + result.timestamp_ = timestamp_; + if (((from_bitField0_ & 0x00000010) == 0x00000010)) { to_bitField0_ |= 0x00000008; } - result.timestamp_ = timestamp_; + result.message_ = message_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -955,38 +431,12 @@ public final class PushMessageProtos { } onChanged(); } - if (other.hasMessage()) { - setMessage(other.getMessage()); - } - if (attachmentsBuilder_ == null) { - if (!other.attachments_.isEmpty()) { - if (attachments_.isEmpty()) { - attachments_ = other.attachments_; - bitField0_ = (bitField0_ & ~0x00000010); - } else { - ensureAttachmentsIsMutable(); - attachments_.addAll(other.attachments_); - } - onChanged(); - } - } else { - if (!other.attachments_.isEmpty()) { - if (attachmentsBuilder_.isEmpty()) { - attachmentsBuilder_.dispose(); - attachmentsBuilder_ = null; - attachments_ = other.attachments_; - bitField0_ = (bitField0_ & ~0x00000010); - attachmentsBuilder_ = - com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? - getAttachmentsFieldBuilder() : null; - } else { - attachmentsBuilder_.addAllMessages(other.attachments_); - } - } - } if (other.hasTimestamp()) { setTimestamp(other.getTimestamp()); } + if (other.hasMessage()) { + setMessage(other.getMessage()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -1033,20 +483,14 @@ public final class PushMessageProtos { destinations_.add(input.readBytes()); break; } - case 34: { + case 32: { bitField0_ |= 0x00000008; - message_ = input.readBytes(); + timestamp_ = input.readUInt64(); break; } case 42: { - org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder subBuilder = org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.newBuilder(); - input.readMessage(subBuilder, extensionRegistry); - addAttachments(subBuilder.buildPartial()); - break; - } - case 48: { - bitField0_ |= 0x00000020; - timestamp_ = input.readUInt64(); + bitField0_ |= 0x00000010; + message_ = input.readBytes(); break; } } @@ -1168,10 +612,31 @@ public final class PushMessageProtos { onChanged(); } - // optional bytes message = 4; + // optional uint64 timestamp = 4; + private long timestamp_ ; + public boolean hasTimestamp() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + public long getTimestamp() { + return timestamp_; + } + public Builder setTimestamp(long value) { + bitField0_ |= 0x00000008; + timestamp_ = value; + onChanged(); + return this; + } + public Builder clearTimestamp() { + bitField0_ = (bitField0_ & ~0x00000008); + timestamp_ = 0L; + onChanged(); + return this; + } + + // optional bytes message = 5; private com.google.protobuf.ByteString message_ = com.google.protobuf.ByteString.EMPTY; public boolean hasMessage() { - return ((bitField0_ & 0x00000008) == 0x00000008); + return ((bitField0_ & 0x00000010) == 0x00000010); } public com.google.protobuf.ByteString getMessage() { return message_; @@ -1180,32 +645,986 @@ public final class PushMessageProtos { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000010; message_ = value; onChanged(); return this; } public Builder clearMessage() { - bitField0_ = (bitField0_ & ~0x00000008); + bitField0_ = (bitField0_ & ~0x00000010); message_ = getDefaultInstance().getMessage(); onChanged(); return this; } - // repeated .textsecure.IncomingPushMessageSignal.AttachmentPointer attachments = 5; - private java.util.List attachments_ = + // @@protoc_insertion_point(builder_scope:textsecure.IncomingPushMessageSignal) + } + + static { + defaultInstance = new IncomingPushMessageSignal(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.IncomingPushMessageSignal) + } + + public interface PushMessageContentOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional string body = 1; + boolean hasBody(); + String getBody(); + + // repeated .textsecure.PushMessageContent.AttachmentPointer attachments = 2; + java.util.List + getAttachmentsList(); + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer getAttachments(int index); + int getAttachmentsCount(); + java.util.List + getAttachmentsOrBuilderList(); + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointerOrBuilder getAttachmentsOrBuilder( + int index); + } + public static final class PushMessageContent extends + com.google.protobuf.GeneratedMessage + implements PushMessageContentOrBuilder { + // Use PushMessageContent.newBuilder() to construct. + private PushMessageContent(Builder builder) { + super(builder); + } + private PushMessageContent(boolean noInit) {} + + private static final PushMessageContent defaultInstance; + public static PushMessageContent getDefaultInstance() { + return defaultInstance; + } + + public PushMessageContent getDefaultInstanceForType() { + return defaultInstance; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_fieldAccessorTable; + } + + public interface AttachmentPointerOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional fixed64 id = 1; + boolean hasId(); + long getId(); + + // optional string contentType = 2; + boolean hasContentType(); + String getContentType(); + + // optional bytes key = 3; + boolean hasKey(); + com.google.protobuf.ByteString getKey(); + } + public static final class AttachmentPointer extends + com.google.protobuf.GeneratedMessage + implements AttachmentPointerOrBuilder { + // Use AttachmentPointer.newBuilder() to construct. + private AttachmentPointer(Builder builder) { + super(builder); + } + private AttachmentPointer(boolean noInit) {} + + private static final AttachmentPointer defaultInstance; + public static AttachmentPointer getDefaultInstance() { + return defaultInstance; + } + + public AttachmentPointer getDefaultInstanceForType() { + return defaultInstance; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_AttachmentPointer_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_AttachmentPointer_fieldAccessorTable; + } + + private int bitField0_; + // optional fixed64 id = 1; + public static final int ID_FIELD_NUMBER = 1; + private long id_; + public boolean hasId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public long getId() { + return id_; + } + + // optional string contentType = 2; + public static final int CONTENTTYPE_FIELD_NUMBER = 2; + private java.lang.Object contentType_; + public boolean hasContentType() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public String getContentType() { + java.lang.Object ref = contentType_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (com.google.protobuf.Internal.isValidUtf8(bs)) { + contentType_ = s; + } + return s; + } + } + private com.google.protobuf.ByteString getContentTypeBytes() { + java.lang.Object ref = contentType_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((String) ref); + contentType_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + // optional bytes key = 3; + public static final int KEY_FIELD_NUMBER = 3; + private com.google.protobuf.ByteString key_; + public boolean hasKey() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public com.google.protobuf.ByteString getKey() { + return key_; + } + + private void initFields() { + id_ = 0L; + contentType_ = ""; + key_ = com.google.protobuf.ByteString.EMPTY; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeFixed64(1, id_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, getContentTypeBytes()); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeBytes(3, key_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeFixed64Size(1, id_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, getContentTypeBytes()); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, key_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer parseFrom(java.io.InputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input, extensionRegistry)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointerOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_AttachmentPointer_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_AttachmentPointer_fieldAccessorTable; + } + + // Construct using org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder(BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + id_ = 0L; + bitField0_ = (bitField0_ & ~0x00000001); + contentType_ = ""; + bitField0_ = (bitField0_ & ~0x00000002); + key_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000004); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.getDescriptor(); + } + + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer getDefaultInstanceForType() { + return org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.getDefaultInstance(); + } + + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer build() { + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + private org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer buildParsed() + throws com.google.protobuf.InvalidProtocolBufferException { + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException( + result).asInvalidProtocolBufferException(); + } + return result; + } + + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer buildPartial() { + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer result = new org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.id_ = id_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.contentType_ = contentType_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.key_ = key_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer) { + return mergeFrom((org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer other) { + if (other == org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.getDefaultInstance()) return this; + if (other.hasId()) { + setId(other.getId()); + } + if (other.hasContentType()) { + setContentType(other.getContentType()); + } + if (other.hasKey()) { + setKey(other.getKey()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder( + this.getUnknownFields()); + while (true) { + int tag = input.readTag(); + switch (tag) { + case 0: + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + } + break; + } + case 9: { + bitField0_ |= 0x00000001; + id_ = input.readFixed64(); + break; + } + case 18: { + bitField0_ |= 0x00000002; + contentType_ = input.readBytes(); + break; + } + case 26: { + bitField0_ |= 0x00000004; + key_ = input.readBytes(); + break; + } + } + } + } + + private int bitField0_; + + // optional fixed64 id = 1; + private long id_ ; + public boolean hasId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public long getId() { + return id_; + } + public Builder setId(long value) { + bitField0_ |= 0x00000001; + id_ = value; + onChanged(); + return this; + } + public Builder clearId() { + bitField0_ = (bitField0_ & ~0x00000001); + id_ = 0L; + onChanged(); + return this; + } + + // optional string contentType = 2; + private java.lang.Object contentType_ = ""; + public boolean hasContentType() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public String getContentType() { + java.lang.Object ref = contentType_; + if (!(ref instanceof String)) { + String s = ((com.google.protobuf.ByteString) ref).toStringUtf8(); + contentType_ = s; + return s; + } else { + return (String) ref; + } + } + public Builder setContentType(String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + contentType_ = value; + onChanged(); + return this; + } + public Builder clearContentType() { + bitField0_ = (bitField0_ & ~0x00000002); + contentType_ = getDefaultInstance().getContentType(); + onChanged(); + return this; + } + void setContentType(com.google.protobuf.ByteString value) { + bitField0_ |= 0x00000002; + contentType_ = value; + onChanged(); + } + + // optional bytes key = 3; + private com.google.protobuf.ByteString key_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasKey() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public com.google.protobuf.ByteString getKey() { + return key_; + } + public Builder setKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + key_ = value; + onChanged(); + return this; + } + public Builder clearKey() { + bitField0_ = (bitField0_ & ~0x00000004); + key_ = getDefaultInstance().getKey(); + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:textsecure.PushMessageContent.AttachmentPointer) + } + + static { + defaultInstance = new AttachmentPointer(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.PushMessageContent.AttachmentPointer) + } + + private int bitField0_; + // optional string body = 1; + public static final int BODY_FIELD_NUMBER = 1; + private java.lang.Object body_; + public boolean hasBody() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public String getBody() { + java.lang.Object ref = body_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (com.google.protobuf.Internal.isValidUtf8(bs)) { + body_ = s; + } + return s; + } + } + private com.google.protobuf.ByteString getBodyBytes() { + java.lang.Object ref = body_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((String) ref); + body_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + // repeated .textsecure.PushMessageContent.AttachmentPointer attachments = 2; + public static final int ATTACHMENTS_FIELD_NUMBER = 2; + private java.util.List attachments_; + public java.util.List getAttachmentsList() { + return attachments_; + } + public java.util.List + getAttachmentsOrBuilderList() { + return attachments_; + } + public int getAttachmentsCount() { + return attachments_.size(); + } + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer getAttachments(int index) { + return attachments_.get(index); + } + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointerOrBuilder getAttachmentsOrBuilder( + int index) { + return attachments_.get(index); + } + + private void initFields() { + body_ = ""; + attachments_ = java.util.Collections.emptyList(); + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeBytes(1, getBodyBytes()); + } + for (int i = 0; i < attachments_.size(); i++) { + output.writeMessage(2, attachments_.get(i)); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(1, getBodyBytes()); + } + for (int i = 0; i < attachments_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(2, attachments_.get(i)); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent parseFrom(java.io.InputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input, extensionRegistry)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContentOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_fieldAccessorTable; + } + + // Construct using org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder(BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + getAttachmentsFieldBuilder(); + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + body_ = ""; + bitField0_ = (bitField0_ & ~0x00000001); + if (attachmentsBuilder_ == null) { + attachments_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000002); + } else { + attachmentsBuilder_.clear(); + } + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.getDescriptor(); + } + + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent getDefaultInstanceForType() { + return org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.getDefaultInstance(); + } + + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent build() { + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + private org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent buildParsed() + throws com.google.protobuf.InvalidProtocolBufferException { + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException( + result).asInvalidProtocolBufferException(); + } + return result; + } + + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent buildPartial() { + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent result = new org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.body_ = body_; + if (attachmentsBuilder_ == null) { + if (((bitField0_ & 0x00000002) == 0x00000002)) { + attachments_ = java.util.Collections.unmodifiableList(attachments_); + bitField0_ = (bitField0_ & ~0x00000002); + } + result.attachments_ = attachments_; + } else { + result.attachments_ = attachmentsBuilder_.build(); + } + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent) { + return mergeFrom((org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent other) { + if (other == org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.getDefaultInstance()) return this; + if (other.hasBody()) { + setBody(other.getBody()); + } + if (attachmentsBuilder_ == null) { + if (!other.attachments_.isEmpty()) { + if (attachments_.isEmpty()) { + attachments_ = other.attachments_; + bitField0_ = (bitField0_ & ~0x00000002); + } else { + ensureAttachmentsIsMutable(); + attachments_.addAll(other.attachments_); + } + onChanged(); + } + } else { + if (!other.attachments_.isEmpty()) { + if (attachmentsBuilder_.isEmpty()) { + attachmentsBuilder_.dispose(); + attachmentsBuilder_ = null; + attachments_ = other.attachments_; + bitField0_ = (bitField0_ & ~0x00000002); + attachmentsBuilder_ = + com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + getAttachmentsFieldBuilder() : null; + } else { + attachmentsBuilder_.addAllMessages(other.attachments_); + } + } + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder( + this.getUnknownFields()); + while (true) { + int tag = input.readTag(); + switch (tag) { + case 0: + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + } + break; + } + case 10: { + bitField0_ |= 0x00000001; + body_ = input.readBytes(); + break; + } + case 18: { + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder subBuilder = org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.newBuilder(); + input.readMessage(subBuilder, extensionRegistry); + addAttachments(subBuilder.buildPartial()); + break; + } + } + } + } + + private int bitField0_; + + // optional string body = 1; + private java.lang.Object body_ = ""; + public boolean hasBody() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public String getBody() { + java.lang.Object ref = body_; + if (!(ref instanceof String)) { + String s = ((com.google.protobuf.ByteString) ref).toStringUtf8(); + body_ = s; + return s; + } else { + return (String) ref; + } + } + public Builder setBody(String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + body_ = value; + onChanged(); + return this; + } + public Builder clearBody() { + bitField0_ = (bitField0_ & ~0x00000001); + body_ = getDefaultInstance().getBody(); + onChanged(); + return this; + } + void setBody(com.google.protobuf.ByteString value) { + bitField0_ |= 0x00000001; + body_ = value; + onChanged(); + } + + // repeated .textsecure.PushMessageContent.AttachmentPointer attachments = 2; + private java.util.List attachments_ = java.util.Collections.emptyList(); private void ensureAttachmentsIsMutable() { - if (!((bitField0_ & 0x00000010) == 0x00000010)) { - attachments_ = new java.util.ArrayList(attachments_); - bitField0_ |= 0x00000010; + if (!((bitField0_ & 0x00000002) == 0x00000002)) { + attachments_ = new java.util.ArrayList(attachments_); + bitField0_ |= 0x00000002; } } private com.google.protobuf.RepeatedFieldBuilder< - org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointerOrBuilder> attachmentsBuilder_; + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointerOrBuilder> attachmentsBuilder_; - public java.util.List getAttachmentsList() { + public java.util.List getAttachmentsList() { if (attachmentsBuilder_ == null) { return java.util.Collections.unmodifiableList(attachments_); } else { @@ -1219,7 +1638,7 @@ public final class PushMessageProtos { return attachmentsBuilder_.getCount(); } } - public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer getAttachments(int index) { + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer getAttachments(int index) { if (attachmentsBuilder_ == null) { return attachments_.get(index); } else { @@ -1227,7 +1646,7 @@ public final class PushMessageProtos { } } public Builder setAttachments( - int index, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer value) { + int index, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer value) { if (attachmentsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -1241,7 +1660,7 @@ public final class PushMessageProtos { return this; } public Builder setAttachments( - int index, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder builderForValue) { + int index, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder builderForValue) { if (attachmentsBuilder_ == null) { ensureAttachmentsIsMutable(); attachments_.set(index, builderForValue.build()); @@ -1251,7 +1670,7 @@ public final class PushMessageProtos { } return this; } - public Builder addAttachments(org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer value) { + public Builder addAttachments(org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer value) { if (attachmentsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -1265,7 +1684,7 @@ public final class PushMessageProtos { return this; } public Builder addAttachments( - int index, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer value) { + int index, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer value) { if (attachmentsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -1279,7 +1698,7 @@ public final class PushMessageProtos { return this; } public Builder addAttachments( - org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder builderForValue) { + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder builderForValue) { if (attachmentsBuilder_ == null) { ensureAttachmentsIsMutable(); attachments_.add(builderForValue.build()); @@ -1290,7 +1709,7 @@ public final class PushMessageProtos { return this; } public Builder addAttachments( - int index, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder builderForValue) { + int index, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder builderForValue) { if (attachmentsBuilder_ == null) { ensureAttachmentsIsMutable(); attachments_.add(index, builderForValue.build()); @@ -1301,7 +1720,7 @@ public final class PushMessageProtos { return this; } public Builder addAllAttachments( - java.lang.Iterable values) { + java.lang.Iterable values) { if (attachmentsBuilder_ == null) { ensureAttachmentsIsMutable(); super.addAll(values, attachments_); @@ -1314,7 +1733,7 @@ public final class PushMessageProtos { public Builder clearAttachments() { if (attachmentsBuilder_ == null) { attachments_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000010); + bitField0_ = (bitField0_ & ~0x00000002); onChanged(); } else { attachmentsBuilder_.clear(); @@ -1331,18 +1750,18 @@ public final class PushMessageProtos { } return this; } - public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder getAttachmentsBuilder( + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder getAttachmentsBuilder( int index) { return getAttachmentsFieldBuilder().getBuilder(index); } - public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointerOrBuilder getAttachmentsOrBuilder( + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointerOrBuilder getAttachmentsOrBuilder( int index) { if (attachmentsBuilder_ == null) { return attachments_.get(index); } else { return attachmentsBuilder_.getMessageOrBuilder(index); } } - public java.util.List + public java.util.List getAttachmentsOrBuilderList() { if (attachmentsBuilder_ != null) { return attachmentsBuilder_.getMessageOrBuilderList(); @@ -1350,27 +1769,27 @@ public final class PushMessageProtos { return java.util.Collections.unmodifiableList(attachments_); } } - public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder addAttachmentsBuilder() { + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder addAttachmentsBuilder() { return getAttachmentsFieldBuilder().addBuilder( - org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.getDefaultInstance()); + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.getDefaultInstance()); } - public org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder addAttachmentsBuilder( + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder addAttachmentsBuilder( int index) { return getAttachmentsFieldBuilder().addBuilder( - index, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.getDefaultInstance()); + index, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.getDefaultInstance()); } - public java.util.List + public java.util.List getAttachmentsBuilderList() { return getAttachmentsFieldBuilder().getBuilderList(); } private com.google.protobuf.RepeatedFieldBuilder< - org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointerOrBuilder> + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointerOrBuilder> getAttachmentsFieldBuilder() { if (attachmentsBuilder_ == null) { attachmentsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< - org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointerOrBuilder>( + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointerOrBuilder>( attachments_, - ((bitField0_ & 0x00000010) == 0x00000010), + ((bitField0_ & 0x00000002) == 0x00000002), getParentForChildren(), isClean()); attachments_ = null; @@ -1378,36 +1797,15 @@ public final class PushMessageProtos { return attachmentsBuilder_; } - // optional uint64 timestamp = 6; - private long timestamp_ ; - public boolean hasTimestamp() { - return ((bitField0_ & 0x00000020) == 0x00000020); - } - public long getTimestamp() { - return timestamp_; - } - public Builder setTimestamp(long value) { - bitField0_ |= 0x00000020; - timestamp_ = value; - onChanged(); - return this; - } - public Builder clearTimestamp() { - bitField0_ = (bitField0_ & ~0x00000020); - timestamp_ = 0L; - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:textsecure.IncomingPushMessageSignal) + // @@protoc_insertion_point(builder_scope:textsecure.PushMessageContent) } static { - defaultInstance = new IncomingPushMessageSignal(true); + defaultInstance = new PushMessageContent(true); defaultInstance.initFields(); } - // @@protoc_insertion_point(class_scope:textsecure.IncomingPushMessageSignal) + // @@protoc_insertion_point(class_scope:textsecure.PushMessageContent) } private static com.google.protobuf.Descriptors.Descriptor @@ -1416,10 +1814,15 @@ public final class PushMessageProtos { com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_textsecure_IncomingPushMessageSignal_fieldAccessorTable; private static com.google.protobuf.Descriptors.Descriptor - internal_static_textsecure_IncomingPushMessageSignal_AttachmentPointer_descriptor; + internal_static_textsecure_PushMessageContent_descriptor; private static com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_textsecure_IncomingPushMessageSignal_AttachmentPointer_fieldAccessorTable; + internal_static_textsecure_PushMessageContent_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_textsecure_PushMessageContent_AttachmentPointer_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_textsecure_PushMessageContent_AttachmentPointer_fieldAccessorTable; public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { @@ -1430,14 +1833,15 @@ public final class PushMessageProtos { static { java.lang.String[] descriptorData = { "\n\037IncomingPushMessageSignal.proto\022\ntexts" + - "ecure\"\370\001\n\031IncomingPushMessageSignal\022\014\n\004t" + - "ype\030\001 \001(\r\022\016\n\006source\030\002 \001(\t\022\024\n\014destination" + - "s\030\003 \003(\t\022\017\n\007message\030\004 \001(\014\022L\n\013attachments\030" + - "\005 \003(\01327.textsecure.IncomingPushMessageSi" + - "gnal.AttachmentPointer\022\021\n\ttimestamp\030\006 \001(" + - "\004\0325\n\021AttachmentPointer\022\023\n\013contentType\030\001 " + - "\001(\t\022\013\n\003key\030\002 \001(\tB7\n\"org.whispersystems.t" + - "extsecure.pushB\021PushMessageProtos" + "ecure\"s\n\031IncomingPushMessageSignal\022\014\n\004ty" + + "pe\030\001 \001(\r\022\016\n\006source\030\002 \001(\t\022\024\n\014destinations" + + "\030\003 \003(\t\022\021\n\ttimestamp\030\004 \001(\004\022\017\n\007message\030\005 \001" + + "(\014\"\254\001\n\022PushMessageContent\022\014\n\004body\030\001 \001(\t\022" + + "E\n\013attachments\030\002 \003(\01320.textsecure.PushMe" + + "ssageContent.AttachmentPointer\032A\n\021Attach" + + "mentPointer\022\n\n\002id\030\001 \001(\006\022\023\n\013contentType\030\002" + + " \001(\t\022\013\n\003key\030\003 \001(\014B7\n\"org.whispersystems." + + "textsecure.pushB\021PushMessageProtos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -1449,17 +1853,25 @@ public final class PushMessageProtos { internal_static_textsecure_IncomingPushMessageSignal_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_textsecure_IncomingPushMessageSignal_descriptor, - new java.lang.String[] { "Type", "Source", "Destinations", "Message", "Attachments", "Timestamp", }, + new java.lang.String[] { "Type", "Source", "Destinations", "Timestamp", "Message", }, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.class, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.Builder.class); - internal_static_textsecure_IncomingPushMessageSignal_AttachmentPointer_descriptor = - internal_static_textsecure_IncomingPushMessageSignal_descriptor.getNestedTypes().get(0); - internal_static_textsecure_IncomingPushMessageSignal_AttachmentPointer_fieldAccessorTable = new + internal_static_textsecure_PushMessageContent_descriptor = + getDescriptor().getMessageTypes().get(1); + internal_static_textsecure_PushMessageContent_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_textsecure_IncomingPushMessageSignal_AttachmentPointer_descriptor, - new java.lang.String[] { "ContentType", "Key", }, - org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.class, - org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer.Builder.class); + internal_static_textsecure_PushMessageContent_descriptor, + new java.lang.String[] { "Body", "Attachments", }, + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.class, + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.Builder.class); + internal_static_textsecure_PushMessageContent_AttachmentPointer_descriptor = + internal_static_textsecure_PushMessageContent_descriptor.getNestedTypes().get(0); + internal_static_textsecure_PushMessageContent_AttachmentPointer_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_textsecure_PushMessageContent_AttachmentPointer_descriptor, + new java.lang.String[] { "Id", "ContentType", "Key", }, + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.class, + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder.class); return null; } }; diff --git a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java index acde1f67c1..bee4cd9235 100644 --- a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java +++ b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java @@ -84,31 +84,21 @@ public class PushServiceSocket { sendMessage(new OutgoingPushMessageList(message)); } - public void sendMessage(List recipients, List bodies, - List> attachmentsList, int type) + public void sendMessage(List recipients, List bodies, List types) throws IOException { List messages = new LinkedList(); - Iterator recipientsIterator = recipients.iterator(); - Iterator bodiesIterator = bodies.iterator(); - Iterator> attachmentsIterator = attachmentsList.iterator(); + Iterator recipientsIterator = recipients.iterator(); + Iterator bodiesIterator = bodies.iterator(); + Iterator typesIterator = types.iterator(); while (recipientsIterator.hasNext()) { - String recipient = recipientsIterator.next(); - byte[] body = bodiesIterator.next(); - List attachments = attachmentsIterator.next(); + String recipient = recipientsIterator.next(); + byte[] body = bodiesIterator.next(); + int type = typesIterator.next(); - OutgoingPushMessage message; - - if (!attachments.isEmpty()) { - List attachmentIds = sendAttachments(attachments); - message = new OutgoingPushMessage(recipient, body, attachmentIds, type); - } else { - message = new OutgoingPushMessage(recipient, body, type); - } - - messages.add(message); + messages.add(new OutgoingPushMessage(recipient, body, type)); } sendMessage(new OutgoingPushMessageList(messages)); @@ -149,20 +139,7 @@ public class PushServiceSocket { return PreKeyEntity.fromJson(responseText); } - private List sendAttachments(List attachments) - throws IOException - { - List attachmentIds = new LinkedList(); - - for (PushAttachmentData attachment : attachments) { - attachmentIds.add(new PushAttachmentPointer(attachment.getContentType(), - sendAttachment(attachment))); - } - - return attachmentIds; - } - - private String sendAttachment(PushAttachmentData attachment) throws IOException { + public long sendAttachment(PushAttachmentData attachment) throws IOException { Pair response = makeRequestForResponseHeader(String.format(ATTACHMENT_PATH, ""), "GET", null, "Content-Location"); @@ -178,25 +155,18 @@ public class PushServiceSocket { return new Gson().fromJson(response.second, AttachmentKey.class).getId(); } - public List> retrieveAttachments(List attachmentIds) - throws IOException - { - List> attachments = new LinkedList>(); + public File retrieveAttachment(long attachmentId) throws IOException { + Pair response = makeRequestForResponseHeader(String.format(ATTACHMENT_PATH, String.valueOf(attachmentId)), + "GET", null, "Content-Location"); - for (PushAttachmentPointer attachmentId : attachmentIds) { - Pair response = makeRequestForResponseHeader(String.format(ATTACHMENT_PATH, attachmentId.getKey()), - "GET", null, "Content-Location"); + Log.w("PushServiceSocket", "Attachment: " + attachmentId + " is at: " + response.first); - Log.w("PushServiceSocket", "Attachment: " + attachmentId.getKey() + " is at: " + response.first); + File attachment = File.createTempFile("attachment", ".tmp", context.getFilesDir()); + attachment.deleteOnExit(); - File attachment = File.createTempFile("attachment", ".tmp", context.getFilesDir()); - attachment.deleteOnExit(); + downloadExternalFile(response.first, attachment); - downloadExternalFile(response.first, attachment); - attachments.add(new Pair(attachment, attachmentId.getContentType())); - } - - return attachments; + return attachment; } public Pair retrieveDirectory() { @@ -394,13 +364,13 @@ public class PushServiceSocket { } private static class AttachmentKey { - private String id; + private long id; - public AttachmentKey(String id) { + public AttachmentKey(long id) { this.id = id; } - public String getId() { + public long getId() { return id; } } diff --git a/library/src/org/whispersystems/textsecure/util/Util.java b/library/src/org/whispersystems/textsecure/util/Util.java index 379c250000..800050c631 100644 --- a/library/src/org/whispersystems/textsecure/util/Util.java +++ b/library/src/org/whispersystems/textsecure/util/Util.java @@ -45,6 +45,33 @@ public class Util { } + public static byte[][] split(byte[] input, int firstLength, int secondLength) { + byte[][] parts = new byte[2][]; + + parts[0] = new byte[firstLength]; + System.arraycopy(input, 0, parts[0], 0, firstLength); + + parts[1] = new byte[secondLength]; + System.arraycopy(input, firstLength, parts[1], 0, secondLength); + + return parts; + } + + public static byte[][] split(byte[] input, int firstLength, int secondLength, int thirdLength) { + byte[][] parts = new byte[3][]; + + parts[0] = new byte[firstLength]; + System.arraycopy(input, 0, parts[0], 0, firstLength); + + parts[1] = new byte[secondLength]; + System.arraycopy(input, firstLength, parts[1], 0, secondLength); + + parts[2] = new byte[thirdLength]; + System.arraycopy(input, firstLength + secondLength, parts[2], 0, thirdLength); + + return parts; + } + public static boolean isEmpty(String value) { return value == null || value.trim().length() == 0; } @@ -94,6 +121,18 @@ public class Util { return new String(bout.toByteArray()); } + public static void readFully(InputStream in, byte[] buffer) throws IOException { + int offset = 0; + + for (;;) { + int read = in.read(buffer, offset, buffer.length - offset); + + if (read + offset < buffer.length) offset += read; + else return; + } + } + + public static void copy(InputStream in, OutputStream out) throws IOException { byte[] buffer = new byte[4096]; int read; diff --git a/res/drawable-hdpi/stat_sys_download_anim0.png b/res/drawable-hdpi/stat_sys_download_anim0.png new file mode 100644 index 0000000000..72d5f3fcb2 Binary files /dev/null and b/res/drawable-hdpi/stat_sys_download_anim0.png differ diff --git a/res/drawable-hdpi/stat_sys_download_anim1.png b/res/drawable-hdpi/stat_sys_download_anim1.png new file mode 100644 index 0000000000..e7ba0a5d0a Binary files /dev/null and b/res/drawable-hdpi/stat_sys_download_anim1.png differ diff --git a/res/drawable-hdpi/stat_sys_download_anim2.png b/res/drawable-hdpi/stat_sys_download_anim2.png new file mode 100644 index 0000000000..972e50428f Binary files /dev/null and b/res/drawable-hdpi/stat_sys_download_anim2.png differ diff --git a/res/drawable-hdpi/stat_sys_download_anim3.png b/res/drawable-hdpi/stat_sys_download_anim3.png new file mode 100644 index 0000000000..5a8d9d0d01 Binary files /dev/null and b/res/drawable-hdpi/stat_sys_download_anim3.png differ diff --git a/res/drawable-hdpi/stat_sys_download_anim4.png b/res/drawable-hdpi/stat_sys_download_anim4.png new file mode 100644 index 0000000000..98796a2606 Binary files /dev/null and b/res/drawable-hdpi/stat_sys_download_anim4.png differ diff --git a/res/drawable-hdpi/stat_sys_download_anim5.png b/res/drawable-hdpi/stat_sys_download_anim5.png new file mode 100644 index 0000000000..b82a689e07 Binary files /dev/null and b/res/drawable-hdpi/stat_sys_download_anim5.png differ diff --git a/res/drawable-mdpi/stat_sys_download_anim0.png b/res/drawable-mdpi/stat_sys_download_anim0.png new file mode 100644 index 0000000000..b14d0f671d Binary files /dev/null and b/res/drawable-mdpi/stat_sys_download_anim0.png differ diff --git a/res/drawable-mdpi/stat_sys_download_anim1.png b/res/drawable-mdpi/stat_sys_download_anim1.png new file mode 100644 index 0000000000..7e002201a7 Binary files /dev/null and b/res/drawable-mdpi/stat_sys_download_anim1.png differ diff --git a/res/drawable-mdpi/stat_sys_download_anim2.png b/res/drawable-mdpi/stat_sys_download_anim2.png new file mode 100644 index 0000000000..941839935a Binary files /dev/null and b/res/drawable-mdpi/stat_sys_download_anim2.png differ diff --git a/res/drawable-mdpi/stat_sys_download_anim3.png b/res/drawable-mdpi/stat_sys_download_anim3.png new file mode 100644 index 0000000000..84a9362a47 Binary files /dev/null and b/res/drawable-mdpi/stat_sys_download_anim3.png differ diff --git a/res/drawable-mdpi/stat_sys_download_anim4.png b/res/drawable-mdpi/stat_sys_download_anim4.png new file mode 100644 index 0000000000..8fdc8716d6 Binary files /dev/null and b/res/drawable-mdpi/stat_sys_download_anim4.png differ diff --git a/res/drawable-mdpi/stat_sys_download_anim5.png b/res/drawable-mdpi/stat_sys_download_anim5.png new file mode 100644 index 0000000000..6b64abd82b Binary files /dev/null and b/res/drawable-mdpi/stat_sys_download_anim5.png differ diff --git a/res/drawable-xhdpi/stat_sys_download_anim0.png b/res/drawable-xhdpi/stat_sys_download_anim0.png new file mode 100644 index 0000000000..f465442a89 Binary files /dev/null and b/res/drawable-xhdpi/stat_sys_download_anim0.png differ diff --git a/res/drawable-xhdpi/stat_sys_download_anim1.png b/res/drawable-xhdpi/stat_sys_download_anim1.png new file mode 100644 index 0000000000..15d1a487be Binary files /dev/null and b/res/drawable-xhdpi/stat_sys_download_anim1.png differ diff --git a/res/drawable-xhdpi/stat_sys_download_anim2.png b/res/drawable-xhdpi/stat_sys_download_anim2.png new file mode 100644 index 0000000000..3b47b47d7e Binary files /dev/null and b/res/drawable-xhdpi/stat_sys_download_anim2.png differ diff --git a/res/drawable-xhdpi/stat_sys_download_anim3.png b/res/drawable-xhdpi/stat_sys_download_anim3.png new file mode 100644 index 0000000000..ecc2eef5d7 Binary files /dev/null and b/res/drawable-xhdpi/stat_sys_download_anim3.png differ diff --git a/res/drawable-xhdpi/stat_sys_download_anim4.png b/res/drawable-xhdpi/stat_sys_download_anim4.png new file mode 100644 index 0000000000..1014804a86 Binary files /dev/null and b/res/drawable-xhdpi/stat_sys_download_anim4.png differ diff --git a/res/drawable-xhdpi/stat_sys_download_anim5.png b/res/drawable-xhdpi/stat_sys_download_anim5.png new file mode 100644 index 0000000000..e3d4730ee7 Binary files /dev/null and b/res/drawable-xhdpi/stat_sys_download_anim5.png differ diff --git a/res/drawable/stat_sys_download.xml b/res/drawable/stat_sys_download.xml new file mode 100644 index 0000000000..77ecf8588b --- /dev/null +++ b/res/drawable/stat_sys_download.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + diff --git a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java index 0be4d8a767..6bbefa0406 100644 --- a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java +++ b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java @@ -17,6 +17,7 @@ package org.thoughtcrime.securesms.crypto; import android.content.Context; +import android.content.Intent; import android.database.Cursor; import android.util.Log; @@ -33,22 +34,26 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.Recipients; +import org.thoughtcrime.securesms.service.PushDownloader; +import org.thoughtcrime.securesms.service.PushReceiver; +import org.thoughtcrime.securesms.service.SendReceiveService; import org.thoughtcrime.securesms.sms.SmsTransportDetails; +import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.textsecure.crypto.IdentityKeyPair; import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.InvalidMessageException; import org.whispersystems.textsecure.crypto.InvalidVersionException; -import org.whispersystems.textsecure.crypto.MessageCipher; -import org.whispersystems.textsecure.crypto.SessionCipher; -import org.whispersystems.textsecure.util.Hex; -import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.thoughtcrime.securesms.util.WorkerThread; import org.whispersystems.textsecure.crypto.KeyUtil; import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.crypto.MessageCipher; +import org.whispersystems.textsecure.crypto.SessionCipher; +import org.whispersystems.textsecure.push.IncomingPushMessage; +import org.whispersystems.textsecure.push.PushTransportDetails; +import org.whispersystems.textsecure.util.Hex; import java.io.IOException; -import java.util.LinkedList; -import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import ws.com.google.android.mms.ContentType; import ws.com.google.android.mms.MmsException; @@ -64,21 +69,13 @@ import ws.com.google.android.mms.pdu.RetrieveConf; public class DecryptingQueue { - private static final List workQueue = new LinkedList(); - - static { - Thread workerThread = new WorkerThread(workQueue, "Async Decryption Thread"); - workerThread.start(); - } + private static final Executor executor = Executors.newSingleThreadExecutor(); public static void scheduleDecryption(Context context, MasterSecret masterSecret, long messageId, long threadId, MultimediaMessagePdu mms) { MmsDecryptionItem runnable = new MmsDecryptionItem(context, masterSecret, messageId, threadId, mms); - synchronized (workQueue) { - workQueue.add(runnable); - workQueue.notifyAll(); - } + executor.execute(runnable); } public static void scheduleDecryption(Context context, MasterSecret masterSecret, @@ -87,10 +84,15 @@ public class DecryptingQueue { { DecryptionWorkItem runnable = new DecryptionWorkItem(context, masterSecret, messageId, threadId, originator, body, isSecureMessage, isKeyExchange); - synchronized (workQueue) { - workQueue.add(runnable); - workQueue.notifyAll(); - } + executor.execute(runnable); + } + + public static void scheduleDecryption(Context context, MasterSecret masterSecret, + long messageId, IncomingPushMessage message) + { + PushDecryptionWorkItem runnable = new PushDecryptionWorkItem(context, masterSecret, + messageId, message); + executor.execute(runnable); } public static void schedulePendingDecrypts(Context context, MasterSecret masterSecret) { @@ -143,6 +145,59 @@ public class DecryptingQueue { originator, body, isSecureMessage, isKeyExchange); } + private static class PushDecryptionWorkItem implements Runnable { + + private Context context; + private MasterSecret masterSecret; + private long messageId; + private IncomingPushMessage message; + + public PushDecryptionWorkItem(Context context, MasterSecret masterSecret, + long messageId, IncomingPushMessage message) + { + this.context = context; + this.masterSecret = masterSecret; + this.messageId = messageId; + this.message = message; + } + + public void run() { + synchronized (SessionCipher.CIPHER_LOCK) { + try { + Recipients recipients = RecipientFactory.getRecipientsFromString(context, message.getSource(), false); + Recipient recipient = recipients.getPrimaryRecipient(); + + if (!KeyUtil.isSessionFor(context, recipient)) { + sendResult(PushReceiver.RESULT_NO_SESSION); + return; + } + + IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); + MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey, new PushTransportDetails()); + + byte[] plaintextBody = messageCipher.decrypt(recipient, message.getBody()); + message = message.withBody(plaintextBody); + sendResult(PushReceiver.RESULT_OK); + } catch (InvalidMessageException e) { + Log.w("DecryptionQueue", e); + sendResult(PushReceiver.RESULT_DECRYPT_FAILED); + } catch (RecipientFormattingException e) { + Log.w("DecryptionQueue", e); + sendResult(PushReceiver.RESULT_DECRYPT_FAILED); + } + } + } + + private void sendResult(int result) { + Intent intent = new Intent(context, SendReceiveService.class); + intent.setAction(SendReceiveService.DECRYPTED_PUSH_ACTION); + intent.putExtra("message", message); + intent.putExtra("message_id", messageId); + intent.putExtra("result", result); + context.startService(intent); + } + } + private static class MmsDecryptionItem implements Runnable { private long messageId; private long threadId; @@ -267,13 +322,10 @@ public class DecryptingQueue { synchronized (SessionCipher.CIPHER_LOCK) { try { - Log.w("DecryptingQueue", "Parsing recipient for originator: " + originator); Recipients recipients = RecipientFactory.getRecipientsFromString(context, originator, false); Recipient recipient = recipients.getPrimaryRecipient(); - Log.w("DecryptingQueue", "Parsed Recipient: " + recipient.getNumber()); if (!KeyUtil.isSessionFor(context, recipient)) { - Log.w("DecryptingQueue", "No such recipient session..."); database.markAsNoSession(messageId); return; } diff --git a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java index 4ef1cb5be4..3315f7ada0 100644 --- a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java @@ -51,7 +51,8 @@ public class DatabaseFactory { private static final int INTRODUCED_MMS_BODY_VERSION = 7; private static final int INTRODUCED_MMS_FROM_VERSION = 8; private static final int INTRODUCED_TOFU_IDENTITY_VERSION = 9; - private static final int DATABASE_VERSION = 9; + private static final int INTRODUCED_PUSH_DATABASE_VERSION = 10; + private static final int DATABASE_VERSION = 10; private static final String DATABASE_NAME = "messages.db"; private static final Object lock = new Object(); @@ -71,6 +72,7 @@ public class DatabaseFactory { private final MmsSmsDatabase mmsSmsDatabase; private final IdentityDatabase identityDatabase; private final DraftDatabase draftDatabase; + private final PushDatabase pushDatabase; public static DatabaseFactory getInstance(Context context) { synchronized (lock) { @@ -132,6 +134,10 @@ public class DatabaseFactory { return getInstance(context).draftDatabase; } + public static PushDatabase getPushDatabase(Context context) { + return getInstance(context).pushDatabase; + } + private DatabaseFactory(Context context) { this.databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION); this.sms = new SmsDatabase(context, databaseHelper); @@ -144,6 +150,7 @@ public class DatabaseFactory { this.mmsSmsDatabase = new MmsSmsDatabase(context, databaseHelper); this.identityDatabase = new IdentityDatabase(context, databaseHelper); this.draftDatabase = new DraftDatabase(context, databaseHelper); + this.pushDatabase = new PushDatabase(context, databaseHelper); } public void reset(Context context) { @@ -425,6 +432,7 @@ public class DatabaseFactory { db.execSQL(MmsAddressDatabase.CREATE_TABLE); db.execSQL(IdentityDatabase.CREATE_TABLE); db.execSQL(DraftDatabase.CREATE_TABLE); + db.execSQL(PushDatabase.CREATE_TABLE); executeStatements(db, SmsDatabase.CREATE_INDEXS); executeStatements(db, MmsDatabase.CREATE_INDEXS); @@ -617,6 +625,12 @@ public class DatabaseFactory { db.execSQL("CREATE TABLE identities (_id INTEGER PRIMARY KEY, recipient INTEGER UNIQUE, key TEXT, mac TEXT);"); } + if (oldVersion < INTRODUCED_PUSH_DATABASE_VERSION) { + db.execSQL("CREATE TABLE push (_id INTEGER PRIMARY KEY, type INTEGER, source TEXT, destinations TEXT, body TEXT, TIMESTAMP INTEGER);"); + db.execSQL("ALTER TABLE part ADD COLUMN pending_push INTEGER;"); + db.execSQL("CREATE INDEX IF NOT EXISTS pending_push_index ON parts (pending_push);"); + } + db.setTransactionSuccessful(); db.endTransaction(); } diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 7d6b484d2c..0df05ae1f0 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -51,6 +51,7 @@ import java.io.UnsupportedEncodingException; import java.lang.ref.SoftReference; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; @@ -63,6 +64,7 @@ import ws.com.google.android.mms.pdu.EncodedStringValue; import ws.com.google.android.mms.pdu.NotificationInd; 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.SendReq; // XXXX Clean up MMS efficiency: @@ -289,11 +291,11 @@ public class MmsDatabase extends Database implements MmsSmsColumns { public SendReq[] getOutgoingMessages(MasterSecret masterSecret, long messageId) throws MmsException { - MmsAddressDatabase addr = DatabaseFactory.getMmsAddressDatabase(context); - PartDatabase parts = getPartDatabase(masterSecret); - SQLiteDatabase database = databaseHelper.getReadableDatabase(); - MasterCipher masterCipher = masterSecret == null ? null : new MasterCipher(masterSecret); - Cursor cursor = null; + MmsAddressDatabase addr = DatabaseFactory.getMmsAddressDatabase(context); + PartDatabase partDatabase = getPartDatabase(masterSecret); + SQLiteDatabase database = databaseHelper.getReadableDatabase(); + MasterCipher masterCipher = masterSecret == null ? null : new MasterCipher(masterSecret); + Cursor cursor = null; String selection; @@ -322,8 +324,8 @@ public class MmsDatabase extends Database implements MmsSmsColumns { String messageText = cursor.getString(cursor.getColumnIndexOrThrow(BODY)); PduHeaders headers = getHeadersFromCursor(cursor); addr.getAddressesForId(messageId, headers); - PduBody body = parts.getParts(messageId, true); + PduBody body = getPartsAsBody(partDatabase.getParts(messageId, true)); try { if (!Util.isEmpty(messageText) && Types.isSymmetricEncryption(outboxType)) { @@ -864,9 +866,12 @@ public class MmsDatabase extends Database implements MmsSmsColumns { if (masterSecret == null) return null; - PduBody body = getPartDatabase(masterSecret).getParts(id, false); + PduBody body = getPartsAsBody(getPartDatabase(masterSecret).getParts(id, false)); SlideDeck slideDeck = new SlideDeck(context, masterSecret, body); - slideCache.put(id, new SoftReference(slideDeck)); + + if (!body.containsPushInProgress()) { + slideCache.put(id, new SoftReference(slideDeck)); + } return slideDeck; } @@ -907,4 +912,14 @@ public class MmsDatabase extends Database implements MmsSmsColumns { } } + private PduBody getPartsAsBody(List> parts) { + PduBody body = new PduBody(); + + for (Pair part : parts) { + body.addPart(part.second); + } + + return body; + } + } diff --git a/src/org/thoughtcrime/securesms/database/PartDatabase.java b/src/org/thoughtcrime/securesms/database/PartDatabase.java index 9f981722ec..067a17b14a 100644 --- a/src/org/thoughtcrime/securesms/database/PartDatabase.java +++ b/src/org/thoughtcrime/securesms/database/PartDatabase.java @@ -23,10 +23,12 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; +import android.util.Pair; import org.thoughtcrime.securesms.providers.PartProvider; import org.thoughtcrime.securesms.util.Util; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; @@ -34,6 +36,8 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.LinkedList; +import java.util.List; import ws.com.google.android.mms.ContentType; import ws.com.google.android.mms.MmsException; @@ -42,31 +46,34 @@ import ws.com.google.android.mms.pdu.PduPart; public class PartDatabase extends Database { - private static final String TABLE_NAME = "part"; - private static final String ID = "_id"; - private static final String MMS_ID = "mid"; - private static final String SEQUENCE = "seq"; - private static final String CONTENT_TYPE = "ct"; - private static final String NAME = "name"; - private static final String CHARSET = "chset"; - private static final String CONTENT_DISPOSITION = "cd"; - private static final String FILENAME = "fn"; - private static final String CONTENT_ID = "cid"; - private static final String CONTENT_LOCATION = "cl"; - private static final String CONTENT_TYPE_START = "ctt_s"; - private static final String CONTENT_TYPE_TYPE = "ctt_t"; - private static final String ENCRYPTED = "encrypted"; - private static final String DATA = "_data"; + private static final String TABLE_NAME = "part"; + private static final String ID = "_id"; + private static final String MMS_ID = "mid"; + private static final String SEQUENCE = "seq"; + private static final String CONTENT_TYPE = "ct"; + private static final String NAME = "name"; + private static final String CHARSET = "chset"; + private static final String CONTENT_DISPOSITION = "cd"; + private static final String FILENAME = "fn"; + private static final String CONTENT_ID = "cid"; + private static final String CONTENT_LOCATION = "cl"; + private static final String CONTENT_TYPE_START = "ctt_s"; + private static final String CONTENT_TYPE_TYPE = "ctt_t"; + private static final String ENCRYPTED = "encrypted"; + private static final String DATA = "_data"; + private static final String PENDING_PUSH_ATTACHMENT = "pending_push"; public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + MMS_ID + " INTEGER, " + SEQUENCE + " INTEGER DEFAULT 0, " + CONTENT_TYPE + " TEXT, " + NAME + " TEXT, " + CHARSET + " INTEGER, " + CONTENT_DISPOSITION + " TEXT, " + FILENAME + " TEXT, " + CONTENT_ID + " TEXT, " + CONTENT_LOCATION + " TEXT, " + CONTENT_TYPE_START + " INTEGER, " + - CONTENT_TYPE_TYPE + " TEXT, " + ENCRYPTED + " INTEGER, " + DATA + " TEXT);"; + CONTENT_TYPE_TYPE + " TEXT, " + ENCRYPTED + " INTEGER, " + + PENDING_PUSH_ATTACHMENT + " INTEGER, "+ DATA + " TEXT);"; public static final String[] CREATE_INDEXS = { - "CREATE INDEX IF NOT EXISTS part_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");" + "CREATE INDEX IF NOT EXISTS part_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");", + "CREATE INDEX IF NOT EXISTS pending_push_index ON " + TABLE_NAME + " (" + PENDING_PUSH_ATTACHMENT + ");", }; public PartDatabase(Context context, SQLiteOpenHelper databaseHelper) { @@ -113,6 +120,11 @@ public class PartDatabase extends Database { if (!cursor.isNull(encryptedColumn)) part.setEncrypted(cursor.getInt(encryptedColumn) == 1); + + int pendingPushColumn = cursor.getColumnIndexOrThrow(PENDING_PUSH_ATTACHMENT); + + if (!cursor.isNull(pendingPushColumn)) + part.setPendingPush(cursor.getInt(pendingPushColumn) == 1); } @@ -126,8 +138,9 @@ public class PartDatabase extends Database { if (part.getContentType() != null) { contentValues.put(CONTENT_TYPE, Util.toIsoString(part.getContentType())); - if (Util.toIsoString(part.getContentType()).equals(ContentType.APP_SMIL)) + if (Util.toIsoString(part.getContentType()).equals(ContentType.APP_SMIL)) { contentValues.put(SEQUENCE, -1); + } } else { throw new MmsException("There is no content type for this part."); } @@ -153,6 +166,7 @@ public class PartDatabase extends Database { } contentValues.put(ENCRYPTED, part.getEncrypted() ? 1 : 0); + contentValues.put(PENDING_PUSH_ATTACHMENT, part.isPendingPush() ? 1 : 0); return contentValues; } @@ -186,35 +200,42 @@ public class PartDatabase extends Database { } } - private File writePartData(PduPart part) throws MmsException { + private File writePartData(PduPart part, InputStream in) throws MmsException { try { File partsDirectory = context.getDir("parts", Context.MODE_PRIVATE); File dataFile = File.createTempFile("part", ".mms", partsDirectory); FileOutputStream fout = getPartOutputStream(dataFile, part); + byte[] buf = new byte[512]; + int read; + + while ((read = in.read(buf)) != -1) { + fout.write(buf, 0, read); + } + + fout.close(); + in.close(); + + return dataFile; + } catch (IOException e) { + throw new AssertionError(e); + } + } + + private File writePartData(PduPart part) throws MmsException { + try { if (part.getData() != null) { Log.w("PartDatabase", "Writing part data from buffer"); - fout.write(part.getData()); - fout.close(); - return dataFile; + return writePartData(part, new ByteArrayInputStream(part.getData())); } else if (part.getDataUri() != null) { Log.w("PartDatabase", "Writing part dat from URI"); - byte[] buf = new byte[512]; InputStream in = context.getContentResolver().openInputStream(part.getDataUri()); - int read; - while ((read = in.read(buf)) != -1) - fout.write(buf, 0, read); - - fout.close(); - in.close(); - return dataFile; + return writePartData(part, in); } else { throw new MmsException("Part is empty!"); } } catch (FileNotFoundException e) { throw new AssertionError(e); - } catch (IOException e) { - throw new AssertionError(e); } } @@ -224,7 +245,7 @@ public class PartDatabase extends Database { long partId = cursor.getLong(cursor.getColumnIndexOrThrow(ID)); getPartValues(part, cursor); - if (includeData) + if (includeData && !part.isPendingPush()) readPartData(part, dataLocation); part.setDataUri(ContentUris.withAppendedId(PartProvider.CONTENT_URI, partId)); @@ -232,14 +253,20 @@ public class PartDatabase extends Database { } private long insertPart(PduPart part, long mmsId) throws MmsException { - SQLiteDatabase database = databaseHelper.getWritableDatabase(); - File dataFile = writePartData(part); + SQLiteDatabase database = databaseHelper.getWritableDatabase(); + File dataFile = null; + + if (!part.isPendingPush()) { + dataFile = writePartData(part); + Log.w("PartDatabase", "Wrote part to file: " + dataFile.getAbsolutePath()); + } - Log.w("PartDatabase", "Wrote part to file: " + dataFile.getAbsolutePath()); ContentValues contentValues = getContentValuesForPart(part); - contentValues.put(MMS_ID, mmsId); - contentValues.put(DATA, dataFile.getAbsolutePath()); + + if (dataFile != null) { + contentValues.put(DATA, dataFile.getAbsolutePath()); + } return database.insert(TABLE_NAME, null, contentValues); } @@ -256,6 +283,10 @@ public class PartDatabase extends Database { PduPart part = new PduPart(); part.setEncrypted(cursor.getInt(1) == 1); + if (cursor.isNull(0)) { + throw new FileNotFoundException("No part data for id: " + partId); + } + return getPartInputStream(new File(cursor.getString(0)), part); } else { throw new FileNotFoundException("No part for id: " + partId); @@ -273,6 +304,41 @@ public class PartDatabase extends Database { } } + public void updateDownloadedPart(long messageId, long partId, PduPart part, InputStream data) + throws MmsException + { + SQLiteDatabase database = databaseHelper.getWritableDatabase(); + File partData = writePartData(part, data); + + part.setContentDisposition(new byte[0]); + part.setPendingPush(false); + + ContentValues values = getContentValuesForPart(part); + + if (partData != null) { + values.put(DATA, partData.getAbsolutePath()); + } + + database.update(TABLE_NAME, values, ID_WHERE, new String[] {partId+""}); + notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId)); + } + + public void updateFailedDownloadedPart(long messageId, long partId, PduPart part) + throws MmsException + { + SQLiteDatabase database = databaseHelper.getWritableDatabase(); + + part.setContentDisposition(new byte[0]); + part.setPendingPush(false); + + ContentValues values = getContentValuesForPart(part); + + values.put(DATA, (String)null); + + database.update(TABLE_NAME, values, ID_WHERE, new String[] {partId+""}); + notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId)); + } + public PduPart getPart(long partId, boolean includeData) { SQLiteDatabase database = databaseHelper.getReadableDatabase(); Cursor cursor = null; @@ -290,26 +356,50 @@ public class PartDatabase extends Database { } } - public PduBody getParts(long mmsId, boolean includeData) { - SQLiteDatabase database = databaseHelper.getReadableDatabase(); - PduBody body = new PduBody(); - Cursor cursor = null; + public List> getParts(long mmsId, boolean includeData) { + SQLiteDatabase database = databaseHelper.getReadableDatabase(); + List> results = new LinkedList>(); + Cursor cursor = null; try { cursor = database.query(TABLE_NAME, null, MMS_ID + " = ?", new String[] {mmsId+""}, null, null, null); while (cursor != null && cursor.moveToNext()) { PduPart part = getPart(cursor, includeData); - body.addPart(part); + results.add(new Pair(cursor.getLong(cursor.getColumnIndexOrThrow(ID)), + part)); } - return body; + return results; } finally { if (cursor != null) cursor.close(); } } + public List>> getPushPendingParts() { + SQLiteDatabase database = databaseHelper.getReadableDatabase(); + List>> results = new LinkedList>>(); + Cursor cursor = null; + + try { + cursor = database.query(TABLE_NAME, null, PENDING_PUSH_ATTACHMENT + " = ?", new String[] {"1"}, null, null, null); + + while (cursor != null && cursor.moveToNext()) { + PduPart part = getPart(cursor, false); + results.add(new Pair>(cursor.getLong(cursor.getColumnIndexOrThrow(MMS_ID)), + new Pair(cursor.getLong(cursor.getColumnIndexOrThrow(ID)), + part))); + } + + return results; + } finally { + if (cursor != null) + cursor.close(); + } + + } + public void deleteParts(long mmsId) { SQLiteDatabase database = databaseHelper.getWritableDatabase(); Cursor cursor = null; diff --git a/src/org/thoughtcrime/securesms/database/PushDatabase.java b/src/org/thoughtcrime/securesms/database/PushDatabase.java new file mode 100644 index 0000000000..0ca927423e --- /dev/null +++ b/src/org/thoughtcrime/securesms/database/PushDatabase.java @@ -0,0 +1,42 @@ +package org.thoughtcrime.securesms.database; + +import android.content.ContentValues; +import android.content.Context; +import android.database.sqlite.SQLiteOpenHelper; + +import org.spongycastle.util.encoders.Base64; +import org.whispersystems.textsecure.push.IncomingPushMessage; +import org.whispersystems.textsecure.util.Util; + +public class PushDatabase extends Database { + + private static final String TABLE_NAME = "push"; + public static final String ID = "_id"; + public static final String TYPE = "type"; + public static final String SOURCE = "source"; + public static final String DESTINATIONS = "destinations"; + public static final String BODY = "body"; + public static final String TIMESTAMP = "timestamp"; + + public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + + TYPE + " INTEGER, " + SOURCE + " TEXT, " + DESTINATIONS + " TEXT, " + BODY + " TEXT, " + TIMESTAMP + " INTEGER);"; + + public PushDatabase(Context context, SQLiteOpenHelper databaseHelper) { + super(context, databaseHelper); + } + + public long insert(IncomingPushMessage message) { + ContentValues values = new ContentValues(); + values.put(TYPE, message.getType()); + values.put(SOURCE, message.getSource()); + values.put(DESTINATIONS, Util.join(message.getDestinations(), ",")); + values.put(BODY, Base64.encode(message.getBody())); + values.put(TIMESTAMP, message.getTimestampMillis()); + + return databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, values); + } + + public void delete(long id) { + databaseHelper.getWritableDatabase().delete(TABLE_NAME, ID_WHERE, new String[] {id+""}); + } +} diff --git a/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java b/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java index dd437e29eb..397e34a529 100644 --- a/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java +++ b/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java @@ -7,8 +7,6 @@ import android.util.Log; import com.google.android.gcm.GCMBaseIntentService; import org.thoughtcrime.securesms.service.RegistrationService; import org.thoughtcrime.securesms.service.SendReceiveService; -import org.thoughtcrime.securesms.sms.IncomingTextMessage; -import org.thoughtcrime.securesms.sms.SmsTransportDetails; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.textsecure.crypto.InvalidVersionException; import org.whispersystems.textsecure.push.IncomingEncryptedPushMessage; @@ -17,7 +15,6 @@ import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.util.Util; import java.io.IOException; -import java.util.ArrayList; public class GcmIntentService extends GCMBaseIntentService { @@ -61,8 +58,11 @@ public class GcmIntentService extends GCMBaseIntentService { IncomingEncryptedPushMessage encryptedMessage = new IncomingEncryptedPushMessage(data, sessionKey); IncomingPushMessage message = encryptedMessage.getIncomingPushMessage(); - if (!message.hasAttachments()) handleIncomingTextMessage(context, message); - else handleIncomingMediaMessage(context, message); + Intent service = new Intent(context, SendReceiveService.class); + service.setAction(SendReceiveService.RECEIVE_PUSH_ACTION); + service.putExtra("message", message); + + context.startService(service); } catch (IOException e) { Log.w("GcmIntentService", e); } catch (InvalidVersionException e) { @@ -75,25 +75,6 @@ public class GcmIntentService extends GCMBaseIntentService { Log.w("GcmIntentService", "GCM Error: " + s); } - private void handleIncomingTextMessage(Context context, IncomingPushMessage message) { - ArrayList messages = new ArrayList(); - String encodedBody = new String(new SmsTransportDetails().getEncodedMessage(message.getBody())); - messages.add(new IncomingTextMessage(message, encodedBody)); - - Intent receivedIntent = new Intent(context, SendReceiveService.class); - receivedIntent.setAction(SendReceiveService.RECEIVE_SMS_ACTION); - receivedIntent.putParcelableArrayListExtra("text_messages", messages); - receivedIntent.putExtra("push_type", message.getType()); - context.startService(receivedIntent); - } - - private void handleIncomingMediaMessage(Context context, IncomingPushMessage message) { - Intent receivedIntent = new Intent(context, SendReceiveService.class); - receivedIntent.setAction(SendReceiveService.RECEIVE_PUSH_MMS_ACTION); - receivedIntent.putExtra("media_message", message); - context.startService(receivedIntent); - } - private PushServiceSocket getGcmSocket(Context context) { String localNumber = TextSecurePreferences.getLocalNumber(context); String password = TextSecurePreferences.getPushServerPassword(context); diff --git a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java index 451391b06d..559bb4132f 100644 --- a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -55,21 +55,21 @@ public class AttachmentManager { public void setImage(Uri image) throws IOException, BitmapDecodingException { ImageSlide slide = new ImageSlide(context, image); slideDeck.addSlide(slide); - thumbnail.setImageBitmap(slide.getThumbnail(345, 261)); + thumbnail.setImageDrawable(slide.getThumbnail(345, 261)); attachmentView.setVisibility(View.VISIBLE); } public void setVideo(Uri video) throws IOException, MediaTooLargeException { VideoSlide slide = new VideoSlide(context, video); slideDeck.addSlide(slide); - thumbnail.setImageBitmap(slide.getThumbnail(thumbnail.getWidth(), thumbnail.getHeight())); + thumbnail.setImageDrawable(slide.getThumbnail(thumbnail.getWidth(), thumbnail.getHeight())); attachmentView.setVisibility(View.VISIBLE); } public void setAudio(Uri audio)throws IOException, MediaTooLargeException { AudioSlide slide = new AudioSlide(context, audio); slideDeck.addSlide(slide); - thumbnail.setImageBitmap(slide.getThumbnail(thumbnail.getWidth(), thumbnail.getHeight())); + thumbnail.setImageDrawable(slide.getThumbnail(thumbnail.getWidth(), thumbnail.getHeight())); attachmentView.setVisibility(View.VISIBLE); } diff --git a/src/org/thoughtcrime/securesms/mms/AudioSlide.java b/src/org/thoughtcrime/securesms/mms/AudioSlide.java index aa62e527db..ee27e9e951 100644 --- a/src/org/thoughtcrime/securesms/mms/AudioSlide.java +++ b/src/org/thoughtcrime/securesms/mms/AudioSlide.java @@ -25,6 +25,7 @@ import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.provider.MediaStore.Audio; import android.widget.ImageView; @@ -50,8 +51,8 @@ public class AudioSlide extends Slide { } @Override - public Bitmap getThumbnail(int maxWidth, int maxHeight) { - return BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_menu_add_sound); + public Drawable getThumbnail(int maxWidth, int maxHeight) { + return context.getResources().getDrawable(R.drawable.ic_menu_add_sound); } public static PduPart constructPartFromUri(Context context, Uri uri) throws IOException, MediaTooLargeException { diff --git a/src/org/thoughtcrime/securesms/mms/ImageSlide.java b/src/org/thoughtcrime/securesms/mms/ImageSlide.java index 0b4fc29a9b..16054180fc 100644 --- a/src/org/thoughtcrime/securesms/mms/ImageSlide.java +++ b/src/org/thoughtcrime/securesms/mms/ImageSlide.java @@ -17,9 +17,8 @@ package org.thoughtcrime.securesms.mms; import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.Color; +import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; @@ -30,11 +29,11 @@ import android.util.Log; import android.widget.ImageView; import org.thoughtcrime.securesms.R; -import org.whispersystems.textsecure.crypto.MasterSecret; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.util.BitmapDecodingException; import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.LRUCache; +import org.whispersystems.textsecure.crypto.MasterSecret; import java.io.FileNotFoundException; import java.io.IOException; @@ -50,8 +49,8 @@ import ws.com.google.android.mms.pdu.PduPart; public class ImageSlide extends Slide { private static final int MAX_CACHE_SIZE = 10; - private static final Map> thumbnailCache = - Collections.synchronizedMap(new LRUCache>(MAX_CACHE_SIZE)); + private static final Map> thumbnailCache = + Collections.synchronizedMap(new LRUCache>(MAX_CACHE_SIZE)); public ImageSlide(Context context, MasterSecret masterSecret, PduPart part) { super(context, masterSecret, part); @@ -62,32 +61,37 @@ public class ImageSlide extends Slide { } @Override - public Bitmap getThumbnail(int maxWidth, int maxHeight) { - Bitmap thumbnail = getCachedThumbnail(); + public Drawable getThumbnail(int maxWidth, int maxHeight) { + Drawable thumbnail = getCachedThumbnail(); - if (thumbnail != null) + if (thumbnail != null) { return thumbnail; + } + + if (part.isPendingPush()) { + return context.getResources().getDrawable(R.drawable.stat_sys_download); + } try { InputStream measureStream = getPartDataInputStream(); InputStream dataStream = getPartDataInputStream(); - thumbnail = BitmapUtil.createScaledBitmap(measureStream, dataStream, maxWidth, maxHeight); - thumbnailCache.put(part.getDataUri(), new SoftReference(thumbnail)); + thumbnail = new BitmapDrawable(context.getResources(), BitmapUtil.createScaledBitmap(measureStream, dataStream, maxWidth, maxHeight)); + thumbnailCache.put(part.getDataUri(), new SoftReference(thumbnail)); return thumbnail; } catch (FileNotFoundException e) { Log.w("ImageSlide", e); - return BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_missing_thumbnail_picture); + return context.getResources().getDrawable(R.drawable.ic_missing_thumbnail_picture); } catch (BitmapDecodingException e) { Log.w("ImageSlide", e); - return BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_missing_thumbnail_picture); + return context.getResources().getDrawable(R.drawable.ic_missing_thumbnail_picture); } } @Override public void setThumbnailOn(ImageView imageView) { - Bitmap thumbnail = getCachedThumbnail(); + Drawable thumbnail = getCachedThumbnail(); if (thumbnail != null) { Log.w("ImageSlide", "Setting cached thumbnail..."); @@ -109,8 +113,9 @@ public class ImageSlide extends Slide { MmsDatabase.slideResolver.execute(new Runnable() { @Override public void run() { - final Bitmap bitmap = getThumbnail(maxWidth, maxHeight); + final Drawable bitmap = getThumbnail(maxWidth, maxHeight); final ImageView destination = weakImageView.get(); + if (destination != null && destination.getDrawable() == temporaryDrawable) { handler.post(new Runnable() { @Override @@ -123,24 +128,26 @@ public class ImageSlide extends Slide { }); } - private void setThumbnailOn(ImageView imageView, Bitmap thumbnail, boolean fromMemory) { + private void setThumbnailOn(ImageView imageView, Drawable thumbnail, boolean fromMemory) { if (fromMemory) { - imageView.setImageBitmap(thumbnail); + imageView.setImageDrawable(thumbnail); + } else if (thumbnail instanceof AnimationDrawable) { + imageView.setImageDrawable(thumbnail); + ((AnimationDrawable)imageView.getDrawable()).start(); } else { - BitmapDrawable result = new BitmapDrawable(context.getResources(), thumbnail); - TransitionDrawable fadingResult = new TransitionDrawable(new Drawable[]{new ColorDrawable(Color.TRANSPARENT), result}); + TransitionDrawable fadingResult = new TransitionDrawable(new Drawable[]{new ColorDrawable(Color.TRANSPARENT), thumbnail}); imageView.setImageDrawable(fadingResult); fadingResult.startTransition(300); } } - private Bitmap getCachedThumbnail() { + private Drawable getCachedThumbnail() { synchronized (thumbnailCache) { - SoftReference bitmapReference = thumbnailCache.get(part.getDataUri()); + SoftReference bitmapReference = thumbnailCache.get(part.getDataUri()); Log.w("ImageSlide", "Got soft reference: " + bitmapReference); if (bitmapReference != null) { - Bitmap bitmap = bitmapReference.get(); + Drawable bitmap = bitmapReference.get(); Log.w("ImageSlide", "Got cached bitmap: " + bitmap); if (bitmap != null) return bitmap; else thumbnailCache.remove(part.getDataUri()); diff --git a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java index 636b41bef6..29b23bdcec 100644 --- a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java +++ b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java @@ -1,15 +1,13 @@ package org.thoughtcrime.securesms.mms; -import android.util.Log; -import android.util.Pair; - import org.thoughtcrime.securesms.util.Util; +import org.whispersystems.textsecure.crypto.MasterCipher; +import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.push.IncomingPushMessage; +import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent; +import org.whispersystems.textsecure.util.Base64; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.List; +import java.io.UnsupportedEncodingException; import ws.com.google.android.mms.pdu.CharacterSets; import ws.com.google.android.mms.pdu.EncodedStringValue; @@ -28,9 +26,9 @@ public class IncomingMediaMessage { this.body = retreived.getBody(); } - public IncomingMediaMessage(String localNumber, IncomingPushMessage message, - List> attachments) - throws IOException + public IncomingMediaMessage(MasterSecret masterSecret, String localNumber, + IncomingPushMessage message, + PushMessageContent messageContent) { this.headers = new PduHeaders(); this.body = new PduBody(); @@ -39,32 +37,29 @@ public class IncomingMediaMessage { 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.appendEncodedStringValue(new EncodedStringValue(destination), PduHeaders.CC); } this.headers.setLongInteger(message.getTimestampMillis() / 1000, PduHeaders.DATE); - if (message.getBody() != null && message.getBody().length > 0) { + if (messageContent.getBody() != null && messageContent.getBody().length() > 0) { PduPart text = new PduPart(); - text.setData(message.getBody()); - text.setContentType("text/plain".getBytes(CharacterSets.MIMENAME_ISO_8859_1)); + text.setData(Util.toIsoBytes(messageContent.getBody())); + text.setContentType(Util.toIsoBytes("text/plain")); body.addPart(text); } - if (attachments != null) { - for (Pair attachment : attachments) { - PduPart media = new PduPart(); - FileInputStream fin = new FileInputStream(attachment.first); - byte[] data = Util.readFully(fin); + if (messageContent.getAttachmentsCount() > 0) { + for (PushMessageContent.AttachmentPointer attachment : messageContent.getAttachmentsList()) { + PduPart media = new PduPart(); + byte[] encryptedKey = new MasterCipher(masterSecret).encryptBytes(attachment.getKey().toByteArray()); - Log.w("IncomingMediaMessage", "Adding part: " + attachment.second + " with length: " + data.length); + media.setContentType(Util.toIsoBytes(attachment.getContentType())); + media.setContentLocation(Util.toIsoBytes(String.valueOf(attachment.getId()))); + media.setContentDisposition(Util.toIsoBytes(Base64.encodeBytes(encryptedKey))); + media.setPendingPush(true); - media.setContentType(attachment.second.getBytes(CharacterSets.MIMENAME_ISO_8859_1)); - media.setData(data); body.addPart(media); - attachment.first.delete(); } } } diff --git a/src/org/thoughtcrime/securesms/mms/Slide.java b/src/org/thoughtcrime/securesms/mms/Slide.java index d3a0af680f..71467565b0 100644 --- a/src/org/thoughtcrime/securesms/mms/Slide.java +++ b/src/org/thoughtcrime/securesms/mms/Slide.java @@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.providers.PartProvider; import android.content.ContentUris; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.util.Log; import android.widget.ImageView; @@ -90,12 +91,12 @@ public abstract class Slide { return part.getDataUri(); } - public Bitmap getThumbnail(int maxWidth, int maxHeight) { + public Drawable getThumbnail(int maxWidth, int maxHeight) { throw new AssertionError("getThumbnail() called on non-thumbnail producing slide!"); } public void setThumbnailOn(ImageView imageView) { - imageView.setImageBitmap(getThumbnail(imageView.getWidth(), imageView.getHeight())); + imageView.setImageDrawable(getThumbnail(imageView.getWidth(), imageView.getHeight())); } public boolean hasImage() { diff --git a/src/org/thoughtcrime/securesms/mms/VideoSlide.java b/src/org/thoughtcrime/securesms/mms/VideoSlide.java index dfece90c48..5596cd90b5 100644 --- a/src/org/thoughtcrime/securesms/mms/VideoSlide.java +++ b/src/org/thoughtcrime/securesms/mms/VideoSlide.java @@ -26,6 +26,7 @@ import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.provider.MediaStore; import android.util.Log; @@ -42,8 +43,8 @@ public class VideoSlide extends Slide { } @Override - public Bitmap getThumbnail(int width, int height) { - return BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher_video_player); + public Drawable getThumbnail(int width, int height) { + return context.getResources().getDrawable(R.drawable.ic_launcher_video_player); } @Override diff --git a/src/org/thoughtcrime/securesms/service/MmsReceiver.java b/src/org/thoughtcrime/securesms/service/MmsReceiver.java index 871fdbae0d..e51cea39da 100644 --- a/src/org/thoughtcrime/securesms/service/MmsReceiver.java +++ b/src/org/thoughtcrime/securesms/service/MmsReceiver.java @@ -48,14 +48,8 @@ public class MmsReceiver { } 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); + if (intent.getAction().equals(SendReceiveService.RECEIVE_MMS_ACTION)) { + handleMmsNotification(intent); } } @@ -73,28 +67,6 @@ public class MmsReceiver { } } - 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> 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) { Intent intent = new Intent(SendReceiveService.DOWNLOAD_MMS_ACTION, null, context, SendReceiveService.class); intent.putExtra("content_location", new String(pdu.getContentLocation())); diff --git a/src/org/thoughtcrime/securesms/service/PushDownloader.java b/src/org/thoughtcrime/securesms/service/PushDownloader.java new file mode 100644 index 0000000000..506a74c62c --- /dev/null +++ b/src/org/thoughtcrime/securesms/service/PushDownloader.java @@ -0,0 +1,107 @@ +package org.thoughtcrime.securesms.service; + + +import android.content.Context; +import android.content.Intent; +import android.util.Log; +import android.util.Pair; + +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.EncryptingPartDatabase; +import org.thoughtcrime.securesms.database.PartDatabase; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.Util; +import org.whispersystems.textsecure.crypto.AttachmentCipherInputStream; +import org.whispersystems.textsecure.crypto.InvalidMessageException; +import org.whispersystems.textsecure.crypto.MasterCipher; +import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.push.PushServiceSocket; +import org.whispersystems.textsecure.util.Base64; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import ws.com.google.android.mms.MmsException; +import ws.com.google.android.mms.pdu.PduPart; + +public class PushDownloader { + + private final Context context; + + public PushDownloader(Context context) { + this.context = context.getApplicationContext(); + } + + public void process(MasterSecret masterSecret, Intent intent) { + if (!intent.getAction().equals(SendReceiveService.DOWNLOAD_PUSH_ACTION)) + return; + + long messageId = intent.getLongExtra("message_id", -1); + PartDatabase database = DatabaseFactory.getEncryptingPartDatabase(context, masterSecret); + + Log.w("PushDownloader", "Downloading push parts for: " + messageId); + + if (messageId != -1) { + List> parts = database.getParts(messageId, false); + + for (Pair partPair : parts) { + retrievePart(masterSecret, partPair.second, messageId, partPair.first); + Log.w("PushDownloader", "Got part: " + partPair.first); + } + } else { + List>> parts = database.getPushPendingParts(); + + for (Pair> partPair : parts) { + retrievePart(masterSecret, partPair.second.second, partPair.first, partPair.second.first); + Log.w("PushDownloader", "Got part: " + partPair.second.first); + } + } + } + + private void retrievePart(MasterSecret masterSecret, PduPart part, long messageId, long partId) { + EncryptingPartDatabase database = DatabaseFactory.getEncryptingPartDatabase(context, masterSecret); + File attachmentFile = null; + + try { + MasterCipher masterCipher = new MasterCipher(masterSecret); + long contentLocation = Long.parseLong(Util.toIsoString(part.getContentLocation())); + byte[] key = masterCipher.decryptBytes(Base64.decode(Util.toIsoString(part.getContentDisposition()))); + + attachmentFile = downloadAttachment(contentLocation); + InputStream attachmentInput = new AttachmentCipherInputStream(attachmentFile, key); + + database.updateDownloadedPart(messageId, partId, part, attachmentInput); + } catch (InvalidMessageException e) { + Log.w("PushDownloader", e); + try { + database.updateFailedDownloadedPart(messageId, partId, part); + } catch (MmsException mme) { + Log.w("PushDownloader", mme); + } + } catch (MmsException e) { + Log.w("PushDownloader", e); + try { + database.updateFailedDownloadedPart(messageId, partId, part); + } catch (MmsException mme) { + Log.w("PushDownloader", mme); + } + } catch (IOException e) { + Log.w("PushDownloader", e); + /// XXX schedule some kind of soft failure retry action + } finally { + if (attachmentFile != null) + attachmentFile.delete(); + } + } + + private File downloadAttachment(long contentLocation) throws IOException { + String localNumber = TextSecurePreferences.getLocalNumber(context); + String password = TextSecurePreferences.getPushServerPassword(context); + PushServiceSocket socket = new PushServiceSocket(context, localNumber, password); + + return socket.retrieveAttachment(contentLocation); + } + +} diff --git a/src/org/thoughtcrime/securesms/service/PushReceiver.java b/src/org/thoughtcrime/securesms/service/PushReceiver.java new file mode 100644 index 0000000000..4750615d20 --- /dev/null +++ b/src/org/thoughtcrime/securesms/service/PushReceiver.java @@ -0,0 +1,211 @@ +package org.thoughtcrime.securesms.service; + +import android.content.Context; +import android.content.Intent; +import android.util.Log; +import android.util.Pair; + +import com.google.protobuf.InvalidProtocolBufferException; +import org.thoughtcrime.securesms.crypto.DecryptingQueue; +import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; +import org.thoughtcrime.securesms.database.MmsDatabase; +import org.thoughtcrime.securesms.mms.IncomingMediaMessage; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; +import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage; +import org.thoughtcrime.securesms.sms.IncomingTextMessage; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.textsecure.crypto.InvalidKeyException; +import org.whispersystems.textsecure.crypto.InvalidVersionException; +import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; +import org.whispersystems.textsecure.push.IncomingPushMessage; +import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent; +import org.whispersystems.textsecure.storage.InvalidKeyIdException; + +import ws.com.google.android.mms.MmsException; + +public class PushReceiver { + + public static final int RESULT_OK = 0; + public static final int RESULT_NO_SESSION = 1; + public static final int RESULT_DECRYPT_FAILED = 2; + + private final Context context; + + public PushReceiver(Context context) { + this.context = context.getApplicationContext(); + } + + public void process(MasterSecret masterSecret, Intent intent) { + if (intent.getAction().equals(SendReceiveService.RECEIVE_PUSH_ACTION)) { + handleMessage(masterSecret, intent); + } else if (intent.getAction().equals(SendReceiveService.DECRYPTED_PUSH_ACTION)) { + handleDecrypt(masterSecret, intent); + } + } + + private void handleDecrypt(MasterSecret masterSecret, Intent intent) { + IncomingPushMessage message = intent.getParcelableExtra("message"); + long messageId = intent.getLongExtra("message_id", -1); + int result = intent.getIntExtra("result", 0); + + if (result == RESULT_OK) handleReceivedMessage(masterSecret, message, true); + else if (result == RESULT_NO_SESSION) handleReceivedMessageForNoSession(masterSecret, message); + else if (result == RESULT_DECRYPT_FAILED) handleReceivedCorruptedMessage(masterSecret, message, true); + + DatabaseFactory.getPushDatabase(context).delete(messageId); + } + + private void handleMessage(MasterSecret masterSecret, Intent intent) { + IncomingPushMessage message = intent.getExtras().getParcelable("message"); + + if (message.isSecureMessage()) handleReceivedSecureMessage(masterSecret, message); + else if (message.isPreKeyBundle()) handleReceivedPreKeyBundle(masterSecret, message); + else handleReceivedMessage(masterSecret, message, false); + } + + private void handleReceivedSecureMessage(MasterSecret masterSecret, IncomingPushMessage message) { + long id = DatabaseFactory.getPushDatabase(context).insert(message); + DecryptingQueue.scheduleDecryption(context, masterSecret, id, message); + } + + private void handleReceivedPreKeyBundle(MasterSecret masterSecret, IncomingPushMessage message) { + try { + Recipient recipient = new Recipient(null, message.getSource(), null, null); + KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient); + PreKeyBundleMessage preKeyExchange = new PreKeyBundleMessage(message.getBody()); + + if (processor.isTrusted(preKeyExchange)) { + processor.processKeyExchangeMessage(preKeyExchange); + + IncomingPushMessage bundledMessage = message.withBody(preKeyExchange.getBundledMessage()); + handleReceivedSecureMessage(masterSecret, bundledMessage); + } else { + /// XXX + } + } catch (InvalidKeyException e) { + Log.w("SmsReceiver", e); + handleReceivedCorruptedKey(masterSecret, message, false); + } catch (InvalidVersionException e) { + Log.w("SmsReceiver", e); + handleReceivedCorruptedKey(masterSecret, message, true); + } catch (InvalidKeyIdException e) { + Log.w("SmsReceiver", e); + handleReceivedCorruptedKey(masterSecret, message, false); + } + } + + private void handleReceivedMessage(MasterSecret masterSecret, + IncomingPushMessage message, + boolean secure) + { + try { + PushMessageContent messageContent = PushMessageContent.parseFrom(message.getBody()); + + if (messageContent.getAttachmentsCount() > 0) { + Log.w("PushReceiver", "Received push media message..."); + handleReceivedMediaMessage(masterSecret, message, messageContent, secure); + } else { + Log.w("PushReceiver", "Received push text message..."); + handleReceivedTextMessage(masterSecret, message, messageContent, secure); + } + } catch (InvalidProtocolBufferException e) { + Log.w("PushReceiver", e); + handleReceivedCorruptedMessage(masterSecret, message, secure); + } + } + + private void handleReceivedMediaMessage(MasterSecret masterSecret, + IncomingPushMessage message, + PushMessageContent messageContent, + boolean secure) + { + + try { + String localNumber = TextSecurePreferences.getLocalNumber(context); + MmsDatabase database = DatabaseFactory.getMmsDatabase(context); + IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterSecret, localNumber, + message, messageContent); + + Pair messageAndThreadId; + + if (secure) { + messageAndThreadId = database.insertSecureDecryptedMessageInbox(masterSecret, mediaMessage, -1); + } else { + messageAndThreadId = database.insertMessageInbox(masterSecret, mediaMessage, null, -1); + } + + Intent intent = new Intent(context, SendReceiveService.class); + intent.setAction(SendReceiveService.DOWNLOAD_PUSH_ACTION); + intent.putExtra("message_id", messageAndThreadId.first); + context.startService(intent); + + } catch (MmsException e) { + Log.w("PushReceiver", e); + // XXX + } + } + + private void handleReceivedTextMessage(MasterSecret masterSecret, + IncomingPushMessage message, + PushMessageContent messageContent, + boolean secure) + { + EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); + IncomingTextMessage textMessage = new IncomingTextMessage(message, ""); + + if (secure) { + textMessage = new IncomingEncryptedMessage(textMessage, ""); + } + + Pair messageAndThreadId = database.insertMessageInbox(masterSecret, textMessage); + database.updateMessageBody(masterSecret, messageAndThreadId.first, messageContent.getBody()); + } + + private void handleReceivedCorruptedMessage(MasterSecret masterSecret, + IncomingPushMessage message, + boolean secure) + { + long messageId = insertMessagePlaceholder(masterSecret, message, secure); + DatabaseFactory.getEncryptingSmsDatabase(context).markAsDecryptFailed(messageId); + } + + private void handleReceivedCorruptedKey(MasterSecret masterSecret, + IncomingPushMessage message, + boolean invalidVersion) + { + IncomingTextMessage corruptedMessage = new IncomingTextMessage(message, ""); + IncomingKeyExchangeMessage corruptedKeyMessage = new IncomingKeyExchangeMessage(corruptedMessage, ""); + + if (!invalidVersion) corruptedKeyMessage.setCorrupted(true); + else corruptedKeyMessage.setInvalidVersion(true); + + DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageInbox(masterSecret, corruptedKeyMessage); + } + + private void handleReceivedMessageForNoSession(MasterSecret masterSecret, + IncomingPushMessage message) + { + long messageId = insertMessagePlaceholder(masterSecret, message, true); + DatabaseFactory.getEncryptingSmsDatabase(context).markAsNoSession(messageId); + } + + private long insertMessagePlaceholder(MasterSecret masterSecret, + IncomingPushMessage message, + boolean secure) + { + IncomingTextMessage placeholder = new IncomingTextMessage(message, ""); + + if (secure) { + placeholder = new IncomingEncryptedMessage(placeholder, ""); + } + + Pair messageAndThreadId = DatabaseFactory.getEncryptingSmsDatabase(context) + .insertMessageInbox(masterSecret, + placeholder); + return messageAndThreadId.first; + } +} diff --git a/src/org/thoughtcrime/securesms/service/SendReceiveService.java b/src/org/thoughtcrime/securesms/service/SendReceiveService.java index 28573124d8..c1759b3cfc 100644 --- a/src/org/thoughtcrime/securesms/service/SendReceiveService.java +++ b/src/org/thoughtcrime/securesms/service/SendReceiveService.java @@ -51,10 +51,12 @@ public class SendReceiveService extends Service { 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 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_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 RECEIVE_PUSH_ACTION = "org.thoughtcrime.securesms.SendReceiveService.RECEIVE_PUSH_ACTION"; + public static final String DECRYPTED_PUSH_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DECRYPTED_PUSH_ACTION"; + public static final String DOWNLOAD_PUSH_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_PUSH_ACTION"; private static final int SEND_SMS = 0; private static final int RECEIVE_SMS = 1; @@ -62,14 +64,18 @@ public class SendReceiveService extends Service { private static final int RECEIVE_MMS = 3; private static final int DOWNLOAD_MMS = 4; private static final int DOWNLOAD_MMS_PENDING = 5; + private static final int RECEIVE_PUSH = 6; + private static final int DOWNLOAD_PUSH = 7; private ToastHandler toastHandler; - private SmsReceiver smsReceiver; - private SmsSender smsSender; - private MmsReceiver mmsReceiver; - private MmsSender mmsSender; - private MmsDownloader mmsDownloader; + private SmsReceiver smsReceiver; + private SmsSender smsSender; + private MmsReceiver mmsReceiver; + private MmsSender mmsSender; + private MmsDownloader mmsDownloader; + private PushReceiver pushReceiver; + private PushDownloader pushDownloader; private MasterSecret masterSecret; private boolean hasSecret; @@ -78,7 +84,6 @@ public class SendReceiveService extends Service { private ClearKeyReceiver clearKeyReceiver; private List workQueue; private List pendingSecretList; - private Thread workerThread; @Override public void onCreate() { @@ -105,12 +110,18 @@ public class SendReceiveService extends Service { scheduleIntent(SEND_SMS, intent); else if (action.equals(SEND_MMS_ACTION)) scheduleSecretRequiredIntent(SEND_MMS, intent); - else if (action.equals(RECEIVE_MMS_ACTION) || action.equals(RECEIVE_PUSH_MMS_ACTION)) + else if (action.equals(RECEIVE_MMS_ACTION)) scheduleIntent(RECEIVE_MMS, intent); else if (action.equals(DOWNLOAD_MMS_ACTION)) scheduleSecretRequiredIntent(DOWNLOAD_MMS, intent); else if (intent.getAction().equals(DOWNLOAD_MMS_PENDING_APN_ACTION)) scheduleSecretRequiredIntent(DOWNLOAD_MMS_PENDING, intent); + else if (action.equals(RECEIVE_PUSH_ACTION)) + scheduleIntent(RECEIVE_PUSH, intent); + else if (action.equals(DECRYPTED_PUSH_ACTION)) + scheduleSecretRequiredIntent(RECEIVE_PUSH, intent); + else if (action.equals(DOWNLOAD_PUSH_ACTION)) + scheduleSecretRequiredIntent(DOWNLOAD_PUSH, intent); else Log.w("SendReceiveService", "Received intent with unknown action: " + intent.getAction()); } @@ -142,13 +153,15 @@ public class SendReceiveService extends Service { mmsReceiver = new MmsReceiver(this); mmsSender = new MmsSender(this, toastHandler); mmsDownloader = new MmsDownloader(this, toastHandler); + pushReceiver = new PushReceiver(this); + pushDownloader = new PushDownloader(this); } private void initializeWorkQueue() { pendingSecretList = new LinkedList(); workQueue = new LinkedList(); - workerThread = new WorkerThread(workQueue, "SendReceveService-WorkerThread"); + Thread workerThread = new WorkerThread(workQueue, "SendReceveService-WorkerThread"); workerThread.start(); } @@ -222,12 +235,14 @@ public class SendReceiveService extends Service { @Override public void run() { switch (what) { - case RECEIVE_SMS: smsReceiver.process(masterSecret, intent); return; - case SEND_SMS: smsSender.process(masterSecret, intent); return; - case RECEIVE_MMS: mmsReceiver.process(masterSecret, intent); return; - case SEND_MMS: mmsSender.process(masterSecret, intent); return; - case DOWNLOAD_MMS: mmsDownloader.process(masterSecret, intent); return; + case RECEIVE_SMS: smsReceiver.process(masterSecret, intent); return; + case SEND_SMS: smsSender.process(masterSecret, intent); return; + case RECEIVE_MMS: mmsReceiver.process(masterSecret, intent); return; + case SEND_MMS: mmsSender.process(masterSecret, intent); return; + case DOWNLOAD_MMS: mmsDownloader.process(masterSecret, intent); return; case DOWNLOAD_MMS_PENDING: mmsDownloader.process(masterSecret, intent); return; + case RECEIVE_PUSH: pushReceiver.process(masterSecret, intent); return; + case DOWNLOAD_PUSH: pushDownloader.process(masterSecret, intent); return; } } } diff --git a/src/org/thoughtcrime/securesms/transport/PushTransport.java b/src/org/thoughtcrime/securesms/transport/PushTransport.java index 4f6623cf98..8fbb04e7d4 100644 --- a/src/org/thoughtcrime/securesms/transport/PushTransport.java +++ b/src/org/thoughtcrime/securesms/transport/PushTransport.java @@ -4,11 +4,18 @@ import android.content.Context; import android.util.Log; import android.util.Pair; +import com.google.protobuf.ByteString; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.mms.PartParser; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientFactory; +import org.thoughtcrime.securesms.recipients.RecipientFormattingException; +import org.thoughtcrime.securesms.recipients.Recipients; +import org.whispersystems.textsecure.crypto.AttachmentCipher; +import org.whispersystems.textsecure.push.PushAttachmentPointer; +import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent; import org.whispersystems.textsecure.push.RawTransportDetails; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; @@ -24,6 +31,7 @@ import org.whispersystems.textsecure.push.PushAttachmentData; import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.push.PushTransportDetails; import org.whispersystems.textsecure.push.RateLimitException; +import org.whispersystems.textsecure.util.Hex; import org.whispersystems.textsecure.util.PhoneNumberFormatter; import java.io.IOException; @@ -50,10 +58,12 @@ public class PushTransport extends BaseTransport { String password = TextSecurePreferences.getPushServerPassword(context); PushServiceSocket socket = new PushServiceSocket(context, localNumber, password); - Recipient recipient = message.getIndividualRecipient(); - String plaintext = message.getBody().getBody(); - String recipientCanonicalNumber = PhoneNumberFormatter.formatNumber(recipient.getNumber(), - localNumber); + Recipient recipient = message.getIndividualRecipient(); + String plaintextBody = message.getBody().getBody(); + PushMessageContent.Builder builder = PushMessageContent.newBuilder(); + byte[] plaintext = builder.setBody(plaintextBody).build().toByteArray(); + String recipientCanonicalNumber = PhoneNumberFormatter.formatNumber(recipient.getNumber(), + localNumber); Pair typeAndCiphertext = getEncryptedMessage(socket, recipient, recipientCanonicalNumber, plaintext); @@ -68,39 +78,70 @@ public class PushTransport extends BaseTransport { public void deliver(SendReq message, List destinations) throws IOException { try { - String localNumber = TextSecurePreferences.getLocalNumber(context); - String password = TextSecurePreferences.getPushServerPassword(context); - PushServiceSocket socket = new PushServiceSocket(context, localNumber, password); - byte[] messageText = PartParser.getMessageText(message.getBody()).getBytes(); - List attachments = getAttachmentsFromBody(message.getBody()); + String localNumber = TextSecurePreferences.getLocalNumber(context); + String password = TextSecurePreferences.getPushServerPassword(context); + PushServiceSocket socket = new PushServiceSocket(context, localNumber, password); + String messageBody = PartParser.getMessageText(message.getBody()); + List ciphertext = new LinkedList (); + List types = new LinkedList(); - List messagesList = new LinkedList(); - List> attachmentsList = new LinkedList>(); + for (String destination : destinations) { + Recipients recipients = RecipientFactory.getRecipientsFromString(context, destination, false); + List attachments = getPushAttachmentPointers(socket, message.getBody()); + PushMessageContent.Builder builder = PushMessageContent.newBuilder(); - for (String recipient : destinations) { - messagesList.add(messageText); - attachmentsList.add(attachments); + if (messageBody != null) { + builder.setBody(messageBody); + } + + for (PushAttachmentPointer attachment : attachments) { + PushMessageContent.AttachmentPointer.Builder attachmentBuilder = + PushMessageContent.AttachmentPointer.newBuilder(); + + attachmentBuilder.setId(attachment.getId()); + attachmentBuilder.setContentType(attachment.getContentType()); + attachmentBuilder.setKey(ByteString.copyFrom(attachment.getKey())); + + builder.addAttachments(attachmentBuilder.build()); + } + + byte[] plaintext = builder.build().toByteArray(); + Pair typeAndCiphertext = getEncryptedMessage(socket, recipients.getPrimaryRecipient(), + destination, plaintext); + + types.add(typeAndCiphertext.first); + ciphertext.add(typeAndCiphertext.second); } - socket.sendMessage(destinations, messagesList, attachmentsList, - OutgoingPushMessage.TYPE_MESSAGE_PLAINTEXT); + socket.sendMessage(destinations, ciphertext, types); + } catch (RateLimitException e) { Log.w("PushTransport", e); throw new IOException("Rate limit exceeded."); + } catch (RecipientFormattingException e) { + Log.w("PushTransport", e); + throw new IOException("Bad destination!"); } } - private List getAttachmentsFromBody(PduBody body) { - List attachments = new LinkedList(); + private List getPushAttachmentPointers(PushServiceSocket socket, PduBody body) + throws IOException + { + List attachments = new LinkedList(); for (int i=0;i getEncryptedMessage(PushServiceSocket socket, Recipient recipient, - String canonicalRecipientNumber, String plaintext) + String canonicalRecipientNumber, byte[] plaintext) throws IOException { if (KeyUtil.isNonPrekeySessionFor(context, masterSecret, recipient)) { @@ -127,13 +168,13 @@ public class PushTransport extends BaseTransport { } private byte[] getEncryptedPrekeyBundleMessageForExistingSession(Recipient recipient, - String plaintext) + byte[] plaintext) { IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); IdentityKey identityKey = identityKeyPair.getPublicKey(); MessageCipher message = new MessageCipher(context, masterSecret, identityKeyPair, new RawTransportDetails()); - byte[] bundledMessage = message.encrypt(recipient, plaintext.getBytes()); + byte[] bundledMessage = message.encrypt(recipient, plaintext); PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(identityKey, bundledMessage); return preKeyBundleMessage.serialize(); @@ -142,7 +183,7 @@ public class PushTransport extends BaseTransport { private byte[] getEncryptedPrekeyBundleMessageForNewSession(PushServiceSocket socket, Recipient recipient, String canonicalRecipientNumber, - String plaintext) + byte[] plaintext) throws IOException { IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); @@ -153,20 +194,20 @@ public class PushTransport extends BaseTransport { processor.processKeyExchangeMessage(preKey); MessageCipher message = new MessageCipher(context, masterSecret, identityKeyPair, new RawTransportDetails()); - byte[] bundledMessage = message.encrypt(recipient, plaintext.getBytes()); + byte[] bundledMessage = message.encrypt(recipient, plaintext); PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(identityKey, bundledMessage); return preKeyBundleMessage.serialize(); } - private byte[] getEncryptedMessageForExistingSession(Recipient recipient, String plaintext) + private byte[] getEncryptedMessageForExistingSession(Recipient recipient, byte[] plaintext) throws IOException { IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair, new PushTransportDetails()); - return messageCipher.encrypt(recipient, plaintext.getBytes()); + return messageCipher.encrypt(recipient, plaintext); } } diff --git a/src/ws/com/google/android/mms/pdu/PduBody.java b/src/ws/com/google/android/mms/pdu/PduBody.java index 1947dbe7d1..9cb5cb17b0 100644 --- a/src/ws/com/google/android/mms/pdu/PduBody.java +++ b/src/ws/com/google/android/mms/pdu/PduBody.java @@ -41,6 +41,16 @@ public class PduBody { mPartMapByFileName = new HashMap(); } + public boolean containsPushInProgress() { + for (int i=0;i