diff --git a/res/layout/conversation_list_item_view.xml b/res/layout/conversation_list_item_view.xml index a0706d3044..2dafff95fc 100644 --- a/res/layout/conversation_list_item_view.xml +++ b/res/layout/conversation_list_item_view.xml @@ -22,66 +22,81 @@ android:layout_marginRight="10dp" android:layout_marginLeft="10dp" /> - + - + - + - + - - + - + tools:text="30 mins" + android:singleLine="true"/> - + diff --git a/res/values/strings.xml b/res/values/strings.xml index ba9d1c988f..3a39e7367f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -449,6 +449,7 @@ You called Called you Missed call + Media message You do not have an identity key. diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index 72c20837a8..f61e477e18 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -1080,7 +1080,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (threadId == -1) threadId = threadDatabase.getThreadIdFor(getRecipients(), thisDistributionType); draftDatabase.insertDrafts(new MasterCipher(thisMasterSecret), threadId, drafts); - threadDatabase.updateSnippet(threadId, drafts.getSnippet(ConversationActivity.this), System.currentTimeMillis(), Types.BASE_DRAFT_TYPE); + threadDatabase.updateSnippet(threadId, drafts.getSnippet(ConversationActivity.this), + drafts.getUriSnippet(ConversationActivity.this), + System.currentTimeMillis(), Types.BASE_DRAFT_TYPE); } else if (threadId > 0) { threadDatabase.update(threadId); } diff --git a/src/org/thoughtcrime/securesms/ConversationListAdapter.java b/src/org/thoughtcrime/securesms/ConversationListAdapter.java index ceba38875f..1b30d6adb6 100644 --- a/src/org/thoughtcrime/securesms/ConversationListAdapter.java +++ b/src/org/thoughtcrime/securesms/ConversationListAdapter.java @@ -47,6 +47,7 @@ import java.util.Set; public class ConversationListAdapter extends CursorRecyclerViewAdapter { private final ThreadDatabase threadDatabase; + private final MasterSecret masterSecret; private final MasterCipher masterCipher; private final Locale locale; private final Context context; @@ -86,6 +87,7 @@ public class ConversationListAdapter extends CursorRecyclerViewAdapter selectedThreads, boolean batchMode) { + public void set(@NonNull MasterSecret masterSecret, @NonNull ThreadRecord thread, + @NonNull Locale locale, @NonNull Set selectedThreads, boolean batchMode) + { this.selectedThreads = selectedThreads; this.recipients = thread.getRecipients(); this.threadId = thread.getThreadId(); @@ -109,6 +117,7 @@ public class ConversationListItem extends RelativeLayout dateView.setTypeface(read ? LIGHT_TYPEFACE : BOLD_TYPEFACE); } + setThumbnailSnippet(masterSecret, thread); setBatchState(batchMode); setBackground(thread); setRippleColor(recipients); @@ -136,6 +145,23 @@ public class ConversationListItem extends RelativeLayout return distributionType; } + private void setThumbnailSnippet(MasterSecret masterSecret, ThreadRecord thread) { + if (thread.getSnippetUri() != null) { + this.thumbnailView.setVisibility(View.VISIBLE); + this.thumbnailView.setImageResource(masterSecret, thread.getSnippetUri()); + + LayoutParams subjectParams = (RelativeLayout.LayoutParams)this.subjectView.getLayoutParams(); + subjectParams.addRule(RelativeLayout.LEFT_OF, R.id.thumbnail); + this.subjectView.setLayoutParams(subjectParams); + } else { + this.thumbnailView.setVisibility(View.GONE); + + LayoutParams subjectParams = (RelativeLayout.LayoutParams)this.subjectView.getLayoutParams(); + subjectParams.addRule(RelativeLayout.LEFT_OF, 0); + this.subjectView.setLayoutParams(subjectParams); + } + } + private void setBackground(ThreadRecord thread) { if (thread.isRead()) setBackgroundResource(readBackground); else setBackgroundResource(unreadBackround); diff --git a/src/org/thoughtcrime/securesms/components/ThumbnailView.java b/src/org/thoughtcrime/securesms/components/ThumbnailView.java index 8820c6e3b0..8847da76f5 100644 --- a/src/org/thoughtcrime/securesms/components/ThumbnailView.java +++ b/src/org/thoughtcrime/securesms/components/ThumbnailView.java @@ -5,6 +5,7 @@ import android.app.Activity; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; +import android.net.Uri; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.support.annotation.NonNull; @@ -124,6 +125,15 @@ public class ThumbnailView extends FrameLayout { else Glide.clear(image); } + public void setImageResource(@NonNull MasterSecret masterSecret, @NonNull Uri uri) { + if (transferControls.isPresent()) getTransferControls().setVisibility(View.GONE); + + Glide.with(getContext()).load(new DecryptableUri(masterSecret, uri)) + .crossFade() + .transform(new RoundedCorners(getContext(), true, radius, backgroundColorHint)) + .into(image); + } + public void setThumbnailClickListener(SlideClickListener listener) { this.thumbnailClickListener = listener; } diff --git a/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java b/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java index dbe572b829..43d4d9df83 100644 --- a/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java +++ b/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java @@ -266,6 +266,7 @@ public class AttachmentDatabase extends Database { partData.first.delete(); } else { notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(mmsId)); + notifyConversationListListeners(); } thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret, attachmentId)); diff --git a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java index 66a46ef716..18cbd066cb 100644 --- a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java @@ -46,28 +46,29 @@ import ws.com.google.android.mms.ContentType; public class DatabaseFactory { - private static final int INTRODUCED_IDENTITIES_VERSION = 2; - private static final int INTRODUCED_INDEXES_VERSION = 3; - private static final int INTRODUCED_DATE_SENT_VERSION = 4; - private static final int INTRODUCED_DRAFTS_VERSION = 5; - private static final int INTRODUCED_NEW_TYPES_VERSION = 6; - private static final int INTRODUCED_MMS_BODY_VERSION = 7; - private static final int INTRODUCED_MMS_FROM_VERSION = 8; - private static final int INTRODUCED_TOFU_IDENTITY_VERSION = 9; - private static final int INTRODUCED_PUSH_DATABASE_VERSION = 10; - private static final int INTRODUCED_GROUP_DATABASE_VERSION = 11; - private static final int INTRODUCED_PUSH_FIX_VERSION = 12; - private static final int INTRODUCED_DELIVERY_RECEIPTS = 13; - private static final int INTRODUCED_PART_DATA_SIZE_VERSION = 14; - private static final int INTRODUCED_THUMBNAILS_VERSION = 15; - private static final int INTRODUCED_IDENTITY_COLUMN_VERSION = 16; - private static final int INTRODUCED_UNIQUE_PART_IDS_VERSION = 17; - private static final int INTRODUCED_RECIPIENT_PREFS_DB = 18; - private static final int INTRODUCED_ENVELOPE_CONTENT_VERSION = 19; - private static final int INTRODUCED_COLOR_PREFERENCE_VERSION = 20; - private static final int INTRODUCED_DB_OPTIMIZATIONS_VERSION = 21; - private static final int INTRODUCED_INVITE_REMINDERS_VERSION = 22; - private static final int DATABASE_VERSION = 22; + private static final int INTRODUCED_IDENTITIES_VERSION = 2; + private static final int INTRODUCED_INDEXES_VERSION = 3; + private static final int INTRODUCED_DATE_SENT_VERSION = 4; + private static final int INTRODUCED_DRAFTS_VERSION = 5; + private static final int INTRODUCED_NEW_TYPES_VERSION = 6; + private static final int INTRODUCED_MMS_BODY_VERSION = 7; + private static final int INTRODUCED_MMS_FROM_VERSION = 8; + private static final int INTRODUCED_TOFU_IDENTITY_VERSION = 9; + private static final int INTRODUCED_PUSH_DATABASE_VERSION = 10; + private static final int INTRODUCED_GROUP_DATABASE_VERSION = 11; + private static final int INTRODUCED_PUSH_FIX_VERSION = 12; + private static final int INTRODUCED_DELIVERY_RECEIPTS = 13; + private static final int INTRODUCED_PART_DATA_SIZE_VERSION = 14; + private static final int INTRODUCED_THUMBNAILS_VERSION = 15; + private static final int INTRODUCED_IDENTITY_COLUMN_VERSION = 16; + private static final int INTRODUCED_UNIQUE_PART_IDS_VERSION = 17; + private static final int INTRODUCED_RECIPIENT_PREFS_DB = 18; + private static final int INTRODUCED_ENVELOPE_CONTENT_VERSION = 19; + private static final int INTRODUCED_COLOR_PREFERENCE_VERSION = 20; + private static final int INTRODUCED_DB_OPTIMIZATIONS_VERSION = 21; + private static final int INTRODUCED_INVITE_REMINDERS_VERSION = 22; + private static final int INTRODUCED_CONVERSATION_LIST_THUMBNAILS_VERSION = 23; + private static final int DATABASE_VERSION = 23; private static final String DATABASE_NAME = "messages.db"; private static final Object lock = new Object(); @@ -773,6 +774,10 @@ public class DatabaseFactory { db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN seen_invite_reminder INTEGER DEFAULT 0"); } + if (oldVersion < INTRODUCED_CONVERSATION_LIST_THUMBNAILS_VERSION) { + db.execSQL("ALTER TABLE thread ADD COLUMN snippet_uri TEXT DEFAULT NULL"); + } + db.setTransactionSuccessful(); db.endTransaction(); } diff --git a/src/org/thoughtcrime/securesms/database/DraftDatabase.java b/src/org/thoughtcrime/securesms/database/DraftDatabase.java index 631878f938..b9a5ceab8d 100644 --- a/src/org/thoughtcrime/securesms/database/DraftDatabase.java +++ b/src/org/thoughtcrime/securesms/database/DraftDatabase.java @@ -5,6 +5,8 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.net.Uri; +import android.support.annotation.Nullable; import android.util.Log; import org.thoughtcrime.securesms.R; @@ -136,7 +138,7 @@ public class DraftDatabase extends Database { public static class Drafts extends LinkedList { private Draft getDraftOfType(String type) { for (Draft draft : this) { - if (Draft.TEXT.equals(draft.getType())) { + if (type.equals(draft.getType())) { return draft; } } @@ -153,5 +155,15 @@ public class DraftDatabase extends Database { return ""; } } + + public @Nullable Uri getUriSnippet(Context context) { + Draft imageDraft = getDraftOfType(Draft.IMAGE); + + if (imageDraft != null && imageDraft.getValue() != null) { + return Uri.parse(imageDraft.getValue()); + } + + return null; + } } } diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 89c125c35d..63d047da8d 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -791,14 +791,14 @@ public class MmsDatabase extends MessagingDatabase { addressDatabase.insertAddressesForId(messageId, addresses); partsDatabase.insertAttachmentsForMessage(masterSecret, messageId, attachments); - notifyConversationListeners(contentValues.getAsLong(THREAD_ID)); - DatabaseFactory.getThreadDatabase(context).update(contentValues.getAsLong(THREAD_ID)); db.setTransactionSuccessful(); return messageId; } finally { db.endTransaction(); - } + notifyConversationListeners(contentValues.getAsLong(THREAD_ID)); + DatabaseFactory.getThreadDatabase(context).update(contentValues.getAsLong(THREAD_ID)); + } } public boolean delete(long messageId) { diff --git a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java index 1a99d950af..ff24daeb6b 100644 --- a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -22,6 +22,7 @@ import android.database.Cursor; import android.database.MergeCursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.net.Uri; import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.Log; @@ -29,8 +30,11 @@ import android.util.Log; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.crypto.MasterCipher; 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.ThreadRecord; +import org.thoughtcrime.securesms.mms.Slide; +import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.Recipients; @@ -45,6 +49,8 @@ import java.util.Set; public class ThreadDatabase extends Database { + private static final String TAG = ThreadDatabase.class.getSimpleName(); + static final String TABLE_NAME = "thread"; public static final String ID = "_id"; public static final String DATE = "date"; @@ -55,14 +61,14 @@ public class ThreadDatabase extends Database { public static final String READ = "read"; private static final String TYPE = "type"; private static final String ERROR = "error"; - private static final String HAS_ATTACHMENT = "has_attachment"; public static final String SNIPPET_TYPE = "snippet_type"; + private static final String SNIPPET_URI = "snippet_uri"; public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + DATE + " INTEGER DEFAULT 0, " + MESSAGE_COUNT + " INTEGER DEFAULT 0, " + RECIPIENT_IDS + " TEXT, " + SNIPPET + " TEXT, " + SNIPPET_CHARSET + " INTEGER DEFAULT 0, " + READ + " INTEGER DEFAULT 1, " + TYPE + " INTEGER DEFAULT 0, " + ERROR + " INTEGER DEFAULT 0, " + - SNIPPET_TYPE + " INTEGER DEFAULT 0);"; + SNIPPET_TYPE + " INTEGER DEFAULT 0, " + SNIPPET_URI + " TEXT DEFAULT NULL);"; public static final String[] CREATE_INDEXS = { "CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + RECIPIENT_IDS + ");", @@ -73,7 +79,7 @@ public class ThreadDatabase extends Database { } private long[] getRecipientIds(Recipients recipients) { - Set recipientSet = new HashSet(); + Set recipientSet = new HashSet<>(); List recipientList = recipients.getRecipientsList(); for (Recipient recipient : recipientList) { @@ -118,12 +124,13 @@ public class ThreadDatabase extends Database { return db.insert(TABLE_NAME, null, contentValues); } - private void updateThread(long threadId, long count, String body, long date, long type) + private void updateThread(long threadId, long count, String body, @Nullable Uri attachment, long date, long type) { ContentValues contentValues = new ContentValues(4); contentValues.put(DATE, date - date % 1000); contentValues.put(MESSAGE_COUNT, count); contentValues.put(SNIPPET, body); + contentValues.put(SNIPPET_URI, attachment == null ? null : attachment.toString()); contentValues.put(SNIPPET_TYPE, type); SQLiteDatabase db = databaseHelper.getWritableDatabase(); @@ -131,12 +138,13 @@ public class ThreadDatabase extends Database { notifyConversationListListeners(); } - public void updateSnippet(long threadId, String snippet, long date, long type) { + public void updateSnippet(long threadId, String snippet, @Nullable Uri attachment, long date, long type) { ContentValues contentValues = new ContentValues(3); contentValues.put(DATE, date - date % 1000); contentValues.put(SNIPPET, snippet); contentValues.put(SNIPPET_TYPE, type); + contentValues.put(SNIPPET_URI, attachment == null ? null : attachment.toString()); SQLiteDatabase db = databaseHelper.getWritableDatabase(); db.update(TABLE_NAME, contentValues, ID + " = ?", new String[] {threadId + ""}); notifyConversationListListeners(); @@ -144,7 +152,7 @@ public class ThreadDatabase extends Database { private void deleteThread(long threadId) { SQLiteDatabase db = databaseHelper.getWritableDatabase(); - db.delete(TABLE_NAME, ID_WHERE, new String[] {threadId+""}); + db.delete(TABLE_NAME, ID_WHERE, new String[] {threadId + ""}); notifyConversationListListeners(); } @@ -247,7 +255,7 @@ public class ThreadDatabase extends Database { contentValues.put(READ, 0); SQLiteDatabase db = databaseHelper.getWritableDatabase(); - db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId+""}); + db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId + ""}); notifyConversationListListeners(); } @@ -256,7 +264,7 @@ public class ThreadDatabase extends Database { contentValues.put(TYPE, distributionType); SQLiteDatabase db = databaseHelper.getWritableDatabase(); - db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId+""}); + db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId + ""}); notifyConversationListListeners(); } @@ -413,7 +421,7 @@ public class ThreadDatabase extends Database { if (record.isPush()) timestamp = record.getDateSent(); else timestamp = record.getDateReceived(); - updateThread(threadId, count, record.getBody().getBody(), timestamp, record.getType()); + updateThread(threadId, count, record.getBody().getBody(), getAttachmentUriFor(record), timestamp, record.getType()); notifyConversationListListeners(); return false; } else { @@ -427,6 +435,15 @@ public class ThreadDatabase extends Database { } } + private @Nullable Uri getAttachmentUriFor(MessageRecord record) { + if (!record.isMms() || record.isMmsNotification()) return null; + + SlideDeck slideDeck = ((MediaMmsMessageRecord)record).getSlideDeck(); + Slide thumbnail = slideDeck.getThumbnailSlide(); + + return thumbnail != null ? thumbnail.getThumbnailUri() : null; + } + public static interface ProgressListener { public void onProgress(int complete, int total); } @@ -459,9 +476,9 @@ public class ThreadDatabase extends Database { } public ThreadRecord getCurrent() { - long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.ID)); - String recipientId = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.RECIPIENT_IDS)); - Recipients recipients = RecipientFactory.getRecipientsForIds(context, recipientId, true); + long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.ID)); + String recipientId = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.RECIPIENT_IDS)); + Recipients recipients = RecipientFactory.getRecipientsForIds(context, recipientId, true); DisplayRecord.Body body = getPlaintextBody(cursor); long date = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.DATE)); @@ -469,8 +486,9 @@ public class ThreadDatabase extends Database { long read = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.READ)); long type = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_TYPE)); int distributionType = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.TYPE)); + Uri snippetUri = getSnippetUri(cursor); - return new ThreadRecord(context, body, recipients, date, count, + return new ThreadRecord(context, body, snippetUri, recipients, date, count, read == 1, threadId, type, distributionType); } @@ -492,6 +510,19 @@ public class ThreadDatabase extends Database { } } + private @Nullable Uri getSnippetUri(Cursor cursor) { + if (cursor.isNull(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_URI))) { + return null; + } + + try { + return Uri.parse(cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_URI))); + } catch (IllegalArgumentException e) { + Log.w(TAG, e); + return null; + } + } + public void close() { cursor.close(); } diff --git a/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java index 0ec4bfc11a..96223e04df 100644 --- a/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -17,6 +17,9 @@ package org.thoughtcrime.securesms.database.model; import android.content.Context; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.text.Spannable; import android.text.SpannableString; import android.text.TextUtils; @@ -36,22 +39,28 @@ import org.thoughtcrime.securesms.util.GroupUtil; */ public class ThreadRecord extends DisplayRecord { - private final Context context; - private final long count; - private final boolean read; - private final int distributionType; + private @NonNull final Context context; + private @Nullable final Uri snippetUri; + private final long count; + private final boolean read; + private final int distributionType; - public ThreadRecord(Context context, Body body, Recipients recipients, long date, - long count, boolean read, long threadId, long snippetType, - int distributionType) + public ThreadRecord(@NonNull Context context, @NonNull Body body, @Nullable Uri snippetUri, + @NonNull Recipients recipients, long date, long count, boolean read, + long threadId, long snippetType, int distributionType) { super(context, body, recipients, date, date, threadId, snippetType); this.context = context.getApplicationContext(); + this.snippetUri = snippetUri; this.count = count; this.read = read; this.distributionType = distributionType; } + public @Nullable Uri getSnippetUri() { + return snippetUri; + } + @Override public SpannableString getDisplayBody() { if (SmsDatabase.Types.isDecryptInProgressType(type)) { @@ -83,7 +92,7 @@ public class ThreadRecord extends DisplayRecord { return emphasisAdded(context.getString(org.thoughtcrime.securesms.R.string.ThreadRecord_missed_call)); } else { if (TextUtils.isEmpty(getBody().getBody())) { - return new SpannableString(context.getString(R.string.MessageNotifier_no_subject)); + return new SpannableString(emphasisAdded(context.getString(R.string.ThreadRecord_media_message))); } else { return new SpannableString(getBody().getBody()); }