From c8238df8ffae640cb0c70503940a1710dc625c2f Mon Sep 17 00:00:00 2001
From: Mikunj <mikunj@live.com.au>
Date: Wed, 16 Oct 2019 14:32:10 +1100
Subject: [PATCH 01/14] Update checks to isFromUnkownContact.

---
 .../securesms/util/AttachmentUtil.java            | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/src/org/thoughtcrime/securesms/util/AttachmentUtil.java b/src/org/thoughtcrime/securesms/util/AttachmentUtil.java
index 81c338b370..84ea6cf386 100644
--- a/src/org/thoughtcrime/securesms/util/AttachmentUtil.java
+++ b/src/org/thoughtcrime/securesms/util/AttachmentUtil.java
@@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
 import org.thoughtcrime.securesms.database.DatabaseFactory;
 import org.thoughtcrime.securesms.database.model.MessageRecord;
 import org.thoughtcrime.securesms.logging.Log;
+import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus;
 
 import java.util.Collections;
 import java.util.Set;
@@ -106,14 +107,16 @@ public class AttachmentUtil {
 
   @WorkerThread
   private static boolean isFromUnknownContact(@NonNull Context context, @NonNull DatabaseAttachment attachment) {
+    MessageRecord message;
     try (Cursor messageCursor = DatabaseFactory.getMmsDatabase(context).getMessage(attachment.getMmsId())) {
-      final MessageRecord message = DatabaseFactory.getMmsDatabase(context).readerFor(messageCursor).getNext();
-
-      if (message == null || (!message.getRecipient().isSystemContact() && !message.isOutgoing() && !Util.isOwnNumber(context, message.getRecipient().getAddress()))) {
-        return true;
-      }
+      message = DatabaseFactory.getMmsDatabase(context).readerFor(messageCursor).getNext();
     }
 
-    return false;
+    if (message == null) { return true; }
+
+    // check to see if we're friends with the person
+    long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(message.getRecipient());
+    boolean isFriend = threadId >= 0 && DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId) == LokiThreadFriendRequestStatus.FRIENDS;
+    return (!isFriend && !message.isOutgoing() && !Util.isOwnNumber(context, message.getRecipient().getAddress()));
   }
 }

From c6f5adb447c5b48fe9cd8393b78dba3ee11f44f1 Mon Sep 17 00:00:00 2001
From: Mikunj <mikunj@live.com.au>
Date: Thu, 17 Oct 2019 10:39:59 +1100
Subject: [PATCH 02/14] Update attachments to use url.

---
 res/layout/conversation_input_panel.xml         |  3 +--
 .../securesms/attachments/Attachment.java       |  8 +++++++-
 .../attachments/DatabaseAttachment.java         |  4 ++--
 .../attachments/MmsNotificationAttachment.java  |  2 +-
 .../attachments/PointerAttachment.java          | 10 ++++++----
 .../securesms/attachments/UriAttachment.java    |  2 +-
 .../securesms/database/AttachmentDatabase.java  | 17 +++++++++++------
 .../securesms/database/GroupDatabase.java       | 17 ++++++++++++++---
 .../securesms/database/MmsDatabase.java         |  3 ++-
 .../database/helpers/SQLCipherOpenHelper.java   |  3 +++
 .../securesms/jobs/AttachmentDownloadJob.java   |  2 +-
 .../securesms/jobs/AvatarDownloadJob.java       |  5 +++--
 .../securesms/jobs/PushMediaSendJob.java        |  4 ++--
 .../securesms/jobs/PushSendJob.java             |  2 +-
 14 files changed, 55 insertions(+), 27 deletions(-)

diff --git a/res/layout/conversation_input_panel.xml b/res/layout/conversation_input_panel.xml
index be0ffb8ee1..8c744fa541 100644
--- a/res/layout/conversation_input_panel.xml
+++ b/res/layout/conversation_input_panel.xml
@@ -108,8 +108,7 @@
                         android:layout_width="wrap_content"
                         android:layout_height="match_parent"
                         android:clipChildren="false"
-                        android:clipToPadding="false"
-                        android:visibility="gone">
+                        android:clipToPadding="false">
 
                         <org.thoughtcrime.securesms.components.HidingLinearLayout
                             android:id="@+id/quick_attachment_toggle"
diff --git a/src/org/thoughtcrime/securesms/attachments/Attachment.java b/src/org/thoughtcrime/securesms/attachments/Attachment.java
index 760ef65e33..e4b056af29 100644
--- a/src/org/thoughtcrime/securesms/attachments/Attachment.java
+++ b/src/org/thoughtcrime/securesms/attachments/Attachment.java
@@ -44,10 +44,13 @@ public abstract class Attachment {
   @Nullable
   private final StickerLocator stickerLocator;
 
+  // Loki
+  private final String url;
+
   public Attachment(@NonNull String contentType, int transferState, long size, @Nullable String fileName,
                     @Nullable String location, @Nullable String key, @Nullable String relay,
                     @Nullable byte[] digest, @Nullable String fastPreflightId, boolean voiceNote,
-                    int width, int height, boolean quote, @Nullable String caption, @Nullable StickerLocator stickerLocator)
+                    int width, int height, boolean quote, @Nullable String caption, @Nullable StickerLocator stickerLocator, String url)
   {
     this.contentType     = contentType;
     this.transferState   = transferState;
@@ -64,6 +67,7 @@ public abstract class Attachment {
     this.quote           = quote;
     this.stickerLocator  = stickerLocator;
     this.caption         = caption;
+    this.url             = url;
   }
 
   @Nullable
@@ -147,4 +151,6 @@ public abstract class Attachment {
   public @Nullable String getCaption() {
     return caption;
   }
+
+  public String getUrl() { return url; }
 }
diff --git a/src/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java b/src/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java
index bcb4f7b056..498bcaf490 100644
--- a/src/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java
+++ b/src/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java
@@ -19,9 +19,9 @@ public class DatabaseAttachment extends Attachment {
                             String fileName, String location, String key, String relay,
                             byte[] digest, String fastPreflightId, boolean voiceNote,
                             int width, int height, boolean quote, @Nullable String caption,
-                            @Nullable StickerLocator stickerLocator)
+                            @Nullable StickerLocator stickerLocator, String url)
   {
-    super(contentType, transferProgress, size, fileName, location, key, relay, digest, fastPreflightId, voiceNote, width, height, quote, caption, stickerLocator);
+    super(contentType, transferProgress, size, fileName, location, key, relay, digest, fastPreflightId, voiceNote, width, height, quote, caption, stickerLocator, url);
     this.attachmentId = attachmentId;
     this.hasData      = hasData;
     this.hasThumbnail = hasThumbnail;
diff --git a/src/org/thoughtcrime/securesms/attachments/MmsNotificationAttachment.java b/src/org/thoughtcrime/securesms/attachments/MmsNotificationAttachment.java
index 1826b7f214..8885849a8f 100644
--- a/src/org/thoughtcrime/securesms/attachments/MmsNotificationAttachment.java
+++ b/src/org/thoughtcrime/securesms/attachments/MmsNotificationAttachment.java
@@ -10,7 +10,7 @@ import org.thoughtcrime.securesms.database.MmsDatabase;
 public class MmsNotificationAttachment extends Attachment {
 
   public MmsNotificationAttachment(int status, long size) {
-    super("application/mms", getTransferStateFromStatus(status), size, null, null, null, null, null, null, false, 0, 0, false, null, null);
+    super("application/mms", getTransferStateFromStatus(status), size, null, null, null, null, null, null, false, 0, 0, false, null, null, "");
   }
 
   @Nullable
diff --git a/src/org/thoughtcrime/securesms/attachments/PointerAttachment.java b/src/org/thoughtcrime/securesms/attachments/PointerAttachment.java
index b862142bbf..43b201767c 100644
--- a/src/org/thoughtcrime/securesms/attachments/PointerAttachment.java
+++ b/src/org/thoughtcrime/securesms/attachments/PointerAttachment.java
@@ -20,9 +20,9 @@ public class PointerAttachment extends Attachment {
                             @Nullable String fileName,  @NonNull String location,
                             @Nullable String key, @Nullable String relay,
                             @Nullable byte[] digest, @Nullable String fastPreflightId, boolean voiceNote,
-                            int width, int height, @Nullable String caption, @Nullable StickerLocator stickerLocator)
+                            int width, int height, @Nullable String caption, @Nullable StickerLocator stickerLocator, String url)
   {
-    super(contentType, transferState, size, fileName, location, key, relay, digest, fastPreflightId, voiceNote, width, height, false, caption, stickerLocator);
+    super(contentType, transferState, size, fileName, location, key, relay, digest, fastPreflightId, voiceNote, width, height, false, caption, stickerLocator, url);
   }
 
   @Nullable
@@ -99,7 +99,8 @@ public class PointerAttachment extends Attachment {
                                       pointer.get().asPointer().getWidth(),
                                       pointer.get().asPointer().getHeight(),
                                       pointer.get().asPointer().getCaption().orNull(),
-        stickerLocator));
+                                      stickerLocator,
+                                      pointer.get().asPointer().getUrl()));
 
   }
 
@@ -119,6 +120,7 @@ public class PointerAttachment extends Attachment {
                                              thumbnail != null ? thumbnail.asPointer().getWidth() : 0,
                                              thumbnail != null ? thumbnail.asPointer().getHeight() : 0,
                                              thumbnail != null ? thumbnail.asPointer().getCaption().orNull() : null,
-                                             null));
+                                             null,
+                                             thumbnail != null ? thumbnail.asPointer().getUrl() : ""));
   }
 }
diff --git a/src/org/thoughtcrime/securesms/attachments/UriAttachment.java b/src/org/thoughtcrime/securesms/attachments/UriAttachment.java
index f7c5f21808..c238630bff 100644
--- a/src/org/thoughtcrime/securesms/attachments/UriAttachment.java
+++ b/src/org/thoughtcrime/securesms/attachments/UriAttachment.java
@@ -23,7 +23,7 @@ public class UriAttachment extends Attachment {
                        @Nullable String fileName, @Nullable String fastPreflightId,
                        boolean voiceNote, boolean quote, @Nullable String caption, @Nullable StickerLocator stickerLocator)
   {
-    super(contentType, transferState, size, fileName, null, null, null, null, fastPreflightId, voiceNote, width, height, quote, caption, stickerLocator);
+    super(contentType, transferState, size, fileName, null, null, null, null, fastPreflightId, voiceNote, width, height, quote, caption, stickerLocator, "");
     this.dataUri      = dataUri;
     this.thumbnailUri = thumbnailUri;
   }
diff --git a/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java b/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java
index 92f5d10467..2beec07057 100644
--- a/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java
@@ -103,7 +103,7 @@ public class AttachmentDatabase extends Database {
           static final String WIDTH                  = "width";
           static final String HEIGHT                 = "height";
           static final String CAPTION                = "caption";
-
+  public  static final String URL                    = "url";
   public  static final String DIRECTORY              = "parts";
 
   public static final int TRANSFER_PROGRESS_DONE    = 0;
@@ -119,7 +119,7 @@ public class AttachmentDatabase extends Database {
                                                            SIZE, FILE_NAME, THUMBNAIL, THUMBNAIL_ASPECT_RATIO,
                                                            UNIQUE_ID, DIGEST, FAST_PREFLIGHT_ID, VOICE_NOTE,
                                                            QUOTE, DATA_RANDOM, THUMBNAIL_RANDOM, WIDTH, HEIGHT,
-                                                           CAPTION, STICKER_PACK_ID, STICKER_PACK_KEY, STICKER_ID};
+                                                           CAPTION, STICKER_PACK_ID, STICKER_PACK_KEY, STICKER_ID, URL};
 
   public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ROW_ID + " INTEGER PRIMARY KEY, " +
     MMS_ID + " INTEGER, " + "seq" + " INTEGER DEFAULT 0, "                        +
@@ -132,7 +132,7 @@ public class AttachmentDatabase extends Database {
     UNIQUE_ID + " INTEGER NOT NULL, " + DIGEST + " BLOB, " + FAST_PREFLIGHT_ID + " TEXT, " +
     VOICE_NOTE + " INTEGER DEFAULT 0, " + DATA_RANDOM + " BLOB, " + THUMBNAIL_RANDOM + " BLOB, " +
     QUOTE + " INTEGER DEFAULT 0, " + WIDTH + " INTEGER DEFAULT 0, " + HEIGHT + " INTEGER DEFAULT 0, " +
-    CAPTION + " TEXT DEFAULT NULL, " + STICKER_PACK_ID + " TEXT DEFAULT NULL, " +
+    CAPTION + " TEXT DEFAULT NULL, " + URL + " TEXT, " + STICKER_PACK_ID + " TEXT DEFAULT NULL, " +
     STICKER_PACK_KEY + " DEFAULT NULL, " + STICKER_ID + " INTEGER DEFAULT -1);";
 
   public static final String[] CREATE_INDEXS = {
@@ -361,6 +361,7 @@ public class AttachmentDatabase extends Database {
     values.put(DIGEST, (byte[])null);
     values.put(NAME, (String) null);
     values.put(FAST_PREFLIGHT_ID, (String)null);
+    values.put(URL, "");
 
     if (database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings()) == 0) {
       //noinspection ResultOfMethodCallIgnored
@@ -384,6 +385,7 @@ public class AttachmentDatabase extends Database {
     values.put(NAME, attachment.getRelay());
     values.put(SIZE, attachment.getSize());
     values.put(FAST_PREFLIGHT_ID, attachment.getFastPreflightId());
+    values.put(URL, attachment.getUrl());
 
     database.update(TABLE_NAME, values, PART_ID_WHERE, id.toStrings());
   }
@@ -451,7 +453,8 @@ public class AttachmentDatabase extends Database {
                                   mediaStream.getHeight(),
                                   databaseAttachment.isQuote(),
                                   databaseAttachment.getCaption(),
-                                  databaseAttachment.getSticker());
+                                  databaseAttachment.getSticker(),
+                                  databaseAttachment.getUrl());
   }
 
 
@@ -650,7 +653,7 @@ public class AttachmentDatabase extends Database {
                                                   ? new StickerLocator(object.getString(STICKER_PACK_ID),
                                                                        object.getString(STICKER_PACK_KEY),
                                                                        object.getInt(STICKER_ID))
-                                                  : null));
+                                                  : null, "")); // TODO: Not sure if this will break something
           }
         }
 
@@ -679,7 +682,8 @@ public class AttachmentDatabase extends Database {
                                                                     ? new StickerLocator(cursor.getString(cursor.getColumnIndexOrThrow(STICKER_PACK_ID)),
                                                                                          cursor.getString(cursor.getColumnIndexOrThrow(STICKER_PACK_KEY)),
                                                                                          cursor.getInt(cursor.getColumnIndexOrThrow(STICKER_ID)))
-                                                                    : null));
+                                                                    : null,
+                                                                cursor.getString(cursor.getColumnIndexOrThrow(URL))));
       }
     } catch (JSONException e) {
       throw new AssertionError(e);
@@ -718,6 +722,7 @@ public class AttachmentDatabase extends Database {
     contentValues.put(HEIGHT, attachment.getHeight());
     contentValues.put(QUOTE, quote);
     contentValues.put(CAPTION, attachment.getCaption());
+    contentValues.put(URL, attachment.getUrl());
 
     if (attachment.isSticker()) {
       contentValues.put(STICKER_PACK_ID, attachment.getSticker().getPackId());
diff --git a/src/org/thoughtcrime/securesms/database/GroupDatabase.java b/src/org/thoughtcrime/securesms/database/GroupDatabase.java
index 845ba29cc6..747ed36e41 100644
--- a/src/org/thoughtcrime/securesms/database/GroupDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/GroupDatabase.java
@@ -50,6 +50,9 @@ public class GroupDatabase extends Database {
   private static final String ACTIVE              = "active";
   private static final String MMS                 = "mms";
 
+  // Loki
+  private static final String AVATAR_URL          = "avatar_url";
+
   public static final String CREATE_TABLE =
       "CREATE TABLE " + TABLE_NAME +
           " (" + ID + " INTEGER PRIMARY KEY, " +
@@ -64,6 +67,7 @@ public class GroupDatabase extends Database {
           TIMESTAMP + " INTEGER, " +
           ACTIVE + " INTEGER DEFAULT 1, " +
           AVATAR_DIGEST + " BLOB, " +
+          AVATAR_URL + " TEXT, " +
           MMS + " INTEGER DEFAULT 0);";
 
   public static final String[] CREATE_INDEXS = {
@@ -72,7 +76,7 @@ public class GroupDatabase extends Database {
 
   private static final String[] GROUP_PROJECTION = {
       GROUP_ID, TITLE, MEMBERS, AVATAR, AVATAR_ID, AVATAR_KEY, AVATAR_CONTENT_TYPE, AVATAR_RELAY, AVATAR_DIGEST,
-      TIMESTAMP, ACTIVE, MMS
+      TIMESTAMP, ACTIVE, MMS, AVATAR_URL
   };
 
   static final List<String> TYPED_GROUP_PROJECTION = Stream.of(GROUP_PROJECTION).map(columnName -> TABLE_NAME + "." + columnName).toList();
@@ -167,6 +171,7 @@ public class GroupDatabase extends Database {
       contentValues.put(AVATAR_KEY, avatar.getKey());
       contentValues.put(AVATAR_CONTENT_TYPE, avatar.getContentType());
       contentValues.put(AVATAR_DIGEST, avatar.getDigest().orNull());
+      contentValues.put(AVATAR_URL, avatar.getUrl());
     }
 
     contentValues.put(AVATAR_RELAY, relay);
@@ -194,6 +199,7 @@ public class GroupDatabase extends Database {
       contentValues.put(AVATAR_CONTENT_TYPE, avatar.getContentType());
       contentValues.put(AVATAR_KEY, avatar.getKey());
       contentValues.put(AVATAR_DIGEST, avatar.getDigest().orNull());
+      contentValues.put(AVATAR_URL, avatar.getUrl());
     }
 
     databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues,
@@ -344,7 +350,8 @@ public class GroupDatabase extends Database {
                              cursor.getString(cursor.getColumnIndexOrThrow(AVATAR_RELAY)),
                              cursor.getInt(cursor.getColumnIndexOrThrow(ACTIVE)) == 1,
                              cursor.getBlob(cursor.getColumnIndexOrThrow(AVATAR_DIGEST)),
-                             cursor.getInt(cursor.getColumnIndexOrThrow(MMS)) == 1);
+                             cursor.getInt(cursor.getColumnIndexOrThrow(MMS)) == 1,
+                             cursor.getString(cursor.getColumnIndexOrThrow(AVATAR_URL)));
     }
 
     @Override
@@ -367,10 +374,11 @@ public class GroupDatabase extends Database {
     private final String        relay;
     private final boolean       active;
     private final boolean       mms;
+    private final String        url;
 
     public GroupRecord(String id, String title, String members, byte[] avatar,
                        long avatarId, byte[] avatarKey, String avatarContentType,
-                       String relay, boolean active, byte[] avatarDigest, boolean mms)
+                       String relay, boolean active, byte[] avatarDigest, boolean mms, String url)
     {
       this.id                = id;
       this.title             = title;
@@ -382,6 +390,7 @@ public class GroupDatabase extends Database {
       this.relay             = relay;
       this.active            = active;
       this.mms               = mms;
+      this.url               = url;
 
       if (!TextUtils.isEmpty(members)) this.members = Address.fromSerializedList(members, ',');
       else                             this.members = new LinkedList<>();
@@ -438,5 +447,7 @@ public class GroupDatabase extends Database {
     public boolean isMms() {
       return mms;
     }
+
+    public String getUrl() { return url; }
   }
 }
diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java
index 6fea8f244e..5ce830b3b4 100644
--- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java
@@ -778,7 +778,8 @@ public class MmsDatabase extends MessagingDatabase {
                                                databaseAttachment.getHeight(),
                                                databaseAttachment.isQuote(),
                                                databaseAttachment.getCaption(),
-                                               databaseAttachment.getSticker()));
+                                               databaseAttachment.getSticker(),
+                                               databaseAttachment.getUrl()));
       }
 
       return insertMediaMessage(request.getBody(),
diff --git a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
index 28122e956c..9216a2314b 100644
--- a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
+++ b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
@@ -499,6 +499,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
       if (oldVersion < lokiV3) {
         db.execSQL(LokiAPIDatabase.getCreatePairingAuthorisationTableCommand());
         db.execSQL(LokiThreadDatabase.getCreatePublicChatTableCommand());
+
+        db.execSQL("ALTER TABLE groups ADD COLUMN avatar_url TEXT");
+        db.execSQL("ALTER TABLE part ADD COLUMN url TEXT");
       }
 
       db.setTransactionSuccessful();
diff --git a/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java
index 947ed11250..4734ee2e5c 100644
--- a/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java
@@ -213,7 +213,7 @@ public class AttachmentDownloadJob extends BaseJob implements InjectableType {
                                                 Optional.fromNullable(attachment.getDigest()),
                                                 Optional.fromNullable(attachment.getFileName()),
                                                 attachment.isVoiceNote(),
-                                                Optional.absent());
+                                                Optional.absent(), attachment.getUrl());
     } catch (IOException | ArithmeticException e) {
       Log.w(TAG, e);
       throw new InvalidPartException(e);
diff --git a/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java
index b97a410dbd..3536051086 100644
--- a/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java
@@ -80,8 +80,9 @@ public class AvatarDownloadJob extends BaseJob implements InjectableType {
         String           relay       = record.get().getRelay();
         Optional<byte[]> digest      = Optional.fromNullable(record.get().getAvatarDigest());
         Optional<String> fileName    = Optional.absent();
+        String url = record.get().getUrl();
 
-        if (avatarId == -1 || key == null) {
+        if (avatarId == -1 || key == null || url.isEmpty()) {
           return;
         }
 
@@ -92,7 +93,7 @@ public class AvatarDownloadJob extends BaseJob implements InjectableType {
         attachment = File.createTempFile("avatar", "tmp", context.getCacheDir());
         attachment.deleteOnExit();
 
-        SignalServiceAttachmentPointer pointer     = new SignalServiceAttachmentPointer(avatarId, contentType, key, Optional.of(0), Optional.absent(), 0, 0, digest, fileName, false, Optional.absent());
+        SignalServiceAttachmentPointer pointer     = new SignalServiceAttachmentPointer(avatarId, contentType, key, Optional.of(0), Optional.absent(), 0, 0, digest, fileName, false, Optional.absent(), url);
         InputStream                    inputStream = receiver.retrieveAttachment(pointer, attachment, MAX_AVATAR_SIZE);
         Bitmap                         avatar      = BitmapUtil.createScaledBitmap(context, new AttachmentModel(attachment, key, 0, digest), 500, 500);
 
diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
index 478557be40..1b3de7a276 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
@@ -105,8 +105,8 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
       List<Attachment>     attachments = new LinkedList<>();
 
       // Loki - For now all attachments are re-fetched by the receiver
-      // attachments.addAll(message.getAttachments());
-      // attachments.addAll(Stream.of(message.getLinkPreviews()).filter(p -> p.getThumbnail().isPresent()).map(p -> p.getThumbnail().get()).toList());
+      attachments.addAll(message.getAttachments());
+      attachments.addAll(Stream.of(message.getLinkPreviews()).filter(p -> p.getThumbnail().isPresent()).map(p -> p.getThumbnail().get()).toList());
       attachments.addAll(Stream.of(message.getSharedContacts()).filter(c -> c.getAvatar() != null).map(c -> c.getAvatar().getAttachment()).withoutNulls().toList());
 
       List<AttachmentUploadJob> attachmentJobs = Stream.of(attachments).map(a -> new AttachmentUploadJob(((DatabaseAttachment) a).getAttachmentId())).toList();
diff --git a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java
index e64db9029a..aaedf8f25a 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java
@@ -172,7 +172,7 @@ public abstract class PushSendJob extends SendJob {
                                                 Optional.fromNullable(attachment.getDigest()),
                                                 Optional.fromNullable(attachment.getFileName()),
                                                 attachment.isVoiceNote(),
-                                                Optional.fromNullable(attachment.getCaption()));
+                                                Optional.fromNullable(attachment.getCaption()), attachment.getUrl());
     } catch (IOException | ArithmeticException e) {
       Log.w(TAG, e);
       return null;

From fd9f1b712fcc99833c16a5efb921bc39e26c2985 Mon Sep 17 00:00:00 2001
From: Mikunj <mikunj@live.com.au>
Date: Fri, 18 Oct 2019 12:40:41 +1100
Subject: [PATCH 03/14] Get attachment logic to work.

---
 res/layout/attachment_type_selector.xml       | 168 ++++++++++--------
 res/menu/conversation.xml                     |   4 +-
 .../conversation/ConversationActivity.java    |   4 +-
 .../conversation/ConversationItem.java        |   2 +-
 .../securesms/database/MmsSmsDatabase.java    |   2 +-
 .../securesms/jobs/PushDecryptJob.java        |   5 +
 .../securesms/jobs/RetrieveProfileJob.java    |   3 +
 .../securesms/sms/MessageSender.java          |  37 ++--
 8 files changed, 115 insertions(+), 110 deletions(-)

diff --git a/res/layout/attachment_type_selector.xml b/res/layout/attachment_type_selector.xml
index 7f96eaae2c..31bdb96240 100644
--- a/res/layout/attachment_type_selector.xml
+++ b/res/layout/attachment_type_selector.xml
@@ -27,7 +27,7 @@
                       android:layout_marginStart="16dp"
                       android:layout_marginEnd="16dp"
                       android:layout_marginTop="16dp"
-                      android:weightSum="4">
+                      android:weightSum="2">
 
             <LinearLayout android:layout_width="match_parent"
                           android:layout_height="wrap_content"
@@ -53,48 +53,78 @@
             </LinearLayout>
 
             <LinearLayout android:layout_width="match_parent"
-                          android:layout_height="wrap_content"
-                          android:gravity="center"
-                          android:layout_weight="1"
-                          android:orientation="vertical">
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:orientation="vertical">
 
                 <org.thoughtcrime.securesms.components.CircleColorImageView
-                        android:id="@+id/audio_button"
-                        android:layout_width="53dp"
-                        android:layout_height="53dp"
-                        android:src="@drawable/ic_headset_white_24dp"
-                        android:scaleType="center"
-                        android:contentDescription="@string/attachment_type_selector__audio_description"
-                        app:circleColor="@color/orange_400"/>
+                    android:id="@+id/camera_button"
+                    android:layout_width="53dp"
+                    android:layout_height="53dp"
+                    android:src="@drawable/ic_camera_white_24dp"
+                    android:scaleType="center"
+                    android:contentDescription="@string/attachment_type_selector__camera_description"
+                    app:circleColor="@color/green_400"/>
 
                 <TextView android:layout_marginTop="10dp"
-                          android:layout_width="wrap_content"
-                          android:layout_height="wrap_content"
-                          style="@style/AttachmentTypeLabel"
-                          android:text="@string/attachment_type_selector__audio"/>
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:gravity="center"
+                    style="@style/AttachmentTypeLabel"
+                    android:text="@string/attachment_type_selector__camera"/>
 
             </LinearLayout>
 
-            <LinearLayout android:layout_width="match_parent"
-                          android:layout_height="wrap_content"
-                          android:gravity="center"
-                          android:layout_weight="1"
-                          android:orientation="vertical">
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:orientation="vertical"
+                android:visibility="gone">
 
                 <org.thoughtcrime.securesms.components.CircleColorImageView
-                        android:id="@+id/document_button"
-                        android:layout_width="53dp"
-                        android:layout_height="53dp"
-                        android:src="@drawable/ic_insert_drive_file_white_24dp"
-                        android:scaleType="center"
-                        android:contentDescription="@string/attachment_type_selector__file_description"
-                        app:circleColor="@color/red_400"/>
+                    android:id="@+id/audio_button"
+                    android:layout_width="53dp"
+                    android:layout_height="53dp"
+                    android:contentDescription="@string/attachment_type_selector__audio_description"
+                    android:scaleType="center"
+                    android:src="@drawable/ic_headset_white_24dp"
+                    app:circleColor="@color/orange_400" />
+
+                <TextView
+                    style="@style/AttachmentTypeLabel"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="10dp"
+                    android:text="@string/attachment_type_selector__audio" />
 
-                <TextView android:layout_marginTop="10dp"
-                          style="@style/AttachmentTypeLabel"
-                          android:layout_width="wrap_content"
-                          android:layout_height="wrap_content"
-                          android:text="@string/attachment_type_selector__file"/>
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:orientation="vertical"
+                android:visibility="gone">
+
+                <org.thoughtcrime.securesms.components.CircleColorImageView
+                    android:id="@+id/document_button"
+                    android:layout_width="53dp"
+                    android:layout_height="53dp"
+                    android:contentDescription="@string/attachment_type_selector__file_description"
+                    android:scaleType="center"
+                    android:src="@drawable/ic_insert_drive_file_white_24dp"
+                    app:circleColor="@color/red_400" />
+
+                <TextView
+                    style="@style/AttachmentTypeLabel"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="10dp"
+                    android:text="@string/attachment_type_selector__file" />
 
             </LinearLayout>
 
@@ -102,7 +132,8 @@
                           android:layout_height="wrap_content"
                           android:gravity="center"
                           android:orientation="vertical"
-                          android:layout_weight="1">
+                          android:layout_weight="1"
+                          android:visibility="gone">
 
                 <org.thoughtcrime.securesms.components.CircleColorImageView
                         android:id="@+id/contact_button"
@@ -130,53 +161,32 @@
                       android:layout_marginStart="16dp"
                       android:layout_marginEnd="16dp"
                       android:layout_marginBottom="16dp"
-                      android:weightSum="4">
+                      android:weightSum="2">
 
-            <LinearLayout android:layout_width="match_parent"
-                          android:layout_height="wrap_content"
-                          android:layout_weight="1"
-                          android:gravity="center"
-                          android:orientation="vertical">
-
-                <org.thoughtcrime.securesms.components.CircleColorImageView
-                        android:id="@+id/camera_button"
-                        android:layout_width="53dp"
-                        android:layout_height="53dp"
-                        android:src="@drawable/ic_camera_white_24dp"
-                        android:scaleType="center"
-                        android:contentDescription="@string/attachment_type_selector__camera_description"
-                        app:circleColor="@color/green_400"/>
-
-                <TextView android:layout_marginTop="10dp"
-                          android:layout_width="wrap_content"
-                          android:layout_height="wrap_content"
-                          android:gravity="center"
-                          style="@style/AttachmentTypeLabel"
-                          android:text="@string/attachment_type_selector__camera"/>
-
-            </LinearLayout>
-
-            <LinearLayout android:id="@+id/location_linear_layout"
-                          android:layout_width="match_parent"
-                          android:layout_height="wrap_content"
-                          android:layout_weight="1"
-                          android:gravity="center"
-                          android:orientation="vertical">
+            <LinearLayout
+                android:id="@+id/location_linear_layout"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:orientation="vertical"
+                android:visibility="gone">
 
                 <org.thoughtcrime.securesms.components.CircleColorImageView
-                        android:id="@+id/location_button"
-                        android:layout_width="53dp"
-                        android:layout_height="53dp"
-                        android:src="@drawable/ic_location_on_white_24dp"
-                        android:scaleType="center"
-                        android:contentDescription="@string/attachment_type_selector__location_description"
-                        app:circleColor="@color/blue_grey_400"/>
-
-                <TextView android:layout_marginTop="10dp"
-                          android:layout_width="wrap_content"
-                          android:layout_height="wrap_content"
-                          style="@style/AttachmentTypeLabel"
-                          android:text="@string/attachment_type_selector__location"/>
+                    android:id="@+id/location_button"
+                    android:layout_width="53dp"
+                    android:layout_height="53dp"
+                    android:contentDescription="@string/attachment_type_selector__location_description"
+                    android:scaleType="center"
+                    android:src="@drawable/ic_location_on_white_24dp"
+                    app:circleColor="@color/blue_grey_400" />
+
+                <TextView
+                    style="@style/AttachmentTypeLabel"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="10dp"
+                    android:text="@string/attachment_type_selector__location" />
 
             </LinearLayout>
 
@@ -223,7 +233,7 @@
                           android:layout_width="wrap_content"
                           android:layout_height="wrap_content"
                           style="@style/AttachmentTypeLabel"
-                          android:text=" "/>
+                          android:text=""/>
 
             </LinearLayout>
 
diff --git a/res/menu/conversation.xml b/res/menu/conversation.xml
index 7213e498c3..cd33628267 100644
--- a/res/menu/conversation.xml
+++ b/res/menu/conversation.xml
@@ -2,10 +2,10 @@
 <menu xmlns:android="http://schemas.android.com/apk/res/android"
       xmlns:app="http://schemas.android.com/apk/res-auto">
 
-    <!-- <item android:title="@string/conversation__menu_view_all_media"
+    <item android:title="@string/conversation__menu_view_all_media"
           android:id="@+id/menu_view_media" />
 
-    <item android:title="@string/conversation__menu_conversation_settings"
+    <!-- <item android:title="@string/conversation__menu_conversation_settings"
           android:id="@+id/menu_conversation_settings"/> -->
 
 
diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java
index 39412b48ee..d5903cce96 100644
--- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java
@@ -792,7 +792,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
     switch (item.getItemId()) {
     case R.id.menu_call_secure:               handleDial(getRecipient(), true);                  return true;
     case R.id.menu_call_insecure:             handleDial(getRecipient(), false);                 return true;
-    // case R.id.menu_view_media:                handleViewMedia();                                 return true;
+    case R.id.menu_view_media:                handleViewMedia();                                 return true;
     case R.id.menu_add_shortcut:              handleAddShortcut();                               return true;
     case R.id.menu_search:                    handleSearch();                                    return true;
     case R.id.menu_add_to_contacts:           handleAddToContacts();                             return true;
@@ -2408,7 +2408,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
     }
 
     if (composeText.getText().length() == 0 && !attachmentManager.isAttachmentPresent()) {
-      buttonToggle.display(sendButton);
+      buttonToggle.display(attachButton);
       quickAttachmentToggle.show();
       inlineAttachmentToggle.hide();
     } else {
diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationItem.java b/src/org/thoughtcrime/securesms/conversation/ConversationItem.java
index 57fbe13391..7e66fbbcd3 100644
--- a/src/org/thoughtcrime/securesms/conversation/ConversationItem.java
+++ b/src/org/thoughtcrime/securesms/conversation/ConversationItem.java
@@ -473,7 +473,7 @@ public class ConversationItem extends LinearLayout
 
     if (isCaptionlessMms(messageRecord)) {
       bodyText.setVisibility(View.GONE);
-    } else { ;
+    } else {
       Spannable text = MentionUtilities.highlightMentions(linkifyMessageBody(messageRecord.getDisplayBody(context), batchSelected.isEmpty()), messageRecord.isOutgoing(), messageRecord.getThreadId(), context);
       text = SearchUtil.getHighlightedSpan(locale, () -> new BackgroundColorSpan(Color.YELLOW), text, searchQuery);
       text = SearchUtil.getHighlightedSpan(locale, () -> new ForegroundColorSpan(Color.BLACK), text, searchQuery);
diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
index a2c4da21ff..bfe549eb29 100644
--- a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
@@ -98,7 +98,7 @@ public class MmsSmsDatabase extends Database {
 
   public Cursor getConversation(long threadId, long offset, long limit) {
     String order     = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
-    String selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND LENGTH(" + MmsSmsColumns.BODY + ") > 0";
+    String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
     String limitStr  = limit > 0 || offset > 0 ? offset + ", " + limit : null;
 
     Cursor cursor = queryTables(PROJECTION, selection, order, limitStr);
diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
index 0007d8c87c..3abcbcdaea 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
@@ -795,6 +795,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
     MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
     database.beginTransaction();
 
+    // Ignore message if it has no body and no attachments or anything
+    if (mediaMessage.getBody().isEmpty() && mediaMessage.getAttachments().isEmpty() && mediaMessage.getSharedContacts().isEmpty() && mediaMessage.getLinkPreviews().isEmpty()) {
+      return;
+    }
+
     Optional<InsertResult> insertResult;
 
     try {
diff --git a/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java b/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java
index e7effa2eb5..43d3fdde7d 100644
--- a/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java
@@ -77,12 +77,15 @@ public class RetrieveProfileJob extends BaseJob implements InjectableType {
 
   @Override
   public void onRun() throws IOException, InvalidKeyException {
+    // Loki - Disable retrieve profile
+    /*
     try {
       if (recipient.isGroupRecipient()) handleGroupRecipient(recipient);
       else                              handleIndividualRecipient(recipient);
     } catch (InvalidNumberException e) {
       Log.w(TAG, e);
     }
+    */
   }
 
   @Override
diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java
index d6cb29279d..195db0e6d9 100644
--- a/src/org/thoughtcrime/securesms/sms/MessageSender.java
+++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java
@@ -58,6 +58,7 @@ import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
 import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus;
 
 import java.io.IOException;
+import java.util.function.Function;
 
 import kotlin.Unit;
 
@@ -117,32 +118,18 @@ public class MessageSender {
     // Loki - Turn into a GIF message if possible
     if (message.getLinkPreviews().isEmpty() && message.getAttachments().isEmpty() && LinkPreviewUtil.isWhitelistedMediaUrl(message.getBody())) {
       new LinkPreviewRepository(context).fetchGIF(context, message.getBody(), attachmentOrNull -> Util.runOnMain(() -> {
-        if (attachmentOrNull.isPresent()) {
-          Attachment attachment = attachmentOrNull.get();
-          try {
-            message.getAttachments().add(attachment);
-            long messageID = database.insertMessageOutbox(message, allocatedThreadId, forceSms, insertListener);
-            // Loki - Set the message's friend request status as soon as it has hit the database
-            if (message.isFriendRequest) {
-              DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_SENDING);
-            }
-            sendMediaMessage(context, recipient, forceSms, messageID, message.getExpiresIn());
-          } catch (Exception e) {
-            Log.w(TAG, e);
-            // TODO: Handle
-          }
-        } else {
-          try {
-            long messageID = database.insertMessageOutbox(message, allocatedThreadId, forceSms, insertListener);
-            // Loki - Set the message's friend request status as soon as it has hit the database
-            if (message.isFriendRequest) {
-              DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_SENDING);
-            }
-            sendMediaMessage(context, recipient, forceSms, messageID, message.getExpiresIn());
-          } catch (MmsException e) {
-            Log.w(TAG, e);
-            // TODO: Handle
+        Attachment attachment = attachmentOrNull.orNull();
+        try {
+          if (attachment != null) { message.getAttachments().add(attachment); }
+          long messageID = database.insertMessageOutbox(message, allocatedThreadId, forceSms, insertListener);
+          // Loki - Set the message's friend request status as soon as it has hit the database
+          if (message.isFriendRequest) {
+            DatabaseFactory.getLokiMessageDatabase(context).setFriendRequestStatus(messageID, LokiMessageFriendRequestStatus.REQUEST_SENDING);
           }
+          sendMediaMessage(context, recipient, forceSms, messageID, message.getExpiresIn());
+        } catch (Exception e) {
+          Log.w(TAG, e);
+          // TODO: Handle
         }
       }));
     } else {

From 8d6094ecd62e47ec69d3399e0344109ef0a67295 Mon Sep 17 00:00:00 2001
From: Mikunj <mikunj@live.com.au>
Date: Fri, 18 Oct 2019 15:16:41 +1100
Subject: [PATCH 04/14] Fix crash when viewing media

---
 .../thoughtcrime/securesms/database/AttachmentDatabase.java    | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java b/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java
index 2beec07057..918ffbd9d1 100644
--- a/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java
@@ -659,6 +659,7 @@ public class AttachmentDatabase extends Database {
 
         return result;
       } else {
+        int urlIndex = cursor.getColumnIndex(URL);
         return Collections.singletonList(new DatabaseAttachment(new AttachmentId(cursor.getLong(cursor.getColumnIndexOrThrow(ROW_ID)),
                                                                                  cursor.getLong(cursor.getColumnIndexOrThrow(UNIQUE_ID))),
                                                                 cursor.getLong(cursor.getColumnIndexOrThrow(MMS_ID)),
@@ -683,7 +684,7 @@ public class AttachmentDatabase extends Database {
                                                                                          cursor.getString(cursor.getColumnIndexOrThrow(STICKER_PACK_KEY)),
                                                                                          cursor.getInt(cursor.getColumnIndexOrThrow(STICKER_ID)))
                                                                     : null,
-                                                                cursor.getString(cursor.getColumnIndexOrThrow(URL))));
+                                                                urlIndex > 0 ? cursor.getString(urlIndex) : ""));
       }
     } catch (JSONException e) {
       throw new AssertionError(e);

From b12e6b838caa64c1689df02392db3179d6892737 Mon Sep 17 00:00:00 2001
From: Mikunj <mikunj@live.com.au>
Date: Fri, 18 Oct 2019 16:12:49 +1100
Subject: [PATCH 05/14] Disable attachments on non-friends.

---
 res/layout/conversation_input_panel.xml                |  3 ++-
 .../securesms/conversation/ConversationActivity.java   | 10 ++++++++++
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/res/layout/conversation_input_panel.xml b/res/layout/conversation_input_panel.xml
index 8c744fa541..6812e6c775 100644
--- a/res/layout/conversation_input_panel.xml
+++ b/res/layout/conversation_input_panel.xml
@@ -127,7 +127,8 @@
                                 android:paddingStart="6dp"
                                 android:paddingEnd="6dp"
                                 android:background="?selectableItemBackgroundBorderless"
-                                android:contentDescription="@string/conversation_activity__quick_attachment_drawer_toggle_camera_description" />
+                                android:contentDescription="@string/conversation_activity__quick_attachment_drawer_toggle_camera_description"
+                                android:visibility="gone"/>
 
                             <org.thoughtcrime.securesms.components.MicrophoneRecorderView
                                 android:id="@+id/recorder_view"
diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java
index d5903cce96..25ee20998d 100644
--- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java
@@ -2189,6 +2189,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
 
   private void updateInputPanel() {
     boolean hasPendingFriendRequest = !recipient.isGroupRecipient() && DatabaseFactory.getLokiThreadDatabase(this).hasPendingFriendRequest(threadId);
+    updateToggleButtonState();
     inputPanel.setEnabled(!hasPendingFriendRequest);
     int hintID = hasPendingFriendRequest ? R.string.activity_conversation_pending_friend_request_hint : R.string.activity_conversation_default_hint;
     inputPanel.setHint(getResources().getString(hintID));
@@ -2400,6 +2401,15 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
   }
 
   private void updateToggleButtonState() {
+    // Don't allow attachments if we're not friends
+    LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(this).getFriendRequestStatus(threadId);
+    if (!recipient.isGroupRecipient() && friendRequestStatus != LokiThreadFriendRequestStatus.FRIENDS) {
+      buttonToggle.display(sendButton);
+      quickAttachmentToggle.hide();
+      inlineAttachmentToggle.hide();
+      return;
+    }
+
     if (inputPanel.isRecordingInLockedMode()) {
       buttonToggle.display(sendButton);
       quickAttachmentToggle.show();

From e438d09a626cc5115622253ecfe08924703b774b Mon Sep 17 00:00:00 2001
From: Mikunj <mikunj@live.com.au>
Date: Mon, 21 Oct 2019 10:52:53 +1100
Subject: [PATCH 06/14] Update attachments for public group chats.

---
 res/layout/conversation_input_panel.xml       |  9 ++--
 .../securesms/jobs/AttachmentDownloadJob.java | 15 +++++-
 .../securesms/jobs/AttachmentUploadJob.java   | 20 +++++---
 .../securesms/jobs/PushGroupSendJob.java      |  7 ++-
 .../securesms/jobs/PushMediaSendJob.java      |  2 +-
 .../securesms/loki/LokiPublicChatPoller.kt    | 47 +++++++++++++------
 .../securesms/util/AttachmentUtil.java        |  5 ++
 7 files changed, 73 insertions(+), 32 deletions(-)

diff --git a/res/layout/conversation_input_panel.xml b/res/layout/conversation_input_panel.xml
index 6812e6c775..54f8563e54 100644
--- a/res/layout/conversation_input_panel.xml
+++ b/res/layout/conversation_input_panel.xml
@@ -127,8 +127,7 @@
                                 android:paddingStart="6dp"
                                 android:paddingEnd="6dp"
                                 android:background="?selectableItemBackgroundBorderless"
-                                android:contentDescription="@string/conversation_activity__quick_attachment_drawer_toggle_camera_description"
-                                android:visibility="gone"/>
+                                android:contentDescription="@string/conversation_activity__quick_attachment_drawer_toggle_camera_description" />
 
                             <org.thoughtcrime.securesms.components.MicrophoneRecorderView
                                 android:id="@+id/recorder_view"
@@ -136,7 +135,8 @@
                                 android:layout_width="36dp"
                                 android:layout_gravity="center_vertical"
                                 android:clipChildren="false"
-                                android:clipToPadding="false">
+                                android:clipToPadding="false"
+                                android:visibility="gone">
 
                                 <include layout="@layout/microphone_recorder_view" />
 
@@ -148,8 +148,7 @@
                             android:id="@+id/inline_attachment_container"
                             android:layout_width="wrap_content"
                             android:layout_height="match_parent"
-                            android:layout_gravity="right|end"
-                            android:visibility="gone">
+                            android:layout_gravity="right|end">
 
                             <ImageButton
                                 android:id="@+id/inline_attachment_button"
diff --git a/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java
index 4734ee2e5c..5f62d06a3d 100644
--- a/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java
@@ -183,16 +183,29 @@ public class AttachmentDownloadJob extends BaseJob implements InjectableType {
   SignalServiceAttachmentPointer createAttachmentPointer(Attachment attachment)
       throws InvalidPartException
   {
+    boolean isPublicAttachment = TextUtils.isEmpty(attachment.getKey()) && attachment.getDigest() == null;
+
     if (TextUtils.isEmpty(attachment.getLocation())) {
       throw new InvalidPartException("empty content id");
     }
 
-    if (TextUtils.isEmpty(attachment.getKey())) {
+    if (TextUtils.isEmpty(attachment.getKey()) && !isPublicAttachment) {
       throw new InvalidPartException("empty encrypted key");
     }
 
     try {
       long   id    = Long.parseLong(attachment.getLocation());
+      if (isPublicAttachment) {
+        return new SignalServiceAttachmentPointer(id, null, new byte[0],
+                Optional.of(Util.toIntExact(attachment.getSize())),
+                Optional.absent(),
+                0, 0,
+                Optional.fromNullable(attachment.getDigest()),
+                Optional.fromNullable(attachment.getFileName()),
+                attachment.isVoiceNote(),
+                Optional.absent(), attachment.getUrl());
+      }
+
       byte[] key   = Base64.decode(attachment.getKey());
       String relay = null;
 
diff --git a/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java b/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java
index c09d50b334..0b31b7ecfe 100644
--- a/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java
@@ -7,6 +7,7 @@ import org.thoughtcrime.securesms.attachments.Attachment;
 import org.thoughtcrime.securesms.attachments.AttachmentId;
 import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
 import org.thoughtcrime.securesms.attachments.PointerAttachment;
+import org.thoughtcrime.securesms.database.Address;
 import org.thoughtcrime.securesms.database.AttachmentDatabase;
 import org.thoughtcrime.securesms.database.DatabaseFactory;
 import org.thoughtcrime.securesms.dependencies.InjectableType;
@@ -25,6 +26,7 @@ import org.whispersystems.libsignal.util.guava.Optional;
 import org.whispersystems.signalservice.api.SignalServiceMessageSender;
 import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
 import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
+import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -38,30 +40,34 @@ public class AttachmentUploadJob extends BaseJob implements InjectableType {
 
   private static final String TAG = AttachmentUploadJob.class.getSimpleName();
 
-  private static final String KEY_ROW_ID    = "row_id";
-  private static final String KEY_UNIQUE_ID = "unique_id";
+  private static final String KEY_ROW_ID      = "row_id";
+  private static final String KEY_UNIQUE_ID   = "unique_id";
+  private static final String KEY_DESTINATION = "destination";
 
   private AttachmentId               attachmentId;
+  private Address                    destination;
   @Inject SignalServiceMessageSender messageSender;
 
-  public AttachmentUploadJob(AttachmentId attachmentId) {
+  public AttachmentUploadJob(AttachmentId attachmentId, Address destination) {
     this(new Job.Parameters.Builder()
                            .addConstraint(NetworkConstraint.KEY)
                            .setLifespan(TimeUnit.DAYS.toMillis(1))
                            .setMaxAttempts(Parameters.UNLIMITED)
                            .build(),
-         attachmentId);
+         attachmentId, destination);
   }
 
-  private AttachmentUploadJob(@NonNull Job.Parameters parameters, @NonNull AttachmentId attachmentId) {
+  private AttachmentUploadJob(@NonNull Job.Parameters parameters, @NonNull AttachmentId attachmentId, Address destination) {
     super(parameters);
     this.attachmentId = attachmentId;
+    this.destination = destination;
   }
 
   @Override
   public @NonNull Data serialize() {
     return new Data.Builder().putLong(KEY_ROW_ID, attachmentId.getRowId())
                              .putLong(KEY_UNIQUE_ID, attachmentId.getUniqueId())
+                             .putString(KEY_DESTINATION, destination.serialize())
                              .build();
   }
 
@@ -82,7 +88,7 @@ public class AttachmentUploadJob extends BaseJob implements InjectableType {
     MediaConstraints               mediaConstraints = MediaConstraints.getPushMediaConstraints();
     Attachment                     scaledAttachment = scaleAndStripExif(database, mediaConstraints, databaseAttachment);
     SignalServiceAttachment        localAttachment  = getAttachmentFor(scaledAttachment);
-    SignalServiceAttachmentPointer remoteAttachment = messageSender.uploadAttachment(localAttachment.asStream(), databaseAttachment.isSticker());
+    SignalServiceAttachmentPointer remoteAttachment = messageSender.uploadAttachment(localAttachment.asStream(), databaseAttachment.isSticker(), new SignalServiceAddress(destination.serialize()));
     Attachment                     attachment       = PointerAttachment.forPointer(Optional.of(remoteAttachment), null, databaseAttachment.getFastPreflightId()).get();
 
     database.updateAttachmentAfterUpload(databaseAttachment.getAttachmentId(), attachment);
@@ -144,7 +150,7 @@ public class AttachmentUploadJob extends BaseJob implements InjectableType {
   public static final class Factory implements Job.Factory<AttachmentUploadJob> {
     @Override
     public @NonNull AttachmentUploadJob create(@NonNull Parameters parameters, @NonNull org.thoughtcrime.securesms.jobmanager.Data data) {
-      return new AttachmentUploadJob(parameters, new AttachmentId(data.getLong(KEY_ROW_ID), data.getLong(KEY_UNIQUE_ID)));
+      return new AttachmentUploadJob(parameters, new AttachmentId(data.getLong(KEY_ROW_ID), data.getLong(KEY_UNIQUE_ID)), Address.fromSerialized(data.getString(KEY_DESTINATION)));
     }
   }
 }
diff --git a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java
index be4dd9ed4e..d5d4fa6c99 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java
@@ -96,12 +96,11 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
       OutgoingMediaMessage message     = database.getOutgoingMessage(messageId);
       List<Attachment>     attachments = new LinkedList<>();
 
-      // Loki - For now all attachments are re-fetched by the receiver
-      // attachments.addAll(message.getAttachments());
-      // attachments.addAll(Stream.of(message.getLinkPreviews()).filter(p -> p.getThumbnail().isPresent()).map(p -> p.getThumbnail().get()).toList());
+      attachments.addAll(message.getAttachments());
+      attachments.addAll(Stream.of(message.getLinkPreviews()).filter(p -> p.getThumbnail().isPresent()).map(p -> p.getThumbnail().get()).toList());
       attachments.addAll(Stream.of(message.getSharedContacts()).filter(c -> c.getAvatar() != null).map(c -> c.getAvatar().getAttachment()).withoutNulls().toList());
 
-      List<AttachmentUploadJob> attachmentJobs = Stream.of(attachments).map(a -> new AttachmentUploadJob(((DatabaseAttachment) a).getAttachmentId())).toList();
+      List<AttachmentUploadJob> attachmentJobs = Stream.of(attachments).map(a -> new AttachmentUploadJob(((DatabaseAttachment) a).getAttachmentId(), destination)).toList();
 
       if (attachmentJobs.isEmpty()) {
         jobManager.add(new PushGroupSendJob(messageId, destination, filterAddress));
diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
index 1b3de7a276..bcfe4b9de0 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
@@ -109,7 +109,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
       attachments.addAll(Stream.of(message.getLinkPreviews()).filter(p -> p.getThumbnail().isPresent()).map(p -> p.getThumbnail().get()).toList());
       attachments.addAll(Stream.of(message.getSharedContacts()).filter(c -> c.getAvatar() != null).map(c -> c.getAvatar().getAttachment()).withoutNulls().toList());
 
-      List<AttachmentUploadJob> attachmentJobs = Stream.of(attachments).map(a -> new AttachmentUploadJob(((DatabaseAttachment) a).getAttachmentId())).toList();
+      List<AttachmentUploadJob> attachmentJobs = Stream.of(attachments).map(a -> new AttachmentUploadJob(((DatabaseAttachment) a).getAttachmentId(), destination)).toList();
 
       if (attachmentJobs.isEmpty()) {
         jobManager.add(new PushMediaSendJob(templateMessageId, messageId, destination, isFriendRequest, customFriendRequestMessage));
diff --git a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt
index 2530ae021c..98e6e8d1dd 100644
--- a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt
+++ b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt
@@ -17,10 +17,9 @@ import org.thoughtcrime.securesms.util.GroupUtil
 import org.thoughtcrime.securesms.util.TextSecurePreferences
 import org.thoughtcrime.securesms.util.Util
 import org.whispersystems.libsignal.util.guava.Optional
-import org.whispersystems.signalservice.api.messages.SignalServiceContent
-import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
-import org.whispersystems.signalservice.api.messages.SignalServiceGroup
+import org.whispersystems.signalservice.api.messages.*
 import org.whispersystems.signalservice.api.push.SignalServiceAddress
+import org.whispersystems.signalservice.internal.push.SignalServiceProtos
 import org.whispersystems.signalservice.loki.api.LokiPublicChat
 import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI
 import org.whispersystems.signalservice.loki.api.LokiPublicChatMessage
@@ -96,21 +95,35 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
     private fun pollForNewMessages() {
         fun processIncomingMessage(message: LokiPublicChatMessage) {
             val id = group.id.toByteArray()
-            val x1 = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, null, null, null)
-            val quote: SignalServiceDataMessage.Quote?
-            if (message.quote != null) {
-                quote = SignalServiceDataMessage.Quote(message.quote!!.quotedMessageTimestamp, SignalServiceAddress(message.quote!!.quoteeHexEncodedPublicKey), message.quote!!.quotedMessageBody, listOf())
+            val serviceGroup = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, null, null, null)
+            val quote = if (message.quote != null) {
+                SignalServiceDataMessage.Quote(message.quote!!.quotedMessageTimestamp, SignalServiceAddress(message.quote!!.quoteeHexEncodedPublicKey), message.quote!!.quotedMessageBody, listOf())
             } else {
-                quote = null
+                null
+            }
+            val attachments = message.attachments.map { attachment ->
+                SignalServiceAttachmentPointer(
+                        attachment.serverID,
+                        attachment.contentType,
+                        ByteArray(0),
+                        Optional.of(attachment.size),
+                        Optional.absent(),
+                        attachment.width, attachment.height,
+                        Optional.absent(),
+                        Optional.of(attachment.fileName),
+                        false,
+                        Optional.fromNullable(attachment.caption),
+                        attachment.url)
             }
-            val x2 = SignalServiceDataMessage(message.timestamp, x1, listOf(), message.body, false, 0, false, null, false, quote, null, null, null)
-            val x3 = SignalServiceContent(x2, message.hexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false)
+            val body = if (message.body == message.timestamp.toString()) "" else message.body // Workaround for the fact that the back-end doesn't accept messages without a body
+            val serviceDataMessage = SignalServiceDataMessage(message.timestamp, serviceGroup, attachments, body, false, 0, false, null, false, quote, null, null, null)
+            val serviceContent = SignalServiceContent(serviceDataMessage, message.hexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false)
             val senderDisplayName = "${message.displayName} (...${message.hexEncodedPublicKey.takeLast(8)})"
             DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.hexEncodedPublicKey, senderDisplayName)
-            if (quote != null) {
-                PushDecryptJob(context).handleMediaMessage(x3, x2, Optional.absent(), Optional.of(message.serverID))
+            if (quote != null || attachments.count() > 0) {
+                PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))
             } else {
-                PushDecryptJob(context).handleTextMessage(x3, x2, Optional.absent(), Optional.of(message.serverID))
+                PushDecryptJob(context).handleTextMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))
             }
         }
         fun processOutgoingMessage(message: LokiPublicChatMessage) {
@@ -118,6 +131,10 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
             val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context)
             val isDuplicate = lokiMessageDatabase.getMessageID(messageServerID) != null
             if (isDuplicate) { return }
+
+            // Ignore empty messages with no attachments or quotes
+            if (message.body.isEmpty() && message.attachments.isEmpty() && message.quote == null) { return }
+
             val id = group.id.toByteArray()
             val mmsDatabase = DatabaseFactory.getMmsDatabase(context)
             val recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(id, false)), false)
@@ -127,7 +144,9 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
             } else {
                 quote = null
             }
-            val signalMessage = OutgoingMediaMessage(recipient, message.body, listOf(), message.timestamp, 0, 0,
+            // TODO: Handle attachments correctly for our previous messages
+            val body = if (message.body == message.timestamp.toString()) "" else message.body // Workaround for the fact that the back-end doesn't accept messages without a body
+            val signalMessage = OutgoingMediaMessage(recipient, body, listOf(), message.timestamp, 0, 0,
                ThreadDatabase.DistributionTypes.DEFAULT, quote, listOf(), listOf(), listOf(), listOf())
             val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient)
             fun finalize() {
diff --git a/src/org/thoughtcrime/securesms/util/AttachmentUtil.java b/src/org/thoughtcrime/securesms/util/AttachmentUtil.java
index 84ea6cf386..5e2f88b978 100644
--- a/src/org/thoughtcrime/securesms/util/AttachmentUtil.java
+++ b/src/org/thoughtcrime/securesms/util/AttachmentUtil.java
@@ -114,9 +114,14 @@ public class AttachmentUtil {
 
     if (message == null) { return true; }
 
+    // TODO: Fix this so we can detect whether attachment is from a public group or not
+    return false;
+
+    /*
     // check to see if we're friends with the person
     long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(message.getRecipient());
     boolean isFriend = threadId >= 0 && DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId) == LokiThreadFriendRequestStatus.FRIENDS;
     return (!isFriend && !message.isOutgoing() && !Util.isOwnNumber(context, message.getRecipient().getAddress()));
+    */
   }
 }

From a978253e006742f792cea0ad7ae8d6e8e8ad689f Mon Sep 17 00:00:00 2001
From: Niels Andriesse <andriesseniels@gmail.com>
Date: Mon, 21 Oct 2019 15:26:57 +1100
Subject: [PATCH 07/14] Fix indentation

---
 res/layout/attachment_type_selector.xml       | 221 +++++++++---------
 .../securesms/loki/LokiPublicChatPoller.kt    |   9 +-
 2 files changed, 120 insertions(+), 110 deletions(-)

diff --git a/res/layout/attachment_type_selector.xml b/res/layout/attachment_type_selector.xml
index 31bdb96240..212cbf9daf 100644
--- a/res/layout/attachment_type_selector.xml
+++ b/res/layout/attachment_type_selector.xml
@@ -1,54 +1,59 @@
 <?xml version="1.0" encoding="utf-8"?>
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              xmlns:app="http://schemas.android.com/apk/res-auto"
-              android:orientation="vertical"
-              android:layout_width="match_parent"
-              android:layout_height="wrap_content">
-
-    <View android:layout_width="match_parent"
-          android:layout_height="4dp"
-          android:background="@drawable/attachment_selector_shadow"/>
-
-    <LinearLayout android:orientation="vertical"
-                  android:layout_width="match_parent"
-                  android:layout_height="wrap_content"
-                  android:background="?attachment_type_selector_background">
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="4dp"
+        android:background="@drawable/attachment_selector_shadow"/>
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="?attachment_type_selector_background">
 
         <org.thoughtcrime.securesms.components.RecentPhotoViewRail
-                android:id="@+id/recent_photos"
-                android:layout_width="match_parent"
-                android:layout_height="90dp"
-                android:padding="4dp"/>
-
-        <LinearLayout android:orientation="horizontal"
-                      android:layout_width="match_parent"
-                      android:layout_height="wrap_content"
-                      android:layout_marginStart="16dp"
-                      android:layout_marginEnd="16dp"
-                      android:layout_marginTop="16dp"
-                      android:weightSum="2">
+            android:id="@+id/recent_photos"
+            android:layout_width="match_parent"
+            android:layout_height="90dp"
+            android:padding="4dp"/>
+
+        <LinearLayout
+            android:orientation="horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:layout_marginEnd="16dp"
+            android:layout_marginTop="16dp"
+            android:weightSum="2">
 
-            <LinearLayout android:layout_width="match_parent"
-                          android:layout_height="wrap_content"
-                          android:layout_weight="1"
-                          android:gravity="center"
-                          android:orientation="vertical">
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:orientation="vertical">
 
                 <org.thoughtcrime.securesms.components.CircleColorImageView
-                        android:id="@+id/gallery_button"
-                        android:layout_width="53dp"
-                        android:layout_height="53dp"
-                        android:src="@drawable/ic_image_white_24dp"
-                        android:scaleType="center"
-                        android:contentDescription="@string/attachment_type_selector__gallery_description"
-                        app:circleColor="@color/purple_400"/>
+                    android:id="@+id/gallery_button"
+                    android:layout_width="53dp"
+                    android:layout_height="53dp"
+                    android:src="@drawable/ic_image_white_24dp"
+                    android:scaleType="center"
+                    android:contentDescription="@string/attachment_type_selector__gallery_description"
+                    app:circleColor="@color/purple_400"/>
 
-                <TextView android:layout_marginTop="10dp"
-                          style="@style/AttachmentTypeLabel"
-                          android:layout_width="wrap_content"
-                          android:layout_height="wrap_content"
-                          android:text="@string/attachment_type_selector__gallery"/>
+                <TextView
+                    android:layout_marginTop="10dp"
+                    style="@style/AttachmentTypeLabel"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/attachment_type_selector__gallery"/>
 
             </LinearLayout>
 
@@ -128,40 +133,43 @@
 
             </LinearLayout>
 
-            <LinearLayout android:layout_width="match_parent"
-                          android:layout_height="wrap_content"
-                          android:gravity="center"
-                          android:orientation="vertical"
-                          android:layout_weight="1"
-                          android:visibility="gone">
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center"
+                android:orientation="vertical"
+                android:layout_weight="1"
+                android:visibility="gone">
 
                 <org.thoughtcrime.securesms.components.CircleColorImageView
-                        android:id="@+id/contact_button"
-                        android:layout_width="53dp"
-                        android:layout_height="53dp"
-                        android:src="@drawable/ic_person_white_24dp"
-                        android:scaleType="center"
-                        android:contentDescription="@string/attachment_type_selector__contact_description"
-                        app:circleColor="@color/blue_400"/>
+                    android:id="@+id/contact_button"
+                    android:layout_width="53dp"
+                    android:layout_height="53dp"
+                    android:src="@drawable/ic_person_white_24dp"
+                    android:scaleType="center"
+                    android:contentDescription="@string/attachment_type_selector__contact_description"
+                    app:circleColor="@color/blue_400"/>
 
-                <TextView android:layout_marginTop="10dp"
-                          android:layout_width="wrap_content"
-                          android:layout_height="wrap_content"
-                          style="@style/AttachmentTypeLabel"
-                          android:text="@string/attachment_type_selector__contact"/>
+                <TextView
+                    android:layout_marginTop="10dp"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    style="@style/AttachmentTypeLabel"
+                    android:text="@string/attachment_type_selector__contact"/>
 
             </LinearLayout>
 
         </LinearLayout>
 
-        <LinearLayout android:orientation="horizontal"
-                      android:layout_width="match_parent"
-                      android:layout_height="wrap_content"
-                      android:layout_marginTop="16dp"
-                      android:layout_marginStart="16dp"
-                      android:layout_marginEnd="16dp"
-                      android:layout_marginBottom="16dp"
-                      android:weightSum="2">
+        <LinearLayout
+            android:orientation="horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="16dp"
+            android:layout_marginStart="16dp"
+            android:layout_marginEnd="16dp"
+            android:layout_marginBottom="16dp"
+            android:weightSum="2">
 
             <LinearLayout
                 android:id="@+id/location_linear_layout"
@@ -191,53 +199,56 @@
             </LinearLayout>
 
 
-            <LinearLayout android:layout_width="match_parent"
-                          android:layout_height="wrap_content"
-                          android:layout_weight="1"
-                          android:gravity="center"
-                          android:orientation="vertical">
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:orientation="vertical">
 
                 <org.thoughtcrime.securesms.components.CircleColorImageView
-                        android:id="@+id/giphy_button"
-                        android:layout_width="53dp"
-                        android:layout_height="53dp"
-                        android:src="@drawable/ic_gif_white_24dp"
-                        android:scaleType="center"
-                        android:contentDescription="@string/attachment_type_selector__gif_description"
-                        app:circleColor="@color/cyan_400"/>
+                    android:id="@+id/giphy_button"
+                    android:layout_width="53dp"
+                    android:layout_height="53dp"
+                    android:src="@drawable/ic_gif_white_24dp"
+                    android:scaleType="center"
+                    android:contentDescription="@string/attachment_type_selector__gif_description"
+                    app:circleColor="@color/cyan_400"/>
 
-                <TextView android:layout_marginTop="10dp"
-                          android:layout_width="wrap_content"
-                          android:layout_height="wrap_content"
-                          style="@style/AttachmentTypeLabel"
-                          android:text="@string/attachment_type_selector__gif"/>
+                <TextView
+                    android:layout_marginTop="10dp"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    style="@style/AttachmentTypeLabel"
+                    android:text="@string/attachment_type_selector__gif"/>
 
             </LinearLayout>
 
-            <LinearLayout android:layout_width="match_parent"
-                          android:layout_height="wrap_content"
-                          android:layout_weight="1"
-                          android:gravity="center"
-                          android:orientation="vertical">
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:orientation="vertical">
 
                 <org.thoughtcrime.securesms.components.CircleColorImageView
-                        android:id="@+id/close_button"
-                        android:layout_width="53dp"
-                        android:layout_height="53dp"
-                        android:src="@drawable/ic_keyboard_arrow_down_white_24dp"
-                        android:scaleType="center"
-                        android:contentDescription="@string/attachment_type_selector__drawer_description"
-                        app:circleColor="@color/gray50"/>
+                    android:id="@+id/close_button"
+                    android:layout_width="53dp"
+                    android:layout_height="53dp"
+                    android:src="@drawable/ic_keyboard_arrow_down_white_24dp"
+                    android:scaleType="center"
+                    android:contentDescription="@string/attachment_type_selector__drawer_description"
+                    app:circleColor="@color/gray50"/>
 
-                <TextView android:layout_marginTop="10dp"
-                          android:layout_width="wrap_content"
-                          android:layout_height="wrap_content"
-                          style="@style/AttachmentTypeLabel"
-                          android:text=""/>
+                <TextView
+                    android:layout_marginTop="10dp"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    style="@style/AttachmentTypeLabel"
+                    android:text=""/>
 
             </LinearLayout>
 
-
         </LinearLayout>
 
     </LinearLayout>
diff --git a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt
index 98e6e8d1dd..5cd1b4c95c 100644
--- a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt
+++ b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt
@@ -17,9 +17,11 @@ import org.thoughtcrime.securesms.util.GroupUtil
 import org.thoughtcrime.securesms.util.TextSecurePreferences
 import org.thoughtcrime.securesms.util.Util
 import org.whispersystems.libsignal.util.guava.Optional
-import org.whispersystems.signalservice.api.messages.*
+import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer
+import org.whispersystems.signalservice.api.messages.SignalServiceContent
+import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
+import org.whispersystems.signalservice.api.messages.SignalServiceGroup
 import org.whispersystems.signalservice.api.push.SignalServiceAddress
-import org.whispersystems.signalservice.internal.push.SignalServiceProtos
 import org.whispersystems.signalservice.loki.api.LokiPublicChat
 import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI
 import org.whispersystems.signalservice.loki.api.LokiPublicChatMessage
@@ -131,10 +133,7 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
             val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context)
             val isDuplicate = lokiMessageDatabase.getMessageID(messageServerID) != null
             if (isDuplicate) { return }
-
-            // Ignore empty messages with no attachments or quotes
             if (message.body.isEmpty() && message.attachments.isEmpty() && message.quote == null) { return }
-
             val id = group.id.toByteArray()
             val mmsDatabase = DatabaseFactory.getMmsDatabase(context)
             val recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(id, false)), false)

From f7e01688b0f87d708b3be49f841b24c417fbb207 Mon Sep 17 00:00:00 2001
From: Niels Andriesse <andriesseniels@gmail.com>
Date: Mon, 21 Oct 2019 15:35:52 +1100
Subject: [PATCH 08/14] Disable device linking for now

---
 res/layout/activity_seed.xml                                  | 3 ++-
 res/xml/preferences.xml                                       | 2 +-
 .../securesms/ApplicationPreferencesActivity.java             | 4 ++++
 src/org/thoughtcrime/securesms/loki/SeedActivity.kt           | 3 ++-
 4 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/res/layout/activity_seed.xml b/res/layout/activity_seed.xml
index 30e7062e11..cd79c01125 100644
--- a/res/layout/activity_seed.xml
+++ b/res/layout/activity_seed.xml
@@ -123,7 +123,8 @@
             android:layout_height="50dp"
             android:background="@color/transparent"
             android:textColor="@color/signal_primary"
-            android:text="@string/activity_key_pair_toggle_mode_button_title_3"
+            android:text="Link Device (Coming Soon)"
+            android:alpha="0.24"
             android:elevation="0dp"
             android:stateListAnimator="@null" />
 
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
index 21598d3421..666ee32098 100644
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -42,7 +42,7 @@
                 android:icon="@drawable/icon_qr_code"/>
 
     <Preference android:key="preference_category_link_device"
-                android:title="@string/activity_settings_link_device_button_title"
+                android:title="Link Device (Coming Soon)"
                 android:icon="@drawable/icon_link"/>
 
     <Preference android:key="preference_category_seed"
diff --git a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java
index f9b4cd3b15..840029356e 100644
--- a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java
+++ b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java
@@ -187,10 +187,14 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
         .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_PUBLIC_KEY));
       this.findPreference(PREFERENCE_CATEGORY_QR_CODE)
         .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_QR_CODE));
+
+      // TODO: Enable this again later
+      /*
       Preference linkDevicePreference = this.findPreference(PREFERENCE_CATEGORY_LINK_DEVICE);
       // Hide if this is a slave device
       linkDevicePreference.setVisible(isMasterDevice);
       linkDevicePreference.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_LINK_DEVICE));
+       */
       Preference seedPreference = this.findPreference(PREFERENCE_CATEGORY_SEED);
       // Hide if this is a slave device
       seedPreference.setVisible(isMasterDevice);
diff --git a/src/org/thoughtcrime/securesms/loki/SeedActivity.kt b/src/org/thoughtcrime/securesms/loki/SeedActivity.kt
index a98b6c5a46..33dd27e62c 100644
--- a/src/org/thoughtcrime/securesms/loki/SeedActivity.kt
+++ b/src/org/thoughtcrime/securesms/loki/SeedActivity.kt
@@ -54,7 +54,8 @@ class SeedActivity : BaseActionBarActivity(), DeviceLinkingDialogDelegate {
         copyButton.setOnClickListener { copy() }
         toggleRegisterModeButton.setOnClickListener { mode = Mode.Register }
         toggleRestoreModeButton.setOnClickListener { mode = Mode.Restore }
-        toggleLinkModeButton.setOnClickListener { mode = Mode.Link }
+        // TODO: Enable this again later
+//        toggleLinkModeButton.setOnClickListener { mode = Mode.Link }
         mainButton.setOnClickListener { handleMainButtonTapped() }
         Analytics.shared.track("Seed Screen Viewed")
     }

From 6d0858cf355ff0f99dcc6c50b3994cb15f36dc89 Mon Sep 17 00:00:00 2001
From: Niels Andriesse <andriesseniels@gmail.com>
Date: Mon, 21 Oct 2019 16:28:05 +1100
Subject: [PATCH 09/14] Limit allowed characters in display names

---
 src/org/thoughtcrime/securesms/CreateProfileActivity.java  | 7 +++++++
 src/org/thoughtcrime/securesms/loki/DisplayNameActivity.kt | 3 +++
 2 files changed, 10 insertions(+)

diff --git a/src/org/thoughtcrime/securesms/CreateProfileActivity.java b/src/org/thoughtcrime/securesms/CreateProfileActivity.java
index 975a9b443f..d30324ae2a 100644
--- a/src/org/thoughtcrime/securesms/CreateProfileActivity.java
+++ b/src/org/thoughtcrime/securesms/CreateProfileActivity.java
@@ -67,6 +67,8 @@ import java.io.IOException;
 import java.security.SecureRandom;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import javax.inject.Inject;
 
@@ -227,9 +229,14 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
       public void onTextChanged(CharSequence s, int start, int before, int count) {}
       @Override
       public void afterTextChanged(Editable s) {
+        Pattern pattern = Pattern.compile("[a-zA-Z0-9_]+");
+        Matcher matcher = pattern.matcher(s.toString());
         if (s.toString().isEmpty()) {
           name.getInput().setError("Invalid");
           finishButton.setEnabled(false);
+        } else if (!matcher.matches()) {
+          name.getInput().setError("Invalid (a-z, A-Z, 0-9 and _ only)");
+          finishButton.setEnabled(false);
         } else if (s.toString().getBytes().length > ProfileCipher.NAME_PADDED_LENGTH) {
           name.getInput().setError(getString(R.string.CreateProfileActivity_too_long));
           finishButton.setEnabled(false);
diff --git a/src/org/thoughtcrime/securesms/loki/DisplayNameActivity.kt b/src/org/thoughtcrime/securesms/loki/DisplayNameActivity.kt
index bb7f97504e..c99f480a75 100644
--- a/src/org/thoughtcrime/securesms/loki/DisplayNameActivity.kt
+++ b/src/org/thoughtcrime/securesms/loki/DisplayNameActivity.kt
@@ -27,6 +27,9 @@ class DisplayNameActivity : BaseActionBarActivity() {
         if (name.isEmpty()) {
             return nameEditText.input.setError("Invalid")
         }
+        if (!name.matches(Regex("[a-zA-Z0-9_]+"))) {
+            return nameEditText.input.setError("Invalid (a-z, A-Z, 0-9 and _ only)")
+        }
         if (name.toByteArray().size > ProfileCipher.NAME_PADDED_LENGTH) {
             return nameEditText.input.setError("Too Long")
         } else {

From 0421794b414d24fe871d2c8c8239141aa1de6b2c Mon Sep 17 00:00:00 2001
From: Mikunj <mikunj@live.com.au>
Date: Tue, 22 Oct 2019 08:41:57 +1100
Subject: [PATCH 10/14] Limit upload retries.

---
 src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java b/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java
index 0b31b7ecfe..9a091150ce 100644
--- a/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java
@@ -52,7 +52,7 @@ public class AttachmentUploadJob extends BaseJob implements InjectableType {
     this(new Job.Parameters.Builder()
                            .addConstraint(NetworkConstraint.KEY)
                            .setLifespan(TimeUnit.DAYS.toMillis(1))
-                           .setMaxAttempts(Parameters.UNLIMITED)
+                           .setMaxAttempts(3)
                            .build(),
          attachmentId, destination);
   }

From 2d287076875b01310ff7a42013600a7b06917b55 Mon Sep 17 00:00:00 2001
From: Niels Andriesse <andriesseniels@gmail.com>
Date: Tue, 22 Oct 2019 09:39:42 +1100
Subject: [PATCH 11/14] Switch over to new link preview system

---
 .../securesms/loki/LokiPublicChatPoller.kt    | 36 ++++++++++++++-----
 1 file changed, 27 insertions(+), 9 deletions(-)

diff --git a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt
index 5cd1b4c95c..e448b2ef0a 100644
--- a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt
+++ b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt
@@ -103,22 +103,40 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
             } else {
                 null
             }
-            val attachments = message.attachments.map { attachment ->
+            val attachments = message.attachments.mapNotNull { attachment ->
+                if (attachment.kind != LokiPublicChatMessage.Attachment.Kind.Attachment) { return@mapNotNull null }
                 SignalServiceAttachmentPointer(
-                        attachment.serverID,
-                        attachment.contentType,
+                    attachment.serverID,
+                    attachment.contentType,
+                    ByteArray(0),
+                    Optional.of(attachment.size),
+                    Optional.absent(),
+                    attachment.width, attachment.height,
+                    Optional.absent(),
+                    Optional.of(attachment.fileName),
+                    false,
+                    Optional.fromNullable(attachment.caption),
+                    attachment.url)
+            }
+            val linkPreview = message.attachments.firstOrNull { it.kind == LokiPublicChatMessage.Attachment.Kind.LinkPreview }
+            val signalLinkPreviews = mutableListOf<SignalServiceDataMessage.Preview>()
+            if (linkPreview != null) {
+                val attachment = SignalServiceAttachmentPointer(
+                        linkPreview.serverID,
+                        linkPreview.contentType,
                         ByteArray(0),
-                        Optional.of(attachment.size),
+                        Optional.of(linkPreview.size),
                         Optional.absent(),
-                        attachment.width, attachment.height,
+                        linkPreview.width, linkPreview.height,
                         Optional.absent(),
-                        Optional.of(attachment.fileName),
+                        Optional.of(linkPreview.fileName),
                         false,
-                        Optional.fromNullable(attachment.caption),
-                        attachment.url)
+                        Optional.fromNullable(linkPreview.caption),
+                        linkPreview.url)
+                signalLinkPreviews.add(SignalServiceDataMessage.Preview(linkPreview.linkPreviewURL!!, linkPreview.linkPreviewTitle!!, Optional.of(attachment)))
             }
             val body = if (message.body == message.timestamp.toString()) "" else message.body // Workaround for the fact that the back-end doesn't accept messages without a body
-            val serviceDataMessage = SignalServiceDataMessage(message.timestamp, serviceGroup, attachments, body, false, 0, false, null, false, quote, null, null, null)
+            val serviceDataMessage = SignalServiceDataMessage(message.timestamp, serviceGroup, attachments, body, false, 0, false, null, false, quote, null, signalLinkPreviews, null)
             val serviceContent = SignalServiceContent(serviceDataMessage, message.hexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false)
             val senderDisplayName = "${message.displayName} (...${message.hexEncodedPublicKey.takeLast(8)})"
             DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.hexEncodedPublicKey, senderDisplayName)

From 17ee4bf7a8bd85e12446342422830cf83c391331 Mon Sep 17 00:00:00 2001
From: Niels Andriesse <andriesseniels@gmail.com>
Date: Tue, 22 Oct 2019 09:46:47 +1100
Subject: [PATCH 12/14] Ditch temporary link preview setup

---
 .../securesms/jobs/PushDecryptJob.java        | 124 +++---------------
 1 file changed, 21 insertions(+), 103 deletions(-)

diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
index 3abcbcdaea..20fac5ef31 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
@@ -64,7 +64,6 @@ import org.thoughtcrime.securesms.jobmanager.Job;
 import org.thoughtcrime.securesms.jobmanager.JobManager;
 import org.thoughtcrime.securesms.linkpreview.Link;
 import org.thoughtcrime.securesms.linkpreview.LinkPreview;
-import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
 import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
 import org.thoughtcrime.securesms.logging.Log;
 import org.thoughtcrime.securesms.loki.LokiAPIUtilities;
@@ -760,38 +759,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
        message.getExpiresInSeconds() * 1000L, false, content.isNeedsReceipt(), message.getBody(), message.getGroupInfo(), message.getAttachments(),
         quote, sharedContacts, linkPreviews, sticker);
 
-      if (linkPreviews.isPresent()) {
-        int linkPreviewCount = linkPreviews.get().size();
-        if (linkPreviewCount != 0) {
-          LinkPreviewRepository lpr = new LinkPreviewRepository(context);
-          final int[] count = { 0 };
-          for (LinkPreview linkPreview : linkPreviews.get()) {
-            lpr.getLinkPreview(context, linkPreview.getUrl(), lp -> Util.runOnMain(() -> {
-              int c = count[0];
-              c = c + 1;
-              count[0] = c;
-              if (lp.isPresent() && lp.get().getThumbnail().isPresent()) {
-                Attachment thumbnail = lp.get().getThumbnail().get();
-                linkPreview.thumbnail = Optional.of(thumbnail);
-              }
-              if (c == linkPreviewCount) {
-                try {
-                  handleMediaMessage(content, mediaMessage, smsMessageId, messageServerIDOrNull);
-                } catch (Exception e) {
-                  // TODO: Handle
-                }
-              }
-            }));
-          }
-        } else {
-          handleMediaMessage(content, mediaMessage, smsMessageId, messageServerIDOrNull);
-        }
-      } else {
-        handleMediaMessage(content, mediaMessage, smsMessageId, messageServerIDOrNull);
-      }
-  }
-
-  private void handleMediaMessage(@NonNull SignalServiceContent content, @NonNull IncomingMediaMessage mediaMessage, @NonNull Optional<Long> smsMessageID, @Nullable Optional<Long> messageServerIDOrNull) throws StorageFailedException {
     MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
     database.beginTransaction();
 
@@ -816,8 +783,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
           ApplicationContext.getInstance(context).getJobManager().add(new AttachmentDownloadJob(insertResult.get().getMessageId(), attachment.getAttachmentId(), false));
         }
 
-        if (smsMessageID.isPresent()) {
-          DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageID.get());
+        if (smsMessageId.isPresent()) {
+          DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get());
         }
 
         database.setTransactionSuccessful();
@@ -967,79 +934,30 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
 
       IncomingEncryptedMessage textMessage = new IncomingEncryptedMessage(_textMessage, body);
 
-      List<Link> urls = LinkPreviewUtil.findWhitelistedUrls(body);
-      int urlCount = urls.size();
-      if (urlCount != 0) {
-        IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, content.getSender()), message.getTimestamp(), -1,
-           message.getExpiresInSeconds() * 1000L, false, content.isNeedsReceipt(), message.getBody(), message.getGroupInfo(), message.getAttachments(),
-            Optional.absent(), Optional.absent(), Optional.of(new ArrayList<>()), Optional.absent());
-        LinkPreviewRepository lpr = new LinkPreviewRepository(context);
-        final int[] count = { 0 };
-        for (Link url : urls) {
-          lpr.getLinkPreview(context, url.getUrl(), lp -> Util.runOnMain(() -> {
-            int c = count[0];
-            c = c + 1;
-            count[0] = c;
-            if (lp.isPresent()) { mediaMessage.getLinkPreviews().add(lp.get()); }
-            if (c == urlCount) {
-              try {
-                handleMediaMessage(content, mediaMessage, smsMessageId, messageServerIDOrNull);
-              } catch (Exception e) {
-                // TODO: Handle
-              }
-            }
-          }));
-        }
-      } else if (LinkPreviewUtil.isWhitelistedMediaUrl(body)) {
-        new LinkPreviewRepository(context).fetchGIF(context, body, attachmentOrNull -> Util.runOnMain(() -> {
-          if (attachmentOrNull.isPresent()) {
-            Attachment attachment = attachmentOrNull.get();
-            try {
-              IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, content.getSender()), message.getTimestamp(), -1,
-                 message.getExpiresInSeconds() * 1000L, false, content.isNeedsReceipt(), message.getBody(), message.getGroupInfo(), Optional.of(new ArrayList<>()),
-                  Optional.absent(), Optional.absent(), Optional.absent(), Optional.absent());
-              mediaMessage.getAttachments().add(attachment);
-              handleMediaMessage(content, mediaMessage, smsMessageId, messageServerIDOrNull);
-            } catch (Exception e) {
-              // TODO: Handle
-            }
-          } else {
-            handleTextMessage(message, textMessage, smsMessageId, messageServerIDOrNull);
-          }
-        }));
-      } else {
-        handleTextMessage(message, textMessage, smsMessageId, messageServerIDOrNull);
-      }
-    }
-  }
-
-  private void handleTextMessage(@NonNull SignalServiceDataMessage message, @NonNull IncomingTextMessage textMessage, @NonNull Optional<Long> smsMessageId, @NonNull Optional<Long> messageServerIDOrNull) {
-    SmsDatabase database  = DatabaseFactory.getSmsDatabase(context);
-
-    // Ignore the message if the body is empty
-    if (textMessage.getMessageBody().length() == 0) { return; }
+      // Ignore the message if the body is empty
+      if (textMessage.getMessageBody().length() == 0) { return; }
 
-    // Insert the message into the database
-    Optional<InsertResult> insertResult = database.insertMessageInbox(textMessage);
-
-    Long threadId;
-    if (insertResult.isPresent()) threadId = insertResult.get().getThreadId();
-    else                          threadId = null;
+      // Insert the message into the database
+      Optional<InsertResult> insertResult = database.insertMessageInbox(textMessage);
+      
+      if (insertResult.isPresent()) threadId = insertResult.get().getThreadId();
+      else                          threadId = null;
 
-    if (smsMessageId.isPresent()) database.deleteMessage(smsMessageId.get());
+      if (smsMessageId.isPresent()) database.deleteMessage(smsMessageId.get());
 
-    // Loki - Cache the user hex encoded public key (for mentions)
-    if (threadId != null) {
-      LokiAPIUtilities.INSTANCE.populateUserHexEncodedPublicKeyCacheIfNeeded(threadId, context);
-      LokiAPI.Companion.cache(textMessage.getSender().serialize(), threadId);
-    }
+      // Loki - Cache the user hex encoded public key (for mentions)
+      if (threadId != null) {
+        LokiAPIUtilities.INSTANCE.populateUserHexEncodedPublicKeyCacheIfNeeded(threadId, context);
+        LokiAPI.Companion.cache(textMessage.getSender().serialize(), threadId);
+      }
 
-    // Loki - Store message server ID
-    updateGroupChatMessageServerID(messageServerIDOrNull, insertResult);
+      // Loki - Store message server ID
+      updateGroupChatMessageServerID(messageServerIDOrNull, insertResult);
 
-    boolean isGroupMessage = message.getGroupInfo().isPresent();
-    if (threadId != null && !isGroupMessage) {
-      MessageNotifier.updateNotification(context, threadId);
+      boolean isGroupMessage = message.getGroupInfo().isPresent();
+      if (threadId != null && !isGroupMessage) {
+        MessageNotifier.updateNotification(context, threadId);
+      }
     }
   }
 

From 4aa1eed9794a2121ecbf16f3e654ec5e03ba1c93 Mon Sep 17 00:00:00 2001
From: Niels Andriesse <andriesseniels@gmail.com>
Date: Tue, 22 Oct 2019 10:09:46 +1100
Subject: [PATCH 13/14] Debug

---
 src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java     | 2 +-
 src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java   | 1 -
 src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt | 2 +-
 3 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
index 20fac5ef31..625efd9455 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
@@ -939,7 +939,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
 
       // Insert the message into the database
       Optional<InsertResult> insertResult = database.insertMessageInbox(textMessage);
-      
+
       if (insertResult.isPresent()) threadId = insertResult.get().getThreadId();
       else                          threadId = null;
 
diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
index bcfe4b9de0..263dedc322 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
@@ -104,7 +104,6 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
       OutgoingMediaMessage message     = database.getOutgoingMessage(messageId);
       List<Attachment>     attachments = new LinkedList<>();
 
-      // Loki - For now all attachments are re-fetched by the receiver
       attachments.addAll(message.getAttachments());
       attachments.addAll(Stream.of(message.getLinkPreviews()).filter(p -> p.getThumbnail().isPresent()).map(p -> p.getThumbnail().get()).toList());
       attachments.addAll(Stream.of(message.getSharedContacts()).filter(c -> c.getAvatar() != null).map(c -> c.getAvatar().getAttachment()).withoutNulls().toList());
diff --git a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt
index e448b2ef0a..3e6685e429 100644
--- a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt
+++ b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt
@@ -140,7 +140,7 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
             val serviceContent = SignalServiceContent(serviceDataMessage, message.hexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false)
             val senderDisplayName = "${message.displayName} (...${message.hexEncodedPublicKey.takeLast(8)})"
             DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.hexEncodedPublicKey, senderDisplayName)
-            if (quote != null || attachments.count() > 0) {
+            if (quote != null || attachments.count() > 0 || linkPreview != null) {
                 PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))
             } else {
                 PushDecryptJob(context).handleTextMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))

From a08f4caccbfd5750c4978bfa605d3503682180f7 Mon Sep 17 00:00:00 2001
From: Niels Andriesse <andriesseniels@gmail.com>
Date: Tue, 22 Oct 2019 11:25:00 +1100
Subject: [PATCH 14/14] Fix camera button position

---
 res/layout/conversation_activity.xml    | 1 -
 res/layout/conversation_input_panel.xml | 1 +
 2 files changed, 1 insertion(+), 1 deletion(-)

diff --git a/res/layout/conversation_activity.xml b/res/layout/conversation_activity.xml
index 9c791f205f..6d2e36cbb8 100644
--- a/res/layout/conversation_activity.xml
+++ b/res/layout/conversation_activity.xml
@@ -125,7 +125,6 @@
                 android:text="160/160 (1)"
                 android:visibility="gone" />
 
-
             <ViewStub
                 android:id="@+id/emoji_drawer_stub"
                 android:layout_width="match_parent"
diff --git a/res/layout/conversation_input_panel.xml b/res/layout/conversation_input_panel.xml
index 54f8563e54..adf8b378d3 100644
--- a/res/layout/conversation_input_panel.xml
+++ b/res/layout/conversation_input_panel.xml
@@ -126,6 +126,7 @@
                                 android:src="?quick_camera_icon"
                                 android:paddingStart="6dp"
                                 android:paddingEnd="6dp"
+                                android:layout_marginEnd="8dp"
                                 android:background="?selectableItemBackgroundBorderless"
                                 android:contentDescription="@string/conversation_activity__quick_attachment_drawer_toggle_camera_description" />