From 43622e603dc880262e72ebd205fd3309ea15560a Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Tue, 24 Apr 2018 11:09:54 -0700 Subject: [PATCH] Save replies in drafts. Previously, quotes were not saved to drafts, meaning they would be lost when leaving the conversation or app. Now, a QuoteId (which represents the necessary data to restore the QuoteModel) is serialized and stored in the DraftDatabase. Fixes #7716 Closes #7729 --- res/values/strings.xml | 1 + .../securesms/ConversationActivity.java | 40 +++++++++++++ .../securesms/database/DraftDatabase.java | 2 + .../securesms/database/MmsSmsDatabase.java | 21 ++++++- .../securesms/jobs/PushDecryptJob.java | 28 +++------ .../thoughtcrime/securesms/mms/QuoteId.java | 59 +++++++++++++++++++ 6 files changed, 130 insertions(+), 21 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/mms/QuoteId.java 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; + } + } +}