diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4ff44a9089..1c62de864e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -53,6 +53,7 @@
(audio)
(video)
(location)
+ (reply)
Can\'t find an app to select media.
diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java
index 3b6e8c2f8a..39fc077963 100644
--- a/src/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationActivity.java
@@ -128,6 +128,8 @@ import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage;
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage;
+import org.thoughtcrime.securesms.mms.QuoteId;
+import org.thoughtcrime.securesms.mms.QuoteModel;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
@@ -1035,6 +1037,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
case Draft.VIDEO:
setMedia(Uri.parse(draft.getValue()), MediaType.VIDEO);
break;
+ case Draft.QUOTE:
+ new QuoteRestorationTask(draft.getValue()).execute();
+ break;
}
} catch (IOException e) {
Log.w(TAG, e);
@@ -1431,6 +1436,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
else if (slide.hasImage() && slide.getUri() != null) drafts.add(new Draft(Draft.IMAGE, slide.getUri().toString()));
}
+ Optional quote = inputPanel.getQuote();
+
+ if (quote.isPresent()) {
+ drafts.add(new Draft(Draft.QUOTE, new QuoteId(quote.get().getId(), quote.get().getAuthor()).serialize()));
+ }
+
return drafts;
}
@@ -2134,4 +2145,33 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
}
}
+
+ private class QuoteRestorationTask extends AsyncTask {
+
+ private final String serialized;
+
+ QuoteRestorationTask(@NonNull String serialized) {
+ this.serialized = serialized;
+ }
+
+ @Override
+ protected MessageRecord doInBackground(Void... voids) {
+ QuoteId quoteId = QuoteId.deserialize(serialized);
+
+ if (quoteId != null) {
+ return DatabaseFactory.getMmsSmsDatabase(getApplicationContext()).getMessageFor(quoteId.getId(), quoteId.getAuthor());
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(MessageRecord messageRecord) {
+ if (messageRecord != null) {
+ handleReplyMessage(messageRecord);
+ } else {
+ Log.e(TAG, "Failed to restore a quote from a draft. No matching message record.");
+ }
+ }
+ }
}
diff --git a/src/org/thoughtcrime/securesms/database/DraftDatabase.java b/src/org/thoughtcrime/securesms/database/DraftDatabase.java
index 5ca274cd51..9b66d0bfb6 100644
--- a/src/org/thoughtcrime/securesms/database/DraftDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/DraftDatabase.java
@@ -101,6 +101,7 @@ public class DraftDatabase extends Database {
public static final String VIDEO = "video";
public static final String AUDIO = "audio";
public static final String LOCATION = "location";
+ public static final String QUOTE = "quote";
private final String type;
private final String value;
@@ -125,6 +126,7 @@ public class DraftDatabase extends Database {
case VIDEO: return context.getString(R.string.DraftDatabase_Draft_video_snippet);
case AUDIO: return context.getString(R.string.DraftDatabase_Draft_audio_snippet);
case LOCATION: return context.getString(R.string.DraftDatabase_Draft_location_snippet);
+ case QUOTE: return context.getString(R.string.DraftDatabase_Draft_quote_snippet);
default: return null;
}
}
diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
index 68ffc478d0..7b688c794d 100644
--- a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
@@ -19,6 +19,7 @@ package org.thoughtcrime.securesms.database;
import android.content.Context;
import android.database.Cursor;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.util.Log;
import net.sqlcipher.database.SQLiteDatabase;
@@ -71,8 +72,24 @@ public class MmsSmsDatabase extends Database {
super(context, databaseHelper);
}
- public Cursor getMessagesFor(long timestamp) {
- return queryTables(PROJECTION, MmsSmsColumns.NORMALIZED_DATE_SENT + " = " + timestamp, null, null);
+ public @Nullable MessageRecord getMessageFor(long timestamp, Address author) {
+ MmsSmsDatabase db = DatabaseFactory.getMmsSmsDatabase(context);
+
+ try (Cursor cursor = queryTables(PROJECTION, MmsSmsColumns.NORMALIZED_DATE_SENT + " = " + timestamp, null, null)) {
+ MmsSmsDatabase.Reader reader = db.readerFor(cursor);
+
+ MessageRecord messageRecord;
+
+ while ((messageRecord = reader.getNext()) != null) {
+ if ((Util.isOwnNumber(context, author) && messageRecord.isOutgoing()) ||
+ (!Util.isOwnNumber(context, author) && messageRecord.getIndividualRecipient().getAddress().equals(author)))
+ {
+ return messageRecord;
+ }
+ }
+ }
+
+ return null;
}
public Cursor getConversation(long threadId, long limit) {
diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
index ab48fd9d1e..77442177b5 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
@@ -865,32 +865,22 @@ public class PushDecryptJob extends ContextJob {
return Optional.absent();
}
- MmsSmsDatabase db = DatabaseFactory.getMmsSmsDatabase(context);
- Address author = Address.fromExternal(context, quote.get().getAuthor().getNumber());
+ Address author = Address.fromExternal(context, quote.get().getAuthor().getNumber());
+ MessageRecord message = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(quote.get().getId(), author);
- try (Cursor cursor = db.getMessagesFor(quote.get().getId())) {
- MmsSmsDatabase.Reader reader = db.readerFor(cursor);
+ if (message != null) {
+ Log.w(TAG, "Found matching message record...");
- MessageRecord messageRecord;
+ List attachments = new LinkedList<>();
- while ((messageRecord = reader.getNext()) != null) {
- if ((Util.isOwnNumber(context, author) && messageRecord.isOutgoing()) ||
- (!Util.isOwnNumber(context, author) && messageRecord.getIndividualRecipient().getAddress().equals(author)))
- {
- Log.w(TAG, "Found matching message record...");
- List attachments = new LinkedList<>();
-
- if (messageRecord.isMms()) {
- attachments = ((MmsMessageRecord)messageRecord).getSlideDeck().asAttachments();
- }
-
- return Optional.of(new QuoteModel(quote.get().getId(), author, messageRecord.getBody(), attachments));
- }
+ if (message.isMms()) {
+ attachments = ((MmsMessageRecord) message).getSlideDeck().asAttachments();
}
+
+ return Optional.of(new QuoteModel(quote.get().getId(), author, message.getBody(), attachments));
}
Log.w(TAG, "Didn't find matching message record...");
-
return Optional.of(new QuoteModel(quote.get().getId(),
author,
quote.get().getText(),
diff --git a/src/org/thoughtcrime/securesms/mms/QuoteId.java b/src/org/thoughtcrime/securesms/mms/QuoteId.java
new file mode 100644
index 0000000000..7cfa56a03a
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/mms/QuoteId.java
@@ -0,0 +1,59 @@
+package org.thoughtcrime.securesms.mms;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.thoughtcrime.securesms.database.Address;
+import org.thoughtcrime.securesms.database.model.MessageRecord;
+
+/**
+ * Represents the information required to find the {@link MessageRecord} pointed to by a quote.
+ */
+public class QuoteId {
+
+ private static final String TAG = QuoteId.class.getSimpleName();
+
+ private static final String ID = "id";
+ private static final String AUTHOR = "author";
+
+ private final long id;
+ private final Address author;
+
+ public QuoteId(long id, @NonNull Address author) {
+ this.id = id;
+ this.author = author;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public @NonNull Address getAuthor() {
+ return author;
+ }
+
+ public @NonNull String serialize() {
+ try {
+ JSONObject object = new JSONObject();
+ object.put(ID, id);
+ object.put(AUTHOR, author.serialize());
+ return object.toString();
+ } catch (JSONException e) {
+ Log.e(TAG, "Failed to serialize to json", e);
+ return "";
+ }
+ }
+
+ public static @Nullable QuoteId deserialize(@NonNull String serialized) {
+ try {
+ JSONObject json = new JSONObject(serialized);
+ return new QuoteId(json.getLong(ID), Address.fromSerialized(json.getString(AUTHOR)));
+ } catch (JSONException e) {
+ Log.e(TAG, "Failed to deserialize from json", e);
+ return null;
+ }
+ }
+}