Improve UI send latency

// FREEBIE
This commit is contained in:
Moxie Marlinspike 2017-04-22 16:29:26 -07:00
parent 4d889a45e2
commit cb670d6783
30 changed files with 459 additions and 94 deletions

View file

@ -102,6 +102,7 @@ import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.MmsSmsColumns.Types; import org.thoughtcrime.securesms.database.MmsSmsColumns.Types;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientPreferenceEvent; import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientPreferenceEvent;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences; import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob;
import org.thoughtcrime.securesms.mms.AttachmentManager; import org.thoughtcrime.securesms.mms.AttachmentManager;
@ -274,7 +275,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if (!Util.isEmpty(composeText) || attachmentManager.isAttachmentPresent()) { if (!Util.isEmpty(composeText) || attachmentManager.isAttachmentPresent()) {
saveDraft(); saveDraft();
attachmentManager.clear(); attachmentManager.clear(false);
composeText.setText(""); composeText.setText("");
} }
@ -566,7 +567,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
recipients.setExpireMessages(expirationTime); recipients.setExpireMessages(expirationTime);
OutgoingExpirationUpdateMessage outgoingMessage = new OutgoingExpirationUpdateMessage(getRecipients(), System.currentTimeMillis(), expirationTime * 1000); 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; return null;
} }
@ -692,7 +693,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
new AsyncTask<OutgoingEndSessionMessage, Void, Long>() { new AsyncTask<OutgoingEndSessionMessage, Void, Long>() {
@Override @Override
protected Long doInBackground(OutgoingEndSessionMessage... messages) { 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 @Override
@ -740,7 +741,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
.build(); .build();
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(getRecipients(), context, null, System.currentTimeMillis(), 0); 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)); DatabaseFactory.getGroupDatabase(self).remove(groupId, TextSecurePreferences.getLocalNumber(self));
initializeEnabledCheck(); initializeEnabledCheck();
} catch (IOException e) { } catch (IOException e) {
@ -1511,13 +1512,19 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
outgoingMessage = new OutgoingSecureMediaMessage(outgoingMessage); outgoingMessage = new OutgoingSecureMediaMessage(outgoingMessage);
} }
attachmentManager.clear(); attachmentManager.clear(false);
composeText.setText(""); composeText.setText("");
final long id = fragment.stageOutgoingMessage(outgoingMessage);
new AsyncTask<OutgoingMediaMessage, Void, Long>() { new AsyncTask<OutgoingMediaMessage, Void, Long>() {
@Override @Override
protected Long doInBackground(OutgoingMediaMessage... messages) { 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 @Override
@ -1543,11 +1550,17 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
this.composeText.setText(""); this.composeText.setText("");
final long id = fragment.stageOutgoingMessage(message);
new AsyncTask<OutgoingTextMessage, Void, Long>() { new AsyncTask<OutgoingTextMessage, Void, Long>() {
@Override @Override
protected Long doInBackground(OutgoingTextMessage... messages) { 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 @Override

View file

@ -33,12 +33,14 @@ import android.widget.TextView;
import org.thoughtcrime.securesms.ConversationAdapter.HeaderViewHolder; import org.thoughtcrime.securesms.ConversationAdapter.HeaderViewHolder;
import org.thoughtcrime.securesms.crypto.MasterSecret; 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.DatabaseFactory;
import org.thoughtcrime.securesms.database.FastCursorRecyclerViewAdapter;
import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.MmsSmsDatabase; import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.Conversions; import org.thoughtcrime.securesms.util.Conversions;
import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.DateUtils;
@ -67,7 +69,7 @@ import java.util.Set;
* *
*/ */
public class ConversationAdapter <V extends View & BindableConversationItem> public class ConversationAdapter <V extends View & BindableConversationItem>
extends CursorRecyclerViewAdapter<ConversationAdapter.ViewHolder> extends FastCursorRecyclerViewAdapter<ConversationAdapter.ViewHolder, MessageRecord>
implements StickyHeaderDecoration.StickyHeaderAdapter<HeaderViewHolder> implements StickyHeaderDecoration.StickyHeaderAdapter<HeaderViewHolder>
{ {
@ -179,14 +181,13 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
@Override @Override
public void changeCursor(Cursor cursor) { public void changeCursor(Cursor cursor) {
messageRecordCache.clear(); messageRecordCache.clear();
super.cleanFastRecords();
super.changeCursor(cursor); super.changeCursor(cursor);
} }
@Override @Override
public void onBindItemViewHolder(ViewHolder viewHolder, @NonNull Cursor cursor) { protected void onBindItemViewHolder(ViewHolder viewHolder, @NonNull MessageRecord messageRecord) {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
MessageRecord messageRecord = getMessageRecord(cursor);
viewHolder.getView().bind(masterSecret, messageRecord, locale, batchSelected, recipients); viewHolder.getView().bind(masterSecret, messageRecord, locale, batchSelected, recipients);
Log.w(TAG, "Bind time: " + (System.currentTimeMillis() - start)); Log.w(TAG, "Bind time: " + (System.currentTimeMillis() - start));
} }
@ -237,9 +238,7 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
} }
@Override @Override
public int getItemViewType(@NonNull Cursor cursor) { public int getItemViewType(@NonNull MessageRecord messageRecord) {
MessageRecord messageRecord = getMessageRecord(cursor);
if (messageRecord.isGroupAction() || messageRecord.isCallLog() || messageRecord.isJoined() || if (messageRecord.isGroupAction() || messageRecord.isCallLog() || messageRecord.isJoined() ||
messageRecord.isExpirationTimerUpdate() || messageRecord.isEndSession() || messageRecord.isIdentityUpdate()) { messageRecord.isExpirationTimerUpdate() || messageRecord.isEndSession() || messageRecord.isIdentityUpdate()) {
return MESSAGE_TYPE_UPDATE; return MESSAGE_TYPE_UPDATE;
@ -259,14 +258,39 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
} }
} }
@Override
protected boolean isRecordForId(@NonNull MessageRecord record, long id) {
return record.getId() == id;
}
@Override @Override
public long getItemId(@NonNull Cursor cursor) { 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 String unique = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsColumns.UNIQUE_ROW_ID));
final byte[] bytes = digest.digest(unique.getBytes()); final byte[] bytes = digest.digest(unique.getBytes());
return Conversions.byteArrayToLong(bytes); 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)); long messageId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.ID));
String type = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT)); String type = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT));
@ -293,8 +317,7 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
int count = getItemCount(); int count = getItemCount();
for (int i=0;i<count;i++) { for (int i=0;i<count;i++) {
Cursor cursor = getCursorAtPositionOrThrow(i); MessageRecord messageRecord = getRecordForPositionOrThrow(i);
MessageRecord messageRecord = getMessageRecord(cursor);
if (messageRecord.isOutgoing() || messageRecord.getDateReceived() <= lastSeen) { if (messageRecord.isOutgoing() || messageRecord.getDateReceived() <= lastSeen) {
return i; return i;
@ -339,8 +362,7 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
if (position >= getItemCount()) return -1; if (position >= getItemCount()) return -1;
if (position < 0) return -1; if (position < 0) return -1;
Cursor cursor = getCursorAtPositionOrThrow(position); MessageRecord record = getRecordForPositionOrThrow(position);
MessageRecord record = getMessageRecord(cursor);
calendar.setTime(new Date(record.getDateSent())); calendar.setTime(new Date(record.getDateSent()));
return Util.hashCode(calendar.get(Calendar.YEAR), calendar.get(Calendar.DAY_OF_YEAR)); return Util.hashCode(calendar.get(Calendar.YEAR), calendar.get(Calendar.DAY_OF_YEAR));
@ -353,8 +375,7 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
if (position >= getItemCount()) return 0; if (position >= getItemCount()) return 0;
if (position < 0) return 0; if (position < 0) return 0;
Cursor cursor = getCursorAtPositionOrThrow(position); MessageRecord messageRecord = getRecordForPositionOrThrow(position);
MessageRecord messageRecord = getMessageRecord(cursor);
if (messageRecord.isOutgoing()) return 0; if (messageRecord.isOutgoing()) return 0;
else return messageRecord.getDateReceived(); else return messageRecord.getDateReceived();
@ -371,8 +392,8 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
@Override @Override
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) { public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) {
Cursor cursor = getCursorAtPositionOrThrow(position); MessageRecord messageRecord = getRecordForPositionOrThrow(position);
viewHolder.setText(DateUtils.getRelativeDate(getContext(), locale, getMessageRecord(cursor).getDateReceived())); viewHolder.setText(DateUtils.getRelativeDate(getContext(), locale, messageRecord.getDateReceived()));
} }
public void onBindLastSeenViewHolder(HeaderViewHolder viewHolder, int position) { public void onBindLastSeenViewHolder(HeaderViewHolder viewHolder, int position) {

View file

@ -54,20 +54,30 @@ import org.thoughtcrime.securesms.ConversationAdapter.HeaderViewHolder;
import org.thoughtcrime.securesms.ConversationAdapter.ItemClickListener; import org.thoughtcrime.securesms.ConversationAdapter.ItemClickListener;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.MmsSmsDatabase; 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.loaders.ConversationLoader;
import org.thoughtcrime.securesms.database.model.DisplayRecord;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord; 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.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.SaveAttachmentTask; import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment; import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
import org.thoughtcrime.securesms.util.StickyHeaderDecoration; import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.LinkedList; 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) { private void scrollToLastSeenPosition(final int lastSeenPosition) {
if (lastSeenPosition > 0) { if (lastSeenPosition > 0) {
list.post(new Runnable() { list.post(new Runnable() {

View file

@ -236,7 +236,7 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity implemen
Optional<RecipientsPreferences> preferences = DatabaseFactory.getRecipientPreferenceDatabase(context).getRecipientsPreferences(recipients.getIds()); Optional<RecipientsPreferences> preferences = DatabaseFactory.getRecipientPreferenceDatabase(context).getRecipientsPreferences(recipients.getIds());
int subscriptionId = preferences.isPresent() ? preferences.get().getDefaultSubscriptionId().or(-1) : -1; 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) { if (recipients.getPrimaryRecipient().getContactUri() != null) {
DatabaseFactory.getRecipientPreferenceDatabase(context).setSeenInviteReminder(recipients, true); DatabaseFactory.getRecipientPreferenceDatabase(context).setSeenInviteReminder(recipients, true);

View file

@ -28,9 +28,12 @@ public abstract class Attachment {
@Nullable @Nullable
private final byte[] digest; private final byte[] digest;
@Nullable
private final String fastPreflightId;
public Attachment(@NonNull String contentType, int transferState, long size, @Nullable String fileName, public Attachment(@NonNull String contentType, int transferState, long size, @Nullable String fileName,
@Nullable String location, @Nullable String key, @Nullable String relay, @Nullable String location, @Nullable String key, @Nullable String relay,
@Nullable byte[] digest) @Nullable byte[] digest, @Nullable String fastPreflightId)
{ {
this.contentType = contentType; this.contentType = contentType;
this.transferState = transferState; this.transferState = transferState;
@ -40,6 +43,7 @@ public abstract class Attachment {
this.key = key; this.key = key;
this.relay = relay; this.relay = relay;
this.digest = digest; this.digest = digest;
this.fastPreflightId = fastPreflightId;
} }
@Nullable @Nullable
@ -90,4 +94,9 @@ public abstract class Attachment {
public byte[] getDigest() { public byte[] getDigest() {
return digest; return digest;
} }
@Nullable
public String getFastPreflightId() {
return fastPreflightId;
}
} }

View file

@ -16,9 +16,9 @@ public class DatabaseAttachment extends Attachment {
boolean hasData, boolean hasThumbnail, boolean hasData, boolean hasThumbnail,
String contentType, int transferProgress, long size, String contentType, int transferProgress, long size,
String fileName, String location, String key, String relay, 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.attachmentId = attachmentId;
this.hasData = hasData; this.hasData = hasData;
this.hasThumbnail = hasThumbnail; this.hasThumbnail = hasThumbnail;

View file

@ -10,7 +10,7 @@ import org.thoughtcrime.securesms.database.MmsDatabase;
public class MmsNotificationAttachment extends Attachment { public class MmsNotificationAttachment extends Attachment {
public MmsNotificationAttachment(int status, long size) { 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 @Nullable

View file

@ -20,7 +20,7 @@ public class PointerAttachment extends Attachment {
@NonNull String key, @NonNull String relay, @NonNull String key, @NonNull String relay,
@Nullable byte[] digest) @Nullable byte[] digest)
{ {
super(contentType, transferState, size, fileName, location, key, relay, digest); super(contentType, transferState, size, fileName, location, key, relay, digest, null);
} }
@Nullable @Nullable

View file

@ -12,14 +12,14 @@ public class UriAttachment extends Attachment {
public UriAttachment(@NonNull Uri uri, @NonNull String contentType, int transferState, long size, public UriAttachment(@NonNull Uri uri, @NonNull String contentType, int transferState, long size,
@Nullable String fileName) @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, public UriAttachment(@NonNull Uri dataUri, @Nullable Uri thumbnailUri,
@NonNull String contentType, int transferState, long size, @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.dataUri = dataUri;
this.thumbnailUri = thumbnailUri; this.thumbnailUri = thumbnailUri;
} }

View file

@ -66,7 +66,7 @@ public class ThumbnailView extends FrameLayout {
if (attrs != null) { if (attrs != null) {
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ThumbnailView, 0, 0); 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(); typedArray.recycle();
} }
} }
@ -120,13 +120,22 @@ public class ThumbnailView extends FrameLayout {
return; 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()) { if (!isContextValid()) {
Log.w(TAG, "Not loading slide, context is invalid"); Log.w(TAG, "Not loading slide, context is invalid");
return; return;
} }
Log.w(TAG, "loading part with id " + slide.asAttachment().getDataUri() 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; this.slide = slide;

View file

@ -86,6 +86,7 @@ public class AttachmentDatabase extends Database {
static final String THUMBNAIL_ASPECT_RATIO = "aspect_ratio"; static final String THUMBNAIL_ASPECT_RATIO = "aspect_ratio";
static final String UNIQUE_ID = "unique_id"; static final String UNIQUE_ID = "unique_id";
static final String DIGEST = "digest"; 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_DONE = 0;
public static final int TRANSFER_PROGRESS_STARTED = 1; public static final int TRANSFER_PROGRESS_STARTED = 1;
@ -98,7 +99,7 @@ public class AttachmentDatabase extends Database {
MMS_ID, CONTENT_TYPE, NAME, CONTENT_DISPOSITION, MMS_ID, CONTENT_TYPE, NAME, CONTENT_DISPOSITION,
CONTENT_LOCATION, DATA, THUMBNAIL, TRANSFER_STATE, CONTENT_LOCATION, DATA, THUMBNAIL, TRANSFER_STATE,
SIZE, FILE_NAME, THUMBNAIL, THUMBNAIL_ASPECT_RATIO, 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, " + public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ROW_ID + " INTEGER PRIMARY KEY, " +
MMS_ID + " INTEGER, " + "seq" + " INTEGER DEFAULT 0, " + MMS_ID + " INTEGER, " + "seq" + " INTEGER DEFAULT 0, " +
@ -108,7 +109,7 @@ public class AttachmentDatabase extends Database {
"ctt_t" + " TEXT, " + "encrypted" + " INTEGER, " + "ctt_t" + " TEXT, " + "encrypted" + " INTEGER, " +
TRANSFER_STATE + " INTEGER, "+ DATA + " TEXT, " + SIZE + " INTEGER, " + TRANSFER_STATE + " INTEGER, "+ DATA + " TEXT, " + SIZE + " INTEGER, " +
FILE_NAME + " TEXT, " + THUMBNAIL + " TEXT, " + THUMBNAIL_ASPECT_RATIO + " REAL, " + 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 = { public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS part_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");", "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(CONTENT_DISPOSITION, (String)null);
values.put(DIGEST, (byte[])null); values.put(DIGEST, (byte[])null);
values.put(NAME, (String) 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) { if (database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings()) == 0) {
//noinspection ResultOfMethodCallIgnored //noinspection ResultOfMethodCallIgnored
@ -334,7 +336,8 @@ public class AttachmentDatabase extends Database {
databaseAttachment.getLocation(), databaseAttachment.getLocation(),
databaseAttachment.getKey(), databaseAttachment.getKey(),
databaseAttachment.getRelay(), 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_LOCATION)),
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_DISPOSITION)), cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_DISPOSITION)),
cursor.getString(cursor.getColumnIndexOrThrow(NAME)), 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(NAME, attachment.getRelay());
contentValues.put(FILE_NAME, fileName); contentValues.put(FILE_NAME, fileName);
contentValues.put(SIZE, attachment.getSize()); contentValues.put(SIZE, attachment.getSize());
contentValues.put(FAST_PREFLIGHT_ID, attachment.getFastPreflightId());
if (partData != null) { if (partData != null) {
contentValues.put(DATA, partData.first.getAbsolutePath()); contentValues.put(DATA, partData.first.getAbsolutePath());

View file

@ -115,6 +115,7 @@ public abstract class CursorRecyclerViewAdapter<VH extends RecyclerView.ViewHold
if (!isActiveCursor()) return 0; if (!isActiveCursor()) return 0;
return cursor.getCount() return cursor.getCount()
+ getFastAccessSize()
+ (hasHeaderView() ? 1 : 0) + (hasHeaderView() ? 1 : 0)
+ (hasFooterView() ? 1 : 0); + (hasFooterView() ? 1 : 0);
} }
@ -144,16 +145,22 @@ public abstract class CursorRecyclerViewAdapter<VH extends RecyclerView.ViewHold
@Override @Override
public final void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { public final void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
if (!isHeaderPosition(position) && !isFooterPosition(position)) { if (!isHeaderPosition(position) && !isFooterPosition(position)) {
onBindItemViewHolder((VH)viewHolder, getCursorAtPositionOrThrow(position)); if (isFastAccessPosition(position)) onBindFastAccessItemViewHolder((VH)viewHolder, position);
else onBindItemViewHolder((VH)viewHolder, getCursorAtPositionOrThrow(position));
} }
} }
public abstract void onBindItemViewHolder(VH viewHolder, @NonNull Cursor cursor); public abstract void onBindItemViewHolder(VH viewHolder, @NonNull Cursor cursor);
protected void onBindFastAccessItemViewHolder(VH viewHolder, int position) {
}
@Override @Override
public final int getItemViewType(int position) { public final int getItemViewType(int position) {
if (isHeaderPosition(position)) return HEADER_TYPE; if (isHeaderPosition(position)) return HEADER_TYPE;
if (isFooterPosition(position)) return FOOTER_TYPE; if (isFooterPosition(position)) return FOOTER_TYPE;
if (isFastAccessPosition(position)) return getFastAccessItemViewType(position);
return getItemViewType(getCursorAtPositionOrThrow(position)); return getItemViewType(getCursorAtPositionOrThrow(position));
} }
@ -165,6 +172,7 @@ public abstract class CursorRecyclerViewAdapter<VH extends RecyclerView.ViewHold
public final long getItemId(int position) { public final long getItemId(int position) {
if (isHeaderPosition(position)) return HEADER_ID; if (isHeaderPosition(position)) return HEADER_ID;
if (isFooterPosition(position)) return FOOTER_ID; if (isFooterPosition(position)) return FOOTER_ID;
if (isFastAccessPosition(position)) return getFastAccessItemId(position);
long itemId = getItemId(getCursorAtPositionOrThrow(position)); long itemId = getItemId(getCursorAtPositionOrThrow(position));
return itemId <= Long.MIN_VALUE + 1 ? itemId + 2 : itemId; return itemId <= Long.MIN_VALUE + 1 ? itemId + 2 : itemId;
} }
@ -196,7 +204,27 @@ public abstract class CursorRecyclerViewAdapter<VH extends RecyclerView.ViewHold
} }
private int getCursorPosition(int position) { private int getCursorPosition(int position) {
return hasHeaderView() ? position - 1 : position; if (hasHeaderView()) {
position -= 1;
}
return position - getFastAccessSize();
}
protected int getFastAccessItemViewType(int position) {
return 0;
}
protected boolean isFastAccessPosition(int position) {
return false;
}
protected long getFastAccessItemId(int position) {
return 0;
}
protected int getFastAccessSize() {
return 0;
} }
private class AdapterDataSetObserver extends DataSetObserver { private class AdapterDataSetObserver extends DataSetObserver {

View file

@ -77,7 +77,8 @@ public class DatabaseFactory {
private static final int INTRODUCED_DIGEST = 30; private static final int INTRODUCED_DIGEST = 30;
private static final int INTRODUCED_NOTIFIED = 31; private static final int INTRODUCED_NOTIFIED = 31;
private static final int INTRODUCED_DOCUMENTS = 32; private static final int INTRODUCED_DOCUMENTS = 32;
private static final int DATABASE_VERSION = 32; private static final int INTRODUCED_FAST_PREFLIGHT = 33;
private static final int DATABASE_VERSION = 33;
private static final String DATABASE_NAME = "messages.db"; private static final String DATABASE_NAME = "messages.db";
private static final Object lock = new Object(); private static final Object lock = new Object();
@ -858,6 +859,10 @@ public class DatabaseFactory {
db.execSQL("ALTER TABLE part ADD COLUMN file_name TEXT"); db.execSQL("ALTER TABLE part ADD COLUMN file_name TEXT");
} }
if (oldVersion < INTRODUCED_FAST_PREFLIGHT) {
db.execSQL("ALTER TABLE part ADD COLUMN fast_preflight_id TEXT");
}
db.setTransactionSuccessful(); db.setTransactionSuccessful();
db.endTransaction(); db.endTransaction();
} }

View file

@ -64,7 +64,7 @@ public class EncryptingSmsDatabase extends SmsDatabase {
public long insertMessageOutbox(MasterSecretUnion masterSecret, long threadId, public long insertMessageOutbox(MasterSecretUnion masterSecret, long threadId,
OutgoingTextMessage message, boolean forceSms, OutgoingTextMessage message, boolean forceSms,
long timestamp) long timestamp, InsertListener insertListener)
{ {
long type = Types.BASE_SENDING_TYPE; long type = Types.BASE_SENDING_TYPE;
@ -76,7 +76,7 @@ public class EncryptingSmsDatabase extends SmsDatabase {
type |= Types.ENCRYPTION_ASYMMETRIC_BIT; type |= Types.ENCRYPTION_ASYMMETRIC_BIT;
} }
return insertMessageOutbox(threadId, message, type, forceSms, timestamp); return insertMessageOutbox(threadId, message, type, forceSms, timestamp, insertListener);
} }
public Optional<InsertResult> insertMessageInbox(@NonNull MasterSecretUnion masterSecret, public Optional<InsertResult> insertMessageInbox(@NonNull MasterSecretUnion masterSecret,

View file

@ -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<VH extends RecyclerView.ViewHolder, T>
extends CursorRecyclerViewAdapter<VH>
{
private static final String TAG = FastCursorRecyclerViewAdapter.class.getSimpleName();
private final LinkedList<T> fastRecords = new LinkedList<>();
private final List<Long> 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<Long> releaseIdIterator = releasedRecordIds.iterator();
while (releaseIdIterator.hasNext()) {
long releasedId = releaseIdIterator.next();
Iterator<T> 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;
}
}

View file

@ -26,6 +26,7 @@ public class MediaDatabase extends Database {
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_LOCATION + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_LOCATION + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_DISPOSITION + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_DISPOSITION + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DIGEST + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DIGEST + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.FAST_PREFLIGHT_ID + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.NAME + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.NAME + ", "
+ MmsDatabase.TABLE_NAME + "." + MmsDatabase.MESSAGE_BOX + ", " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.MESSAGE_BOX + ", "
+ MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_SENT + ", " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_SENT + ", "

View file

@ -19,6 +19,7 @@ package org.thoughtcrime.securesms.database;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri; import android.net.Uri;
@ -69,6 +70,8 @@ import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.api.util.InvalidNumberException;
import java.io.IOException; import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -144,6 +147,7 @@ public class MmsDatabase extends MessagingDatabase {
AttachmentDatabase.CONTENT_TYPE, AttachmentDatabase.CONTENT_TYPE,
AttachmentDatabase.CONTENT_LOCATION, AttachmentDatabase.CONTENT_LOCATION,
AttachmentDatabase.DIGEST, AttachmentDatabase.DIGEST,
AttachmentDatabase.FAST_PREFLIGHT_ID,
AttachmentDatabase.CONTENT_DISPOSITION, AttachmentDatabase.CONTENT_DISPOSITION,
AttachmentDatabase.NAME, AttachmentDatabase.NAME,
AttachmentDatabase.TRANSFER_STATE AttachmentDatabase.TRANSFER_STATE
@ -694,7 +698,8 @@ public class MmsDatabase extends MessagingDatabase {
databaseAttachment.getLocation(), databaseAttachment.getLocation(),
databaseAttachment.getKey(), databaseAttachment.getKey(),
databaseAttachment.getRelay(), databaseAttachment.getRelay(),
databaseAttachment.getDigest())); databaseAttachment.getDigest(),
databaseAttachment.getFastPreflightId()));
} }
return insertMediaMessage(new MasterSecretUnion(masterSecret), return insertMediaMessage(new MasterSecretUnion(masterSecret),
@ -867,7 +872,8 @@ public class MmsDatabase extends MessagingDatabase {
public long insertMessageOutbox(@NonNull MasterSecretUnion masterSecret, public long insertMessageOutbox(@NonNull MasterSecretUnion masterSecret,
@NonNull OutgoingMediaMessage message, @NonNull OutgoingMediaMessage message,
long threadId, boolean forceSms) long threadId, boolean forceSms,
SmsDatabase.InsertListener insertListener)
throws MmsException throws MmsException
{ {
long type = Types.BASE_SENDING_TYPE; long type = Types.BASE_SENDING_TYPE;
@ -924,6 +930,10 @@ public class MmsDatabase extends MessagingDatabase {
long messageId = insertMediaMessage(masterSecret, addresses, message.getBody(), long messageId = insertMediaMessage(masterSecret, addresses, message.getBody(),
message.getAttachments(), contentValues); message.getAttachments(), contentValues);
if (insertListener != null) {
insertListener.onComplete();
}
DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId); DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId);
jobManager.add(new TrimThreadJob(context, threadId)); jobManager.add(new TrimThreadJob(context, threadId));
@ -1106,6 +1116,10 @@ public class MmsDatabase extends MessagingDatabase {
return new Reader(masterSecret, cursor); return new Reader(masterSecret, cursor);
} }
public OutgoingMessageReader readerFor(OutgoingMediaMessage message, long threadId) {
return new OutgoingMessageReader(message, threadId);
}
public static class Status { public static class Status {
public static final int DOWNLOAD_INITIALIZED = 1; public static final int DOWNLOAD_INITIALIZED = 1;
public static final int DOWNLOAD_NO_CONNECTIVITY = 2; 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 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<IdentityKeyMismatch>(),
new LinkedList<NetworkFailure>(),
message.getSubscriptionId(),
message.getExpiresIn(),
System.currentTimeMillis());
}
}
public class Reader { public class Reader {
private final Cursor cursor; private final Cursor cursor;

View file

@ -97,6 +97,14 @@ public interface MmsSmsColumns {
return false; 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) { public static boolean isForcedSms(long type) {
return (type & MESSAGE_FORCE_SMS_BIT) != 0; return (type & MESSAGE_FORCE_SMS_BIT) != 0;
} }

View file

@ -70,6 +70,7 @@ public class MmsSmsDatabase extends Database {
AttachmentDatabase.CONTENT_TYPE, AttachmentDatabase.CONTENT_TYPE,
AttachmentDatabase.CONTENT_LOCATION, AttachmentDatabase.CONTENT_LOCATION,
AttachmentDatabase.DIGEST, AttachmentDatabase.DIGEST,
AttachmentDatabase.FAST_PREFLIGHT_ID,
AttachmentDatabase.CONTENT_DISPOSITION, AttachmentDatabase.CONTENT_DISPOSITION,
AttachmentDatabase.NAME, AttachmentDatabase.NAME,
AttachmentDatabase.TRANSFER_STATE}; AttachmentDatabase.TRANSFER_STATE};
@ -165,6 +166,7 @@ public class MmsSmsDatabase extends Database {
AttachmentDatabase.CONTENT_TYPE, AttachmentDatabase.CONTENT_TYPE,
AttachmentDatabase.CONTENT_LOCATION, AttachmentDatabase.CONTENT_LOCATION,
AttachmentDatabase.DIGEST, AttachmentDatabase.DIGEST,
AttachmentDatabase.FAST_PREFLIGHT_ID,
AttachmentDatabase.CONTENT_DISPOSITION, AttachmentDatabase.CONTENT_DISPOSITION,
AttachmentDatabase.NAME, AttachmentDatabase.NAME,
AttachmentDatabase.TRANSFER_STATE}; AttachmentDatabase.TRANSFER_STATE};
@ -194,6 +196,7 @@ public class MmsSmsDatabase extends Database {
AttachmentDatabase.CONTENT_TYPE, AttachmentDatabase.CONTENT_TYPE,
AttachmentDatabase.CONTENT_LOCATION, AttachmentDatabase.CONTENT_LOCATION,
AttachmentDatabase.DIGEST, AttachmentDatabase.DIGEST,
AttachmentDatabase.FAST_PREFLIGHT_ID,
AttachmentDatabase.CONTENT_DISPOSITION, AttachmentDatabase.CONTENT_DISPOSITION,
AttachmentDatabase.NAME, AttachmentDatabase.NAME,
AttachmentDatabase.TRANSFER_STATE}; AttachmentDatabase.TRANSFER_STATE};
@ -249,6 +252,7 @@ public class MmsSmsDatabase extends Database {
mmsColumnsPresent.add(AttachmentDatabase.CONTENT_TYPE); mmsColumnsPresent.add(AttachmentDatabase.CONTENT_TYPE);
mmsColumnsPresent.add(AttachmentDatabase.CONTENT_LOCATION); mmsColumnsPresent.add(AttachmentDatabase.CONTENT_LOCATION);
mmsColumnsPresent.add(AttachmentDatabase.DIGEST); mmsColumnsPresent.add(AttachmentDatabase.DIGEST);
mmsColumnsPresent.add(AttachmentDatabase.FAST_PREFLIGHT_ID);
mmsColumnsPresent.add(AttachmentDatabase.CONTENT_DISPOSITION); mmsColumnsPresent.add(AttachmentDatabase.CONTENT_DISPOSITION);
mmsColumnsPresent.add(AttachmentDatabase.NAME); mmsColumnsPresent.add(AttachmentDatabase.NAME);
mmsColumnsPresent.add(AttachmentDatabase.TRANSFER_STATE); mmsColumnsPresent.add(AttachmentDatabase.TRANSFER_STATE);

View file

@ -32,6 +32,7 @@ import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchList; import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchList;
import org.thoughtcrime.securesms.database.model.DisplayRecord; 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.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.jobs.TrimThreadJob; import org.thoughtcrime.securesms.jobs.TrimThreadJob;
import org.thoughtcrime.securesms.recipients.Recipient; 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 org.whispersystems.signalservice.api.util.InvalidNumberException;
import java.io.IOException; import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -594,7 +597,8 @@ public class SmsDatabase extends MessagingDatabase {
} }
protected long insertMessageOutbox(long threadId, OutgoingTextMessage message, 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; if (message.isKeyExchange()) type |= Types.KEY_EXCHANGE_BIT;
else if (message.isSecureMessage()) type |= (Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_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(); SQLiteDatabase db = databaseHelper.getWritableDatabase();
long messageId = db.insert(TABLE_NAME, ADDRESS, contentValues); long messageId = db.insert(TABLE_NAME, ADDRESS, contentValues);
if (insertListener != null) {
insertListener.onComplete();
}
DatabaseFactory.getThreadDatabase(context).update(threadId, true); DatabaseFactory.getThreadDatabase(context).update(threadId, true);
DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId); DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId);
notifyConversationListeners(threadId); notifyConversationListeners(threadId);
@ -768,6 +776,37 @@ public class SmsDatabase extends MessagingDatabase {
return new Reader(cursor); 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<IdentityKeyMismatch>(),
message.getSubscriptionId(), message.getExpiresIn(),
System.currentTimeMillis());
}
}
public class Reader { public class Reader {
private final Cursor cursor; private final Cursor cursor;
@ -858,4 +897,8 @@ public class SmsDatabase extends MessagingDatabase {
} }
} }
public interface InsertListener {
public void onComplete();
}
} }

View file

@ -108,7 +108,7 @@ public class GroupManager {
} }
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, groupContext, avatarAttachment, System.currentTimeMillis(), 0); 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); return new GroupActionResult(groupRecipient, threadId);
} }

View file

@ -205,7 +205,7 @@ public class GroupMessageProcessor {
Recipients recipients = RecipientFactory.getRecipientsFromString(context, GroupUtil.getEncodedId(group.getGroupId()), false); Recipients recipients = RecipientFactory.getRecipientsFromString(context, GroupUtil.getEncodedId(group.getGroupId()), false);
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipients, storage, null, envelope.getTimestamp(), 0); OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipients, storage, null, envelope.getTimestamp(), 0);
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); 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); mmsDatabase.markAsSent(messageId, true);

View file

@ -321,7 +321,7 @@ public class PushDecryptJob extends ContextJob {
SecurityEvent.broadcastSecurityUpdateEvent(context); SecurityEvent.broadcastSecurityUpdateEvent(context);
long messageId = database.insertMessageOutbox(masterSecret, threadId, outgoingEndSessionMessage, long messageId = database.insertMessageOutbox(masterSecret, threadId, outgoingEndSessionMessage,
false, message.getTimestamp()); false, message.getTimestamp(), null);
database.markAsSent(messageId, true); database.markAsSent(messageId, true);
} }
@ -521,7 +521,7 @@ public class PushDecryptJob extends ContextJob {
message.getMessage().getExpiresInSeconds() * 1000); message.getMessage().getExpiresInSeconds() * 1000);
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); 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); database.markAsSent(messageId, true);
@ -554,7 +554,7 @@ public class PushDecryptJob extends ContextJob {
} }
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); 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); database.markAsSent(messageId, true);
@ -635,7 +635,7 @@ public class PushDecryptJob extends ContextJob {
} }
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); 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); database.markAsSent(messageId, true);

View file

@ -35,6 +35,7 @@ import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.Toast; import android.widget.Toast;
import com.bumptech.glide.Glide;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException; import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import com.google.android.gms.common.GooglePlayServicesRepairableException; import com.google.android.gms.common.GooglePlayServicesRepairableException;
import com.google.android.gms.location.places.ui.PlacePicker; import com.google.android.gms.location.places.ui.PlacePicker;
@ -109,8 +110,10 @@ public class AttachmentManager {
} }
public void clear() { public void clear(boolean animate) {
if (attachmentViewStub.resolved()) { if (attachmentViewStub.resolved()) {
if (animate) {
ViewUtil.fadeOut(attachmentViewStub.get(), 200).addListener(new Listener<Boolean>() { ViewUtil.fadeOut(attachmentViewStub.get(), 200).addListener(new Listener<Boolean>() {
@Override @Override
public void onSuccess(Boolean result) { public void onSuccess(Boolean result) {
@ -123,6 +126,11 @@ public class AttachmentManager {
public void onFailure(ExecutionException e) { public void onFailure(ExecutionException e) {
} }
}); });
} else {
thumbnail.clear();
attachmentViewStub.get().setVisibility(View.GONE);
attachmentListener.onAttachmentChanged();
}
markGarbage(getSlideUri()); markGarbage(getSlideUri());
slide = Optional.absent(); slide = Optional.absent();
@ -413,7 +421,7 @@ public class AttachmentManager {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
cleanup(); cleanup();
clear(); clear(true);
} }
} }

View file

@ -41,7 +41,7 @@ public class AudioSlide extends Slide {
} }
public AudioSlide(Context context, Uri uri, long dataSize, String contentType) { 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) { public AudioSlide(Context context, Attachment attachment) {

View file

@ -30,6 +30,9 @@ import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public abstract class Slide { public abstract class Slide {
protected final Attachment attachment; protected final Attachment attachment;
@ -65,6 +68,11 @@ public abstract class Slide {
return Optional.fromNullable(attachment.getFileName()); return Optional.fromNullable(attachment.getFileName());
} }
@Nullable
public String getFastPreflightId() {
return attachment.getFastPreflightId();
}
public long getFileSize() { public long getFileSize() {
return attachment.getSize(); return attachment.getSize();
} }
@ -127,12 +135,18 @@ public abstract class Slide {
boolean hasThumbnail, boolean hasThumbnail,
@Nullable String fileName) @Nullable String fileName)
{ {
try {
Optional<String> resolvedType = Optional.fromNullable(MediaUtil.getMimeType(context, uri)); Optional<String> resolvedType = Optional.fromNullable(MediaUtil.getMimeType(context, uri));
return new UriAttachment(uri, hasThumbnail ? uri : null, resolvedType.or(defaultMime), AttachmentDatabase.TRANSFER_PROGRESS_STARTED, size, fileName); 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 @Override
public boolean equals(Object other) { public boolean equals(Object other) {
if (other == null) return false;
if (!(other instanceof Slide)) return false; if (!(other instanceof Slide)) return false;
Slide that = (Slide)other; Slide that = (Slide)other;

View file

@ -81,13 +81,13 @@ public class AndroidAutoReplyReceiver extends MasterSecretBroadcastReceiver {
long expiresIn = preferences.isPresent() ? preferences.get().getExpireMessages() * 1000 : 0; long expiresIn = preferences.isPresent() ? preferences.get().getExpireMessages() * 1000 : 0;
if (recipients.isGroupRecipient()) { 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<Attachment>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0); OutgoingMediaMessage reply = new OutgoingMediaMessage(recipients, responseText.toString(), new LinkedList<Attachment>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0);
replyThreadId = MessageSender.send(context, masterSecret, reply, threadId, false); replyThreadId = MessageSender.send(context, masterSecret, reply, threadId, false, null);
} else { } else {
Log.i("AndroidAutoReplyReceiver", "Sending regular message "); Log.w("AndroidAutoReplyReceiver", "Sending regular message ");
OutgoingTextMessage reply = new OutgoingTextMessage(recipients, responseText.toString(), expiresIn, subscriptionId); 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<MarkedMessageInfo> messageIds = DatabaseFactory.getThreadDatabase(context).setRead(replyThreadId, true); List<MarkedMessageInfo> messageIds = DatabaseFactory.getThreadDatabase(context).setRead(replyThreadId, true);

View file

@ -74,10 +74,10 @@ public class RemoteReplyReceiver extends MasterSecretBroadcastReceiver {
Recipients recipients = RecipientFactory.getRecipientsForIds(context, recipientIds, false); Recipients recipients = RecipientFactory.getRecipientsForIds(context, recipientIds, false);
if (recipients.isGroupRecipient()) { if (recipients.isGroupRecipient()) {
OutgoingMediaMessage reply = new OutgoingMediaMessage(recipients, responseText.toString(), new LinkedList<Attachment>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0); OutgoingMediaMessage reply = new OutgoingMediaMessage(recipients, responseText.toString(), new LinkedList<Attachment>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0);
threadId = MessageSender.send(context, masterSecret, reply, -1, false); threadId = MessageSender.send(context, masterSecret, reply, -1, false, null);
} else { } else {
OutgoingTextMessage reply = new OutgoingTextMessage(recipients, responseText.toString(), expiresIn, subscriptionId); 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<MarkedMessageInfo> messageIds = DatabaseFactory.getThreadDatabase(context).setRead(threadId, true); List<MarkedMessageInfo> messageIds = DatabaseFactory.getThreadDatabase(context).setRead(threadId, true);

View file

@ -60,10 +60,10 @@ public class QuickResponseService extends MasterSecretIntentService {
if (!TextUtils.isEmpty(content)) { if (!TextUtils.isEmpty(content)) {
if (recipients.isSingleRecipient()) { 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 { } else {
MessageSender.send(this, masterSecret, new OutgoingMediaMessage(recipients, new SlideDeck(), content, System.currentTimeMillis(), 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) { } catch (URISyntaxException e) {

View file

@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.NotInDirectoryException; import org.thoughtcrime.securesms.database.NotInDirectoryException;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.TextSecureDirectory; import org.thoughtcrime.securesms.database.TextSecureDirectory;
import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
@ -61,7 +62,8 @@ public class MessageSender {
final MasterSecret masterSecret, final MasterSecret masterSecret,
final OutgoingTextMessage message, final OutgoingTextMessage message,
final long threadId, final long threadId,
final boolean forceSms) final boolean forceSms,
final SmsDatabase.InsertListener insertListener)
{ {
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
Recipients recipients = message.getRecipients(); Recipients recipients = message.getRecipients();
@ -76,7 +78,7 @@ public class MessageSender {
} }
long messageId = database.insertMessageOutbox(new MasterSecretUnion(masterSecret), allocatedThreadId, 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()); sendTextMessage(context, recipients, forceSms, keyExchange, messageId, message.getExpiresIn());
@ -87,7 +89,8 @@ public class MessageSender {
final MasterSecret masterSecret, final MasterSecret masterSecret,
final OutgoingMediaMessage message, final OutgoingMediaMessage message,
final long threadId, final long threadId,
final boolean forceSms) final boolean forceSms,
final SmsDatabase.InsertListener insertListener)
{ {
try { try {
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context); ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
@ -102,7 +105,7 @@ public class MessageSender {
} }
Recipients recipients = message.getRecipients(); 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()); sendMediaMessage(context, masterSecret, recipients, forceSms, messageId, message.getExpiresIn());