From 49daa45dca48b6e85b5d97d00606577e5a1885af Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Tue, 14 Jan 2014 00:26:43 -0800 Subject: [PATCH] wip --- .../protobuf/IncomingPushMessageSignal.proto | 22 +- .../textsecure/push/IncomingPushMessage.java | 16 - .../textsecure/push/PushMessageProtos.java | 1158 +++++++++++++++-- .../whispersystems/textsecure/util/Util.java | 17 + .../securesms/database/DatabaseFactory.java | 18 +- .../securesms/database/GroupDatabase.java | 241 ++++ .../securesms/database/MmsDatabase.java | 22 +- .../securesms/database/PushDatabase.java | 7 +- .../securesms/database/SmsDatabase.java | 7 +- .../securesms/database/ThreadDatabase.java | 33 +- .../securesms/mms/IncomingMediaMessage.java | 25 +- .../recipients/RecipientFactory.java | 18 +- .../recipients/RecipientProvider.java | 87 +- .../securesms/service/AvatarDownloader.java | 75 ++ .../securesms/service/PushReceiver.java | 94 +- .../securesms/service/SendReceiveService.java | 51 +- .../securesms/sms/IncomingTextMessage.java | 13 +- .../securesms/sms/MessageSender.java | 9 +- .../securesms/util/BitmapUtil.java | 5 + 19 files changed, 1674 insertions(+), 244 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/database/GroupDatabase.java create mode 100644 src/org/thoughtcrime/securesms/service/AvatarDownloader.java diff --git a/library/protobuf/IncomingPushMessageSignal.proto b/library/protobuf/IncomingPushMessageSignal.proto index a86c2e1a40..08faf9671b 100644 --- a/library/protobuf/IncomingPushMessageSignal.proto +++ b/library/protobuf/IncomingPushMessageSignal.proto @@ -7,19 +7,35 @@ message IncomingPushMessageSignal { optional uint32 type = 1; optional string source = 2; optional string relay = 3; - repeated string destinations = 4; +// repeated string destinations = 4; // No longer supported optional uint64 timestamp = 5; optional bytes message = 6; // Contains an encrypted PushMessageContent } message PushMessageContent { - optional string body = 1; - message AttachmentPointer { optional fixed64 id = 1; optional string contentType = 2; optional bytes key = 3; } + message GroupContext { + enum Type { + UNKNOWN = 0; + CREATE = 1; + MODIFY = 2; + DELIVER = 3; + ADD = 4; + QUIT = 5; + } + optional bytes id = 1; + optional Type type = 2; + optional string name = 3; + repeated string members = 4; + optional AttachmentPointer avatar = 5; + } + + optional string body = 1; repeated AttachmentPointer attachments = 2; + optional GroupContext group = 3; } \ No newline at end of file diff --git a/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java b/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java index ad53ae2694..34bf72ff12 100644 --- a/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java +++ b/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java @@ -21,9 +21,6 @@ import android.os.Parcelable; import org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal; -import java.util.LinkedList; -import java.util.List; - public class IncomingPushMessage implements PushMessage, Parcelable { public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @@ -40,7 +37,6 @@ public class IncomingPushMessage implements PushMessage, Parcelable { private int type; private String source; - private List destinations; private byte[] message; private long timestamp; private String relay; @@ -51,21 +47,17 @@ public class IncomingPushMessage implements PushMessage, Parcelable { this.timestamp = message.timestamp; this.relay = message.relay; this.message = body; - this.destinations = new LinkedList(); - this.destinations.addAll(message.destinations); } public IncomingPushMessage(IncomingPushMessageSignal signal) { this.type = signal.getType(); this.source = signal.getSource(); - this.destinations = signal.getDestinationsList(); this.message = signal.getMessage().toByteArray(); this.timestamp = signal.getTimestamp(); this.relay = signal.getRelay(); } public IncomingPushMessage(Parcel in) { - this.destinations = new LinkedList(); this.type = in.readInt(); this.source = in.readString(); @@ -73,19 +65,16 @@ public class IncomingPushMessage implements PushMessage, Parcelable { this.relay = in.readString(); } - in.readStringList(destinations); this.message = new byte[in.readInt()]; in.readByteArray(this.message); this.timestamp = in.readLong(); } public IncomingPushMessage(int type, String source, - List destinations, byte[] body, long timestamp) { this.type = type; this.source = source; - this.destinations = destinations; this.message = body; this.timestamp = timestamp; } @@ -106,10 +95,6 @@ public class IncomingPushMessage implements PushMessage, Parcelable { return message; } - public List getDestinations() { - return destinations; - } - @Override public int describeContents() { return 0; @@ -123,7 +108,6 @@ public class IncomingPushMessage implements PushMessage, Parcelable { if (relay != null) { dest.writeString(relay); } - dest.writeStringList(destinations); dest.writeInt(message.length); dest.writeByteArray(message); dest.writeLong(timestamp); diff --git a/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java b/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java index b0d528e90c..840ddfd749 100644 --- a/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java +++ b/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java @@ -23,11 +23,6 @@ public final class PushMessageProtos { boolean hasRelay(); String getRelay(); - // repeated string destinations = 4; - java.util.List getDestinationsList(); - int getDestinationsCount(); - String getDestinations(int index); - // optional uint64 timestamp = 5; boolean hasTimestamp(); long getTimestamp(); @@ -139,20 +134,6 @@ public final class PushMessageProtos { } } - // repeated string destinations = 4; - public static final int DESTINATIONS_FIELD_NUMBER = 4; - private com.google.protobuf.LazyStringList destinations_; - public java.util.List - getDestinationsList() { - return destinations_; - } - public int getDestinationsCount() { - return destinations_.size(); - } - public String getDestinations(int index) { - return destinations_.get(index); - } - // optional uint64 timestamp = 5; public static final int TIMESTAMP_FIELD_NUMBER = 5; private long timestamp_; @@ -177,7 +158,6 @@ public final class PushMessageProtos { type_ = 0; source_ = ""; relay_ = ""; - destinations_ = com.google.protobuf.LazyStringArrayList.EMPTY; timestamp_ = 0L; message_ = com.google.protobuf.ByteString.EMPTY; } @@ -202,9 +182,6 @@ public final class PushMessageProtos { if (((bitField0_ & 0x00000004) == 0x00000004)) { output.writeBytes(3, getRelayBytes()); } - for (int i = 0; i < destinations_.size(); i++) { - output.writeBytes(4, destinations_.getByteString(i)); - } if (((bitField0_ & 0x00000008) == 0x00000008)) { output.writeUInt64(5, timestamp_); } @@ -232,15 +209,6 @@ public final class PushMessageProtos { size += com.google.protobuf.CodedOutputStream .computeBytesSize(3, getRelayBytes()); } - { - int dataSize = 0; - for (int i = 0; i < destinations_.size(); i++) { - dataSize += com.google.protobuf.CodedOutputStream - .computeBytesSizeNoTag(destinations_.getByteString(i)); - } - size += dataSize; - size += 1 * getDestinationsList().size(); - } if (((bitField0_ & 0x00000008) == 0x00000008)) { size += com.google.protobuf.CodedOutputStream .computeUInt64Size(5, timestamp_); @@ -379,12 +347,10 @@ public final class PushMessageProtos { bitField0_ = (bitField0_ & ~0x00000002); relay_ = ""; bitField0_ = (bitField0_ & ~0x00000004); - destinations_ = com.google.protobuf.LazyStringArrayList.EMPTY; - bitField0_ = (bitField0_ & ~0x00000008); timestamp_ = 0L; - bitField0_ = (bitField0_ & ~0x00000010); + bitField0_ = (bitField0_ & ~0x00000008); message_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000020); + bitField0_ = (bitField0_ & ~0x00000010); return this; } @@ -435,17 +401,11 @@ public final class PushMessageProtos { to_bitField0_ |= 0x00000004; } result.relay_ = relay_; - if (((bitField0_ & 0x00000008) == 0x00000008)) { - destinations_ = new com.google.protobuf.UnmodifiableLazyStringList( - destinations_); - bitField0_ = (bitField0_ & ~0x00000008); - } - result.destinations_ = destinations_; - if (((from_bitField0_ & 0x00000010) == 0x00000010)) { + if (((from_bitField0_ & 0x00000008) == 0x00000008)) { to_bitField0_ |= 0x00000008; } result.timestamp_ = timestamp_; - if (((from_bitField0_ & 0x00000020) == 0x00000020)) { + if (((from_bitField0_ & 0x00000010) == 0x00000010)) { to_bitField0_ |= 0x00000010; } result.message_ = message_; @@ -474,16 +434,6 @@ public final class PushMessageProtos { if (other.hasRelay()) { setRelay(other.getRelay()); } - if (!other.destinations_.isEmpty()) { - if (destinations_.isEmpty()) { - destinations_ = other.destinations_; - bitField0_ = (bitField0_ & ~0x00000008); - } else { - ensureDestinationsIsMutable(); - destinations_.addAll(other.destinations_); - } - onChanged(); - } if (other.hasTimestamp()) { setTimestamp(other.getTimestamp()); } @@ -536,18 +486,13 @@ public final class PushMessageProtos { relay_ = input.readBytes(); break; } - case 34: { - ensureDestinationsIsMutable(); - destinations_.add(input.readBytes()); - break; - } case 40: { - bitField0_ |= 0x00000010; + bitField0_ |= 0x00000008; timestamp_ = input.readUInt64(); break; } case 50: { - bitField0_ |= 0x00000020; + bitField0_ |= 0x00000010; message_ = input.readBytes(); break; } @@ -650,78 +595,22 @@ public final class PushMessageProtos { onChanged(); } - // repeated string destinations = 4; - private com.google.protobuf.LazyStringList destinations_ = com.google.protobuf.LazyStringArrayList.EMPTY; - private void ensureDestinationsIsMutable() { - if (!((bitField0_ & 0x00000008) == 0x00000008)) { - destinations_ = new com.google.protobuf.LazyStringArrayList(destinations_); - bitField0_ |= 0x00000008; - } - } - public java.util.List - getDestinationsList() { - return java.util.Collections.unmodifiableList(destinations_); - } - public int getDestinationsCount() { - return destinations_.size(); - } - public String getDestinations(int index) { - return destinations_.get(index); - } - public Builder setDestinations( - int index, String value) { - if (value == null) { - throw new NullPointerException(); - } - ensureDestinationsIsMutable(); - destinations_.set(index, value); - onChanged(); - return this; - } - public Builder addDestinations(String value) { - if (value == null) { - throw new NullPointerException(); - } - ensureDestinationsIsMutable(); - destinations_.add(value); - onChanged(); - return this; - } - public Builder addAllDestinations( - java.lang.Iterable values) { - ensureDestinationsIsMutable(); - super.addAll(values, destinations_); - onChanged(); - return this; - } - public Builder clearDestinations() { - destinations_ = com.google.protobuf.LazyStringArrayList.EMPTY; - bitField0_ = (bitField0_ & ~0x00000008); - onChanged(); - return this; - } - void addDestinations(com.google.protobuf.ByteString value) { - ensureDestinationsIsMutable(); - destinations_.add(value); - onChanged(); - } - // optional uint64 timestamp = 5; private long timestamp_ ; public boolean hasTimestamp() { - return ((bitField0_ & 0x00000010) == 0x00000010); + return ((bitField0_ & 0x00000008) == 0x00000008); } public long getTimestamp() { return timestamp_; } public Builder setTimestamp(long value) { - bitField0_ |= 0x00000010; + bitField0_ |= 0x00000008; timestamp_ = value; onChanged(); return this; } public Builder clearTimestamp() { - bitField0_ = (bitField0_ & ~0x00000010); + bitField0_ = (bitField0_ & ~0x00000008); timestamp_ = 0L; onChanged(); return this; @@ -730,7 +619,7 @@ public final class PushMessageProtos { // optional bytes message = 6; private com.google.protobuf.ByteString message_ = com.google.protobuf.ByteString.EMPTY; public boolean hasMessage() { - return ((bitField0_ & 0x00000020) == 0x00000020); + return ((bitField0_ & 0x00000010) == 0x00000010); } public com.google.protobuf.ByteString getMessage() { return message_; @@ -739,13 +628,13 @@ public final class PushMessageProtos { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000020; + bitField0_ |= 0x00000010; message_ = value; onChanged(); return this; } public Builder clearMessage() { - bitField0_ = (bitField0_ & ~0x00000020); + bitField0_ = (bitField0_ & ~0x00000010); message_ = getDefaultInstance().getMessage(); onChanged(); return this; @@ -778,6 +667,11 @@ public final class PushMessageProtos { getAttachmentsOrBuilderList(); org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointerOrBuilder getAttachmentsOrBuilder( int index); + + // optional .textsecure.PushMessageContent.GroupContext group = 3; + boolean hasGroup(); + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext getGroup(); + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContextOrBuilder getGroupOrBuilder(); } public static final class PushMessageContent extends com.google.protobuf.GeneratedMessage @@ -1299,6 +1193,842 @@ public final class PushMessageProtos { // @@protoc_insertion_point(class_scope:textsecure.PushMessageContent.AttachmentPointer) } + public interface GroupContextOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional bytes id = 1; + boolean hasId(); + com.google.protobuf.ByteString getId(); + + // optional .textsecure.PushMessageContent.GroupContext.Type type = 2; + boolean hasType(); + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.Type getType(); + + // optional string name = 3; + boolean hasName(); + String getName(); + + // repeated string members = 4; + java.util.List getMembersList(); + int getMembersCount(); + String getMembers(int index); + + // optional .textsecure.PushMessageContent.AttachmentPointer avatar = 5; + boolean hasAvatar(); + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer getAvatar(); + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointerOrBuilder getAvatarOrBuilder(); + } + public static final class GroupContext extends + com.google.protobuf.GeneratedMessage + implements GroupContextOrBuilder { + // Use GroupContext.newBuilder() to construct. + private GroupContext(Builder builder) { + super(builder); + } + private GroupContext(boolean noInit) {} + + private static final GroupContext defaultInstance; + public static GroupContext getDefaultInstance() { + return defaultInstance; + } + + public GroupContext getDefaultInstanceForType() { + return defaultInstance; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_GroupContext_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_GroupContext_fieldAccessorTable; + } + + public enum Type + implements com.google.protobuf.ProtocolMessageEnum { + UNKNOWN(0, 0), + CREATE(1, 1), + MODIFY(2, 2), + DELIVER(3, 3), + ADD(4, 4), + QUIT(5, 5), + ; + + public static final int UNKNOWN_VALUE = 0; + public static final int CREATE_VALUE = 1; + public static final int MODIFY_VALUE = 2; + public static final int DELIVER_VALUE = 3; + public static final int ADD_VALUE = 4; + public static final int QUIT_VALUE = 5; + + + public final int getNumber() { return value; } + + public static Type valueOf(int value) { + switch (value) { + case 0: return UNKNOWN; + case 1: return CREATE; + case 2: return MODIFY; + case 3: return DELIVER; + case 4: return ADD; + case 5: return QUIT; + default: return null; + } + } + + public static com.google.protobuf.Internal.EnumLiteMap + internalGetValueMap() { + return internalValueMap; + } + private static com.google.protobuf.Internal.EnumLiteMap + internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public Type findValueByNumber(int number) { + return Type.valueOf(number); + } + }; + + public final com.google.protobuf.Descriptors.EnumValueDescriptor + getValueDescriptor() { + return getDescriptor().getValues().get(index); + } + public final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptorForType() { + return getDescriptor(); + } + public static final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptor() { + return org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.getDescriptor().getEnumTypes().get(0); + } + + private static final Type[] VALUES = { + UNKNOWN, CREATE, MODIFY, DELIVER, ADD, QUIT, + }; + + public static Type valueOf( + com.google.protobuf.Descriptors.EnumValueDescriptor desc) { + if (desc.getType() != getDescriptor()) { + throw new java.lang.IllegalArgumentException( + "EnumValueDescriptor is not for this type."); + } + return VALUES[desc.getIndex()]; + } + + private final int index; + private final int value; + + private Type(int index, int value) { + this.index = index; + this.value = value; + } + + // @@protoc_insertion_point(enum_scope:textsecure.PushMessageContent.GroupContext.Type) + } + + private int bitField0_; + // optional bytes id = 1; + public static final int ID_FIELD_NUMBER = 1; + private com.google.protobuf.ByteString id_; + public boolean hasId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public com.google.protobuf.ByteString getId() { + return id_; + } + + // optional .textsecure.PushMessageContent.GroupContext.Type type = 2; + public static final int TYPE_FIELD_NUMBER = 2; + private org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.Type type_; + public boolean hasType() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.Type getType() { + return type_; + } + + // optional string name = 3; + public static final int NAME_FIELD_NUMBER = 3; + private java.lang.Object name_; + public boolean hasName() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public String getName() { + java.lang.Object ref = name_; + 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)) { + name_ = s; + } + return s; + } + } + private com.google.protobuf.ByteString getNameBytes() { + java.lang.Object ref = name_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((String) ref); + name_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + // repeated string members = 4; + public static final int MEMBERS_FIELD_NUMBER = 4; + private com.google.protobuf.LazyStringList members_; + public java.util.List + getMembersList() { + return members_; + } + public int getMembersCount() { + return members_.size(); + } + public String getMembers(int index) { + return members_.get(index); + } + + // optional .textsecure.PushMessageContent.AttachmentPointer avatar = 5; + public static final int AVATAR_FIELD_NUMBER = 5; + private org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer avatar_; + public boolean hasAvatar() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer getAvatar() { + return avatar_; + } + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointerOrBuilder getAvatarOrBuilder() { + return avatar_; + } + + private void initFields() { + id_ = com.google.protobuf.ByteString.EMPTY; + type_ = org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.Type.UNKNOWN; + name_ = ""; + members_ = com.google.protobuf.LazyStringArrayList.EMPTY; + avatar_ = org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.getDefaultInstance(); + } + 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, id_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeEnum(2, type_.getNumber()); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeBytes(3, getNameBytes()); + } + for (int i = 0; i < members_.size(); i++) { + output.writeBytes(4, members_.getByteString(i)); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeMessage(5, avatar_); + } + 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, id_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeEnumSize(2, type_.getNumber()); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, getNameBytes()); + } + { + int dataSize = 0; + for (int i = 0; i < members_.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream + .computeBytesSizeNoTag(members_.getByteString(i)); + } + size += dataSize; + size += 1 * getMembersList().size(); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(5, avatar_); + } + 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.GroupContext parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext 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.GroupContext parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext 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.GroupContext parseFrom(java.io.InputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext 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.GroupContext 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.GroupContext 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.GroupContext parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext 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.GroupContext 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.GroupContextOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_GroupContext_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_GroupContext_fieldAccessorTable; + } + + // Construct using org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder(BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + getAvatarFieldBuilder(); + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + id_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000001); + type_ = org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.Type.UNKNOWN; + bitField0_ = (bitField0_ & ~0x00000002); + name_ = ""; + bitField0_ = (bitField0_ & ~0x00000004); + members_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00000008); + if (avatarBuilder_ == null) { + avatar_ = org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.getDefaultInstance(); + } else { + avatarBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000010); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.getDescriptor(); + } + + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext getDefaultInstanceForType() { + return org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.getDefaultInstance(); + } + + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext build() { + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + private org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext buildParsed() + throws com.google.protobuf.InvalidProtocolBufferException { + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException( + result).asInvalidProtocolBufferException(); + } + return result; + } + + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext buildPartial() { + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext result = new org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext(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.type_ = type_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.name_ = name_; + if (((bitField0_ & 0x00000008) == 0x00000008)) { + members_ = new com.google.protobuf.UnmodifiableLazyStringList( + members_); + bitField0_ = (bitField0_ & ~0x00000008); + } + result.members_ = members_; + if (((from_bitField0_ & 0x00000010) == 0x00000010)) { + to_bitField0_ |= 0x00000008; + } + if (avatarBuilder_ == null) { + result.avatar_ = avatar_; + } else { + result.avatar_ = avatarBuilder_.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.GroupContext) { + return mergeFrom((org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext other) { + if (other == org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.getDefaultInstance()) return this; + if (other.hasId()) { + setId(other.getId()); + } + if (other.hasType()) { + setType(other.getType()); + } + if (other.hasName()) { + setName(other.getName()); + } + if (!other.members_.isEmpty()) { + if (members_.isEmpty()) { + members_ = other.members_; + bitField0_ = (bitField0_ & ~0x00000008); + } else { + ensureMembersIsMutable(); + members_.addAll(other.members_); + } + onChanged(); + } + if (other.hasAvatar()) { + mergeAvatar(other.getAvatar()); + } + 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; + id_ = input.readBytes(); + break; + } + case 16: { + int rawValue = input.readEnum(); + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.Type value = org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.Type.valueOf(rawValue); + if (value == null) { + unknownFields.mergeVarintField(2, rawValue); + } else { + bitField0_ |= 0x00000002; + type_ = value; + } + break; + } + case 26: { + bitField0_ |= 0x00000004; + name_ = input.readBytes(); + break; + } + case 34: { + ensureMembersIsMutable(); + members_.add(input.readBytes()); + break; + } + case 42: { + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder subBuilder = org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.newBuilder(); + if (hasAvatar()) { + subBuilder.mergeFrom(getAvatar()); + } + input.readMessage(subBuilder, extensionRegistry); + setAvatar(subBuilder.buildPartial()); + break; + } + } + } + } + + private int bitField0_; + + // optional bytes id = 1; + private com.google.protobuf.ByteString id_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public com.google.protobuf.ByteString getId() { + return id_; + } + public Builder setId(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + id_ = value; + onChanged(); + return this; + } + public Builder clearId() { + bitField0_ = (bitField0_ & ~0x00000001); + id_ = getDefaultInstance().getId(); + onChanged(); + return this; + } + + // optional .textsecure.PushMessageContent.GroupContext.Type type = 2; + private org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.Type type_ = org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.Type.UNKNOWN; + public boolean hasType() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.Type getType() { + return type_; + } + public Builder setType(org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.Type value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + type_ = value; + onChanged(); + return this; + } + public Builder clearType() { + bitField0_ = (bitField0_ & ~0x00000002); + type_ = org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.Type.UNKNOWN; + onChanged(); + return this; + } + + // optional string name = 3; + private java.lang.Object name_ = ""; + public boolean hasName() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public String getName() { + java.lang.Object ref = name_; + if (!(ref instanceof String)) { + String s = ((com.google.protobuf.ByteString) ref).toStringUtf8(); + name_ = s; + return s; + } else { + return (String) ref; + } + } + public Builder setName(String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + name_ = value; + onChanged(); + return this; + } + public Builder clearName() { + bitField0_ = (bitField0_ & ~0x00000004); + name_ = getDefaultInstance().getName(); + onChanged(); + return this; + } + void setName(com.google.protobuf.ByteString value) { + bitField0_ |= 0x00000004; + name_ = value; + onChanged(); + } + + // repeated string members = 4; + private com.google.protobuf.LazyStringList members_ = com.google.protobuf.LazyStringArrayList.EMPTY; + private void ensureMembersIsMutable() { + if (!((bitField0_ & 0x00000008) == 0x00000008)) { + members_ = new com.google.protobuf.LazyStringArrayList(members_); + bitField0_ |= 0x00000008; + } + } + public java.util.List + getMembersList() { + return java.util.Collections.unmodifiableList(members_); + } + public int getMembersCount() { + return members_.size(); + } + public String getMembers(int index) { + return members_.get(index); + } + public Builder setMembers( + int index, String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureMembersIsMutable(); + members_.set(index, value); + onChanged(); + return this; + } + public Builder addMembers(String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureMembersIsMutable(); + members_.add(value); + onChanged(); + return this; + } + public Builder addAllMembers( + java.lang.Iterable values) { + ensureMembersIsMutable(); + super.addAll(values, members_); + onChanged(); + return this; + } + public Builder clearMembers() { + members_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00000008); + onChanged(); + return this; + } + void addMembers(com.google.protobuf.ByteString value) { + ensureMembersIsMutable(); + members_.add(value); + onChanged(); + } + + // optional .textsecure.PushMessageContent.AttachmentPointer avatar = 5; + private org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer avatar_ = org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointerOrBuilder> avatarBuilder_; + public boolean hasAvatar() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer getAvatar() { + if (avatarBuilder_ == null) { + return avatar_; + } else { + return avatarBuilder_.getMessage(); + } + } + public Builder setAvatar(org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer value) { + if (avatarBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + avatar_ = value; + onChanged(); + } else { + avatarBuilder_.setMessage(value); + } + bitField0_ |= 0x00000010; + return this; + } + public Builder setAvatar( + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder builderForValue) { + if (avatarBuilder_ == null) { + avatar_ = builderForValue.build(); + onChanged(); + } else { + avatarBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000010; + return this; + } + public Builder mergeAvatar(org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer value) { + if (avatarBuilder_ == null) { + if (((bitField0_ & 0x00000010) == 0x00000010) && + avatar_ != org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.getDefaultInstance()) { + avatar_ = + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.newBuilder(avatar_).mergeFrom(value).buildPartial(); + } else { + avatar_ = value; + } + onChanged(); + } else { + avatarBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000010; + return this; + } + public Builder clearAvatar() { + if (avatarBuilder_ == null) { + avatar_ = org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.getDefaultInstance(); + onChanged(); + } else { + avatarBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000010); + return this; + } + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder getAvatarBuilder() { + bitField0_ |= 0x00000010; + onChanged(); + return getAvatarFieldBuilder().getBuilder(); + } + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointerOrBuilder getAvatarOrBuilder() { + if (avatarBuilder_ != null) { + return avatarBuilder_.getMessageOrBuilder(); + } else { + return avatar_; + } + } + private com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointerOrBuilder> + getAvatarFieldBuilder() { + if (avatarBuilder_ == null) { + avatarBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointerOrBuilder>( + avatar_, + getParentForChildren(), + isClean()); + avatar_ = null; + } + return avatarBuilder_; + } + + // @@protoc_insertion_point(builder_scope:textsecure.PushMessageContent.GroupContext) + } + + static { + defaultInstance = new GroupContext(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.PushMessageContent.GroupContext) + } + private int bitField0_; // optional string body = 1; public static final int BODY_FIELD_NUMBER = 1; @@ -1353,9 +2083,23 @@ public final class PushMessageProtos { return attachments_.get(index); } + // optional .textsecure.PushMessageContent.GroupContext group = 3; + public static final int GROUP_FIELD_NUMBER = 3; + private org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext group_; + public boolean hasGroup() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext getGroup() { + return group_; + } + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContextOrBuilder getGroupOrBuilder() { + return group_; + } + private void initFields() { body_ = ""; attachments_ = java.util.Collections.emptyList(); + group_ = org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.getDefaultInstance(); } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -1375,6 +2119,9 @@ public final class PushMessageProtos { for (int i = 0; i < attachments_.size(); i++) { output.writeMessage(2, attachments_.get(i)); } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeMessage(3, group_); + } getUnknownFields().writeTo(output); } @@ -1392,6 +2139,10 @@ public final class PushMessageProtos { size += com.google.protobuf.CodedOutputStream .computeMessageSize(2, attachments_.get(i)); } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(3, group_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -1509,6 +2260,7 @@ public final class PushMessageProtos { private void maybeForceBuilderInitialization() { if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { getAttachmentsFieldBuilder(); + getGroupFieldBuilder(); } } private static Builder create() { @@ -1525,6 +2277,12 @@ public final class PushMessageProtos { } else { attachmentsBuilder_.clear(); } + if (groupBuilder_ == null) { + group_ = org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.getDefaultInstance(); + } else { + groupBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000004); return this; } @@ -1576,6 +2334,14 @@ public final class PushMessageProtos { } else { result.attachments_ = attachmentsBuilder_.build(); } + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000002; + } + if (groupBuilder_ == null) { + result.group_ = group_; + } else { + result.group_ = groupBuilder_.build(); + } result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -1621,6 +2387,9 @@ public final class PushMessageProtos { } } } + if (other.hasGroup()) { + mergeGroup(other.getGroup()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -1663,6 +2432,15 @@ public final class PushMessageProtos { addAttachments(subBuilder.buildPartial()); break; } + case 26: { + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.Builder subBuilder = org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.newBuilder(); + if (hasGroup()) { + subBuilder.mergeFrom(getGroup()); + } + input.readMessage(subBuilder, extensionRegistry); + setGroup(subBuilder.buildPartial()); + break; + } } } } @@ -1891,6 +2669,96 @@ public final class PushMessageProtos { return attachmentsBuilder_; } + // optional .textsecure.PushMessageContent.GroupContext group = 3; + private org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext group_ = org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.Builder, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContextOrBuilder> groupBuilder_; + public boolean hasGroup() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext getGroup() { + if (groupBuilder_ == null) { + return group_; + } else { + return groupBuilder_.getMessage(); + } + } + public Builder setGroup(org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext value) { + if (groupBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + group_ = value; + onChanged(); + } else { + groupBuilder_.setMessage(value); + } + bitField0_ |= 0x00000004; + return this; + } + public Builder setGroup( + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.Builder builderForValue) { + if (groupBuilder_ == null) { + group_ = builderForValue.build(); + onChanged(); + } else { + groupBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000004; + return this; + } + public Builder mergeGroup(org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext value) { + if (groupBuilder_ == null) { + if (((bitField0_ & 0x00000004) == 0x00000004) && + group_ != org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.getDefaultInstance()) { + group_ = + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.newBuilder(group_).mergeFrom(value).buildPartial(); + } else { + group_ = value; + } + onChanged(); + } else { + groupBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000004; + return this; + } + public Builder clearGroup() { + if (groupBuilder_ == null) { + group_ = org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.getDefaultInstance(); + onChanged(); + } else { + groupBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000004); + return this; + } + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.Builder getGroupBuilder() { + bitField0_ |= 0x00000004; + onChanged(); + return getGroupFieldBuilder().getBuilder(); + } + public org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContextOrBuilder getGroupOrBuilder() { + if (groupBuilder_ != null) { + return groupBuilder_.getMessageOrBuilder(); + } else { + return group_; + } + } + private com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.Builder, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContextOrBuilder> + getGroupFieldBuilder() { + if (groupBuilder_ == null) { + groupBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.Builder, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContextOrBuilder>( + group_, + getParentForChildren(), + isClean()); + group_ = null; + } + return groupBuilder_; + } + // @@protoc_insertion_point(builder_scope:textsecure.PushMessageContent) } @@ -1917,6 +2785,11 @@ public final class PushMessageProtos { private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_textsecure_PushMessageContent_AttachmentPointer_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_textsecure_PushMessageContent_GroupContext_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_textsecure_PushMessageContent_GroupContext_fieldAccessorTable; public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { @@ -1927,16 +2800,23 @@ public final class PushMessageProtos { static { java.lang.String[] descriptorData = { "\n\037IncomingPushMessageSignal.proto\022\ntexts" + - "ecure\"\202\001\n\031IncomingPushMessageSignal\022\014\n\004t" + - "ype\030\001 \001(\r\022\016\n\006source\030\002 \001(\t\022\r\n\005relay\030\003 \001(\t" + - "\022\024\n\014destinations\030\004 \003(\t\022\021\n\ttimestamp\030\005 \001(" + - "\004\022\017\n\007message\030\006 \001(\014\"\254\001\n\022PushMessageConten" + - "t\022\014\n\004body\030\001 \001(\t\022E\n\013attachments\030\002 \003(\01320.t" + - "extsecure.PushMessageContent.AttachmentP" + - "ointer\032A\n\021AttachmentPointer\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\021PushMes", - "sageProtos" + "ecure\"l\n\031IncomingPushMessageSignal\022\014\n\004ty" + + "pe\030\001 \001(\r\022\016\n\006source\030\002 \001(\t\022\r\n\005relay\030\003 \001(\t\022" + + "\021\n\ttimestamp\030\005 \001(\004\022\017\n\007message\030\006 \001(\014\"\363\003\n\022" + + "PushMessageContent\022\014\n\004body\030\001 \001(\t\022E\n\013atta" + + "chments\030\002 \003(\01320.textsecure.PushMessageCo" + + "ntent.AttachmentPointer\022:\n\005group\030\003 \001(\0132+" + + ".textsecure.PushMessageContent.GroupCont" + + "ext\032A\n\021AttachmentPointer\022\n\n\002id\030\001 \001(\006\022\023\n\013" + + "contentType\030\002 \001(\t\022\013\n\003key\030\003 \001(\014\032\210\002\n\014Group", + "Context\022\n\n\002id\030\001 \001(\014\022>\n\004type\030\002 \001(\01620.text" + + "secure.PushMessageContent.GroupContext.T" + + "ype\022\014\n\004name\030\003 \001(\t\022\017\n\007members\030\004 \003(\t\022@\n\006av" + + "atar\030\005 \001(\01320.textsecure.PushMessageConte" + + "nt.AttachmentPointer\"K\n\004Type\022\013\n\007UNKNOWN\020" + + "\000\022\n\n\006CREATE\020\001\022\n\n\006MODIFY\020\002\022\013\n\007DELIVER\020\003\022\007" + + "\n\003ADD\020\004\022\010\n\004QUIT\020\005B7\n\"org.whispersystems." + + "textsecure.pushB\021PushMessageProtos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -1948,7 +2828,7 @@ 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", "Relay", "Destinations", "Timestamp", "Message", }, + new java.lang.String[] { "Type", "Source", "Relay", "Timestamp", "Message", }, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.class, org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.Builder.class); internal_static_textsecure_PushMessageContent_descriptor = @@ -1956,7 +2836,7 @@ public final class PushMessageProtos { internal_static_textsecure_PushMessageContent_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_textsecure_PushMessageContent_descriptor, - new java.lang.String[] { "Body", "Attachments", }, + new java.lang.String[] { "Body", "Attachments", "Group", }, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.class, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.Builder.class); internal_static_textsecure_PushMessageContent_AttachmentPointer_descriptor = @@ -1967,6 +2847,14 @@ public final class PushMessageProtos { new java.lang.String[] { "Id", "ContentType", "Key", }, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.class, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer.Builder.class); + internal_static_textsecure_PushMessageContent_GroupContext_descriptor = + internal_static_textsecure_PushMessageContent_descriptor.getNestedTypes().get(1); + internal_static_textsecure_PushMessageContent_GroupContext_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_textsecure_PushMessageContent_GroupContext_descriptor, + new java.lang.String[] { "Id", "Type", "Name", "Members", "Avatar", }, + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.class, + org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.Builder.class); return null; } }; diff --git a/library/src/org/whispersystems/textsecure/util/Util.java b/library/src/org/whispersystems/textsecure/util/Util.java index 64bbfd8486..afa3ac9d67 100644 --- a/library/src/org/whispersystems/textsecure/util/Util.java +++ b/library/src/org/whispersystems/textsecure/util/Util.java @@ -2,6 +2,7 @@ package org.whispersystems.textsecure.util; import android.app.AlertDialog; import android.content.Context; +import android.telephony.TelephonyManager; import android.widget.EditText; import java.io.ByteArrayOutputStream; @@ -169,6 +170,22 @@ public class Util { return results; } + public static String getDeviceE164Number(Context context) { + String localNumber = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)) + .getLine1Number(); + + if (!org.whispersystems.textsecure.util.Util.isEmpty(localNumber) && + !localNumber.startsWith("+")) + { + if (localNumber.length() == 10) localNumber = "+1" + localNumber; + else localNumber = "+" + localNumber; + + return localNumber; + } + + return null; + } + public static SecureRandom getSecureRandom() { try { return SecureRandom.getInstance("SHA1PRNG"); diff --git a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java index 2c694c32ad..3bc6b49819 100644 --- a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java @@ -52,7 +52,8 @@ public class DatabaseFactory { private static final int INTRODUCED_MMS_FROM_VERSION = 8; private static final int INTRODUCED_TOFU_IDENTITY_VERSION = 9; private static final int INTRODUCED_PUSH_DATABASE_VERSION = 10; - private static final int DATABASE_VERSION = 10; + private static final int INTRODUCED_GROUP_DATABASE_VERSION = 11; + private static final int DATABASE_VERSION = 11; private static final String DATABASE_NAME = "messages.db"; private static final Object lock = new Object(); @@ -73,6 +74,7 @@ public class DatabaseFactory { private final IdentityDatabase identityDatabase; private final DraftDatabase draftDatabase; private final PushDatabase pushDatabase; + private final GroupDatabase groupDatabase; public static DatabaseFactory getInstance(Context context) { synchronized (lock) { @@ -138,6 +140,10 @@ public class DatabaseFactory { return getInstance(context).pushDatabase; } + public static GroupDatabase getGroupDatabase(Context context) { + return getInstance(context).groupDatabase; + } + private DatabaseFactory(Context context) { this.databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION); this.sms = new SmsDatabase(context, databaseHelper); @@ -151,6 +157,7 @@ public class DatabaseFactory { this.identityDatabase = new IdentityDatabase(context, databaseHelper); this.draftDatabase = new DraftDatabase(context, databaseHelper); this.pushDatabase = new PushDatabase(context, databaseHelper); + this.groupDatabase = new GroupDatabase(context, databaseHelper); } public void reset(Context context) { @@ -166,6 +173,8 @@ public class DatabaseFactory { this.mmsSmsDatabase.reset(databaseHelper); this.identityDatabase.reset(databaseHelper); this.draftDatabase.reset(databaseHelper); + this.pushDatabase.reset(databaseHelper); + this.groupDatabase.reset(databaseHelper); old.close(); this.address.reset(context); @@ -432,6 +441,7 @@ public class DatabaseFactory { db.execSQL(IdentityDatabase.CREATE_TABLE); db.execSQL(DraftDatabase.CREATE_TABLE); db.execSQL(PushDatabase.CREATE_TABLE); + db.execSQL(GroupDatabase.CREATE_TABLE); executeStatements(db, SmsDatabase.CREATE_INDEXS); executeStatements(db, MmsDatabase.CREATE_INDEXS); @@ -439,6 +449,7 @@ public class DatabaseFactory { executeStatements(db, ThreadDatabase.CREATE_INDEXS); executeStatements(db, MmsAddressDatabase.CREATE_INDEXS); executeStatements(db, DraftDatabase.CREATE_INDEXS); + executeStatements(db, GroupDatabase.CREATE_INDEXS); } @Override @@ -630,6 +641,11 @@ public class DatabaseFactory { db.execSQL("CREATE INDEX IF NOT EXISTS pending_push_index ON part (pending_push);"); } + if (oldVersion < INTRODUCED_GROUP_DATABASE_VERSION) { + db.execSQL("CREATE TABLE groups (_id INTEGER PRIMARY KEY, group_id TEXT, owner TEXT, title TEXT, members TEXT, avatar BLOB, avatar_id INTEGER, avatar_key BLOB, avatar_content_type TEXT, timestamp INTEGER);"); + db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS group_id_index ON groups (GROUP_ID);"); + } + db.setTransactionSuccessful(); db.endTransaction(); } diff --git a/src/org/thoughtcrime/securesms/database/GroupDatabase.java b/src/org/thoughtcrime/securesms/database/GroupDatabase.java new file mode 100644 index 0000000000..1ccb077e5f --- /dev/null +++ b/src/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -0,0 +1,241 @@ +package org.thoughtcrime.securesms.database; + + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteOpenHelper; +import android.graphics.Bitmap; + +import org.thoughtcrime.securesms.util.BitmapUtil; +import org.whispersystems.textsecure.util.Hex; +import org.whispersystems.textsecure.util.Util; + +import java.util.LinkedList; +import java.util.List; + +import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer; + +public class GroupDatabase extends Database { + + private static final String TABLE_NAME = "groups"; + private static final String ID = "_id"; + private static final String GROUP_ID = "group_id"; + private static final String OWNER = "owner"; + private static final String TITLE = "title"; + private static final String MEMBERS = "members"; + private static final String AVATAR = "avatar"; + private static final String AVATAR_ID = "avatar_id"; + private static final String AVATAR_KEY = "avatar_key"; + private static final String AVATAR_CONTENT_TYPE = "avatar_content_type"; + private static final String RELAY = "relay"; + private static final String TIMESTAMP = "timestamp"; + + public static final String CREATE_TABLE = + "CREATE TABLE " + TABLE_NAME + + " (" + ID + " INTEGER PRIMARY KEY, " + + GROUP_ID + " TEXT, " + + OWNER + " TEXT, " + + TITLE + " TEXT, " + + MEMBERS + " TEXT, " + + AVATAR + " BLOB, " + + AVATAR_ID + " INTEGER, " + + AVATAR_KEY + " BLOB, " + + AVATAR_CONTENT_TYPE + " TEXT, " + + TIMESTAMP + " INTEGER);"; + + public static final String[] CREATE_INDEXS = { + "CREATE UNIQUE INDEX IF NOT EXISTS group_id_index ON " + TABLE_NAME + " (" + GROUP_ID + ");", + }; + + public GroupDatabase(Context context, SQLiteOpenHelper databaseHelper) { + super(context, databaseHelper); + } + + public Reader getGroup(String groupId) { + Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, GROUP_ID + " = ?", + new String[] {groupId}, null, null, null); + + return new Reader(cursor); + } + + public void create(byte[] groupId, String owner, String title, + List members, AttachmentPointer avatar, + String relay) + { + ContentValues contentValues = new ContentValues(); + contentValues.put(GROUP_ID, Hex.toString(groupId)); + contentValues.put(OWNER, owner); + contentValues.put(TITLE, title); + contentValues.put(MEMBERS, Util.join(members, ",")); + + if (avatar != null) { + contentValues.put(AVATAR_ID, avatar.getId()); + contentValues.put(AVATAR_KEY, avatar.getKey().toByteArray()); + contentValues.put(AVATAR_CONTENT_TYPE, avatar.getContentType()); + } + + contentValues.put(RELAY, relay); + contentValues.put(TIMESTAMP, System.currentTimeMillis()); + + databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, contentValues); + } + + public void update(byte[] groupId, String source, String title, AttachmentPointer avatar) { + ContentValues contentValues = new ContentValues(); + if (title != null) contentValues.put(TITLE, title); + + if (avatar != null) { + contentValues.put(AVATAR_ID, avatar.getId()); + contentValues.put(AVATAR_CONTENT_TYPE, avatar.getContentType()); + contentValues.put(AVATAR_KEY, avatar.getKey().toByteArray()); + } + + databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, + GROUP_ID + " = ? AND " + OWNER + " = ?", + new String[] {Hex.toString(groupId), source}); + } + + public void updateAvatar(String groupId, Bitmap avatar) { + ContentValues contentValues = new ContentValues(); + contentValues.put(AVATAR, BitmapUtil.toByteArray(avatar)); + + databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?", new String[] {groupId}); + } + + + public void add(byte[] id, String source, List members) { + List currentMembers = getCurrentMembers(id); + + for (String currentMember : currentMembers) { + if (currentMember.equals(source)) { + List concatenatedMembers = new LinkedList(currentMembers); + concatenatedMembers.addAll(members); + + ContentValues contents = new ContentValues(); + contents.put(MEMBERS, Util.join(concatenatedMembers, ",")); + + databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?", + new String[] {Hex.toString(id)}); + } + } + } + + public void remove(byte[] id, String source) { + List currentMembers = getCurrentMembers(id); + currentMembers.remove(source); + + ContentValues contents = new ContentValues(); + contents.put(MEMBERS, Util.join(currentMembers, ",")); + + databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?", + new String[]{Hex.toString(id)}); + } + + private List getCurrentMembers(byte[] id) { + Cursor cursor = null; + + try { + cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[] {MEMBERS}, + GROUP_ID + " = ?", new String[] {Hex.toString(id)}, + null, null, null); + + if (cursor != null && cursor.moveToFirst()) { + return Util.split(cursor.getString(cursor.getColumnIndexOrThrow(MEMBERS)), ","); + } + + return new LinkedList(); + } finally { + if (cursor != null) + cursor.close(); + } + } + + + public static class Reader { + + private final Cursor cursor; + + public Reader(Cursor cursor) { + this.cursor = cursor; + } + + public GroupRecord getNext() { + if (cursor == null || !cursor.moveToNext()) { + return null; + } + + return new GroupRecord(cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID)), + cursor.getString(cursor.getColumnIndexOrThrow(TITLE)), + cursor.getString(cursor.getColumnIndexOrThrow(MEMBERS)), + cursor.getBlob(cursor.getColumnIndexOrThrow(AVATAR)), + cursor.getLong(cursor.getColumnIndexOrThrow(AVATAR_ID)), + cursor.getBlob(cursor.getColumnIndexOrThrow(AVATAR_KEY)), + cursor.getString(cursor.getColumnIndexOrThrow(AVATAR_CONTENT_TYPE)), + cursor.getString(cursor.getColumnIndexOrThrow(RELAY))); + } + + public void close() { + if (this.cursor != null) + this.cursor.close(); + } + } + + public static class GroupRecord { + + private final String id; + private final String title; + private final List members; + private final byte[] avatar; + private final long avatarId; + private final byte[] avatarKey; + private final String avatarContentType; + private final String relay; + + public GroupRecord(String id, String title, String members, byte[] avatar, + long avatarId, byte[] avatarKey, String avatarContentType, + String relay) + { + this.id = id; + this.title = title; + this.members = Util.split(members, ","); + this.avatar = avatar; + this.avatarId = avatarId; + this.avatarKey = avatarKey; + this.avatarContentType = avatarContentType; + this.relay = relay; + } + + public String getId() { + return id; + } + + public String getTitle() { + return title; + } + + public List getMembers() { + return members; + } + + public byte[] getAvatar() { + return avatar; + } + + public long getAvatarId() { + return avatarId; + } + + public byte[] getAvatarKey() { + return avatarKey; + } + + public String getAvatarContentType() { + return avatarContentType; + } + + public String getRelay() { + return relay; + } + } +} diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 666070916e..4a32f76deb 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -179,20 +179,38 @@ public class MmsDatabase extends Database implements MmsSmsColumns { } private long getThreadIdFor(IncomingMediaMessage retrieved) throws RecipientFormattingException { + if (retrieved.getGroupId() != null) { + return DatabaseFactory.getThreadDatabase(context).getThreadIdForGroup(retrieved.getGroupId()); + } + try { PduHeaders headers = retrieved.getPduHeaders(); Set group = new HashSet(); - EncodedStringValue encodedFrom = headers.getEncodedStringValue(PduHeaders.FROM); + EncodedStringValue encodedFrom = headers.getEncodedStringValue(PduHeaders.FROM); + EncodedStringValue[] encodedCcList = headers.getEncodedStringValues(PduHeaders.CC); + EncodedStringValue[] encodedToList = headers.getEncodedStringValues(PduHeaders.TO); + group.add(new String(encodedFrom.getTextString(), CharacterSets.MIMENAME_ISO_8859_1)); - EncodedStringValue[] encodedCcList = headers.getEncodedStringValues(PduHeaders.CC); if (encodedCcList != null) { for (EncodedStringValue encodedCc : encodedCcList) { group.add(new String(encodedCc.getTextString(), CharacterSets.MIMENAME_ISO_8859_1)); } } + if (encodedToList != null) { + String localNumber = Util.getDeviceE164Number(context); + + for (EncodedStringValue encodedTo : encodedToList) { + String to = new String(encodedTo.getTextString(), CharacterSets.MIMENAME_ISO_8859_1); + + if (!localNumber.equals(to)) { + group.add(to); + } + } + } + String recipientsList = Util.join(group, ","); Recipients recipients = RecipientFactory.getRecipientsFromString(context, recipientsList, false); return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); diff --git a/src/org/thoughtcrime/securesms/database/PushDatabase.java b/src/org/thoughtcrime/securesms/database/PushDatabase.java index d004f0b9ca..8df5ec7a3e 100644 --- a/src/org/thoughtcrime/securesms/database/PushDatabase.java +++ b/src/org/thoughtcrime/securesms/database/PushDatabase.java @@ -18,12 +18,11 @@ public class PushDatabase extends Database { 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);"; + TYPE + " INTEGER, " + SOURCE + " TEXT, " + BODY + " TEXT, " + TIMESTAMP + " INTEGER);"; public PushDatabase(Context context, SQLiteOpenHelper databaseHelper) { super(context, databaseHelper); @@ -33,7 +32,6 @@ public class PushDatabase extends Database { 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.encodeBytes(message.getBody())); values.put(TIMESTAMP, message.getTimestampMillis()); @@ -66,11 +64,10 @@ public class PushDatabase extends Database { int type = cursor.getInt(cursor.getColumnIndexOrThrow(TYPE)); String source = cursor.getString(cursor.getColumnIndexOrThrow(SOURCE)); - List destinations = Util.split(cursor.getString(cursor.getColumnIndexOrThrow(DESTINATIONS)), ","); byte[] body = Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(BODY))); long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP)); - return new IncomingPushMessage(type, source, destinations, body, timestamp); + return new IncomingPushMessage(type, source, body, timestamp); } catch (IOException e) { throw new AssertionError(e); } diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index 1e3a314c3b..46ca52a280 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -259,10 +259,15 @@ public class SmsDatabase extends Database implements MmsSmsColumns { Recipient recipient = new Recipient(null, message.getSender(), null, null); Recipients recipients = new Recipients(recipient); - long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); + String groupId = message.getGroupId(); boolean unread = org.thoughtcrime.securesms.util.Util.isDefaultSmsProvider(context) || message.isSecureMessage() || message.isKeyExchange(); + long threadId; + + if (groupId == null) threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); + else threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdForGroup(groupId); + ContentValues values = new ContentValues(6); values.put(ADDRESS, message.getSender()); values.put(DATE_RECEIVED, System.currentTimeMillis()); diff --git a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java index d629dd69e5..ec124ace47 100644 --- a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -101,6 +101,17 @@ public class ThreadDatabase extends Database { return sb.toString(); } + private long createThreadForGroup(String group) { + long date = System.currentTimeMillis(); + + ContentValues values = new ContentValues(); + values.put(DATE, date - date % 1000); + values.put(RECIPIENT_IDS, group); + values.put(MESSAGE_COUNT, 0); + + return databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, values); + } + private long createThreadForRecipients(String recipients, int recipientCount, int distributionType) { ContentValues contentValues = new ContentValues(4); long date = System.currentTimeMillis(); @@ -325,7 +336,7 @@ public class ThreadDatabase extends Database { } public long getThreadIdFor(Recipients recipients) { - return getThreadIdFor(recipients, 0); + return getThreadIdFor(recipients, DistributionTypes.DEFAULT); } public long getThreadIdFor(Recipients recipients, int distributionType) { @@ -349,6 +360,26 @@ public class ThreadDatabase extends Database { } } + public long getThreadIdForGroup(String groupId) { + SQLiteDatabase db = databaseHelper.getReadableDatabase(); + String where = RECIPIENT_IDS + " = ?"; + String[] recipientsArg = new String[] {groupId}; + Cursor cursor = null; + + try { + cursor = db.query(TABLE_NAME, new String[]{ID}, where, recipientsArg, null, null, null); + + if (cursor != null && cursor.moveToFirst()) { + return cursor.getLong(cursor.getColumnIndexOrThrow(ID)); + } else { + return createThreadForGroup(groupId); + } + } finally { + if (cursor != null) + cursor.close(); + } + } + public Recipients getRecipientsForThreadId(long threadId) { SQLiteDatabase db = databaseHelper.getReadableDatabase(); Cursor cursor = null; diff --git a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java index c3f80bf87e..9b66b822f3 100644 --- a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java +++ b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java @@ -7,9 +7,6 @@ import org.whispersystems.textsecure.push.IncomingPushMessage; import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent; import org.whispersystems.textsecure.util.Base64; -import java.io.UnsupportedEncodingException; - -import ws.com.google.android.mms.pdu.CharacterSets; import ws.com.google.android.mms.pdu.EncodedStringValue; import ws.com.google.android.mms.pdu.PduBody; import ws.com.google.android.mms.pdu.PduHeaders; @@ -20,29 +17,28 @@ public class IncomingMediaMessage { private final PduHeaders headers; private final PduBody body; + private final String groupId; public IncomingMediaMessage(RetrieveConf retreived) { this.headers = retreived.getPduHeaders(); this.body = retreived.getBody(); + this.groupId = null; } public IncomingMediaMessage(MasterSecret masterSecret, String localNumber, IncomingPushMessage message, - PushMessageContent messageContent) + PushMessageContent messageContent, + String groupId) { this.headers = new PduHeaders(); this.body = new PduBody(); + this.groupId = groupId; this.headers.setEncodedStringValue(new EncodedStringValue(message.getSource()), PduHeaders.FROM); this.headers.appendEncodedStringValue(new EncodedStringValue(localNumber), PduHeaders.TO); - - for (String destination : message.getDestinations()) { - this.headers.appendEncodedStringValue(new EncodedStringValue(destination), PduHeaders.CC); - } - this.headers.setLongInteger(message.getTimestampMillis() / 1000, PduHeaders.DATE); - if (messageContent.getBody() != null && messageContent.getBody().length() > 0) { + if (!org.whispersystems.textsecure.util.Util.isEmpty(messageContent.getBody())) { PduPart text = new PduPart(); text.setData(Util.toIsoBytes(messageContent.getBody())); text.setContentType(Util.toIsoBytes("text/plain")); @@ -77,8 +73,15 @@ public class IncomingMediaMessage { return body; } + public String getGroupId() { + return groupId; + } + public boolean isGroupMessage() { - return !Util.isEmpty(headers.getEncodedStringValues(PduHeaders.CC)); + return groupId != null || + !Util.isEmpty(headers.getEncodedStringValues(PduHeaders.CC)) || + (headers.getEncodedStringValues(PduHeaders.TO) != null && + headers.getEncodedStringValues(PduHeaders.TO).length > 1); } } diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java b/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java index 44740f96d7..836b6a20f5 100644 --- a/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java +++ b/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java @@ -25,10 +25,8 @@ import org.thoughtcrime.securesms.util.NumberUtil; import org.whispersystems.textsecure.push.IncomingPushMessage; import org.whispersystems.textsecure.util.Util; -import java.util.HashSet; import java.util.LinkedList; import java.util.List; -import java.util.Set; import java.util.StringTokenizer; public class RecipientFactory { @@ -36,7 +34,7 @@ public class RecipientFactory { private static final RecipientProvider provider = new RecipientProvider(); public static Recipients getRecipientsForIds(Context context, String recipientIds, boolean asynchronous) { - if (recipientIds == null || recipientIds.trim().length() == 0) + if (Util.isEmpty(recipientIds)) return new Recipients(new LinkedList()); List results = new LinkedList(); @@ -79,12 +77,8 @@ public class RecipientFactory { IncomingPushMessage message, boolean asynchronous) { - Set recipients = new HashSet(); - recipients.add(message.getSource()); - recipients.addAll(message.getDestinations()); - try { - return getRecipientsFromString(context, Util.join(recipients, ","), asynchronous); + return getRecipientsFromString(context, message.getSource(), asynchronous); } catch (RecipientFormattingException e) { Log.w("RecipientFactory", e); return new Recipients(new Recipient("Unknown", "Unknown", null, @@ -93,8 +87,12 @@ public class RecipientFactory { } private static Recipient getRecipientFromProviderId(Context context, String recipientId, boolean asynchronous) { - String number = DatabaseFactory.getAddressDatabase(context).getAddressFromId(recipientId); - return getRecipientForNumber(context, number, asynchronous); + if (recipientId.startsWith("g_")) { + return provider.getGroupRecipient(context, recipientId, asynchronous); + } else { + String number = DatabaseFactory.getAddressDatabase(context).getAddressFromId(recipientId); + return getRecipientForNumber(context, number, asynchronous); + } } private static boolean hasBracketedNumber(String recipient) { diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java b/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java index 076b68467b..149923caae 100644 --- a/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java +++ b/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java @@ -27,6 +27,8 @@ import android.provider.ContactsContract.PhoneLookup; import android.util.Log; import org.thoughtcrime.securesms.contacts.ContactPhotoFactory; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.util.LRUCache; import org.whispersystems.textsecure.util.ListenableFutureTask; import org.thoughtcrime.securesms.util.Util; @@ -40,7 +42,6 @@ import java.util.concurrent.ExecutorService; public class RecipientProvider { private static final Map recipientCache = Collections.synchronizedMap(new LRUCache(1000)); -// private static final ExecutorService asyncRecipientResolver = Executors.newSingleThreadExecutor(); private static final ExecutorService asyncRecipientResolver = Util.newSingleThreadedLifoExecutor(); private static final String[] CALLER_ID_PROJECTION = new String[] { @@ -57,6 +58,14 @@ public class RecipientProvider { else return getSynchronousRecipient(context, number); } + public Recipient getGroupRecipient(Context context, String groupId, boolean asynchronous) { + Recipient cachedRecipient = recipientCache.get(groupId); + + if (cachedRecipient != null) return cachedRecipient; + else if (asynchronous) return getAsynchronousGroupRecipient(context, groupId); + else return getSynchronousGroupRecipient(context, groupId); + } + private Recipient getSynchronousRecipient(Context context, String number) { Log.w("RecipientProvider", "Cache miss [SYNC]!"); RecipientDetails details = getRecipientDetails(context, number); @@ -72,34 +81,26 @@ public class RecipientProvider { return recipient; } + private Recipient getSynchronousGroupRecipient(Context context, String groupId) { + RecipientDetails details = getGroupRecipientDetails(context, groupId); + Recipient recipient; + + if (details != null) { + recipient = new Recipient(details.name, groupId, details.contactUri, details.avatar); + } else { + recipient = new Recipient(null, groupId, null, ContactPhotoFactory.getDefaultContactPhoto(context)); + } + + recipientCache.put(groupId, recipient); + return recipient; + } + private Recipient getAsynchronousRecipient(final Context context, final String number) { Log.w("RecipientProvider", "Cache miss [ASYNC]!"); -// Recipient recipient = new Recipient(null, number, null, ContactPhotoFactory.getDefaultContactPhoto(context)); -// recipientCache.put(number, recipient); -// -// new AsyncTask() { -// private Recipient recipient; -// -// @Override -// protected RecipientDetails doInBackground(Recipient... recipient) { -// this.recipient = recipient[0]; -// return getRecipientDetails(context, number); -// } -// -// @Override -// protected void onPostExecute(RecipientDetails result) { -// recipient.updateAsynchronousContent(result); -// } -// }.execute(recipient); -// -// return recipient; - -// ListenableFutureTask future = new ListenableFutureTask(new Callable() { Callable task = new Callable() { @Override public RecipientDetails call() throws Exception { -// Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); return getRecipientDetails(context, number); } }; @@ -112,7 +113,24 @@ public class RecipientProvider { recipientCache.put(number, recipient); return recipient; -//// return new Recipient(null, number, ContactPhotoFactory.getDefaultContactPhoto(context)); + } + + private Recipient getAsynchronousGroupRecipient(final Context context, final String groupId) { + Callable task = new Callable() { + @Override + public RecipientDetails call() throws Exception { + return getGroupRecipientDetails(context, groupId); + } + }; + + ListenableFutureTask future = new ListenableFutureTask(task, null); + + asyncRecipientResolver.submit(future); + + Recipient recipient = new Recipient(groupId, ContactPhotoFactory.getDefaultContactPhoto(context), future); + recipientCache.put(groupId, recipient); + + return recipient; } public void clearCache() { @@ -140,6 +158,27 @@ public class RecipientProvider { return null; } + private RecipientDetails getGroupRecipientDetails(Context context, String groupId) { + GroupDatabase.Reader reader = DatabaseFactory.getGroupDatabase(context).getGroup(groupId.substring(2)); + GroupDatabase.GroupRecord record; + + try { + if ((record = reader.getNext()) != null) { + byte[] avatarBytes = record.getAvatar(); + Bitmap avatar; + + if (avatarBytes == null) avatar = ContactPhotoFactory.getDefaultContactPhoto(context); + else avatar = BitmapFactory.decodeByteArray(avatarBytes, 0, avatarBytes.length); + + return new RecipientDetails(record.getTitle(), null, avatar); + } + } finally { + reader.close(); + } + + return null; + } + private Bitmap getContactPhoto(Context context, Uri uri) { InputStream inputStream = ContactsContract.Contacts.openContactPhotoInputStream(context.getContentResolver(), uri); diff --git a/src/org/thoughtcrime/securesms/service/AvatarDownloader.java b/src/org/thoughtcrime/securesms/service/AvatarDownloader.java new file mode 100644 index 0000000000..0e467ff967 --- /dev/null +++ b/src/org/thoughtcrime/securesms/service/AvatarDownloader.java @@ -0,0 +1,75 @@ +package org.thoughtcrime.securesms.service; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.util.Log; + +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.GroupDatabase; +import org.thoughtcrime.securesms.database.PartDatabase; +import org.thoughtcrime.securesms.push.PushServiceSocketFactory; +import org.thoughtcrime.securesms.util.BitmapDecodingException; +import org.thoughtcrime.securesms.util.BitmapUtil; +import org.whispersystems.textsecure.crypto.AttachmentCipherInputStream; +import org.whispersystems.textsecure.crypto.InvalidMessageException; +import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.push.PushServiceSocket; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +public class AvatarDownloader { + + private final Context context; + + public AvatarDownloader(Context context) { + this.context = context.getApplicationContext(); + } + + public void process(MasterSecret masterSecret, Intent intent) { + try { + if (!SendReceiveService.DOWNLOAD_AVATAR_ACTION.equals(intent.getAction())) + return; + + String groupId = intent.getStringExtra("group_id"); + GroupDatabase database = DatabaseFactory.getGroupDatabase(context); + GroupDatabase.Reader reader = database.getGroup(groupId); + + GroupDatabase.GroupRecord record; + + while ((record = reader.getNext()) != null) { + long avatarId = record.getAvatarId(); + byte[] key = record.getAvatarKey(); + String relay = record.getRelay(); + + if (avatarId == -1 || key == null) { + continue; + } + + File attachment = downloadAttachment(relay, avatarId); + InputStream scaleInputStream = new AttachmentCipherInputStream(attachment, key); + InputStream measureInputStream = new AttachmentCipherInputStream(attachment, key); + Bitmap avatar = BitmapUtil.createScaledBitmap(measureInputStream, scaleInputStream, 500, 500); + + database.updateAvatar(groupId, avatar); + + avatar.recycle(); + attachment.delete(); + } + } catch (IOException e) { + Log.w("AvatarDownloader", e); + } catch (InvalidMessageException e) { + Log.w("AvatarDownloader", e); + } catch (BitmapDecodingException e) { + Log.w("AvatarDownloader", e); + } + } + + private File downloadAttachment(String relay, long contentLocation) throws IOException { + PushServiceSocket socket = PushServiceSocketFactory.create(context); + return socket.retrieveAttachment(relay, contentLocation); + } + +} diff --git a/src/org/thoughtcrime/securesms/service/PushReceiver.java b/src/org/thoughtcrime/securesms/service/PushReceiver.java index ce923d91e4..7ee7471670 100644 --- a/src/org/thoughtcrime/securesms/service/PushReceiver.java +++ b/src/org/thoughtcrime/securesms/service/PushReceiver.java @@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.crypto.DecryptingQueue; import org.thoughtcrime.securesms.crypto.KeyExchangeProcessorV2; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; +import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.mms.IncomingMediaMessage; import org.thoughtcrime.securesms.notifications.MessageNotifier; @@ -31,9 +32,13 @@ import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage; import org.whispersystems.textsecure.push.IncomingPushMessage; import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent; import org.whispersystems.textsecure.storage.InvalidKeyIdException; +import org.whispersystems.textsecure.util.Hex; import ws.com.google.android.mms.MmsException; +import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext; +import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.Type; + public class PushReceiver { public static final int RESULT_OK = 0; @@ -67,8 +72,16 @@ public class PushReceiver { } private void handleMessage(MasterSecret masterSecret, Intent intent) { + if (intent.getExtras() == null) { + return; + } + IncomingPushMessage message = intent.getExtras().getParcelable("message"); + if (message == null) { + return; + } + if (message.isSecureMessage()) handleReceivedSecureMessage(masterSecret, message); else if (message.isPreKeyBundle()) handleReceivedPreKeyBundle(masterSecret, message); else handleReceivedMessage(masterSecret, message, false); @@ -105,7 +118,7 @@ public class PushReceiver { } else { SmsTransportDetails transportDetails = new SmsTransportDetails(); String encoded = new String(transportDetails.getEncodedMessage(message.getBody())); - IncomingTextMessage textMessage = new IncomingTextMessage(message, ""); + IncomingTextMessage textMessage = new IncomingTextMessage(message, "", null); textMessage = new IncomingPreKeyBundleMessage(textMessage, encoded); DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageInbox(masterSecret, textMessage); @@ -133,12 +146,15 @@ public class PushReceiver { Log.w("PushReceiver", "Processing: " + new String(message.getBody())); PushMessageContent messageContent = PushMessageContent.parseFrom(message.getBody()); - if (messageContent.getAttachmentsCount() > 0 || message.getDestinations().size() > 0) { + if (messageContent.hasGroup()) { + Log.w("PushReceiver", "Received push group message..."); + handleReceivedGroupMessage(masterSecret, message, messageContent, secure); + } else if (messageContent.getAttachmentsCount() > 0) { Log.w("PushReceiver", "Received push media message..."); - handleReceivedMediaMessage(masterSecret, message, messageContent, secure); + handleReceivedMediaMessage(masterSecret, message, messageContent, secure, null); } else { Log.w("PushReceiver", "Received push text message..."); - handleReceivedTextMessage(masterSecret, message, messageContent, secure); + handleReceivedTextMessage(masterSecret, message, messageContent, secure, null); } } catch (InvalidProtocolBufferException e) { Log.w("PushReceiver", e); @@ -146,17 +162,75 @@ public class PushReceiver { } } - private void handleReceivedMediaMessage(MasterSecret masterSecret, + private void handleReceivedGroupMessage(MasterSecret masterSecret, IncomingPushMessage message, PushMessageContent messageContent, boolean secure) { + if (messageContent.getGroup().getType().equals(Type.UNKNOWN)) { + Log.w("PushReceiver", "Received group message of unknown type: " + + messageContent.getGroup().getType().getNumber()); + return; + } + + if (!messageContent.getGroup().hasId()) { + Log.w("PushReceiver", "Received group message with no id!"); + return; + } + + GroupDatabase database = DatabaseFactory.getGroupDatabase(context); + GroupContext group = messageContent.getGroup(); + byte[] id = group.getId().toByteArray(); + int type = group.getType().getNumber(); + + switch (type) { + case Type.CREATE_VALUE: + database.create(id, message.getSource(), group.getName(), group.getMembersList(), group.getAvatar(), message.getRelay()); + break; + case Type.ADD_VALUE: + database.add(id, message.getSource(), group.getMembersList()); + break; + case Type.QUIT_VALUE: + database.remove(id, message.getSource()); + break; + case Type.MODIFY_VALUE: + database.update(id, message.getSource(), group.getName(), group.getAvatar()); + break; + case Type.DELIVER_VALUE: + break; + case Type.UNKNOWN_VALUE: + default: + Log.w("PushReceiver", "Received group message of unknown type: " + type); + return; + } + + if (group.hasAvatar()) { + Intent intent = new Intent(context, SendReceiveService.class); + intent.setAction(SendReceiveService.DOWNLOAD_AVATAR_ACTION); + context.startService(intent); + } + + String groupId = "g_" + Hex.toString(group.getId().toByteArray()); + + if (messageContent.getAttachmentsCount() > 0) { + handleReceivedMediaMessage(masterSecret, message, messageContent, secure, groupId); + } else if (messageContent.hasBody()) { + handleReceivedTextMessage(masterSecret, message, messageContent, secure, groupId); + } + } + + private void handleReceivedMediaMessage(MasterSecret masterSecret, + IncomingPushMessage message, + PushMessageContent messageContent, + boolean secure, String groupId) + { try { String localNumber = TextSecurePreferences.getLocalNumber(context); MmsDatabase database = DatabaseFactory.getMmsDatabase(context); IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterSecret, localNumber, - message, messageContent); + message, messageContent, + groupId); Pair messageAndThreadId; @@ -181,10 +255,10 @@ public class PushReceiver { private void handleReceivedTextMessage(MasterSecret masterSecret, IncomingPushMessage message, PushMessageContent messageContent, - boolean secure) + boolean secure, String groupId) { EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); - IncomingTextMessage textMessage = new IncomingTextMessage(message, ""); + IncomingTextMessage textMessage = new IncomingTextMessage(message, "", groupId); if (secure) { textMessage = new IncomingEncryptedMessage(textMessage, ""); @@ -210,7 +284,7 @@ public class PushReceiver { IncomingPushMessage message, boolean invalidVersion) { - IncomingTextMessage corruptedMessage = new IncomingTextMessage(message, ""); + IncomingTextMessage corruptedMessage = new IncomingTextMessage(message, "", null); IncomingKeyExchangeMessage corruptedKeyMessage = new IncomingKeyExchangeMessage(corruptedMessage, ""); if (!invalidVersion) corruptedKeyMessage.setCorrupted(true); @@ -235,7 +309,7 @@ public class PushReceiver { IncomingPushMessage message, boolean secure) { - IncomingTextMessage placeholder = new IncomingTextMessage(message, ""); + IncomingTextMessage placeholder = new IncomingTextMessage(message, "", null); if (secure) { placeholder = new IncomingEncryptedMessage(placeholder, ""); diff --git a/src/org/thoughtcrime/securesms/service/SendReceiveService.java b/src/org/thoughtcrime/securesms/service/SendReceiveService.java index c1759b3cfc..710392d675 100644 --- a/src/org/thoughtcrime/securesms/service/SendReceiveService.java +++ b/src/org/thoughtcrime/securesms/service/SendReceiveService.java @@ -57,6 +57,7 @@ public class SendReceiveService extends Service { 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"; + public static final String DOWNLOAD_AVATAR_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_AVATAR_ACTION"; private static final int SEND_SMS = 0; private static final int RECEIVE_SMS = 1; @@ -66,16 +67,18 @@ public class SendReceiveService extends Service { private static final int DOWNLOAD_MMS_PENDING = 5; private static final int RECEIVE_PUSH = 6; private static final int DOWNLOAD_PUSH = 7; + private static final int DOWNLOAD_AVATAR = 8; private ToastHandler toastHandler; - private SmsReceiver smsReceiver; - private SmsSender smsSender; - private MmsReceiver mmsReceiver; - private MmsSender mmsSender; - private MmsDownloader mmsDownloader; - private PushReceiver pushReceiver; - private PushDownloader pushDownloader; + private SmsReceiver smsReceiver; + private SmsSender smsSender; + private MmsReceiver mmsReceiver; + private MmsSender mmsSender; + private MmsDownloader mmsDownloader; + private PushReceiver pushReceiver; + private PushDownloader pushDownloader; + private AvatarDownloader avatarDownloader; private MasterSecret masterSecret; private boolean hasSecret; @@ -122,6 +125,8 @@ public class SendReceiveService extends Service { scheduleSecretRequiredIntent(RECEIVE_PUSH, intent); else if (action.equals(DOWNLOAD_PUSH_ACTION)) scheduleSecretRequiredIntent(DOWNLOAD_PUSH, intent); + else if (action.equals(DOWNLOAD_AVATAR_ACTION)) + scheduleIntent(DOWNLOAD_AVATAR, intent); else Log.w("SendReceiveService", "Received intent with unknown action: " + intent.getAction()); } @@ -148,13 +153,14 @@ public class SendReceiveService extends Service { } private void initializeProcessors() { - smsReceiver = new SmsReceiver(this); - smsSender = new SmsSender(this, toastHandler); - mmsReceiver = new MmsReceiver(this); - mmsSender = new MmsSender(this, toastHandler); - mmsDownloader = new MmsDownloader(this, toastHandler); - pushReceiver = new PushReceiver(this); - pushDownloader = new PushDownloader(this); + smsReceiver = new SmsReceiver(this); + smsSender = new SmsSender(this, toastHandler); + mmsReceiver = new MmsReceiver(this); + mmsSender = new MmsSender(this, toastHandler); + mmsDownloader = new MmsDownloader(this, toastHandler); + pushReceiver = new PushReceiver(this); + pushDownloader = new PushDownloader(this); + avatarDownloader = new AvatarDownloader(this); } private void initializeWorkQueue() { @@ -235,14 +241,15 @@ 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 DOWNLOAD_MMS_PENDING: mmsDownloader.process(masterSecret, intent); return; - case RECEIVE_PUSH: pushReceiver.process(masterSecret, intent); return; - case DOWNLOAD_PUSH: pushDownloader.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; + case DOWNLOAD_AVATAR: avatarDownloader.process(masterSecret, intent); return; } } } diff --git a/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java b/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java index 5e478e8016..3be14955f6 100644 --- a/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java +++ b/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java @@ -29,6 +29,7 @@ public class IncomingTextMessage implements Parcelable { private final boolean replyPathPresent; private final String pseudoSubject; private final long sentTimestampMillis; + private final String groupId; public IncomingTextMessage(SmsMessage message) { this.message = message.getDisplayMessageBody(); @@ -38,9 +39,10 @@ public class IncomingTextMessage implements Parcelable { this.replyPathPresent = message.isReplyPathPresent(); this.pseudoSubject = message.getPseudoSubject(); this.sentTimestampMillis = message.getTimestampMillis(); + this.groupId = null; } - public IncomingTextMessage(IncomingPushMessage message, String encodedBody) { + public IncomingTextMessage(IncomingPushMessage message, String encodedBody, String groupId) { this.message = encodedBody; this.sender = message.getSource(); this.protocol = 31337; @@ -48,6 +50,7 @@ public class IncomingTextMessage implements Parcelable { this.replyPathPresent = true; this.pseudoSubject = ""; this.sentTimestampMillis = message.getTimestampMillis(); + this.groupId = groupId; } public IncomingTextMessage(Parcel in) { @@ -58,6 +61,7 @@ public class IncomingTextMessage implements Parcelable { this.replyPathPresent = (in.readInt() == 1); this.pseudoSubject = in.readString(); this.sentTimestampMillis = in.readLong(); + this.groupId = in.readString(); } public IncomingTextMessage(IncomingTextMessage base, String newBody) { @@ -68,6 +72,7 @@ public class IncomingTextMessage implements Parcelable { this.replyPathPresent = base.isReplyPathPresent(); this.pseudoSubject = base.getPseudoSubject(); this.sentTimestampMillis = base.getSentTimestampMillis(); + this.groupId = base.getGroupId(); } public IncomingTextMessage(List fragments) { @@ -84,6 +89,7 @@ public class IncomingTextMessage implements Parcelable { this.replyPathPresent = fragments.get(0).isReplyPathPresent(); this.pseudoSubject = fragments.get(0).getPseudoSubject(); this.sentTimestampMillis = fragments.get(0).getSentTimestampMillis(); + this.groupId = fragments.get(0).getGroupId(); } public long getSentTimestampMillis() { @@ -130,6 +136,10 @@ public class IncomingTextMessage implements Parcelable { return false; } + public String getGroupId() { + return groupId; + } + @Override public int describeContents() { return 0; @@ -144,5 +154,6 @@ public class IncomingTextMessage implements Parcelable { out.writeInt(replyPathPresent ? 1 : 0); out.writeString(pseudoSubject); out.writeLong(sentTimestampMillis); + out.writeString(groupId); } } diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index b3aed40846..bd7b156e5b 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -116,15 +116,20 @@ public class MessageSender { SendReq sendRequest, long threadId, int distributionType, boolean secure) throws MmsException { + Log.w("MessageSender", "Distribution type: " + distributionType); + String[] recipientsArray = recipients.toNumberStringArray(true); EncodedStringValue[] encodedNumbers = EncodedStringValue.encodeStrings(recipientsArray); if (recipients.isSingleRecipient()) { + Log.w("MessageSender", "Single recipient!?"); sendRequest.setTo(encodedNumbers); } else if (distributionType == ThreadDatabase.DistributionTypes.BROADCAST) { + Log.w("MessageSender", "Broadcast..."); sendRequest.setBcc(encodedNumbers); - } else if (distributionType == ThreadDatabase.DistributionTypes.CONVERSATION) { - sendRequest.setCc(encodedNumbers); + } else if (distributionType == ThreadDatabase.DistributionTypes.CONVERSATION || distributionType == 0) { + Log.w("MessageSender", "Conversation..."); + sendRequest.setTo(encodedNumbers); } long messageId = DatabaseFactory.getMmsDatabase(context) diff --git a/src/org/thoughtcrime/securesms/util/BitmapUtil.java b/src/org/thoughtcrime/securesms/util/BitmapUtil.java index d0f6e1d31d..48f95b3da2 100644 --- a/src/org/thoughtcrime/securesms/util/BitmapUtil.java +++ b/src/org/thoughtcrime/securesms/util/BitmapUtil.java @@ -122,4 +122,9 @@ public class BitmapUtil { return output; } + public static byte[] toByteArray(Bitmap bitmap) { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); + return stream.toByteArray(); + } }