diff --git a/res/layout/conversation_item_received.xml b/res/layout/conversation_item_received.xml
index 1fec73280e..39e3b0705b 100644
--- a/res/layout/conversation_item_received.xml
+++ b/res/layout/conversation_item_received.xml
@@ -77,7 +77,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
- android:text="Download"
+ android:text="@string/conversation_item_received__download"
android:visibility="gone" />
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
TextSecure is a security enhanced text messaging application that serves as a full replacement for the default text messaging application. Messages to other TextSecure users are encrypted over the air, and all text messages are stored in an encrypted database on the device. If your phone is lost or stolen, your messages will be safe, and communication with other TextSecure users can\'t be monitored over the air.
+ Download
+ Downloading
+ Download
+ Downloading
diff --git a/src/org/thoughtcrime/securesms/ConversationAdapter.java b/src/org/thoughtcrime/securesms/ConversationAdapter.java
index e42dce9f96..b322c4ce16 100644
--- a/src/org/thoughtcrime/securesms/ConversationAdapter.java
+++ b/src/org/thoughtcrime/securesms/ConversationAdapter.java
@@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
+import org.thoughtcrime.securesms.database.model.MessageRecord.GroupData;
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.mms.SlideDeck;
@@ -64,8 +65,6 @@ public class ConversationAdapter extends CursorAdapter {
private static final int MAX_CACHE_SIZE = 40;
-
-
private final TouchListener touchListener = new TouchListener();
private final LinkedHashMap messageRecordCache;
private final Handler failedIconClickHandler;
@@ -147,19 +146,36 @@ public class ConversationAdapter extends CursorAdapter {
long date = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.DATE));
long box = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX));
Recipient recipient = getIndividualRecipientFor(null);
+ GroupData groupData = null;
SlideDeck slideDeck;
try {
MultimediaMessagePdu pdu = DatabaseFactory.getEncryptingMmsDatabase(context, masterSecret).getMediaMessage(messageId);
slideDeck = new SlideDeck(context, masterSecret, pdu.getBody());
+
+ if (recipients != null && !recipients.isSingleRecipient()) {
+ int groupSize = pdu.getTo().length;
+ int groupSent = MmsDatabase.Types.isFailedMmsBox(box) ? 0 : groupSize;
+ int groupSendFailed = groupSize - groupSent;
+
+ if (groupSize <= 1) {
+ groupSize = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsDatabase.GROUP_SIZE));
+ groupSent = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsDatabase.MMS_GROUP_SENT_COUNT));
+ groupSendFailed = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsDatabase.MMS_GROUP_SEND_FAILED_COUNT));
+ }
+
+ Log.w("ConversationAdapter", "MMS GroupSize: " + groupSize + " , GroupSent: " + groupSent + " , GroupSendFailed: " + groupSendFailed);
+
+ groupData = new MessageRecord.GroupData(groupSize, groupSent, groupSendFailed);
+ }
} catch (MmsException me) {
Log.w("ConversationAdapter", me);
slideDeck = null;
}
return new MediaMmsMessageRecord(context, id, recipients, recipient,
- date, threadId, slideDeck, box);
+ date, threadId, slideDeck, box, groupData);
}
private NotificationMmsMessageRecord getNotificationMmsMessageRecord(long messageId, Cursor cursor) {
@@ -190,17 +206,22 @@ public class ConversationAdapter extends CursorAdapter {
String body = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY));
String address = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS));
Recipient recipient = getIndividualRecipientFor(address);
-// MessageRecord.GroupData groupData = null;
-//
-// if (recipients != null && recipients.isSingleRecipient()) {
-// int groupSize = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsDatabase.SMS_GROUP_SIZE));
-// int groupSent = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsDatabase.SMS_GROUP_SENT_COUNT));
-// int groupSendFailed = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsDatabase.SMS_GROUP_SEND_FAILED_COUNT));
-//
-// groupData = new MessageRecord.GroupData(groupSize, groupSent, groupSendFailed);
-// }
+
+ MessageRecord.GroupData groupData = null;
+
+ if (recipients != null && !recipients.isSingleRecipient()) {
+ int groupSize = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsDatabase.GROUP_SIZE));
+ int groupSent = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsDatabase.SMS_GROUP_SENT_COUNT));
+ int groupSendFailed = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsDatabase.SMS_GROUP_SEND_FAILED_COUNT));
+
+ Log.w("ConversationAdapter", "GroupSize: " + groupSize + " , GroupSent: " + groupSent + " , GroupSendFailed: " + groupSendFailed);
+
+ groupData = new MessageRecord.GroupData(groupSize, groupSent, groupSendFailed);
+ }
+
SmsMessageRecord messageRecord = new SmsMessageRecord(context, messageId, recipients,
- recipient, date, type, threadId);
+ recipient, date, type, threadId,
+ groupData);
if (body == null) {
body = "";
diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java
index 3140656e69..2fb1f22430 100644
--- a/src/org/thoughtcrime/securesms/ConversationItem.java
+++ b/src/org/thoughtcrime/securesms/ConversationItem.java
@@ -46,6 +46,7 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
+import org.thoughtcrime.securesms.database.model.MessageRecord.GroupData;
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
@@ -78,6 +79,7 @@ public class ConversationItem extends LinearLayout {
private TextView bodyText;
private TextView dateText;
+ private TextView groupStatusText;
private ImageView secureImage;
private ImageView failedImage;
private ImageView keyImage;
@@ -108,6 +110,7 @@ public class ConversationItem extends LinearLayout {
this.bodyText = (TextView) findViewById(R.id.conversation_item_body);
this.dateText = (TextView) findViewById(R.id.conversation_item_date);
+ this.groupStatusText = (TextView) findViewById(R.id.group_message_status);
this.secureImage = (ImageView)findViewById(R.id.sms_secure_indicator);
this.failedImage = (ImageView)findViewById(R.id.sms_failed_indicator);
this.keyImage = (ImageView)findViewById(R.id.key_exchange_indicator);
@@ -130,6 +133,7 @@ public class ConversationItem extends LinearLayout {
setBodyText(messageRecord);
setStatusIcons(messageRecord);
setContactPhoto(messageRecord);
+ setGroupMessageStatus(messageRecord);
setEvents(messageRecord);
if (messageRecord instanceof NotificationMmsMessageRecord) {
@@ -200,6 +204,22 @@ public class ConversationItem extends LinearLayout {
}
}
+ private void setGroupMessageStatus(MessageRecord messageRecord) {
+ GroupData groupData = messageRecord.getGroupData();
+
+ if (groupData != null) {
+ String status = String.format("Sent (%d/%d)", groupData.groupSentCount, groupData.groupSize);
+
+ if (groupData.groupSendFailedCount != 0)
+ status = status + String.format(", Failed (%d/%d)", groupData.groupSendFailedCount, groupData.groupSize);
+
+ this.groupStatusText.setText(status);
+ this.groupStatusText.setVisibility(View.VISIBLE);
+ } else {
+ this.groupStatusText.setVisibility(View.GONE);
+ }
+ }
+
private void setNotificationMmsAttributes(NotificationMmsMessageRecord messageRecord) {
String messageSize = String.format(getContext()
.getString(R.string.ConversationItem_message_size_d_kb),
diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
index d2e857bdf9..f49f720c99 100644
--- a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
@@ -28,18 +28,70 @@ import java.util.Set;
public class MmsSmsDatabase extends Database {
- public static final String TRANSPORT = "transport_type";
+ public static final String TRANSPORT = "transport_type";
+ public static final String GROUP_SIZE = "group_size";
+ public static final String SMS_GROUP_SENT_COUNT = "sms_group_sent_count";
+ public static final String SMS_GROUP_SEND_FAILED_COUNT = "sms_group_sent_failed_count";
+ public static final String MMS_GROUP_SENT_COUNT = "mms_group_sent_count";
+ public static final String MMS_GROUP_SEND_FAILED_COUNT = "mms_group_sent_failed_count";
public MmsSmsDatabase(Context context, SQLiteOpenHelper databaseHelper) {
super(context, databaseHelper);
}
+ public Cursor getCollatedGroupConversation(long threadId) {
+ String smsCaseSecurity = "CASE " + SmsDatabase.TYPE + " " +
+ "WHEN " + SmsDatabase.Types.SENT_TYPE + " THEN 1 " +
+ "WHEN " + SmsDatabase.Types.SENT_PENDING + " THEN 1 " +
+ "WHEN " + SmsDatabase.Types.ENCRYPTED_OUTBOX_TYPE + " THEN 1 " +
+ "WHEN " + SmsDatabase.Types.FAILED_TYPE + " THEN 1 " +
+ "WHEN " + SmsDatabase.Types.ENCRYPTING_TYPE + " THEN 2 " +
+ "WHEN " + SmsDatabase.Types.SECURE_SENT_TYPE + " THEN 2 " +
+ "ELSE 0 END";
+
+ String mmsCaseSecurity = "CASE " + MmsDatabase.MESSAGE_BOX + " " +
+ "WHEN " + MmsDatabase.Types.MESSAGE_BOX_OUTBOX + " THEN 'insecure' " +
+ "WHEN " + MmsDatabase.Types.MESSAGE_BOX_SENT + " THEN 'insecure' " +
+ "WHEN " + MmsDatabase.Types.MESSAGE_BOX_SENT_FAILED + " THEN 'insecure' " +
+ "WHEN " + MmsDatabase.Types.MESSAGE_BOX_SECURE_OUTBOX + " THEN 'secure' " +
+ "WHEN " + MmsDatabase.Types.MESSAGE_BOX_SECURE_SENT + " THEN 'secure' " +
+ "ELSE 0 END";
+
+ String mmsGroupSentCount = "SUM(CASE " + MmsDatabase.MESSAGE_BOX + " " +
+ "WHEN " + MmsDatabase.Types.MESSAGE_BOX_SENT + " THEN 1 " +
+ "WHEN " + MmsDatabase.Types.MESSAGE_BOX_SECURE_SENT + " THEN 1 " +
+ "ELSE 0 END)";
+
+ String smsGroupSentCount = "SUM(CASE " + SmsDatabase.TYPE + " " +
+ "WHEN " + SmsDatabase.Types.SENT_TYPE + " THEN 1 " +
+ "WHEN " + SmsDatabase.Types.SECURE_SENT_TYPE + " THEN 1 " +
+ "ELSE 0 END)";
+
+ String mmsGroupSentFailedCount = "SUM(CASE " + MmsDatabase.MESSAGE_BOX + " " +
+ "WHEN " + MmsDatabase.Types.MESSAGE_BOX_SENT_FAILED + " THEN 1 " +
+ "ELSE 0 END)";
+
+ String smsGroupSentFailedCount = "SUM(CASE " + SmsDatabase.TYPE + " " +
+ "WHEN " + SmsDatabase.Types.FAILED_TYPE + " THEN 1 " +
+ "ELSE 0 END)";
+
+ String[] projection = {"_id", "body", "type", "address", "subject", "normalized_date AS date", "m_type", "msg_box", "transport_type", "COUNT(_id) AS group_size", mmsGroupSentCount + " AS mms_group_sent_count", mmsGroupSentFailedCount + " AS mms_group_sent_failed_count", smsGroupSentCount + " AS sms_group_sent_count", smsGroupSentFailedCount + " AS sms_group_sent_failed_count", smsCaseSecurity + " AS sms_collate", mmsCaseSecurity + " AS mms_collate"};
+ String order = "normalized_date ASC";
+ String selection = "thread_id = " + threadId;
+ String groupBy = "normalized_date / 1000, sms_collate, mms_collate";
+
+ Cursor cursor = queryTables(projection, selection, order, groupBy, null);
+ setNotifyConverationListeners(cursor, threadId);
+
+ return cursor;
+ }
+
public Cursor getConversation(long threadId) {
String[] projection = {"_id", "body", "type", "address", "subject", "normalized_date AS date", "m_type", "msg_box", "transport_type"};
String order = "normalized_date ASC";
String selection = "thread_id = " + threadId;
- Cursor cursor = queryTables(projection, selection, order, null);
+ Cursor cursor = queryTables(projection, selection, order, null, null);
setNotifyConverationListeners(cursor, threadId);
return cursor;
@@ -50,7 +102,7 @@ public class MmsSmsDatabase extends Database {
String order = "normalized_date DESC";
String selection = "thread_id = " + threadId;
- Cursor cursor = queryTables(projection, selection, order, "1");
+ Cursor cursor = queryTables(projection, selection, order, null, "1");
return cursor;
}
@@ -59,7 +111,7 @@ public class MmsSmsDatabase extends Database {
String order = "normalized_date ASC";
String selection = "read = 0";
- Cursor cursor = queryTables(projection, selection, order, null);
+ Cursor cursor = queryTables(projection, selection, order, null, null);
return cursor;
}
@@ -70,7 +122,7 @@ public class MmsSmsDatabase extends Database {
return count;
}
- private Cursor queryTables(String[] projection, String selection, String order, String limit) {
+ private Cursor queryTables(String[] projection, String selection, String order, String groupBy, String limit) {
String[] mmsProjection = {"date * 1000 AS normalized_date", "_id", "body", "read", "thread_id", "type", "address", "subject", "date", "m_type", "msg_box", "transport_type"};
String[] smsProjection = {"date * 1 AS normalized_date", "_id", "body", "read", "thread_id", "type", "address", "subject", "date", "m_type", "msg_box", "transport_type"};
@@ -110,7 +162,7 @@ public class MmsSmsDatabase extends Database {
SQLiteQueryBuilder outerQueryBuilder = new SQLiteQueryBuilder();
outerQueryBuilder.setTables("(" + unionQuery + ")");
- String query = outerQueryBuilder.buildQuery(projection, null, null, null, null, null, limit);
+ String query = outerQueryBuilder.buildQuery(projection, null, null, groupBy, null, null, limit);
Log.w("MmsSmsDatabase", "Executing query: " + query);
SQLiteDatabase db = databaseHelper.getReadableDatabase();
diff --git a/src/org/thoughtcrime/securesms/database/loaders/ConversationLoader.java b/src/org/thoughtcrime/securesms/database/loaders/ConversationLoader.java
index c0e3bd5a85..eb06021ed1 100644
--- a/src/org/thoughtcrime/securesms/database/loaders/ConversationLoader.java
+++ b/src/org/thoughtcrime/securesms/database/loaders/ConversationLoader.java
@@ -10,21 +10,21 @@ public class ConversationLoader extends CursorLoader {
private final Context context;
private final long threadId;
-// private final boolean isGroupConversation;
+ private final boolean isGroupConversation;
public ConversationLoader(Context context, long threadId, boolean isGroupConversation) {
super(context);
this.context = context.getApplicationContext();
this.threadId = threadId;
-// this.isGroupConversation = isGroupConversation;
+ this.isGroupConversation = isGroupConversation;
}
@Override
public Cursor loadInBackground() {
-// if (!isGroupConversation) {
+ if (!isGroupConversation) {
return DatabaseFactory.getMmsSmsDatabase(context).getConversation(threadId);
-// } else {
-// return DatabaseFactory.getMmsSmsDatabase(context).getCollatedGroupConversation(threadId);
-// }
+ } else {
+ return DatabaseFactory.getMmsSmsDatabase(context).getCollatedGroupConversation(threadId);
+ }
}
}
diff --git a/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java
index 4ea6a2e859..07721db144 100644
--- a/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java
+++ b/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java
@@ -43,9 +43,9 @@ public class MediaMmsMessageRecord extends MessageRecord {
public MediaMmsMessageRecord(Context context, long id, Recipients recipients,
Recipient individualRecipient, long date, long threadId,
- SlideDeck slideDeck, long mailbox)
+ SlideDeck slideDeck, long mailbox, GroupData groupData)
{
- super(id, recipients, individualRecipient, date, threadId);
+ super(id, recipients, individualRecipient, date, threadId, groupData);
this.slideDeck = slideDeck;
this.mailbox = mailbox;
diff --git a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java
index e383f3e5e9..32133775f0 100644
--- a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java
+++ b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java
@@ -31,14 +31,17 @@ public abstract class MessageRecord extends DisplayRecord {
private final Recipient individualRecipient;
private final long id;
+ private final GroupData groupData;
public MessageRecord(long id, Recipients recipients,
Recipient individualRecipient,
- long date, long threadId)
+ long date, long threadId,
+ GroupData groupData)
{
super(recipients, date, threadId);
this.id = id;
this.individualRecipient = individualRecipient;
+ this.groupData = groupData;
}
public abstract boolean isOutgoing();
@@ -67,17 +70,29 @@ public abstract class MessageRecord extends DisplayRecord {
return individualRecipient;
}
-//
-// public static class GroupData {
-// public final int groupSize;
-// public final int groupSentCount;
-// public final int groupSendFailedCount;
-//
-// public GroupData(int groupSize, int groupSentCount, int groupSendFailedCount) {
-// this.groupSize = groupSize;
-// this.groupSentCount = groupSentCount;
-// this.groupSendFailedCount = groupSendFailedCount;
-// }
-// }
+ public GroupData getGroupData() {
+ return this.groupData;
+ }
+
+ protected boolean isGroupDeliveryPending() {
+ if (this.groupData != null) {
+ return groupData.groupSentCount + groupData.groupSendFailedCount < groupData.groupSize;
+ }
+
+ return false;
+ }
+
+ public static class GroupData {
+ public final int groupSize;
+ public final int groupSentCount;
+ public final int groupSendFailedCount;
+
+ public GroupData(int groupSize, int groupSentCount, int groupSendFailedCount) {
+ this.groupSize = groupSize;
+ this.groupSentCount = groupSentCount;
+ this.groupSendFailedCount = groupSendFailedCount;
+ }
+ }
+
}
diff --git a/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java
index 7ea21cfbff..99867978bc 100644
--- a/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java
+++ b/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java
@@ -41,7 +41,7 @@ public class NotificationMmsMessageRecord extends MessageRecord {
long messageSize, long expiry,
int status, byte[] transactionId)
{
- super(id, recipients, individualRecipient, date, threadId);
+ super(id, recipients, individualRecipient, date, threadId, null);
this.contentLocation = contentLocation;
this.messageSize = messageSize;
this.expiry = expiry;
diff --git a/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java
index d4cb570317..b55181bbf2 100644
--- a/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java
+++ b/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java
@@ -40,11 +40,12 @@ public class SmsMessageRecord extends MessageRecord {
public SmsMessageRecord(Context context, long id,
Recipients recipients,
Recipient individualRecipient,
- long date, long type, long threadId)
+ long date, long type, long threadId,
+ GroupData groupData)
{
- super(id, recipients, individualRecipient, date, threadId);
- this.context = context.getApplicationContext();
- this.type = type;
+ super(id, recipients, individualRecipient, date, threadId, groupData);
+ this.context = context.getApplicationContext();
+ this.type = type;
}
public long getType() {
@@ -82,7 +83,7 @@ public class SmsMessageRecord extends MessageRecord {
@Override
public boolean isPending() {
- return SmsDatabase.Types.isPendingMessageType(getType());
+ return SmsDatabase.Types.isPendingMessageType(getType()) || isGroupDeliveryPending();
}
@Override
@@ -95,17 +96,4 @@ public class SmsMessageRecord extends MessageRecord {
return false;
}
- public static class GroupData {
- public final int groupSize;
- public final int groupSentCount;
- public final int groupSendFailedCount;
-
- public GroupData(int groupSize, int groupSentCount, int groupSendFailedCount) {
- this.groupSize = groupSize;
- this.groupSentCount = groupSentCount;
- this.groupSendFailedCount = groupSendFailedCount;
- }
- }
-
-
}
diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java
index baa1b4a4c1..cfd3111d62 100644
--- a/src/org/thoughtcrime/securesms/sms/MessageSender.java
+++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java
@@ -76,6 +76,8 @@ public class MessageSender {
if (threadId == -1)
threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
+ long date = System.currentTimeMillis();
+
for (Recipient recipient : recipients.getRecipientsList()) {
boolean isSecure = KeyUtil.isSessionFor(context, recipient) && !forcePlaintext;
@@ -85,12 +87,12 @@ public class MessageSender {
messageId = DatabaseFactory.getEncryptingSmsDatabase(context)
.insertMessageSent(masterSecret,
PhoneNumberUtils.formatNumber(recipient.getNumber()),
- threadId, message, System.currentTimeMillis());
+ threadId, message, date);
} else {
messageId = DatabaseFactory.getEncryptingSmsDatabase(context)
.insertSecureMessageSent(masterSecret,
PhoneNumberUtils.formatNumber(recipient.getNumber()),
- threadId, message, System.currentTimeMillis());
+ threadId, message, date);
}
Log.w("SMSSender", "Got message id for new message: " + messageId);