From cb670d6783e47f8f3c8dc187c8c26a6ee8cbad52 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Sat, 22 Apr 2017 16:29:26 -0700 Subject: [PATCH] Improve UI send latency // FREEBIE --- .../securesms/ConversationActivity.java | 27 +++-- .../securesms/ConversationAdapter.java | 57 ++++++--- .../securesms/ConversationFragment.java | 36 ++++++ .../securesms/InviteActivity.java | 2 +- .../securesms/attachments/Attachment.java | 27 +++-- .../attachments/DatabaseAttachment.java | 4 +- .../MmsNotificationAttachment.java | 2 +- .../attachments/PointerAttachment.java | 2 +- .../securesms/attachments/UriAttachment.java | 6 +- .../securesms/components/ThumbnailView.java | 13 +- .../database/AttachmentDatabase.java | 13 +- .../database/CursorRecyclerViewAdapter.java | 40 ++++++- .../securesms/database/DatabaseFactory.java | 7 +- .../database/EncryptingSmsDatabase.java | 4 +- .../FastCursorRecyclerViewAdapter.java | 111 ++++++++++++++++++ .../securesms/database/MediaDatabase.java | 1 + .../securesms/database/MmsDatabase.java | 51 +++++++- .../securesms/database/MmsSmsColumns.java | 8 ++ .../securesms/database/MmsSmsDatabase.java | 4 + .../securesms/database/SmsDatabase.java | 45 ++++++- .../securesms/groups/GroupManager.java | 2 +- .../groups/GroupMessageProcessor.java | 2 +- .../securesms/jobs/PushDecryptJob.java | 8 +- .../securesms/mms/AttachmentManager.java | 34 ++++-- .../securesms/mms/AudioSlide.java | 2 +- src/org/thoughtcrime/securesms/mms/Slide.java | 18 ++- .../AndroidAutoReplyReceiver.java | 8 +- .../notifications/RemoteReplyReceiver.java | 4 +- .../service/QuickResponseService.java | 4 +- .../securesms/sms/MessageSender.java | 11 +- 30 files changed, 459 insertions(+), 94 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/database/FastCursorRecyclerViewAdapter.java diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index 2aa88f4c16..26ec29b457 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -102,6 +102,7 @@ import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; import org.thoughtcrime.securesms.database.MmsSmsColumns.Types; import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientPreferenceEvent; import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences; +import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob; import org.thoughtcrime.securesms.mms.AttachmentManager; @@ -274,7 +275,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (!Util.isEmpty(composeText) || attachmentManager.isAttachmentPresent()) { saveDraft(); - attachmentManager.clear(); + attachmentManager.clear(false); composeText.setText(""); } @@ -566,7 +567,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity recipients.setExpireMessages(expirationTime); OutgoingExpirationUpdateMessage outgoingMessage = new OutgoingExpirationUpdateMessage(getRecipients(), System.currentTimeMillis(), expirationTime * 1000); - MessageSender.send(ConversationActivity.this, masterSecret, outgoingMessage, threadId, false); + MessageSender.send(ConversationActivity.this, masterSecret, outgoingMessage, threadId, false, null); return null; } @@ -692,7 +693,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity new AsyncTask() { @Override protected Long doInBackground(OutgoingEndSessionMessage... messages) { - return MessageSender.send(context, masterSecret, messages[0], threadId, false); + return MessageSender.send(context, masterSecret, messages[0], threadId, false, null); } @Override @@ -740,7 +741,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity .build(); OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(getRecipients(), context, null, System.currentTimeMillis(), 0); - MessageSender.send(self, masterSecret, outgoingMessage, threadId, false); + MessageSender.send(self, masterSecret, outgoingMessage, threadId, false, null); DatabaseFactory.getGroupDatabase(self).remove(groupId, TextSecurePreferences.getLocalNumber(self)); initializeEnabledCheck(); } catch (IOException e) { @@ -1511,13 +1512,19 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity outgoingMessage = new OutgoingSecureMediaMessage(outgoingMessage); } - attachmentManager.clear(); + attachmentManager.clear(false); composeText.setText(""); + final long id = fragment.stageOutgoingMessage(outgoingMessage); new AsyncTask() { @Override protected Long doInBackground(OutgoingMediaMessage... messages) { - return MessageSender.send(context, masterSecret, messages[0], threadId, forceSms); + return MessageSender.send(context, masterSecret, messages[0], threadId, forceSms, new SmsDatabase.InsertListener() { + @Override + public void onComplete() { + fragment.releaseOutgoingMessage(id); + } + }); } @Override @@ -1543,11 +1550,17 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } this.composeText.setText(""); + final long id = fragment.stageOutgoingMessage(message); new AsyncTask() { @Override protected Long doInBackground(OutgoingTextMessage... messages) { - return MessageSender.send(context, masterSecret, messages[0], threadId, forceSms); + return MessageSender.send(context, masterSecret, messages[0], threadId, forceSms, new SmsDatabase.InsertListener() { + @Override + public void onComplete() { + fragment.releaseOutgoingMessage(id); + } + }); } @Override diff --git a/src/org/thoughtcrime/securesms/ConversationAdapter.java b/src/org/thoughtcrime/securesms/ConversationAdapter.java index 86ad92eacb..357e787d88 100644 --- a/src/org/thoughtcrime/securesms/ConversationAdapter.java +++ b/src/org/thoughtcrime/securesms/ConversationAdapter.java @@ -33,12 +33,14 @@ import android.widget.TextView; import org.thoughtcrime.securesms.ConversationAdapter.HeaderViewHolder; import org.thoughtcrime.securesms.crypto.MasterSecret; -import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter; +import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.FastCursorRecyclerViewAdapter; import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.MmsSmsDatabase; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord; +import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.util.Conversions; import org.thoughtcrime.securesms.util.DateUtils; @@ -67,7 +69,7 @@ import java.util.Set; * */ public class ConversationAdapter - extends CursorRecyclerViewAdapter + extends FastCursorRecyclerViewAdapter implements StickyHeaderDecoration.StickyHeaderAdapter { @@ -179,14 +181,13 @@ public class ConversationAdapter @Override public void changeCursor(Cursor cursor) { messageRecordCache.clear(); + super.cleanFastRecords(); super.changeCursor(cursor); } @Override - public void onBindItemViewHolder(ViewHolder viewHolder, @NonNull Cursor cursor) { - long start = System.currentTimeMillis(); - MessageRecord messageRecord = getMessageRecord(cursor); - + protected void onBindItemViewHolder(ViewHolder viewHolder, @NonNull MessageRecord messageRecord) { + long start = System.currentTimeMillis(); viewHolder.getView().bind(masterSecret, messageRecord, locale, batchSelected, recipients); Log.w(TAG, "Bind time: " + (System.currentTimeMillis() - start)); } @@ -237,9 +238,7 @@ public class ConversationAdapter } @Override - public int getItemViewType(@NonNull Cursor cursor) { - MessageRecord messageRecord = getMessageRecord(cursor); - + public int getItemViewType(@NonNull MessageRecord messageRecord) { if (messageRecord.isGroupAction() || messageRecord.isCallLog() || messageRecord.isJoined() || messageRecord.isExpirationTimerUpdate() || messageRecord.isEndSession() || messageRecord.isIdentityUpdate()) { return MESSAGE_TYPE_UPDATE; @@ -259,14 +258,39 @@ public class ConversationAdapter } } + @Override + protected boolean isRecordForId(@NonNull MessageRecord record, long id) { + return record.getId() == id; + } + @Override public long getItemId(@NonNull Cursor cursor) { + String fastPreflightId = cursor.getString(cursor.getColumnIndexOrThrow(AttachmentDatabase.FAST_PREFLIGHT_ID)); + + if (fastPreflightId != null) { + return Long.valueOf(fastPreflightId); + } + final String unique = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsColumns.UNIQUE_ROW_ID)); final byte[] bytes = digest.digest(unique.getBytes()); return Conversions.byteArrayToLong(bytes); } - private MessageRecord getMessageRecord(Cursor cursor) { + @Override + protected long getItemId(@NonNull MessageRecord record) { + if (record.isOutgoing() && record.isMms()) { + SlideDeck slideDeck = ((MmsMessageRecord)record).getSlideDeck(); + + if (slideDeck.getThumbnailSlide() != null && slideDeck.getThumbnailSlide().getFastPreflightId() != null) { + return Long.valueOf(slideDeck.getThumbnailSlide().getFastPreflightId()); + } + } + + return record.getId(); + } + + @Override + protected MessageRecord getRecordFromCursor(@NonNull Cursor cursor) { long messageId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.ID)); String type = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT)); @@ -293,8 +317,7 @@ public class ConversationAdapter int count = getItemCount(); for (int i=0;i if (position >= getItemCount()) return -1; if (position < 0) return -1; - Cursor cursor = getCursorAtPositionOrThrow(position); - MessageRecord record = getMessageRecord(cursor); + MessageRecord record = getRecordForPositionOrThrow(position); calendar.setTime(new Date(record.getDateSent())); return Util.hashCode(calendar.get(Calendar.YEAR), calendar.get(Calendar.DAY_OF_YEAR)); @@ -353,8 +375,7 @@ public class ConversationAdapter if (position >= getItemCount()) return 0; if (position < 0) return 0; - Cursor cursor = getCursorAtPositionOrThrow(position); - MessageRecord messageRecord = getMessageRecord(cursor); + MessageRecord messageRecord = getRecordForPositionOrThrow(position); if (messageRecord.isOutgoing()) return 0; else return messageRecord.getDateReceived(); @@ -371,8 +392,8 @@ public class ConversationAdapter @Override public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) { - Cursor cursor = getCursorAtPositionOrThrow(position); - viewHolder.setText(DateUtils.getRelativeDate(getContext(), locale, getMessageRecord(cursor).getDateReceived())); + MessageRecord messageRecord = getRecordForPositionOrThrow(position); + viewHolder.setText(DateUtils.getRelativeDate(getContext(), locale, messageRecord.getDateReceived())); } public void onBindLastSeenViewHolder(HeaderViewHolder viewHolder, int position) { diff --git a/src/org/thoughtcrime/securesms/ConversationFragment.java b/src/org/thoughtcrime/securesms/ConversationFragment.java index f1797c3507..e2ea374297 100644 --- a/src/org/thoughtcrime/securesms/ConversationFragment.java +++ b/src/org/thoughtcrime/securesms/ConversationFragment.java @@ -54,20 +54,30 @@ import org.thoughtcrime.securesms.ConversationAdapter.HeaderViewHolder; import org.thoughtcrime.securesms.ConversationAdapter.ItemClickListener; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.MmsSmsDatabase; +import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; +import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.thoughtcrime.securesms.database.loaders.ConversationLoader; +import org.thoughtcrime.securesms.database.model.DisplayRecord; import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord; +import org.thoughtcrime.securesms.database.model.SmsMessageRecord; +import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.mms.Slide; +import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.sms.MessageSender; +import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.util.SaveAttachmentTask; import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment; import org.thoughtcrime.securesms.util.StickyHeaderDecoration; import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; @@ -448,6 +458,32 @@ public class ConversationFragment extends Fragment } } + public long stageOutgoingMessage(OutgoingMediaMessage message) { + MessageRecord messageRecord = DatabaseFactory.getMmsDatabase(getContext()).readerFor(message, threadId).getCurrent(); + + if (getListAdapter() != null) { + getListAdapter().addFastRecord(messageRecord); + } + + return messageRecord.getId(); + } + + public long stageOutgoingMessage(OutgoingTextMessage message) { + MessageRecord messageRecord = DatabaseFactory.getSmsDatabase(getContext()).readerFor(message, threadId).getCurrent(); + + if (getListAdapter() != null) { + getListAdapter().addFastRecord(messageRecord); + } + + return messageRecord.getId(); + } + + public void releaseOutgoingMessage(long id) { + if (getListAdapter() != null) { + getListAdapter().releaseFastRecord(id); + } + } + private void scrollToLastSeenPosition(final int lastSeenPosition) { if (lastSeenPosition > 0) { list.post(new Runnable() { diff --git a/src/org/thoughtcrime/securesms/InviteActivity.java b/src/org/thoughtcrime/securesms/InviteActivity.java index d8e3b46b7f..f07be5fc1e 100644 --- a/src/org/thoughtcrime/securesms/InviteActivity.java +++ b/src/org/thoughtcrime/securesms/InviteActivity.java @@ -236,7 +236,7 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity implemen Optional preferences = DatabaseFactory.getRecipientPreferenceDatabase(context).getRecipientsPreferences(recipients.getIds()); int subscriptionId = preferences.isPresent() ? preferences.get().getDefaultSubscriptionId().or(-1) : -1; - MessageSender.send(context, masterSecret, new OutgoingTextMessage(recipients, message, subscriptionId), -1L, true); + MessageSender.send(context, masterSecret, new OutgoingTextMessage(recipients, message, subscriptionId), -1L, true, null); if (recipients.getPrimaryRecipient().getContactUri() != null) { DatabaseFactory.getRecipientPreferenceDatabase(context).setSeenInviteReminder(recipients, true); diff --git a/src/org/thoughtcrime/securesms/attachments/Attachment.java b/src/org/thoughtcrime/securesms/attachments/Attachment.java index 26e91a0190..42391d9ed8 100644 --- a/src/org/thoughtcrime/securesms/attachments/Attachment.java +++ b/src/org/thoughtcrime/securesms/attachments/Attachment.java @@ -28,18 +28,22 @@ public abstract class Attachment { @Nullable private final byte[] digest; + @Nullable + private final String fastPreflightId; + public Attachment(@NonNull String contentType, int transferState, long size, @Nullable String fileName, @Nullable String location, @Nullable String key, @Nullable String relay, - @Nullable byte[] digest) + @Nullable byte[] digest, @Nullable String fastPreflightId) { - this.contentType = contentType; - this.transferState = transferState; - this.size = size; - this.fileName = fileName; - this.location = location; - this.key = key; - this.relay = relay; - this.digest = digest; + this.contentType = contentType; + this.transferState = transferState; + this.size = size; + this.fileName = fileName; + this.location = location; + this.key = key; + this.relay = relay; + this.digest = digest; + this.fastPreflightId = fastPreflightId; } @Nullable @@ -90,4 +94,9 @@ public abstract class Attachment { public byte[] getDigest() { return digest; } + + @Nullable + public String getFastPreflightId() { + return fastPreflightId; + } } diff --git a/src/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java b/src/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java index 18b08dbdfa..a8636fad0f 100644 --- a/src/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java +++ b/src/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java @@ -16,9 +16,9 @@ public class DatabaseAttachment extends Attachment { boolean hasData, boolean hasThumbnail, String contentType, int transferProgress, long size, String fileName, String location, String key, String relay, - byte[] digest) + byte[] digest, String fastPreflightId) { - super(contentType, transferProgress, size, fileName, location, key, relay, digest); + super(contentType, transferProgress, size, fileName, location, key, relay, digest, fastPreflightId); this.attachmentId = attachmentId; this.hasData = hasData; this.hasThumbnail = hasThumbnail; diff --git a/src/org/thoughtcrime/securesms/attachments/MmsNotificationAttachment.java b/src/org/thoughtcrime/securesms/attachments/MmsNotificationAttachment.java index f885e93856..cf61aebe26 100644 --- a/src/org/thoughtcrime/securesms/attachments/MmsNotificationAttachment.java +++ b/src/org/thoughtcrime/securesms/attachments/MmsNotificationAttachment.java @@ -10,7 +10,7 @@ import org.thoughtcrime.securesms.database.MmsDatabase; public class MmsNotificationAttachment extends Attachment { public MmsNotificationAttachment(int status, long size) { - super("application/mms", getTransferStateFromStatus(status), size, null, null, null, null, null); + super("application/mms", getTransferStateFromStatus(status), size, null, null, null, null, null, null); } @Nullable diff --git a/src/org/thoughtcrime/securesms/attachments/PointerAttachment.java b/src/org/thoughtcrime/securesms/attachments/PointerAttachment.java index 611c5dd12b..955ce63307 100644 --- a/src/org/thoughtcrime/securesms/attachments/PointerAttachment.java +++ b/src/org/thoughtcrime/securesms/attachments/PointerAttachment.java @@ -20,7 +20,7 @@ public class PointerAttachment extends Attachment { @NonNull String key, @NonNull String relay, @Nullable byte[] digest) { - super(contentType, transferState, size, fileName, location, key, relay, digest); + super(contentType, transferState, size, fileName, location, key, relay, digest, null); } @Nullable diff --git a/src/org/thoughtcrime/securesms/attachments/UriAttachment.java b/src/org/thoughtcrime/securesms/attachments/UriAttachment.java index 8ccf036297..8fe6dbbb15 100644 --- a/src/org/thoughtcrime/securesms/attachments/UriAttachment.java +++ b/src/org/thoughtcrime/securesms/attachments/UriAttachment.java @@ -12,14 +12,14 @@ public class UriAttachment extends Attachment { public UriAttachment(@NonNull Uri uri, @NonNull String contentType, int transferState, long size, @Nullable String fileName) { - this(uri, uri, contentType, transferState, size, fileName); + this(uri, uri, contentType, transferState, size, fileName, null); } public UriAttachment(@NonNull Uri dataUri, @Nullable Uri thumbnailUri, @NonNull String contentType, int transferState, long size, - @Nullable String fileName) + @Nullable String fileName, @Nullable String fastPreflightId) { - super(contentType, transferState, size, fileName, null, null, null, null); + super(contentType, transferState, size, fileName, null, null, null, null, fastPreflightId); this.dataUri = dataUri; this.thumbnailUri = thumbnailUri; } diff --git a/src/org/thoughtcrime/securesms/components/ThumbnailView.java b/src/org/thoughtcrime/securesms/components/ThumbnailView.java index 542f2e271c..54c9261627 100644 --- a/src/org/thoughtcrime/securesms/components/ThumbnailView.java +++ b/src/org/thoughtcrime/securesms/components/ThumbnailView.java @@ -66,7 +66,7 @@ public class ThumbnailView extends FrameLayout { if (attrs != null) { TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ThumbnailView, 0, 0); - backgroundColorHint = typedArray.getColor(0, Color.BLACK); + backgroundColorHint = typedArray.getColor(R.styleable.ThumbnailView_backgroundColorHint, Color.BLACK); typedArray.recycle(); } } @@ -120,13 +120,22 @@ public class ThumbnailView extends FrameLayout { return; } + if (this.slide != null && this.slide.getFastPreflightId() != null && + this.slide.getFastPreflightId().equals(slide.getFastPreflightId())) + { + Log.w(TAG, "Not re-loading slide for fast preflight: " + slide.getFastPreflightId()); + this.slide = slide; + return; + } + if (!isContextValid()) { Log.w(TAG, "Not loading slide, context is invalid"); return; } Log.w(TAG, "loading part with id " + slide.asAttachment().getDataUri() - + ", progress " + slide.getTransferState()); + + ", progress " + slide.getTransferState() + ", fast preflight id: " + + slide.asAttachment().getFastPreflightId()); this.slide = slide; diff --git a/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java b/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java index ecf9d84305..b42af578af 100644 --- a/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java +++ b/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java @@ -86,6 +86,7 @@ public class AttachmentDatabase extends Database { static final String THUMBNAIL_ASPECT_RATIO = "aspect_ratio"; static final String UNIQUE_ID = "unique_id"; static final String DIGEST = "digest"; + public static final String FAST_PREFLIGHT_ID = "fast_preflight_id"; public static final int TRANSFER_PROGRESS_DONE = 0; public static final int TRANSFER_PROGRESS_STARTED = 1; @@ -98,7 +99,7 @@ public class AttachmentDatabase extends Database { MMS_ID, CONTENT_TYPE, NAME, CONTENT_DISPOSITION, CONTENT_LOCATION, DATA, THUMBNAIL, TRANSFER_STATE, SIZE, FILE_NAME, THUMBNAIL, THUMBNAIL_ASPECT_RATIO, - UNIQUE_ID, DIGEST}; + UNIQUE_ID, DIGEST, FAST_PREFLIGHT_ID}; public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ROW_ID + " INTEGER PRIMARY KEY, " + MMS_ID + " INTEGER, " + "seq" + " INTEGER DEFAULT 0, " + @@ -108,7 +109,7 @@ public class AttachmentDatabase extends Database { "ctt_t" + " TEXT, " + "encrypted" + " INTEGER, " + TRANSFER_STATE + " INTEGER, "+ DATA + " TEXT, " + SIZE + " INTEGER, " + FILE_NAME + " TEXT, " + THUMBNAIL + " TEXT, " + THUMBNAIL_ASPECT_RATIO + " REAL, " + - UNIQUE_ID + " INTEGER NOT NULL, " + DIGEST + " BLOB);"; + UNIQUE_ID + " INTEGER NOT NULL, " + DIGEST + " BLOB, " + FAST_PREFLIGHT_ID + " TEXT);"; public static final String[] CREATE_INDEXS = { "CREATE INDEX IF NOT EXISTS part_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");", @@ -276,6 +277,7 @@ public class AttachmentDatabase extends Database { values.put(CONTENT_DISPOSITION, (String)null); values.put(DIGEST, (byte[])null); values.put(NAME, (String) null); + values.put(FAST_PREFLIGHT_ID, (String)null); if (database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings()) == 0) { //noinspection ResultOfMethodCallIgnored @@ -334,7 +336,8 @@ public class AttachmentDatabase extends Database { databaseAttachment.getLocation(), databaseAttachment.getKey(), databaseAttachment.getRelay(), - databaseAttachment.getDigest()); + databaseAttachment.getDigest(), + databaseAttachment.getFastPreflightId()); } @@ -485,7 +488,8 @@ public class AttachmentDatabase extends Database { cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_LOCATION)), cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_DISPOSITION)), cursor.getString(cursor.getColumnIndexOrThrow(NAME)), - cursor.getBlob(cursor.getColumnIndexOrThrow(DIGEST))); + cursor.getBlob(cursor.getColumnIndexOrThrow(DIGEST)), + cursor.getString(cursor.getColumnIndexOrThrow(FAST_PREFLIGHT_ID))); } @@ -519,6 +523,7 @@ public class AttachmentDatabase extends Database { contentValues.put(NAME, attachment.getRelay()); contentValues.put(FILE_NAME, fileName); contentValues.put(SIZE, attachment.getSize()); + contentValues.put(FAST_PREFLIGHT_ID, attachment.getFastPreflightId()); if (partData != null) { contentValues.put(DATA, partData.first.getAbsolutePath()); diff --git a/src/org/thoughtcrime/securesms/database/CursorRecyclerViewAdapter.java b/src/org/thoughtcrime/securesms/database/CursorRecyclerViewAdapter.java index fdbcf2824f..31b9cc42ac 100644 --- a/src/org/thoughtcrime/securesms/database/CursorRecyclerViewAdapter.java +++ b/src/org/thoughtcrime/securesms/database/CursorRecyclerViewAdapter.java @@ -115,6 +115,7 @@ public abstract class CursorRecyclerViewAdapter insertMessageInbox(@NonNull MasterSecretUnion masterSecret, diff --git a/src/org/thoughtcrime/securesms/database/FastCursorRecyclerViewAdapter.java b/src/org/thoughtcrime/securesms/database/FastCursorRecyclerViewAdapter.java new file mode 100644 index 0000000000..973ec8f8db --- /dev/null +++ b/src/org/thoughtcrime/securesms/database/FastCursorRecyclerViewAdapter.java @@ -0,0 +1,111 @@ +package org.thoughtcrime.securesms.database; + + +import android.content.Context; +import android.database.Cursor; +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +public abstract class FastCursorRecyclerViewAdapter + extends CursorRecyclerViewAdapter +{ + private static final String TAG = FastCursorRecyclerViewAdapter.class.getSimpleName(); + + private final LinkedList fastRecords = new LinkedList<>(); + private final List releasedRecordIds = new LinkedList<>(); + + protected FastCursorRecyclerViewAdapter(Context context, Cursor cursor) { + super(context, cursor); + } + + public void addFastRecord(@NonNull T record) { + fastRecords.addFirst(record); + notifyDataSetChanged(); + } + + public void releaseFastRecord(long id) { + synchronized (releasedRecordIds) { + releasedRecordIds.add(id); + } + } + + protected void cleanFastRecords() { + synchronized (releasedRecordIds) { + Iterator releaseIdIterator = releasedRecordIds.iterator(); + + while (releaseIdIterator.hasNext()) { + long releasedId = releaseIdIterator.next(); + Iterator fastRecordIterator = fastRecords.iterator(); + + while (fastRecordIterator.hasNext()) { + if (isRecordForId(fastRecordIterator.next(), releasedId)) { + fastRecordIterator.remove(); + releaseIdIterator.remove(); + break; + } + } + } + } + } + + protected abstract T getRecordFromCursor(@NonNull Cursor cursor); + protected abstract void onBindItemViewHolder(VH viewHolder, @NonNull T record); + protected abstract long getItemId(@NonNull T record); + protected abstract int getItemViewType(@NonNull T record); + protected abstract boolean isRecordForId(@NonNull T record, long id); + + @Override + public int getItemViewType(@NonNull Cursor cursor) { + T record = getRecordFromCursor(cursor); + return getItemViewType(record); + } + + @Override + public void onBindItemViewHolder(VH viewHolder, @NonNull Cursor cursor) { + T record = getRecordFromCursor(cursor); + onBindItemViewHolder(viewHolder, record); + } + + @Override + public void onBindFastAccessItemViewHolder(VH viewHolder, int position) { + onBindItemViewHolder(viewHolder, fastRecords.get(getCalculatedPosition(position))); + } + + @Override + protected int getFastAccessSize() { + return fastRecords.size(); + } + + protected T getRecordForPositionOrThrow(int position) { + if (isFastAccessPosition(position)) { + return fastRecords.get(getCalculatedPosition(position)); + } else { + Cursor cursor = getCursorAtPositionOrThrow(position); + return getRecordFromCursor(cursor); + } + } + + protected int getFastAccessItemViewType(int position) { + return getItemViewType(fastRecords.get(getCalculatedPosition(position))); + } + + protected boolean isFastAccessPosition(int position) { + position = getCalculatedPosition(position); + return position >= 0 && position < fastRecords.size(); + } + + protected long getFastAccessItemId(int position) { + return getItemId(fastRecords.get(getCalculatedPosition(position))); + } + + private int getCalculatedPosition(int position) { + return hasHeaderView() ? position - 1 : position; + } + +} diff --git a/src/org/thoughtcrime/securesms/database/MediaDatabase.java b/src/org/thoughtcrime/securesms/database/MediaDatabase.java index 0dd9e4e919..55cf33cbfe 100644 --- a/src/org/thoughtcrime/securesms/database/MediaDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MediaDatabase.java @@ -26,6 +26,7 @@ public class MediaDatabase extends Database { + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_LOCATION + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_DISPOSITION + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DIGEST + ", " + + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.FAST_PREFLIGHT_ID + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.NAME + ", " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.MESSAGE_BOX + ", " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_SENT + ", " diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 8afab52905..4dc4eaf06a 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -19,6 +19,7 @@ package org.thoughtcrime.securesms.database; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; +import android.database.MatrixCursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.net.Uri; @@ -69,6 +70,8 @@ import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.util.InvalidNumberException; import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -144,6 +147,7 @@ public class MmsDatabase extends MessagingDatabase { AttachmentDatabase.CONTENT_TYPE, AttachmentDatabase.CONTENT_LOCATION, AttachmentDatabase.DIGEST, + AttachmentDatabase.FAST_PREFLIGHT_ID, AttachmentDatabase.CONTENT_DISPOSITION, AttachmentDatabase.NAME, AttachmentDatabase.TRANSFER_STATE @@ -694,7 +698,8 @@ public class MmsDatabase extends MessagingDatabase { databaseAttachment.getLocation(), databaseAttachment.getKey(), databaseAttachment.getRelay(), - databaseAttachment.getDigest())); + databaseAttachment.getDigest(), + databaseAttachment.getFastPreflightId())); } return insertMediaMessage(new MasterSecretUnion(masterSecret), @@ -867,7 +872,8 @@ public class MmsDatabase extends MessagingDatabase { public long insertMessageOutbox(@NonNull MasterSecretUnion masterSecret, @NonNull OutgoingMediaMessage message, - long threadId, boolean forceSms) + long threadId, boolean forceSms, + SmsDatabase.InsertListener insertListener) throws MmsException { long type = Types.BASE_SENDING_TYPE; @@ -924,6 +930,10 @@ public class MmsDatabase extends MessagingDatabase { long messageId = insertMediaMessage(masterSecret, addresses, message.getBody(), message.getAttachments(), contentValues); + if (insertListener != null) { + insertListener.onComplete(); + } + DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId); jobManager.add(new TrimThreadJob(context, threadId)); @@ -1106,6 +1116,10 @@ public class MmsDatabase extends MessagingDatabase { return new Reader(masterSecret, cursor); } + public OutgoingMessageReader readerFor(OutgoingMediaMessage message, long threadId) { + return new OutgoingMessageReader(message, threadId); + } + public static class Status { public static final int DOWNLOAD_INITIALIZED = 1; public static final int DOWNLOAD_NO_CONNECTIVITY = 2; @@ -1115,6 +1129,39 @@ public class MmsDatabase extends MessagingDatabase { public static final int DOWNLOAD_APN_UNAVAILABLE = 6; } + public class OutgoingMessageReader { + + private final OutgoingMediaMessage message; + private final long id; + private final long threadId; + + public OutgoingMessageReader(OutgoingMediaMessage message, long threadId) { + try { + this.message = message; + this.id = SecureRandom.getInstance("SHA1PRNG").nextLong(); + this.threadId = threadId; + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + } + + public MessageRecord getCurrent() { + SlideDeck slideDeck = new SlideDeck(context, message.getAttachments()); + + return new MediaMmsMessageRecord(context, id, message.getRecipients(), + message.getRecipients().getPrimaryRecipient(), + 1, System.currentTimeMillis(), System.currentTimeMillis(), + 0, threadId, new DisplayRecord.Body(message.getBody(), true), + slideDeck, slideDeck.getSlides().size(), + message.isSecure() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(), + new LinkedList(), + new LinkedList(), + message.getSubscriptionId(), + message.getExpiresIn(), + System.currentTimeMillis()); + } + } + public class Reader { private final Cursor cursor; diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java index ed5ebf0c04..79a904a680 100644 --- a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -97,6 +97,14 @@ public interface MmsSmsColumns { return false; } + public static long getOutgoingEncryptedMessageType() { + return Types.BASE_SENDING_TYPE | Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT; + } + + public static long getOutgoingSmsMessageType() { + return Types.BASE_SENDING_TYPE; + } + public static boolean isForcedSms(long type) { return (type & MESSAGE_FORCE_SMS_BIT) != 0; } diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index 57b84241d0..99b191dfd3 100644 --- a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -70,6 +70,7 @@ public class MmsSmsDatabase extends Database { AttachmentDatabase.CONTENT_TYPE, AttachmentDatabase.CONTENT_LOCATION, AttachmentDatabase.DIGEST, + AttachmentDatabase.FAST_PREFLIGHT_ID, AttachmentDatabase.CONTENT_DISPOSITION, AttachmentDatabase.NAME, AttachmentDatabase.TRANSFER_STATE}; @@ -165,6 +166,7 @@ public class MmsSmsDatabase extends Database { AttachmentDatabase.CONTENT_TYPE, AttachmentDatabase.CONTENT_LOCATION, AttachmentDatabase.DIGEST, + AttachmentDatabase.FAST_PREFLIGHT_ID, AttachmentDatabase.CONTENT_DISPOSITION, AttachmentDatabase.NAME, AttachmentDatabase.TRANSFER_STATE}; @@ -194,6 +196,7 @@ public class MmsSmsDatabase extends Database { AttachmentDatabase.CONTENT_TYPE, AttachmentDatabase.CONTENT_LOCATION, AttachmentDatabase.DIGEST, + AttachmentDatabase.FAST_PREFLIGHT_ID, AttachmentDatabase.CONTENT_DISPOSITION, AttachmentDatabase.NAME, AttachmentDatabase.TRANSFER_STATE}; @@ -249,6 +252,7 @@ public class MmsSmsDatabase extends Database { mmsColumnsPresent.add(AttachmentDatabase.CONTENT_TYPE); mmsColumnsPresent.add(AttachmentDatabase.CONTENT_LOCATION); mmsColumnsPresent.add(AttachmentDatabase.DIGEST); + mmsColumnsPresent.add(AttachmentDatabase.FAST_PREFLIGHT_ID); mmsColumnsPresent.add(AttachmentDatabase.CONTENT_DISPOSITION); mmsColumnsPresent.add(AttachmentDatabase.NAME); mmsColumnsPresent.add(AttachmentDatabase.TRANSFER_STATE); diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index d262332e59..ec400b249e 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -32,6 +32,7 @@ import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchList; import org.thoughtcrime.securesms.database.model.DisplayRecord; +import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.jobs.TrimThreadJob; import org.thoughtcrime.securesms.recipients.Recipient; @@ -46,6 +47,8 @@ import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.util.InvalidNumberException; import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -594,7 +597,8 @@ public class SmsDatabase extends MessagingDatabase { } protected long insertMessageOutbox(long threadId, OutgoingTextMessage message, - long type, boolean forceSms, long date) + long type, boolean forceSms, long date, + InsertListener insertListener) { if (message.isKeyExchange()) type |= Types.KEY_EXCHANGE_BIT; else if (message.isSecureMessage()) type |= (Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT); @@ -623,6 +627,10 @@ public class SmsDatabase extends MessagingDatabase { SQLiteDatabase db = databaseHelper.getWritableDatabase(); long messageId = db.insert(TABLE_NAME, ADDRESS, contentValues); + if (insertListener != null) { + insertListener.onComplete(); + } + DatabaseFactory.getThreadDatabase(context).update(threadId, true); DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId); notifyConversationListeners(threadId); @@ -768,6 +776,37 @@ public class SmsDatabase extends MessagingDatabase { return new Reader(cursor); } + public OutgoingMessageReader readerFor(OutgoingTextMessage message, long threadId) { + return new OutgoingMessageReader(message, threadId); + } + + public class OutgoingMessageReader { + + private final OutgoingTextMessage message; + private final long id; + private final long threadId; + + public OutgoingMessageReader(OutgoingTextMessage message, long threadId) { + try { + this.message = message; + this.threadId = threadId; + this.id = SecureRandom.getInstance("SHA1PRNG").nextLong(); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + } + + public MessageRecord getCurrent() { + return new SmsMessageRecord(context, id, new DisplayRecord.Body(message.getMessageBody(), true), + message.getRecipients(), message.getRecipients().getPrimaryRecipient(), + 1, System.currentTimeMillis(), System.currentTimeMillis(), + 0, message.isSecureMessage() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(), + threadId, 0, new LinkedList(), + message.getSubscriptionId(), message.getExpiresIn(), + System.currentTimeMillis()); + } + } + public class Reader { private final Cursor cursor; @@ -858,4 +897,8 @@ public class SmsDatabase extends MessagingDatabase { } } + public interface InsertListener { + public void onComplete(); + } + } diff --git a/src/org/thoughtcrime/securesms/groups/GroupManager.java b/src/org/thoughtcrime/securesms/groups/GroupManager.java index 896bdde819..f2c6ce6bf3 100644 --- a/src/org/thoughtcrime/securesms/groups/GroupManager.java +++ b/src/org/thoughtcrime/securesms/groups/GroupManager.java @@ -108,7 +108,7 @@ public class GroupManager { } OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, groupContext, avatarAttachment, System.currentTimeMillis(), 0); - long threadId = MessageSender.send(context, masterSecret, outgoingMessage, -1, false); + long threadId = MessageSender.send(context, masterSecret, outgoingMessage, -1, false, null); return new GroupActionResult(groupRecipient, threadId); } diff --git a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java index fdd050875a..4b749303b8 100644 --- a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java +++ b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java @@ -205,7 +205,7 @@ public class GroupMessageProcessor { Recipients recipients = RecipientFactory.getRecipientsFromString(context, GroupUtil.getEncodedId(group.getGroupId()), false); OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipients, storage, null, envelope.getTimestamp(), 0); long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); - long messageId = mmsDatabase.insertMessageOutbox(masterSecret, outgoingMessage, threadId, false); + long messageId = mmsDatabase.insertMessageOutbox(masterSecret, outgoingMessage, threadId, false, null); mmsDatabase.markAsSent(messageId, true); diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index ac26ab3e56..a88cf42267 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -321,7 +321,7 @@ public class PushDecryptJob extends ContextJob { SecurityEvent.broadcastSecurityUpdateEvent(context); long messageId = database.insertMessageOutbox(masterSecret, threadId, outgoingEndSessionMessage, - false, message.getTimestamp()); + false, message.getTimestamp(), null); database.markAsSent(messageId, true); } @@ -521,7 +521,7 @@ public class PushDecryptJob extends ContextJob { message.getMessage().getExpiresInSeconds() * 1000); long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); - long messageId = database.insertMessageOutbox(masterSecret, expirationUpdateMessage, threadId, false); + long messageId = database.insertMessageOutbox(masterSecret, expirationUpdateMessage, threadId, false, null); database.markAsSent(messageId, true); @@ -554,7 +554,7 @@ public class PushDecryptJob extends ContextJob { } long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); - long messageId = database.insertMessageOutbox(masterSecret, mediaMessage, threadId, false); + long messageId = database.insertMessageOutbox(masterSecret, mediaMessage, threadId, false, null); database.markAsSent(messageId, true); @@ -635,7 +635,7 @@ public class PushDecryptJob extends ContextJob { } long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); - long messageId = database.insertMessageOutbox(masterSecret, threadId, outgoingTextMessage, false, message.getTimestamp()); + long messageId = database.insertMessageOutbox(masterSecret, threadId, outgoingTextMessage, false, message.getTimestamp(), null); database.markAsSent(messageId, true); diff --git a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java index 0cf2054fce..9488a29e51 100644 --- a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -35,6 +35,7 @@ import android.util.Log; import android.view.View; import android.widget.Toast; +import com.bumptech.glide.Glide; import com.google.android.gms.common.GooglePlayServicesNotAvailableException; import com.google.android.gms.common.GooglePlayServicesRepairableException; import com.google.android.gms.location.places.ui.PlacePicker; @@ -109,20 +110,27 @@ public class AttachmentManager { } - public void clear() { + public void clear(boolean animate) { if (attachmentViewStub.resolved()) { - ViewUtil.fadeOut(attachmentViewStub.get(), 200).addListener(new Listener() { - @Override - public void onSuccess(Boolean result) { - thumbnail.clear(); - attachmentViewStub.get().setVisibility(View.GONE); - attachmentListener.onAttachmentChanged(); - } - @Override - public void onFailure(ExecutionException e) { - } - }); + if (animate) { + ViewUtil.fadeOut(attachmentViewStub.get(), 200).addListener(new Listener() { + @Override + public void onSuccess(Boolean result) { + thumbnail.clear(); + attachmentViewStub.get().setVisibility(View.GONE); + attachmentListener.onAttachmentChanged(); + } + + @Override + public void onFailure(ExecutionException e) { + } + }); + } else { + thumbnail.clear(); + attachmentViewStub.get().setVisibility(View.GONE); + attachmentListener.onAttachmentChanged(); + } markGarbage(getSlideUri()); slide = Optional.absent(); @@ -413,7 +421,7 @@ public class AttachmentManager { @Override public void onClick(View v) { cleanup(); - clear(); + clear(true); } } diff --git a/src/org/thoughtcrime/securesms/mms/AudioSlide.java b/src/org/thoughtcrime/securesms/mms/AudioSlide.java index 435e5d1fa0..79419af336 100644 --- a/src/org/thoughtcrime/securesms/mms/AudioSlide.java +++ b/src/org/thoughtcrime/securesms/mms/AudioSlide.java @@ -41,7 +41,7 @@ public class AudioSlide extends Slide { } public AudioSlide(Context context, Uri uri, long dataSize, String contentType) { - super(context, new UriAttachment(uri, null, contentType, AttachmentDatabase.TRANSFER_PROGRESS_STARTED, dataSize, null)); + super(context, new UriAttachment(uri, null, contentType, AttachmentDatabase.TRANSFER_PROGRESS_STARTED, dataSize, null, null)); } public AudioSlide(Context context, Attachment attachment) { diff --git a/src/org/thoughtcrime/securesms/mms/Slide.java b/src/org/thoughtcrime/securesms/mms/Slide.java index 233e52d57e..01e0ce956c 100644 --- a/src/org/thoughtcrime/securesms/mms/Slide.java +++ b/src/org/thoughtcrime/securesms/mms/Slide.java @@ -30,6 +30,9 @@ import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + public abstract class Slide { protected final Attachment attachment; @@ -65,6 +68,11 @@ public abstract class Slide { return Optional.fromNullable(attachment.getFileName()); } + @Nullable + public String getFastPreflightId() { + return attachment.getFastPreflightId(); + } + public long getFileSize() { return attachment.getSize(); } @@ -127,12 +135,18 @@ public abstract class Slide { boolean hasThumbnail, @Nullable String fileName) { - Optional resolvedType = Optional.fromNullable(MediaUtil.getMimeType(context, uri)); - return new UriAttachment(uri, hasThumbnail ? uri : null, resolvedType.or(defaultMime), AttachmentDatabase.TRANSFER_PROGRESS_STARTED, size, fileName); + try { + Optional resolvedType = Optional.fromNullable(MediaUtil.getMimeType(context, uri)); + String fastPreflightId = String.valueOf(SecureRandom.getInstance("SHA1PRNG").nextLong()); + return new UriAttachment(uri, hasThumbnail ? uri : null, resolvedType.or(defaultMime), AttachmentDatabase.TRANSFER_PROGRESS_STARTED, size, fileName, fastPreflightId); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } } @Override public boolean equals(Object other) { + if (other == null) return false; if (!(other instanceof Slide)) return false; Slide that = (Slide)other; diff --git a/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java b/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java index dd29837f4f..346e986d67 100644 --- a/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java +++ b/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java @@ -81,13 +81,13 @@ public class AndroidAutoReplyReceiver extends MasterSecretBroadcastReceiver { long expiresIn = preferences.isPresent() ? preferences.get().getExpireMessages() * 1000 : 0; if (recipients.isGroupRecipient()) { - Log.i("AndroidAutoReplyReceiver", "GroupRecipient, Sending media message"); + Log.w("AndroidAutoReplyReceiver", "GroupRecipient, Sending media message"); OutgoingMediaMessage reply = new OutgoingMediaMessage(recipients, responseText.toString(), new LinkedList(), System.currentTimeMillis(), subscriptionId, expiresIn, 0); - replyThreadId = MessageSender.send(context, masterSecret, reply, threadId, false); + replyThreadId = MessageSender.send(context, masterSecret, reply, threadId, false, null); } else { - Log.i("AndroidAutoReplyReceiver", "Sending regular message "); + Log.w("AndroidAutoReplyReceiver", "Sending regular message "); OutgoingTextMessage reply = new OutgoingTextMessage(recipients, responseText.toString(), expiresIn, subscriptionId); - replyThreadId = MessageSender.send(context, masterSecret, reply, threadId, false); + replyThreadId = MessageSender.send(context, masterSecret, reply, threadId, false, null); } List messageIds = DatabaseFactory.getThreadDatabase(context).setRead(replyThreadId, true); diff --git a/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java b/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java index 73decd5f52..d6cc314890 100644 --- a/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java +++ b/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java @@ -74,10 +74,10 @@ public class RemoteReplyReceiver extends MasterSecretBroadcastReceiver { Recipients recipients = RecipientFactory.getRecipientsForIds(context, recipientIds, false); if (recipients.isGroupRecipient()) { OutgoingMediaMessage reply = new OutgoingMediaMessage(recipients, responseText.toString(), new LinkedList(), System.currentTimeMillis(), subscriptionId, expiresIn, 0); - threadId = MessageSender.send(context, masterSecret, reply, -1, false); + threadId = MessageSender.send(context, masterSecret, reply, -1, false, null); } else { OutgoingTextMessage reply = new OutgoingTextMessage(recipients, responseText.toString(), expiresIn, subscriptionId); - threadId = MessageSender.send(context, masterSecret, reply, -1, false); + threadId = MessageSender.send(context, masterSecret, reply, -1, false, null); } List messageIds = DatabaseFactory.getThreadDatabase(context).setRead(threadId, true); diff --git a/src/org/thoughtcrime/securesms/service/QuickResponseService.java b/src/org/thoughtcrime/securesms/service/QuickResponseService.java index 1a0bfbe9c2..e4c85d64e4 100644 --- a/src/org/thoughtcrime/securesms/service/QuickResponseService.java +++ b/src/org/thoughtcrime/securesms/service/QuickResponseService.java @@ -60,10 +60,10 @@ public class QuickResponseService extends MasterSecretIntentService { if (!TextUtils.isEmpty(content)) { if (recipients.isSingleRecipient()) { - MessageSender.send(this, masterSecret, new OutgoingTextMessage(recipients, content, expiresIn, subscriptionId), -1, false); + MessageSender.send(this, masterSecret, new OutgoingTextMessage(recipients, content, expiresIn, subscriptionId), -1, false, null); } else { MessageSender.send(this, masterSecret, new OutgoingMediaMessage(recipients, new SlideDeck(), content, System.currentTimeMillis(), - subscriptionId, expiresIn, ThreadDatabase.DistributionTypes.DEFAULT), -1, false); + subscriptionId, expiresIn, ThreadDatabase.DistributionTypes.DEFAULT), -1, false, null); } } } catch (URISyntaxException e) { diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 3afd929a7d..80674b7baa 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.NotInDirectoryException; +import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.TextSecureDirectory; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.model.MessageRecord; @@ -61,7 +62,8 @@ public class MessageSender { final MasterSecret masterSecret, final OutgoingTextMessage message, final long threadId, - final boolean forceSms) + final boolean forceSms, + final SmsDatabase.InsertListener insertListener) { EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); Recipients recipients = message.getRecipients(); @@ -76,7 +78,7 @@ public class MessageSender { } long messageId = database.insertMessageOutbox(new MasterSecretUnion(masterSecret), allocatedThreadId, - message, forceSms, System.currentTimeMillis()); + message, forceSms, System.currentTimeMillis(), insertListener); sendTextMessage(context, recipients, forceSms, keyExchange, messageId, message.getExpiresIn()); @@ -87,7 +89,8 @@ public class MessageSender { final MasterSecret masterSecret, final OutgoingMediaMessage message, final long threadId, - final boolean forceSms) + final boolean forceSms, + final SmsDatabase.InsertListener insertListener) { try { ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context); @@ -102,7 +105,7 @@ public class MessageSender { } Recipients recipients = message.getRecipients(); - long messageId = database.insertMessageOutbox(new MasterSecretUnion(masterSecret), message, allocatedThreadId, forceSms); + long messageId = database.insertMessageOutbox(new MasterSecretUnion(masterSecret), message, allocatedThreadId, forceSms, insertListener); sendMediaMessage(context, masterSecret, recipients, forceSms, messageId, message.getExpiresIn());