mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-30 13:35:18 +00:00
Refactor group messaging protocol.
// FREEBIE
This commit is contained in:
parent
b855f8a163
commit
a6e1d56cde
@ -30,11 +30,9 @@ message PushMessageContent {
|
||||
message GroupContext {
|
||||
enum Type {
|
||||
UNKNOWN = 0;
|
||||
CREATE = 1;
|
||||
MODIFY = 2;
|
||||
DELIVER = 3;
|
||||
ADD = 4;
|
||||
QUIT = 5;
|
||||
UPDATE = 1;
|
||||
DELIVER = 2;
|
||||
QUIT = 3;
|
||||
}
|
||||
optional bytes id = 1;
|
||||
optional Type type = 2;
|
||||
|
@ -1463,19 +1463,15 @@ public final class PushMessageProtos {
|
||||
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),
|
||||
UPDATE(1, 1),
|
||||
DELIVER(2, 2),
|
||||
QUIT(3, 3),
|
||||
;
|
||||
|
||||
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 static final int UPDATE_VALUE = 1;
|
||||
public static final int DELIVER_VALUE = 2;
|
||||
public static final int QUIT_VALUE = 3;
|
||||
|
||||
|
||||
public final int getNumber() { return value; }
|
||||
@ -1483,11 +1479,9 @@ public final class PushMessageProtos {
|
||||
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;
|
||||
case 1: return UPDATE;
|
||||
case 2: return DELIVER;
|
||||
case 3: return QUIT;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
@ -1518,7 +1512,7 @@ public final class PushMessageProtos {
|
||||
}
|
||||
|
||||
private static final Type[] VALUES = {
|
||||
UNKNOWN, CREATE, MODIFY, DELIVER, ADD, QUIT,
|
||||
UNKNOWN, UPDATE, DELIVER, QUIT,
|
||||
};
|
||||
|
||||
public static Type valueOf(
|
||||
@ -3073,22 +3067,22 @@ public final class PushMessageProtos {
|
||||
"evice\030\007 \001(\r\022\r\n\005relay\030\003 \001(\t\022\021\n\ttimestamp\030" +
|
||||
"\005 \001(\004\022\017\n\007message\030\006 \001(\014\"W\n\004Type\022\013\n\007UNKNOW" +
|
||||
"N\020\000\022\016\n\nCIPHERTEXT\020\001\022\020\n\014KEY_EXCHANGE\020\002\022\021\n" +
|
||||
"\rPREKEY_BUNDLE\020\003\022\r\n\tPLAINTEXT\020\004\"\234\004\n\022Push" +
|
||||
"\rPREKEY_BUNDLE\020\003\022\r\n\tPLAINTEXT\020\004\"\207\004\n\022Push" +
|
||||
"MessageContent\022\014\n\004body\030\001 \001(\t\022E\n\013attachme" +
|
||||
"nts\030\002 \003(\01320.textsecure.PushMessageConten",
|
||||
"t.AttachmentPointer\022:\n\005group\030\003 \001(\0132+.tex" +
|
||||
"tsecure.PushMessageContent.GroupContext\022" +
|
||||
"\r\n\005flags\030\004 \001(\r\032A\n\021AttachmentPointer\022\n\n\002i" +
|
||||
"d\030\001 \001(\006\022\023\n\013contentType\030\002 \001(\t\022\013\n\003key\030\003 \001(" +
|
||||
"\014\032\210\002\n\014GroupContext\022\n\n\002id\030\001 \001(\014\022>\n\004type\030\002" +
|
||||
"\014\032\363\001\n\014GroupContext\022\n\n\002id\030\001 \001(\014\022>\n\004type\030\002" +
|
||||
" \001(\01620.textsecure.PushMessageContent.Gro" +
|
||||
"upContext.Type\022\014\n\004name\030\003 \001(\t\022\017\n\007members\030" +
|
||||
"\004 \003(\t\022@\n\006avatar\030\005 \001(\01320.textsecure.PushM" +
|
||||
"essageContent.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\007",
|
||||
"DELIVER\020\003\022\007\n\003ADD\020\004\022\010\n\004QUIT\020\005\"\030\n\005Flags\022\017\n" +
|
||||
"\013END_SESSION\020\001B7\n\"org.whispersystems.tex" +
|
||||
"tsecure.pushB\021PushMessageProtos"
|
||||
"essageContent.AttachmentPointer\"6\n\004Type\022" +
|
||||
"\013\n\007UNKNOWN\020\000\022\n\n\006UPDATE\020\001\022\013\n\007DELIVER\020\002\022\010\n",
|
||||
"\004QUIT\020\003\"\030\n\005Flags\022\017\n\013END_SESSION\020\001B7\n\"org" +
|
||||
".whispersystems.textsecure.pushB\021PushMes" +
|
||||
"sageProtos"
|
||||
};
|
||||
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
|
||||
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
|
||||
|
@ -337,6 +337,11 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv
|
||||
}
|
||||
}
|
||||
|
||||
private void handleGroupUpdate() {
|
||||
Log.w("GroupCreateActivity", "Creating...");
|
||||
new UpdateWhisperGroupAsyncTask().execute();
|
||||
}
|
||||
|
||||
private static List<String> recipientsToNormalizedStrings(Collection<Recipient> recipients, String localNumber) {
|
||||
final List<String> e164numbers = new ArrayList<String>(recipients.size());
|
||||
for (Recipient contact : recipients) {
|
||||
@ -349,63 +354,6 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv
|
||||
return e164numbers;
|
||||
}
|
||||
|
||||
private void handleGroupUpdate() {
|
||||
Log.i(TAG, "Updating group info.");
|
||||
GroupDatabase db = DatabaseFactory.getGroupDatabase(this);
|
||||
final String localNumber = TextSecurePreferences.getLocalNumber(this);
|
||||
List<String> e164numbers = recipientsToNormalizedStrings(selectedContacts, localNumber);
|
||||
if (selectedContacts.size() > 0) {
|
||||
db.add(groupId, localNumber, e164numbers);
|
||||
GroupContext context = GroupContext.newBuilder()
|
||||
.setId(ByteString.copyFrom(groupId))
|
||||
.setType(GroupContext.Type.ADD)
|
||||
.addAllMembers(e164numbers)
|
||||
.build();
|
||||
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(this, groupRecipient, context, null);
|
||||
try {
|
||||
MessageSender.send(this, masterSecret, outgoingMessage, groupThread);
|
||||
} catch (MmsException me) {
|
||||
Log.w(TAG, "MmsException encountered when trying to add members to group.", me);
|
||||
}
|
||||
}
|
||||
|
||||
GroupContext.Builder builder = GroupContext.newBuilder()
|
||||
.setId(ByteString.copyFrom(groupId))
|
||||
.setType(GroupContext.Type.MODIFY);
|
||||
boolean shouldSendUpdate = false;
|
||||
final String title = groupName.getText().toString();
|
||||
if (existingTitle == null || (groupName.getText() != null && !existingTitle.equals(title))) {
|
||||
builder.setName(title);
|
||||
db.updateTitle(groupId, title);
|
||||
shouldSendUpdate = true;
|
||||
}
|
||||
byte[] avatarBytes = null;
|
||||
if (existingAvatarBmp == null || !existingAvatarBmp.equals(avatarBmp)) {
|
||||
if (avatarBmp != null) {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
avatarBmp.compress(Bitmap.CompressFormat.PNG, 100, stream);
|
||||
avatarBytes = stream.toByteArray();
|
||||
}
|
||||
db.updateAvatar(groupId, avatarBytes);
|
||||
shouldSendUpdate = true;
|
||||
}
|
||||
|
||||
if (shouldSendUpdate) {
|
||||
GroupContext context = builder.build();
|
||||
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(this, groupRecipient, context, avatarBytes);
|
||||
try {
|
||||
MessageSender.send(this, masterSecret, outgoingMessage, groupThread);
|
||||
} catch (MmsException me) {
|
||||
Log.w(TAG, "MmsException encountered when trying to add members to group.", me);
|
||||
}
|
||||
}
|
||||
|
||||
RecipientFactory.clearCache(groupRecipient.getPrimaryRecipient());
|
||||
|
||||
setResult(RESULT_OK, getIntent());
|
||||
finish();
|
||||
}
|
||||
|
||||
private void enableWhisperGroupCreatingUi() {
|
||||
findViewById(R.id.group_details_layout).setVisibility(View.GONE);
|
||||
findViewById(R.id.creating_group_layout).setVisibility(View.VISIBLE);
|
||||
@ -475,29 +423,56 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv
|
||||
}
|
||||
}
|
||||
|
||||
private Pair<Long, Recipients> handleCreatePushGroup(String groupName,
|
||||
byte[] avatar,
|
||||
private Pair<Long, Recipients> handleCreatePushGroup(String groupName, byte[] avatar,
|
||||
Set<Recipient> members)
|
||||
throws InvalidNumberException, MmsException
|
||||
{
|
||||
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(this);
|
||||
byte[] groupId = groupDatabase.allocateGroupId();
|
||||
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(this);
|
||||
byte[] groupId = groupDatabase.allocateGroupId();
|
||||
List<String> memberE164Numbers = getE164Numbers(members);
|
||||
|
||||
groupDatabase.create(groupId, TextSecurePreferences.getLocalNumber(this), groupName,
|
||||
memberE164Numbers, null, null);
|
||||
groupDatabase.updateAvatar(groupId, avatar);
|
||||
|
||||
return handlePushOperation(groupId, groupName, avatar, memberE164Numbers);
|
||||
}
|
||||
|
||||
private Pair<Long, Recipients> handleUpdatePushGroup(byte[] groupId, String groupName,
|
||||
byte[] avatar, Set<Recipient> members)
|
||||
throws InvalidNumberException, MmsException
|
||||
{
|
||||
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(this);
|
||||
List<String> memberE164Numbers = getE164Numbers(members);
|
||||
|
||||
GroupDatabase.GroupRecord record = groupDatabase.getGroup(groupId);
|
||||
Set<String> newMembers = new HashSet<String>(memberE164Numbers);
|
||||
newMembers.removeAll(record.getMembers());
|
||||
|
||||
groupDatabase.add(groupId, TextSecurePreferences.getLocalNumber(this),
|
||||
new LinkedList<String>(newMembers));
|
||||
|
||||
groupDatabase.updateTitle(groupId, groupName);
|
||||
groupDatabase.updateAvatar(groupId, avatar);
|
||||
|
||||
|
||||
return handlePushOperation(groupId, groupName, avatar, memberE164Numbers);
|
||||
}
|
||||
|
||||
private Pair<Long, Recipients> handlePushOperation(byte[] groupId, String groupName, byte[] avatar,
|
||||
List<String> e164numbers)
|
||||
throws MmsException, InvalidNumberException
|
||||
{
|
||||
|
||||
try {
|
||||
List<String> memberE164Numbers = getE164Numbers(members);
|
||||
String groupRecipientId = GroupUtil.getEncodedId(groupId);
|
||||
|
||||
groupDatabase.create(groupId, TextSecurePreferences.getLocalNumber(this), groupName,
|
||||
memberE164Numbers, null, null);
|
||||
groupDatabase.updateAvatar(groupId, avatar);
|
||||
|
||||
Recipients groupRecipient = RecipientFactory.getRecipientsFromString(this, groupRecipientId, false);
|
||||
String groupRecipientId = GroupUtil.getEncodedId(groupId);
|
||||
Recipients groupRecipient = RecipientFactory.getRecipientsFromString(this, groupRecipientId, false);
|
||||
|
||||
GroupContext context = GroupContext.newBuilder()
|
||||
.setId(ByteString.copyFrom(groupId))
|
||||
.setType(GroupContext.Type.CREATE)
|
||||
.setType(GroupContext.Type.UPDATE)
|
||||
.setName(groupName)
|
||||
.addAllMembers(memberE164Numbers)
|
||||
.addAllMembers(e164numbers)
|
||||
.build();
|
||||
|
||||
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(this, groupRecipient, context, avatar);
|
||||
@ -508,7 +483,6 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv
|
||||
throw new AssertionError(e);
|
||||
} catch (MmsException e) {
|
||||
Log.w(TAG, e);
|
||||
groupDatabase.remove(groupId, TextSecurePreferences.getLocalNumber(this));
|
||||
throw new MmsException(e);
|
||||
}
|
||||
}
|
||||
@ -588,6 +562,30 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv
|
||||
}
|
||||
}
|
||||
|
||||
private class UpdateWhisperGroupAsyncTask extends AsyncTask<Void,Void,Pair<Long,Recipients>> {
|
||||
private long RES_BAD_NUMBER = -2;
|
||||
private long RES_MMS_EXCEPTION = -3;
|
||||
@Override
|
||||
protected Pair<Long, Recipients> doInBackground(Void... params) {
|
||||
byte[] avatarBytes = null;
|
||||
if (avatarBmp != null) {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
avatarBmp.compress(Bitmap.CompressFormat.PNG, 100, stream);
|
||||
avatarBytes = stream.toByteArray();
|
||||
}
|
||||
final String name = (groupName.getText() != null) ? groupName.getText().toString() : null;
|
||||
try {
|
||||
return handleUpdatePushGroup(groupId, name, avatarBytes, selectedContacts);
|
||||
} catch (MmsException e) {
|
||||
Log.w(TAG, e);
|
||||
return new Pair<Long,Recipients>(RES_MMS_EXCEPTION, null);
|
||||
} catch (InvalidNumberException e) {
|
||||
Log.w(TAG, e);
|
||||
return new Pair<Long,Recipients>(RES_BAD_NUMBER, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CreateWhisperGroupAsyncTask extends AsyncTask<Void,Void,Pair<Long,Recipients>> {
|
||||
private long RES_BAD_NUMBER = -2;
|
||||
private long RES_MMS_EXCEPTION = -3;
|
||||
@ -664,8 +662,7 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv
|
||||
existingContacts.addAll(recipientList);
|
||||
}
|
||||
}
|
||||
final GroupDatabase.Reader groupReader = db.getGroup(groupId);
|
||||
GroupDatabase.GroupRecord group = groupReader.getNext();
|
||||
GroupDatabase.GroupRecord group = db.getGroup(groupId);
|
||||
if (group != null) {
|
||||
existingTitle = group.getTitle();
|
||||
final byte[] existingAvatar = group.getAvatar();
|
||||
|
@ -66,12 +66,16 @@ public class GroupDatabase extends Database {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
public Reader getGroup(byte[] groupId) {
|
||||
public GroupRecord getGroup(byte[] groupId) {
|
||||
Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, GROUP_ID + " = ?",
|
||||
new String[] {GroupUtil.getEncodedId(groupId)},
|
||||
null, null, null);
|
||||
|
||||
return new Reader(cursor);
|
||||
Reader reader = new Reader(cursor);
|
||||
GroupRecord record = reader.getNext();
|
||||
|
||||
reader.close();
|
||||
return record;
|
||||
}
|
||||
|
||||
public Recipients getGroupMembers(byte[] groupId) {
|
||||
@ -107,7 +111,6 @@ public class GroupDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(GROUP_ID, GroupUtil.getEncodedId(groupId));
|
||||
contentValues.put(OWNER, owner);
|
||||
|
@ -532,9 +532,8 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
}
|
||||
|
||||
if (message.isGroup()) {
|
||||
if (((OutgoingGroupMediaMessage)message).isGroupAdd()) type |= Types.GROUP_ADD_MEMBERS_BIT;
|
||||
if (((OutgoingGroupMediaMessage)message).isGroupUpdate()) type |= Types.GROUP_UPDATE_BIT;
|
||||
else if (((OutgoingGroupMediaMessage)message).isGroupQuit()) type |= Types.GROUP_QUIT_BIT;
|
||||
else if (((OutgoingGroupMediaMessage)message).isGroupModify()) type |= Types.GROUP_MODIFY_BIT;
|
||||
}
|
||||
|
||||
SendReq sendRequest = new SendReq();
|
||||
|
@ -41,9 +41,8 @@ public interface MmsSmsColumns {
|
||||
protected static final long PUSH_MESSAGE_BIT = 0x200000;
|
||||
|
||||
// Group Message Information
|
||||
protected static final long GROUP_ADD_MEMBERS_BIT = 0x10000;
|
||||
protected static final long GROUP_QUIT_BIT = 0x20000;
|
||||
protected static final long GROUP_MODIFY_BIT = 0x40000;
|
||||
protected static final long GROUP_UPDATE_BIT = 0x10000;
|
||||
protected static final long GROUP_QUIT_BIT = 0x20000;
|
||||
|
||||
// Encrypted Storage Information
|
||||
protected static final long ENCRYPTION_MASK = 0xFF000000;
|
||||
@ -116,12 +115,8 @@ public interface MmsSmsColumns {
|
||||
return (type & KEY_EXCHANGE_IDENTITY_UPDATE_BIT) != 0;
|
||||
}
|
||||
|
||||
public static boolean isGroupAdd(long type) {
|
||||
return (type & GROUP_ADD_MEMBERS_BIT) != 0;
|
||||
}
|
||||
|
||||
public static boolean isGroupModify(long type) {
|
||||
return (type & GROUP_MODIFY_BIT) != 0;
|
||||
public static boolean isGroupUpdate(long type) {
|
||||
return (type & GROUP_UPDATE_BIT) != 0;
|
||||
}
|
||||
|
||||
public static boolean isGroupQuit(long type) {
|
||||
|
@ -262,9 +262,8 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||
type |= Types.ENCRYPTION_REMOTE_BIT;
|
||||
} else if (message.isGroup()) {
|
||||
type |= Types.SECURE_MESSAGE_BIT;
|
||||
if (((IncomingGroupMessage)message).isAdd()) type |= Types.GROUP_ADD_MEMBERS_BIT;
|
||||
if (((IncomingGroupMessage)message).isUpdate()) type |= Types.GROUP_UPDATE_BIT;
|
||||
else if (((IncomingGroupMessage)message).isQuit()) type |= Types.GROUP_QUIT_BIT;
|
||||
else if (((IncomingGroupMessage)message).isModify()) type |= Types.GROUP_MODIFY_BIT;
|
||||
} else if (message.isEndSession()) {
|
||||
type |= Types.SECURE_MESSAGE_BIT;
|
||||
type |= Types.END_SESSION_BIT;
|
||||
|
@ -83,12 +83,8 @@ public abstract class DisplayRecord {
|
||||
return SmsDatabase.Types.isEndSessionType(type);
|
||||
}
|
||||
|
||||
public boolean isGroupAdd() {
|
||||
return SmsDatabase.Types.isGroupAdd(type);
|
||||
}
|
||||
|
||||
public boolean isGroupModify() {
|
||||
return SmsDatabase.Types.isGroupModify(type);
|
||||
public boolean isGroupUpdate() {
|
||||
return SmsDatabase.Types.isGroupUpdate(type);
|
||||
}
|
||||
|
||||
public boolean isGroupQuit() {
|
||||
@ -96,7 +92,7 @@ public abstract class DisplayRecord {
|
||||
}
|
||||
|
||||
public boolean isGroupAction() {
|
||||
return isGroupAdd() || isGroupModify() || isGroupQuit();
|
||||
return isGroupUpdate() || isGroupQuit();
|
||||
}
|
||||
|
||||
public static class Body {
|
||||
|
@ -84,12 +84,10 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
|
||||
@Override
|
||||
public SpannableString getDisplayBody() {
|
||||
if (isGroupAdd()) {
|
||||
return emphasisAdded(context.getString(R.string.ConversationItem_group_action_joined, Util.join(GroupUtil.getSerializedArgumentMembers(getBody().getBody()), ", ")));
|
||||
if (isGroupUpdate()) {
|
||||
return emphasisAdded(GroupUtil.getDescription(getBody().getBody()));
|
||||
} else if (isGroupQuit()) {
|
||||
return emphasisAdded(context.getString(R.string.ConversationItem_group_action_left, getIndividualRecipient().toShortString()));
|
||||
} else if (isGroupModify()) {
|
||||
return emphasisAdded(context.getString(R.string.ConversationItem_group_action_modify, getIndividualRecipient().toShortString()));
|
||||
}
|
||||
|
||||
return new SpannableString(getBody().getBody());
|
||||
|
@ -56,12 +56,10 @@ public class ThreadRecord extends DisplayRecord {
|
||||
// TODO jake is going to fill these in
|
||||
if (SmsDatabase.Types.isDecryptInProgressType(type)) {
|
||||
return emphasisAdded(context.getString(R.string.MessageDisplayHelper_decrypting_please_wait));
|
||||
} else if (isGroupAdd()) {
|
||||
return emphasisAdded(Util.join(GroupUtil.getSerializedArgumentMembers(getBody().getBody()), ", ") + " have joined the group");
|
||||
} else if (isGroupUpdate()) {
|
||||
return emphasisAdded(GroupUtil.getDescription(getBody().getBody()));
|
||||
} else if (isGroupQuit()) {
|
||||
return emphasisAdded(getRecipients().toShortString() + " left the group.");
|
||||
} else if (isGroupModify()) {
|
||||
return emphasisAdded(getRecipients().toShortString() + " modified the group.");
|
||||
} else if (isKeyExchange()) {
|
||||
return emphasisAdded(context.getString(R.string.ConversationListItem_key_exchange_message));
|
||||
} else if (SmsDatabase.Types.isFailedDecryptType(type)) {
|
||||
|
@ -37,17 +37,11 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isGroupAdd() {
|
||||
return
|
||||
group.getType().getNumber() == GroupContext.Type.ADD_VALUE ||
|
||||
group.getType().getNumber() == GroupContext.Type.CREATE_VALUE;
|
||||
public boolean isGroupUpdate() {
|
||||
return group.getType().getNumber() == GroupContext.Type.UPDATE_VALUE;
|
||||
}
|
||||
|
||||
public boolean isGroupQuit() {
|
||||
return group.getType().getNumber() == GroupContext.Type.QUIT_VALUE;
|
||||
}
|
||||
|
||||
public boolean isGroupModify() {
|
||||
return group.getType().getNumber() == GroupContext.Type.MODIFY_VALUE;
|
||||
}
|
||||
}
|
||||
|
@ -144,23 +144,17 @@ public class RecipientProvider {
|
||||
|
||||
private RecipientDetails getGroupRecipientDetails(Context context, String groupId) {
|
||||
try {
|
||||
GroupDatabase.Reader reader = DatabaseFactory.getGroupDatabase(context)
|
||||
.getGroup(GroupUtil.getDecodedId(groupId));
|
||||
GroupDatabase.GroupRecord record = DatabaseFactory.getGroupDatabase(context)
|
||||
.getGroup(GroupUtil.getDecodedId(groupId));
|
||||
|
||||
GroupDatabase.GroupRecord record;
|
||||
if (record != null) {
|
||||
byte[] avatarBytes = record.getAvatar();
|
||||
Bitmap avatar;
|
||||
|
||||
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);
|
||||
|
||||
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 new RecipientDetails(record.getTitle(), null, avatar);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -37,19 +37,17 @@ public class AvatarDownloader {
|
||||
if (!SendReceiveService.DOWNLOAD_AVATAR_ACTION.equals(intent.getAction()))
|
||||
return;
|
||||
|
||||
byte[] groupId = intent.getByteArrayExtra("group_id");
|
||||
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
|
||||
GroupDatabase.Reader reader = database.getGroup(groupId);
|
||||
byte[] groupId = intent.getByteArrayExtra("group_id");
|
||||
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
|
||||
GroupDatabase.GroupRecord record = database.getGroup(groupId);
|
||||
|
||||
GroupDatabase.GroupRecord record;
|
||||
|
||||
while ((record = reader.getNext()) != null) {
|
||||
if (record != null) {
|
||||
long avatarId = record.getAvatarId();
|
||||
byte[] key = record.getAvatarKey();
|
||||
String relay = record.getRelay();
|
||||
|
||||
if (avatarId == -1 || key == null) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
File attachment = downloadAttachment(relay, avatarId);
|
||||
|
159
src/org/thoughtcrime/securesms/service/GroupReceiver.java
Normal file
159
src/org/thoughtcrime/securesms/service/GroupReceiver.java
Normal file
@ -0,0 +1,159 @@
|
||||
package org.thoughtcrime.securesms.service;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.sms.IncomingGroupMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.push.IncomingPushMessage;
|
||||
import org.whispersystems.textsecure.util.Base64;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
|
||||
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent;
|
||||
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext;
|
||||
|
||||
public class GroupReceiver {
|
||||
|
||||
private final Context context;
|
||||
|
||||
public GroupReceiver(Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
}
|
||||
|
||||
public void process(MasterSecret masterSecret,
|
||||
IncomingPushMessage message,
|
||||
PushMessageContent messageContent,
|
||||
boolean secure)
|
||||
{
|
||||
if (!messageContent.getGroup().hasId()) {
|
||||
Log.w("GroupReceiver", "Received group message with no id! Ignoring...");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!secure) {
|
||||
Log.w("GroupReceiver", "Received insecure group push action! Ignoring...");
|
||||
return;
|
||||
}
|
||||
|
||||
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
|
||||
GroupContext group = messageContent.getGroup();
|
||||
byte[] id = group.getId().toByteArray();
|
||||
int type = group.getType().getNumber();
|
||||
GroupRecord record = database.getGroup(id);
|
||||
|
||||
if (record != null && type == GroupContext.Type.UPDATE_VALUE) {
|
||||
handleGroupUpdate(masterSecret, message, group, record);
|
||||
} else if (record == null && type == GroupContext.Type.UPDATE_VALUE) {
|
||||
handleGroupCreate(masterSecret, message, group);
|
||||
} else if (type == GroupContext.Type.QUIT_VALUE) {
|
||||
handleGroupLeave(masterSecret, message, group, record);
|
||||
} else if (type == GroupContext.Type.UNKNOWN_VALUE) {
|
||||
Log.w("GroupReceiver", "Received unknown type, ignoring...");
|
||||
}
|
||||
}
|
||||
|
||||
private void handleGroupCreate(MasterSecret masterSecret,
|
||||
IncomingPushMessage message,
|
||||
GroupContext group)
|
||||
{
|
||||
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
|
||||
byte[] id = group.getId().toByteArray();
|
||||
|
||||
database.create(id, message.getSource(), group.getName(), group.getMembersList(),
|
||||
group.getAvatar(), message.getRelay());
|
||||
|
||||
storeMessage(masterSecret, message, group);
|
||||
}
|
||||
|
||||
private void handleGroupUpdate(MasterSecret masterSecret,
|
||||
IncomingPushMessage message,
|
||||
GroupContext group,
|
||||
GroupRecord groupRecord)
|
||||
{
|
||||
|
||||
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
|
||||
byte[] id = group.getId().toByteArray();
|
||||
|
||||
Set<String> recordMembers = new HashSet<String>(groupRecord.getMembers());
|
||||
Set<String> messageMembers = new HashSet<String>(group.getMembersList());
|
||||
|
||||
Set<String> addedMembers = new HashSet<String>(messageMembers);
|
||||
addedMembers.removeAll(recordMembers);
|
||||
|
||||
Set<String> missingMembers = new HashSet<String>(recordMembers);
|
||||
missingMembers.removeAll(messageMembers);
|
||||
|
||||
if (addedMembers.size() > 0) {
|
||||
Set<String> unionMembers = new HashSet<String>(recordMembers);
|
||||
unionMembers.addAll(messageMembers);
|
||||
database.add(id, message.getSource(), new LinkedList<String>(unionMembers));
|
||||
|
||||
group = group.toBuilder().clearMembers().addAllMembers(addedMembers).build();
|
||||
} else {
|
||||
group = group.toBuilder().clearMembers().build();
|
||||
}
|
||||
|
||||
if (missingMembers.size() > 0) {
|
||||
// TODO We should tell added and missing about each-other.
|
||||
}
|
||||
|
||||
if (group.hasName() || group.hasAvatar()) {
|
||||
database.update(id, message.getSource(), group.getName(), group.getAvatar());
|
||||
}
|
||||
|
||||
if (group.hasName() && group.getName() != null && group.getName().equals(groupRecord.getTitle())) {
|
||||
group = group.toBuilder().clearName().build();
|
||||
}
|
||||
|
||||
storeMessage(masterSecret, message, group);
|
||||
}
|
||||
|
||||
private void handleGroupLeave(MasterSecret masterSecret,
|
||||
IncomingPushMessage message,
|
||||
GroupContext group,
|
||||
GroupRecord record)
|
||||
{
|
||||
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
|
||||
byte[] id = group.getId().toByteArray();
|
||||
List<String> members = record.getMembers();
|
||||
|
||||
if (members.contains(message.getSource())) {
|
||||
database.remove(id, message.getSource());
|
||||
|
||||
storeMessage(masterSecret, message, group);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void storeMessage(MasterSecret masterSecret, IncomingPushMessage message, GroupContext group) {
|
||||
if (group.hasAvatar()) {
|
||||
Intent intent = new Intent(context, SendReceiveService.class);
|
||||
intent.setAction(SendReceiveService.DOWNLOAD_AVATAR_ACTION);
|
||||
intent.putExtra("group_id", group.getId().toByteArray());
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
String body = Base64.encodeBytes(group.toByteArray());
|
||||
IncomingTextMessage incoming = new IncomingTextMessage(message, body, group);
|
||||
IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, group, body);
|
||||
|
||||
Pair<Long, Long> messageAndThreadId = smsDatabase.insertMessageInbox(masterSecret, groupMessage);
|
||||
smsDatabase.updateMessageBody(masterSecret, messageAndThreadId.first, body);
|
||||
|
||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
||||
}
|
||||
|
||||
}
|
@ -12,7 +12,6 @@ import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
|
||||
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;
|
||||
@ -22,7 +21,6 @@ import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingGroupMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
@ -38,11 +36,9 @@ import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent;
|
||||
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||
import org.whispersystems.textsecure.storage.Session;
|
||||
import org.whispersystems.textsecure.util.Base64;
|
||||
|
||||
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 {
|
||||
@ -51,10 +47,12 @@ public class PushReceiver {
|
||||
public static final int RESULT_NO_SESSION = 1;
|
||||
public static final int RESULT_DECRYPT_FAILED = 2;
|
||||
|
||||
private final Context context;
|
||||
private final Context context;
|
||||
private final GroupReceiver groupReceiver;
|
||||
|
||||
public PushReceiver(Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.context = context.getApplicationContext();
|
||||
this.groupReceiver = new GroupReceiver(context);
|
||||
}
|
||||
|
||||
public void process(MasterSecret masterSecret, Intent intent) {
|
||||
@ -160,7 +158,7 @@ public class PushReceiver {
|
||||
handleEndSessionMessage(masterSecret, message, messageContent);
|
||||
} else if (messageContent.hasGroup() && messageContent.getGroup().getType().getNumber() != Type.DELIVER_VALUE) {
|
||||
Log.w("PushReceiver", "Received push group message...");
|
||||
handleReceivedGroupMessage(masterSecret, message, messageContent, secure);
|
||||
groupReceiver.process(masterSecret, message, messageContent, secure);
|
||||
} else if (messageContent.getAttachmentsCount() > 0) {
|
||||
Log.w("PushReceiver", "Received push media message...");
|
||||
handleReceivedMediaMessage(masterSecret, message, messageContent, secure);
|
||||
@ -174,57 +172,6 @@ public class PushReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleReceivedGroupMessage(MasterSecret masterSecret,
|
||||
IncomingPushMessage message,
|
||||
PushMessageContent messageContent,
|
||||
boolean secure)
|
||||
{
|
||||
if (!messageContent.getGroup().hasId()) {
|
||||
Log.w("PushReceiver", "Received group message with no id!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!secure) {
|
||||
Log.w("PushReceiver", "Received insecure group push action!");
|
||||
return;
|
||||
}
|
||||
|
||||
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
|
||||
GroupContext group = messageContent.getGroup();
|
||||
byte[] id = group.getId().toByteArray();
|
||||
int type = group.getType().getNumber();
|
||||
|
||||
if (type == Type.CREATE_VALUE) {
|
||||
database.create(id, message.getSource(), group.getName(), group.getMembersList(), group.getAvatar(), message.getRelay());
|
||||
} else if (type == Type.ADD_VALUE) {
|
||||
database.add(id, message.getSource(), group.getMembersList());
|
||||
} else if (type == Type.QUIT_VALUE) {
|
||||
database.remove(id, message.getSource());
|
||||
} else if (type == Type.MODIFY_VALUE) {
|
||||
database.update(id, message.getSource(), group.getName(), group.getAvatar());
|
||||
} else if (type == Type.UNKNOWN_VALUE) {
|
||||
Log.w("PushReceiver", "Receied group message from unknown type: " + type);
|
||||
return;
|
||||
}
|
||||
|
||||
if (group.hasAvatar()) {
|
||||
Intent intent = new Intent(context, SendReceiveService.class);
|
||||
intent.setAction(SendReceiveService.DOWNLOAD_AVATAR_ACTION);
|
||||
intent.putExtra("group_id", group.getId().toByteArray());
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
String body = Base64.encodeBytes(group.toByteArray());
|
||||
IncomingTextMessage incoming = new IncomingTextMessage(message, body, group);
|
||||
IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, group, body);
|
||||
|
||||
Pair<Long, Long> messageAndThreadId = smsDatabase.insertMessageInbox(masterSecret, groupMessage);
|
||||
smsDatabase.updateMessageBody(masterSecret, messageAndThreadId.first, body);
|
||||
|
||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
||||
}
|
||||
|
||||
private void handleEndSessionMessage(MasterSecret masterSecret,
|
||||
IncomingPushMessage message,
|
||||
PushMessageContent messageContent)
|
||||
|
@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.sms;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.whispersystems.textsecure.push.PushMessageProtos;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@ -28,20 +27,14 @@ public class IncomingGroupMessage extends IncomingTextMessage {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isAdd() {
|
||||
return
|
||||
groupContext.getType().getNumber() == GroupContext.Type.ADD_VALUE ||
|
||||
groupContext.getType().getNumber() == GroupContext.Type.CREATE_VALUE;
|
||||
public boolean isUpdate() {
|
||||
return groupContext.getType().getNumber() == GroupContext.Type.UPDATE_VALUE;
|
||||
}
|
||||
|
||||
public boolean isQuit() {
|
||||
return groupContext.getType().getNumber() == GroupContext.Type.QUIT_VALUE;
|
||||
}
|
||||
|
||||
public boolean isModify() {
|
||||
return groupContext.getType().getNumber() == GroupContext.Type.MODIFY_VALUE;
|
||||
}
|
||||
|
||||
public static IncomingGroupMessage createForQuit(String groupId, String user) throws IOException {
|
||||
IncomingTextMessage base = new IncomingTextMessage(user, groupId);
|
||||
GroupContext context = GroupContext.newBuilder()
|
||||
|
@ -250,8 +250,7 @@ public class PushTransport extends BaseTransport {
|
||||
groupBuilder.setId(ByteString.copyFrom(groupId));
|
||||
groupBuilder.setType(GroupContext.Type.DELIVER);
|
||||
|
||||
if (MmsSmsColumns.Types.isGroupAdd(message.getDatabaseMessageBox()) ||
|
||||
MmsSmsColumns.Types.isGroupModify(message.getDatabaseMessageBox()) ||
|
||||
if (MmsSmsColumns.Types.isGroupUpdate(message.getDatabaseMessageBox()) ||
|
||||
MmsSmsColumns.Types.isGroupQuit(message.getDatabaseMessageBox()))
|
||||
{
|
||||
if (messageBody != null && messageBody.trim().length() > 0) {
|
||||
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.util;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.whispersystems.textsecure.util.Base64;
|
||||
import org.whispersystems.textsecure.util.Hex;
|
||||
@ -33,34 +34,32 @@ public class GroupUtil {
|
||||
return groupId.startsWith(ENCODED_GROUP_PREFIX);
|
||||
}
|
||||
|
||||
public static boolean isMetaGroupAction(int groupAction) {
|
||||
return groupAction > 0
|
||||
&& groupAction != GroupContext.Type.DELIVER_VALUE;
|
||||
}
|
||||
|
||||
public static String serializeArguments(byte[] id, String name, List<String> members) {
|
||||
return Base64.encodeBytes(GroupContext.newBuilder()
|
||||
.setId(ByteString.copyFrom(id))
|
||||
.setName(name)
|
||||
.addAllMembers(members)
|
||||
.build().toByteArray());
|
||||
}
|
||||
|
||||
public static String serializeArguments(GroupContext context) {
|
||||
return Base64.encodeBytes(context.toByteArray());
|
||||
}
|
||||
|
||||
public static List<String> getSerializedArgumentMembers(String serialized) {
|
||||
if (serialized == null) {
|
||||
return new LinkedList<String>();
|
||||
public static String getDescription(String encodedGroup) {
|
||||
if (encodedGroup == null) {
|
||||
return "Group updated.";
|
||||
}
|
||||
|
||||
try {
|
||||
GroupContext context = GroupContext.parseFrom(Base64.decode(serialized));
|
||||
return context.getMembersList();
|
||||
String description = "";
|
||||
GroupContext context = GroupContext.parseFrom(Base64.decode(encodedGroup));
|
||||
List<String> members = context.getMembersList();
|
||||
String title = context.getName();
|
||||
|
||||
if (!members.isEmpty()) {
|
||||
description += org.whispersystems.textsecure.util.Util.join(members, ", ") + " joined the group.";
|
||||
}
|
||||
|
||||
if (title != null && !title.trim().isEmpty()) {
|
||||
description += " Updated title to '" + title + "'.";
|
||||
}
|
||||
|
||||
return description;
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
Log.w("GroupUtil", e);
|
||||
return "Group updated.";
|
||||
} catch (IOException e) {
|
||||
Log.w("GroupUtil", e);
|
||||
return new LinkedList<String>();
|
||||
return "Group updated.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user