Display group actions and correctly handle group delivery.

This commit is contained in:
Moxie Marlinspike 2014-02-14 15:59:57 -08:00
parent 7c46f3cbf8
commit 067799be06
26 changed files with 304 additions and 160 deletions

View File

@ -833,11 +833,12 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
}
private boolean isSingleConversation() {
return getRecipients() != null && getRecipients().isSingleRecipient();
return getRecipients() != null && getRecipients().isSingleRecipient() && !getRecipients().isGroupRecipient();
}
private boolean isGroupConversation() {
return getRecipients() != null && !getRecipients().isSingleRecipient();
return getRecipients() != null &&
(!getRecipients().isSingleRecipient() || getRecipients().isGroupRecipient());
}
private Recipients getRecipients() {

View File

@ -31,6 +31,7 @@ import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
@ -60,6 +61,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.SendReceiveService;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.Emoji;
import org.whispersystems.textsecure.push.PushMessageProtos;
import org.whispersystems.textsecure.util.FutureTaskListener;
import org.whispersystems.textsecure.util.ListenableFutureTask;
@ -69,6 +71,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext;
/**
* A view that displays an individual conversation item within a conversation
* thread. Used by ComposeMessageActivity's ListActivity via a ConversationAdapter.
@ -175,8 +179,25 @@ public class ConversationItem extends LinearLayout {
/// MessageRecord Attribute Parsers
private void setBodyText(MessageRecord messageRecord) {
bodyText.setText(Emoji.getInstance(context).emojify(messageRecord.getDisplayBody(), Emoji.EMOJI_LARGE),
TextView.BufferType.SPANNABLE);
switch (messageRecord.getGroupAction()) {
case GroupContext.Type.QUIT_VALUE:
bodyText.setText(messageRecord.getIndividualRecipient().toShortString() + " has left the group.");
return;
case GroupContext.Type.ADD_VALUE:
case GroupContext.Type.CREATE_VALUE:
bodyText.setText(messageRecord.getGroupActionArguments() + " have joined the group.");
return;
case GroupContext.Type.MODIFY_VALUE:
bodyText.setText(messageRecord.getIndividualRecipient() + " has updated the group.");
return;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
bodyText.setText(Emoji.getInstance(context).emojify(messageRecord.getDisplayBody(), Emoji.EMOJI_LARGE),
TextView.BufferType.SPANNABLE);
} else {
bodyText.setText(messageRecord.getDisplayBody());
}
}
private void setContactPhoto(MessageRecord messageRecord) {

View File

@ -21,6 +21,7 @@ import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.provider.Contacts.Intents;
import android.provider.ContactsContract.QuickContact;
@ -102,9 +103,14 @@ public class ConversationListItem extends RelativeLayout
this.recipients.addListener(this);
this.fromView.setText(formatFrom(recipients, count, read));
this.subjectView.setText(Emoji.getInstance(context).emojify(thread.getDisplayBody(),
Emoji.EMOJI_SMALL),
TextView.BufferType.SPANNABLE);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
this.subjectView.setText(Emoji.getInstance(context).emojify(thread.getDisplayBody(),
Emoji.EMOJI_SMALL),
TextView.BufferType.SPANNABLE);
} else {
this.subjectView.setText(thread.getDisplayBody());
}
if (thread.getDate() > 0)
this.dateView.setText(DateUtils.getBetterRelativeTimeSpanString(getContext(), thread.getDate()));

View File

@ -30,6 +30,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.transport.PushTransport;
import org.thoughtcrime.securesms.util.ActionBarUtil;
import org.thoughtcrime.securesms.util.DynamicLanguage;
@ -366,6 +367,8 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv
byte[] groupId = groupDatabase.allocateGroupId();
AttachmentPointer avatarPointer = null;
memberE164Numbers.add(TextSecurePreferences.getLocalNumber(this));
GroupContext.Builder builder = GroupContext.newBuilder()
.setId(ByteString.copyFrom(groupId))
.setType(GroupContext.Type.CREATE)
@ -389,9 +392,23 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv
groupDatabase.updateAvatar(groupId, avatar);
}
long threadId = threadDatabase.getThreadIdForGroup(GroupUtil.getEncodedId(groupId));
try {
String groupRecipientId = GroupUtil.getEncodedId(groupId);
Recipient groupRecipient = RecipientFactory.getRecipientsFromString(this, groupRecipientId, false).getPrimaryRecipient();
OutgoingTextMessage outgoing = new OutgoingTextMessage(groupRecipient, GroupContext.Type.ADD_VALUE, org.whispersystems.textsecure.util.Util.join(memberE164Numbers, ","));
long threadId = threadDatabase.getThreadIdFor(new Recipients(groupRecipient));
List<Long> messageIds = DatabaseFactory.getEncryptingSmsDatabase(this)
.insertMessageOutbox(masterSecret, threadId, outgoing);
return new Pair<Long, List<Recipient>>(threadId, failures);
for (long messageId : messageIds) {
DatabaseFactory.getEncryptingSmsDatabase(this).markAsSent(messageId);
}
return new Pair<Long, List<Recipient>>(threadId, failures);
} catch (RecipientFormattingException e) {
throw new AssertionError(e);
}
}
private long handleCreateMmsGroup(Set<Recipient> members) {

View File

@ -647,6 +647,10 @@ public class DatabaseFactory {
db.execSQL("ALTER TABLE push ADD COLUMN device_id INTEGER DEFAULT 1;");
db.execSQL("ALTER TABLE sms ADD COLUMN address_device_id INTEGER DEFAULT 1;");
db.execSQL("ALTER TABLE mms ADD COLUMN address_device_id INTEGER DEFAULT 1;");
db.execSQL("ALTER TABLE sms ADD COLUMN group_action INTEGER DEFAULT -1;");
db.execSQL("ALTER TABLE mms ADD COLUMN group_action_arguments TEXT;");
db.execSQL("ALTER TABLE thread ADD COLUMN group_action INTEGER DEFAULT -1;");
db.execSQL("ALTER TABLE thread ADD COLUMN group_action_arguments TEXT;");
}
db.setTransactionSuccessful();

View File

@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.util.Hex;
import org.whispersystems.textsecure.util.Util;
@ -90,11 +91,20 @@ public class GroupDatabase extends Database {
List<String> members, AttachmentPointer avatar,
String relay)
{
List<String> filteredMembers = new LinkedList<String>();
String localNumber = TextSecurePreferences.getLocalNumber(context);
for (String member : members) {
if (!member.equals(localNumber)) {
filteredMembers.add(member);
}
}
ContentValues contentValues = new ContentValues();
contentValues.put(GROUP_ID, GroupUtil.getEncodedId(groupId));
contentValues.put(OWNER, owner);
contentValues.put(TITLE, title);
contentValues.put(MEMBERS, Util.join(members, ","));
contentValues.put(MEMBERS, Util.join(filteredMembers, ","));
if (avatar != null) {
contentValues.put(AVATAR_ID, avatar.getId());
@ -147,7 +157,7 @@ public class GroupDatabase extends Database {
contents.put(MEMBERS, Util.join(concatenatedMembers, ","));
databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?",
new String[] {Hex.toString(id)});
new String[] {GroupUtil.getEncodedId(id)});
}
}
}
@ -160,7 +170,7 @@ public class GroupDatabase extends Database {
contents.put(MEMBERS, Util.join(currentMembers, ","));
databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?",
new String[]{Hex.toString(id)});
new String[]{GroupUtil.getEncodedId(id)});
}
private List<String> getCurrentMembers(byte[] id) {
@ -168,7 +178,8 @@ public class GroupDatabase extends Database {
try {
cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[] {MEMBERS},
GROUP_ID + " = ?", new String[] {Hex.toString(id)},
GROUP_ID + " = ?",
new String[] {GroupUtil.getEncodedId(id)},
null, null, null);
if (cursor != null && cursor.moveToFirst()) {

View File

@ -116,7 +116,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
STATUS + " INTEGER, " + TRANSACTION_ID + " TEXT, " + RETRIEVE_STATUS + " INTEGER, " +
RETRIEVE_TEXT + " TEXT, " + RETRIEVE_TEXT_CS + " INTEGER, " + READ_STATUS + " INTEGER, " +
CONTENT_CLASS + " INTEGER, " + RESPONSE_TEXT + " TEXT, " + DELIVERY_TIME + " INTEGER, " +
DELIVERY_REPORT + " INTEGER);";
DELIVERY_REPORT + " INTEGER, " + GROUP_ACTION + " INTEGER DEFAULT -1, " + GROUP_ACTION_ARGUMENTS + " TEXT);";
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS mms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
@ -181,7 +181,8 @@ 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());
Recipients groupRecipients = RecipientFactory.getRecipientsFromString(context, retrieved.getGroupId(), true);
return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipients);
}
try {
@ -399,6 +400,8 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
contentValues.put(STATUS, Status.DOWNLOAD_INITIALIZED);
contentValues.put(DATE_RECEIVED, System.currentTimeMillis() / 1000);
contentValues.put(READ, unread ? 0 : 1);
contentValues.put(GROUP_ACTION, retrieved.getGroupAction());
contentValues.put(GROUP_ACTION_ARGUMENTS, retrieved.getGroupActionArguments());
if (!contentValues.containsKey(DATE_SENT)) {
contentValues.put(DATE_SENT, contentValues.getAsLong(DATE_RECEIVED));
@ -797,6 +800,8 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
long messageSize = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_SIZE));
long expiry = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRY));
int status = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.STATUS));
int groupAction = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.GROUP_ACTION));
String groupActionArgs = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.GROUP_ACTION_ARGUMENTS));
byte[]contentLocationBytes = null;
byte[]transactionIdBytes = null;
@ -811,7 +816,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
return new NotificationMmsMessageRecord(context, id, recipients, recipients.getPrimaryRecipient(),
addressDeviceId, dateSent, dateReceived, threadId,
contentLocationBytes, messageSize, expiry, status,
transactionIdBytes, mailbox);
transactionIdBytes, mailbox, groupAction, groupActionArgs);
}
private MediaMmsMessageRecord getMediaMmsMessageRecord(Cursor cursor) {
@ -822,6 +827,8 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.THREAD_ID));
String address = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS));
int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS_DEVICE_ID));
int groupAction = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.GROUP_ACTION));
String groupActionArgs = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.GROUP_ACTION_ARGUMENTS));
DisplayRecord.Body body = getBody(cursor);
int partCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.PART_COUNT));
Recipients recipients = getRecipientsFor(address);
@ -830,7 +837,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
return new MediaMmsMessageRecord(context, id, recipients, recipients.getPrimaryRecipient(),
addressDeviceId, dateSent, dateReceived, threadId, body,
slideDeck, partCount, box);
slideDeck, partCount, box, groupAction, groupActionArgs);
}
private Recipients getRecipientsFor(String address) {

View File

@ -10,7 +10,8 @@ public interface MmsSmsColumns {
public static final String BODY = "body";
public static final String ADDRESS = "address";
public static final String ADDRESS_DEVICE_ID = "address_device_id";
public static final String GROUP_ACTION = "group_action";
public static final String GROUP_ACTION_ARGUMENTS = "group_action_arguments";
public static class Types {
protected static final long TOTAL_MASK = 0xFFFFFFFF;

View File

@ -49,7 +49,8 @@ public class MmsSmsDatabase extends Database {
SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
MmsDatabase.STATUS, TRANSPORT};
MmsDatabase.STATUS, MmsSmsColumns.GROUP_ACTION,
MmsSmsColumns.GROUP_ACTION_ARGUMENTS, TRANSPORT};
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
@ -71,7 +72,8 @@ public class MmsSmsDatabase extends Database {
SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
MmsDatabase.STATUS, TRANSPORT};
MmsDatabase.STATUS, MmsSmsColumns.GROUP_ACTION,
MmsSmsColumns.GROUP_ACTION_ARGUMENTS, TRANSPORT};
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
@ -89,7 +91,8 @@ public class MmsSmsDatabase extends Database {
MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
MmsDatabase.STATUS, TRANSPORT};
MmsDatabase.STATUS, MmsSmsColumns.GROUP_ACTION,
MmsSmsColumns.GROUP_ACTION_ARGUMENTS, TRANSPORT};
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
String selection = MmsSmsColumns.READ + " = 0";
@ -112,6 +115,7 @@ public class MmsSmsDatabase extends Database {
MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
MmsSmsColumns.GROUP_ACTION, MmsSmsColumns.GROUP_ACTION_ARGUMENTS,
TRANSPORT};
String[] smsProjection = {SmsDatabase.DATE_SENT + " * 1 AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
@ -121,6 +125,7 @@ public class MmsSmsDatabase extends Database {
MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
MmsSmsColumns.GROUP_ACTION, MmsSmsColumns.GROUP_ACTION_ARGUMENTS,
TRANSPORT};
@ -149,6 +154,8 @@ public class MmsSmsDatabase extends Database {
mmsColumnsPresent.add(MmsDatabase.TRANSACTION_ID);
mmsColumnsPresent.add(MmsDatabase.MESSAGE_SIZE);
mmsColumnsPresent.add(MmsDatabase.EXPIRY);
mmsColumnsPresent.add(MmsSmsColumns.GROUP_ACTION);
mmsColumnsPresent.add(MmsSmsColumns.GROUP_ACTION_ARGUMENTS);
mmsColumnsPresent.add(MmsDatabase.STATUS);
Set<String> smsColumnsPresent = new HashSet<String>();
@ -162,6 +169,8 @@ public class MmsSmsDatabase extends Database {
smsColumnsPresent.add(SmsDatabase.SUBJECT);
smsColumnsPresent.add(SmsDatabase.DATE_SENT);
smsColumnsPresent.add(SmsDatabase.DATE_RECEIVED);
smsColumnsPresent.add(MmsSmsColumns.GROUP_ACTION);
smsColumnsPresent.add(MmsSmsColumns.GROUP_ACTION_ARGUMENTS);
smsColumnsPresent.add(SmsDatabase.STATUS);
String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(TRANSPORT, mmsProjection, mmsColumnsPresent, 2, MMS_TRANSPORT, selection, null, null, null);

View File

@ -26,7 +26,6 @@ import android.telephony.PhoneNumberUtils;
import android.util.Log;
import android.util.Pair;
import org.thoughtcrime.securesms.contacts.ContactPhotoFactory;
import org.thoughtcrime.securesms.database.model.DisplayRecord;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.recipients.Recipient;
@ -66,7 +65,8 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
THREAD_ID + " INTEGER, " + ADDRESS + " TEXT, " + ADDRESS_DEVICE_ID + " INTEGER DEFAULT 1, " + PERSON + " INTEGER, " +
DATE_RECEIVED + " INTEGER, " + DATE_SENT + " INTEGER, " + PROTOCOL + " INTEGER, " + READ + " INTEGER DEFAULT 0, " +
STATUS + " INTEGER DEFAULT -1," + TYPE + " INTEGER, " + REPLY_PATH_PRESENT + " INTEGER, " +
SUBJECT + " TEXT, " + BODY + " TEXT, " + SERVICE_CENTER + " TEXT);";
SUBJECT + " TEXT, " + BODY + " TEXT, " + SERVICE_CENTER + " TEXT, " +
GROUP_ACTION + " INTEGER DEFAULT -1, " + GROUP_ACTION_ARGUMENTS + " TEXT);";
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS sms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
@ -80,7 +80,7 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
DATE_RECEIVED + " AS " + NORMALIZED_DATE_RECEIVED,
DATE_SENT + " AS " + NORMALIZED_DATE_SENT,
PROTOCOL, READ, STATUS, TYPE,
REPLY_PATH_PRESENT, SUBJECT, BODY, SERVICE_CENTER
REPLY_PATH_PRESENT, SUBJECT, BODY, SERVICE_CENTER, GROUP_ACTION, GROUP_ACTION_ARGUMENTS
};
public SmsDatabase(Context context, SQLiteOpenHelper databaseHelper) {
@ -303,6 +303,8 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
values.put(BODY, message.getMessageBody());
values.put(TYPE, type);
values.put(THREAD_ID, threadId);
values.put(GROUP_ACTION, message.getGroupAction());
values.put(GROUP_ACTION_ARGUMENTS, message.getGroupActionArgument());
SQLiteDatabase db = databaseHelper.getWritableDatabase();
long messageId = db.insert(TABLE_NAME, null, values);
@ -338,6 +340,8 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
contentValues.put(DATE_SENT, date);
contentValues.put(READ, 1);
contentValues.put(TYPE, type);
contentValues.put(GROUP_ACTION, message.getGroupAction());
contentValues.put(GROUP_ACTION_ARGUMENTS, message.getGroupActionArguments());
SQLiteDatabase db = databaseHelper.getWritableDatabase();
messageIds.add(db.insert(TABLE_NAME, ADDRESS, contentValues));
@ -494,6 +498,8 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
long dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.NORMALIZED_DATE_SENT));
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.THREAD_ID));
int status = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.STATUS));
int groupAction = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.GROUP_ACTION));
String groupActionArgs = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.GROUP_ACTION_ARGUMENTS));
Recipients recipients = getRecipientsFor(address);
DisplayRecord.Body body = getBody(cursor);
@ -501,7 +507,7 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
recipients.getPrimaryRecipient(),
addressDeviceId,
dateSent, dateReceived, type,
threadId, status);
threadId, status, groupAction, groupActionArgs);
}
private Recipients getRecipientsFor(String address) {

View File

@ -53,13 +53,15 @@ public class ThreadDatabase extends Database {
private static final String ERROR = "error";
private static final String HAS_ATTACHMENT = "has_attachment";
public static final String SNIPPET_TYPE = "snippet_type";
private static final String GROUP_ACTION = "group_action";
private static final String GROUP_ACTION_ARG = "group_action_arguments";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
DATE + " INTEGER DEFAULT 0, " + MESSAGE_COUNT + " INTEGER DEFAULT 0, " +
RECIPIENT_IDS + " TEXT, " + SNIPPET + " TEXT, " + SNIPPET_CHARSET + " INTEGER DEFAULT 0, " +
READ + " INTEGER DEFAULT 1, " + TYPE + " INTEGER DEFAULT 0, " + ERROR + " INTEGER DEFAULT 0, " +
SNIPPET_TYPE + " INTEGER DEFAULT 0, " +
HAS_ATTACHMENT + " INTEGER DEFAULT 0);";
SNIPPET_TYPE + " INTEGER DEFAULT 0, " + GROUP_ACTION + " INTEGER DEFAULT -1, " +
GROUP_ACTION_ARG + " TEXT, " + HAS_ATTACHMENT + " INTEGER DEFAULT 0);";
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + RECIPIENT_IDS + ");",
@ -99,17 +101,6 @@ 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();
@ -126,12 +117,16 @@ public class ThreadDatabase extends Database {
return db.insert(TABLE_NAME, null, contentValues);
}
private void updateThread(long threadId, long count, String body, long date, long type) {
private void updateThread(long threadId, long count, String body, long date, long type,
int groupAction, String groupActionArguments)
{
ContentValues contentValues = new ContentValues(3);
contentValues.put(DATE, date - date % 1000);
contentValues.put(MESSAGE_COUNT, count);
contentValues.put(SNIPPET, body);
contentValues.put(SNIPPET_TYPE, type);
contentValues.put(GROUP_ACTION, groupAction);
contentValues.put(GROUP_ACTION_ARG, groupActionArguments);
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.update(TABLE_NAME, contentValues, ID + " = ?", new String[] {threadId + ""});
@ -358,26 +353,6 @@ 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;
@ -414,7 +389,8 @@ public class ThreadDatabase extends Database {
MessageRecord record = null;
if (reader != null && (record = reader.getNext()) != null) {
updateThread(threadId, count, record.getBody().getBody(), record.getDateReceived(), record.getType());
updateThread(threadId, count, record.getBody().getBody(), record.getDateReceived(),
record.getType(), record.getGroupAction(), record.getGroupActionArguments());
} else {
deleteThread(threadId);
}
@ -470,9 +446,12 @@ public class ThreadDatabase extends Database {
long read = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.READ));
long type = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_TYPE));
int distributionType = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.TYPE));
int groupAction = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.GROUP_ACTION));
String groupActionArg = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.GROUP_ACTION_ARG));
return new ThreadRecord(context, body, recipients, date, count,
read == 1, threadId, type, distributionType);
read == 1, threadId, type, distributionType,
groupAction, groupActionArg);
}
private DisplayRecord.Body getPlaintextBody(Cursor cursor) {

View File

@ -36,22 +36,26 @@ public abstract class DisplayRecord {
protected final long type;
private final Recipients recipients;
private final long dateSent;
private final long dateReceived;
private final long threadId;
private final Body body;
// private final String body;
private final long dateSent;
private final long dateReceived;
private final long threadId;
private final Body body;
private final int groupAction;
private final String groupActionArguments;
public DisplayRecord(Context context, Body body, Recipients recipients, long dateSent,
long dateReceived, long threadId, long type)
long dateReceived, long threadId, long type, int groupAction,
String groupActionArguments)
{
this.context = context.getApplicationContext();
this.threadId = threadId;
this.recipients = recipients;
this.dateSent = dateSent;
this.dateReceived = dateReceived;
this.type = type;
this.body = body;
this.context = context.getApplicationContext();
this.threadId = threadId;
this.recipients = recipients;
this.dateSent = dateSent;
this.dateReceived = dateReceived;
this.type = type;
this.body = body;
this.groupAction = groupAction;
this.groupActionArguments = groupActionArguments;
}
public Body getBody() {
@ -80,6 +84,14 @@ public abstract class DisplayRecord {
return SmsDatabase.Types.isKeyExchangeType(type);
}
public int getGroupAction() {
return groupAction;
}
public String getGroupActionArguments() {
return groupActionArguments;
}
public static class Body {
private final String body;
private final boolean plaintext;

View File

@ -44,10 +44,12 @@ public class MediaMmsMessageRecord extends MessageRecord {
Recipient individualRecipient, int recipientDeviceId,
long dateSent, long dateReceived, long threadId, Body body,
ListenableFutureTask<SlideDeck> slideDeck,
int partCount, long mailbox)
int partCount, long mailbox, int groupAction,
String groupActionArguments)
{
super(context, id, body, recipients, individualRecipient, recipientDeviceId,
dateSent, dateReceived, threadId, DELIVERY_STATUS_NONE, mailbox);
dateSent, dateReceived, threadId, DELIVERY_STATUS_NONE, mailbox,
groupAction, groupActionArguments);
this.context = context.getApplicationContext();
this.partCount = partCount;

View File

@ -53,9 +53,9 @@ public abstract class MessageRecord extends DisplayRecord {
Recipient individualRecipient, int recipientDeviceId,
long dateSent, long dateReceived,
long threadId, int deliveryStatus,
long type)
long type, int groupAction, String groupActionArguments)
{
super(context, body, recipients, dateSent, dateReceived, threadId, type);
super(context, body, recipients, dateSent, dateReceived, threadId, type, groupAction, groupActionArguments);
this.id = id;
this.individualRecipient = individualRecipient;
this.recipientDeviceId = recipientDeviceId;

View File

@ -44,10 +44,11 @@ public class NotificationMmsMessageRecord extends MessageRecord {
Recipient individualRecipient, int recipientDeviceId,
long dateSent, long dateReceived, long threadId,
byte[] contentLocation, long messageSize, long expiry,
int status, byte[] transactionId, long mailbox)
int status, byte[] transactionId, long mailbox,
int groupAction, String groupActionArguments)
{
super(context, id, new Body("", true), recipients, individualRecipient, recipientDeviceId,
dateSent, dateReceived, threadId, DELIVERY_STATUS_NONE, mailbox);
dateSent, dateReceived, threadId, DELIVERY_STATUS_NONE, mailbox, groupAction, groupActionArguments);
this.contentLocation = contentLocation;
this.messageSize = messageSize;

View File

@ -41,10 +41,12 @@ public class SmsMessageRecord extends MessageRecord {
int recipientDeviceId,
long dateSent, long dateReceived,
long type, long threadId,
int status)
int status, int groupAction,
String groupActionArguments)
{
super(context, id, body, recipients, individualRecipient, recipientDeviceId,
dateSent, dateReceived, threadId, getGenericDeliveryStatus(status), type);
dateSent, dateReceived, threadId, getGenericDeliveryStatus(status), type,
groupAction, groupActionArguments);
}
public long getType() {

View File

@ -24,8 +24,11 @@ import android.text.style.StyleSpan;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.whispersystems.textsecure.push.PushMessageProtos;
import org.whispersystems.textsecure.util.Util;
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext;
/**
* The message record model which represents thread heading messages.
*
@ -41,9 +44,9 @@ public class ThreadRecord extends DisplayRecord {
public ThreadRecord(Context context, Body body, Recipients recipients, long date,
long count, boolean read, long threadId, long snippetType,
int distributionType)
int distributionType, int groupAction, String groupActionArg)
{
super(context, body, recipients, date, date, threadId, snippetType);
super(context, body, recipients, date, date, threadId, snippetType, groupAction, groupActionArg);
this.context = context.getApplicationContext();
this.count = count;
this.read = read;
@ -54,6 +57,14 @@ public class ThreadRecord extends DisplayRecord {
public SpannableString getDisplayBody() {
if (SmsDatabase.Types.isDecryptInProgressType(type)) {
return emphasisAdded(context.getString(R.string.MessageDisplayHelper_decrypting_please_wait));
} else if (getGroupAction() == GroupContext.Type.ADD_VALUE ||
getGroupAction() == GroupContext.Type.CREATE_VALUE)
{
return emphasisAdded("Added " + getGroupActionArguments());
} else if (getGroupAction() == GroupContext.Type.QUIT_VALUE) {
return emphasisAdded(getRecipients().toShortString() + " left the group.");
} else if (getGroupAction() == GroupContext.Type.MODIFY_VALUE) {
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)) {

View File

@ -20,11 +20,15 @@ public class IncomingMediaMessage {
private final PduHeaders headers;
private final PduBody body;
private final String groupId;
private final int groupAction;
private final String groupActionArguments;
public IncomingMediaMessage(RetrieveConf retreived) {
this.headers = retreived.getPduHeaders();
this.body = retreived.getBody();
this.groupId = null;
this.headers = retreived.getPduHeaders();
this.body = retreived.getBody();
this.groupId = null;
this.groupAction = -1;
this.groupActionArguments = null;
}
public IncomingMediaMessage(MasterSecret masterSecret, String localNumber,
@ -35,9 +39,13 @@ public class IncomingMediaMessage {
this.body = new PduBody();
if (messageContent.hasGroup()) {
this.groupId = GroupUtil.getEncodedId(messageContent.getGroup().getId().toByteArray());
this.groupId = GroupUtil.getEncodedId(messageContent.getGroup().getId().toByteArray());
this.groupAction = messageContent.getGroup().getType().getNumber();
this.groupActionArguments = GroupUtil.getActionArgument(messageContent.getGroup());
} else {
this.groupId = null;
this.groupId = null;
this.groupAction = -1;
this.groupActionArguments = null;
}
this.headers.setEncodedStringValue(new EncodedStringValue(message.getSource()), PduHeaders.FROM);
@ -90,4 +98,11 @@ public class IncomingMediaMessage {
headers.getEncodedStringValues(PduHeaders.TO).length > 1);
}
public int getGroupAction() {
return groupAction;
}
public String getGroupActionArguments() {
return groupActionArguments;
}
}

View File

@ -1,57 +0,0 @@
package org.thoughtcrime.securesms.push;
import org.thoughtcrime.securesms.recipients.Recipient;
import java.util.Set;
public class GroupActionRecord {
private static final int CREATE_GROUP_TYPE = 1;
private static final int ADD_USERS_TYPE = 2;
private static final int LEAVE_GROUP_TYPE = 3;
private final int type;
private final Set<Recipient> recipients;
private final byte[] groupId;
private final String groupName;
private final byte[] avatar;
public GroupActionRecord(int type, byte[] groupId, String groupName,
byte[] avatar, Set<Recipient> recipients)
{
this.type = type;
this.groupId = groupId;
this.groupName = groupName;
this.avatar = avatar;
this.recipients = recipients;
}
public boolean isCreateAction() {
return type== CREATE_GROUP_TYPE;
}
public boolean isAddUsersAction() {
return type == ADD_USERS_TYPE;
}
public boolean isLeaveAction() {
return type == LEAVE_GROUP_TYPE;
}
public Set<Recipient> getRecipients() {
return recipients;
}
public byte[] getGroupId() {
return groupId;
}
public String getGroupName() {
return groupName;
}
public byte[] getAvatar() {
return avatar;
}
}

View File

@ -108,6 +108,10 @@ public class Recipient implements Parcelable, CanonicalRecipient {
return this.contactUri;
}
public synchronized void setContactPhoto(Bitmap bitmap) {
this.contactPhoto = bitmap;
}
public synchronized String getName() {
return this.name;
}
@ -149,6 +153,18 @@ public class Recipient implements Parcelable, CanonicalRecipient {
listeners.remove(listener);
}
public void notifyListeners() {
HashSet<RecipientModifiedListener> localListeners;
synchronized (this) {
localListeners = (HashSet<RecipientModifiedListener>)listeners.clone();
}
for (RecipientModifiedListener listener : localListeners) {
listener.onModified(this);
}
}
public synchronized void writeToParcel(Parcel dest, int flags) {
dest.writeString(number);
dest.writeString(name);

View File

@ -18,6 +18,7 @@ package org.thoughtcrime.securesms.recipients;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import android.util.Patterns;
import org.thoughtcrime.securesms.recipients.Recipient.RecipientModifiedListener;
@ -145,7 +146,10 @@ public class Recipients implements Parcelable {
while (iterator.hasNext()) {
String number = iterator.next().getNumber();
if (scrub && number != null && !Patterns.EMAIL_ADDRESS.matcher(number).matches()) {
if (scrub && number != null &&
!Patterns.EMAIL_ADDRESS.matcher(number).matches() &&
!GroupUtil.isEncodedGroup(number))
{
number = number.replaceAll("[^0-9+]", "");
}

View File

@ -9,8 +9,12 @@ 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.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.util.BitmapDecodingException;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.whispersystems.textsecure.crypto.AttachmentCipherInputStream;
import org.whispersystems.textsecure.crypto.InvalidMessageException;
import org.whispersystems.textsecure.crypto.MasterSecret;
@ -55,7 +59,16 @@ public class AvatarDownloader {
database.updateAvatar(groupId, avatar);
avatar.recycle();
try {
Recipient groupRecipient = RecipientFactory.getRecipientsFromString(context, GroupUtil.getEncodedId(groupId), true)
.getPrimaryRecipient();
groupRecipient.setContactPhoto(avatar);
groupRecipient.notifyListeners();
} catch (RecipientFormattingException e) {
Log.w("AvatarDownloader", e);
}
// avatar.recycle();
attachment.delete();
}
} catch (IOException e) {

View File

@ -24,7 +24,6 @@ import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.InvalidMessageException;
@ -35,7 +34,6 @@ import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent;
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.util.Hex;
import ws.com.google.android.mms.MmsException;
@ -220,7 +218,7 @@ public class PushReceiver {
if (messageContent.getAttachmentsCount() > 0) {
handleReceivedMediaMessage(masterSecret, message, messageContent, secure);
} else if (messageContent.hasBody()) {
} else {
handleReceivedTextMessage(masterSecret, message, messageContent, secure);
}
}
@ -263,8 +261,7 @@ public class PushReceiver {
boolean secure)
{
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
String groupId = messageContent.hasGroup() ? GroupUtil.getEncodedId(messageContent.getGroup().getId().toByteArray()) : null;
IncomingTextMessage textMessage = new IncomingTextMessage(message, "", groupId);
IncomingTextMessage textMessage = new IncomingTextMessage(message, "", messageContent.getGroup());
if (secure) {
textMessage = new IncomingEncryptedMessage(textMessage, "");

View File

@ -4,11 +4,14 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.SmsMessage;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.storage.RecipientDevice;
import java.util.List;
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext;
public class IncomingTextMessage implements Parcelable {
public static final Parcelable.Creator<IncomingTextMessage> CREATOR = new Parcelable.Creator<IncomingTextMessage>() {
@ -32,6 +35,8 @@ public class IncomingTextMessage implements Parcelable {
private final String pseudoSubject;
private final long sentTimestampMillis;
private final String groupId;
private final int groupAction;
private final String groupActionArgument;
public IncomingTextMessage(SmsMessage message) {
this.message = message.getDisplayMessageBody();
@ -43,9 +48,11 @@ public class IncomingTextMessage implements Parcelable {
this.pseudoSubject = message.getPseudoSubject();
this.sentTimestampMillis = message.getTimestampMillis();
this.groupId = null;
this.groupAction = -1;
this.groupActionArgument = null;
}
public IncomingTextMessage(IncomingPushMessage message, String encodedBody, String groupId) {
public IncomingTextMessage(IncomingPushMessage message, String encodedBody, GroupContext group) {
this.message = encodedBody;
this.sender = message.getSource();
this.senderDeviceId = message.getSourceDevice();
@ -54,7 +61,16 @@ public class IncomingTextMessage implements Parcelable {
this.replyPathPresent = true;
this.pseudoSubject = "";
this.sentTimestampMillis = message.getTimestampMillis();
this.groupId = groupId;
if (group != null) {
this.groupId = GroupUtil.getEncodedId(group.getId().toByteArray());
this.groupAction = group.getType().getNumber();
this.groupActionArgument = GroupUtil.getActionArgument(group);
} else {
this.groupId = null;
this.groupAction = -1;
this.groupActionArgument = null;
}
}
public IncomingTextMessage(Parcel in) {
@ -67,6 +83,8 @@ public class IncomingTextMessage implements Parcelable {
this.pseudoSubject = in.readString();
this.sentTimestampMillis = in.readLong();
this.groupId = in.readString();
this.groupAction = in.readInt();
this.groupActionArgument = in.readString();
}
public IncomingTextMessage(IncomingTextMessage base, String newBody) {
@ -79,6 +97,8 @@ public class IncomingTextMessage implements Parcelable {
this.pseudoSubject = base.getPseudoSubject();
this.sentTimestampMillis = base.getSentTimestampMillis();
this.groupId = base.getGroupId();
this.groupAction = base.getGroupAction();
this.groupActionArgument = base.getGroupActionArgument();
}
public IncomingTextMessage(List<IncomingTextMessage> fragments) {
@ -97,6 +117,8 @@ public class IncomingTextMessage implements Parcelable {
this.pseudoSubject = fragments.get(0).getPseudoSubject();
this.sentTimestampMillis = fragments.get(0).getSentTimestampMillis();
this.groupId = fragments.get(0).getGroupId();
this.groupAction = fragments.get(0).getGroupAction();
this.groupActionArgument = fragments.get(0).getGroupActionArgument();
}
public long getSentTimestampMillis() {
@ -151,6 +173,14 @@ public class IncomingTextMessage implements Parcelable {
return groupId;
}
public int getGroupAction() {
return groupAction;
}
public String getGroupActionArgument() {
return groupActionArgument;
}
@Override
public int describeContents() {
return 0;
@ -167,5 +197,7 @@ public class IncomingTextMessage implements Parcelable {
out.writeString(pseudoSubject);
out.writeLong(sentTimestampMillis);
out.writeString(groupId);
out.writeInt(groupAction);
out.writeString(groupActionArgument);
}
}

View File

@ -7,20 +7,33 @@ import org.thoughtcrime.securesms.recipients.Recipients;
public class OutgoingTextMessage {
private final Recipients recipients;
private String message;
private final String message;
private final int groupAction;
private final String groupActionArguments;
public OutgoingTextMessage(Recipient recipient, String message) {
this(new Recipients(recipient), message);
}
public OutgoingTextMessage(Recipients recipients, String message) {
this.recipients = recipients;
this.message = message;
this.recipients = recipients;
this.message = message;
this.groupAction = -1;
this.groupActionArguments = null;
}
public OutgoingTextMessage(Recipient recipient, int groupAction, String groupActionArguments) {
this.recipients = new Recipients(recipient);
this.groupAction = groupAction;
this.groupActionArguments = groupActionArguments;
this.message = "";
}
protected OutgoingTextMessage(OutgoingTextMessage base, String body) {
this.recipients = base.getRecipients();
this.message = body;
this.recipients = base.getRecipients();
this.groupAction = base.getGroupAction();
this.groupActionArguments = base.getGroupActionArguments();
this.message = body;
}
public String getMessageBody() {
@ -56,4 +69,12 @@ public class OutgoingTextMessage {
public OutgoingTextMessage withBody(String body) {
return new OutgoingTextMessage(this, body);
}
public int getGroupAction() {
return groupAction;
}
public String getGroupActionArguments() {
return groupActionArguments;
}
}

View File

@ -4,6 +4,8 @@ import org.whispersystems.textsecure.util.Hex;
import java.io.IOException;
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext;
public class GroupUtil {
private static final String ENCODED_GROUP_PREFIX = "__textsecure_group__!";
@ -24,4 +26,15 @@ public class GroupUtil {
return groupId.startsWith(ENCODED_GROUP_PREFIX);
}
public static String getActionArgument(GroupContext group) {
if (group.getType().equals(GroupContext.Type.CREATE) ||
group.getType().equals(GroupContext.Type.ADD))
{
return org.whispersystems.textsecure.util.Util.join(group.getMembersList(), ",");
} else if (group.getType().equals(GroupContext.Type.MODIFY)) {
return group.getName();
}
return null;
}
}