Detailed changes
@@ -1,27 +1,16 @@
package eu.siacs.conversations.entities;
+import static eu.siacs.conversations.entities.Bookmark.printableValue;
+
import android.content.ContentValues;
import android.database.Cursor;
import android.text.TextUtils;
-
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-
+import com.google.common.base.Strings;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.concurrent.atomic.AtomicBoolean;
-
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.OmemoSetting;
import eu.siacs.conversations.crypto.PgpDecryptionService;
@@ -34,11 +23,17 @@ import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.mam.MamReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
-import static eu.siacs.conversations.entities.Bookmark.printableValue;
-
-
-public class Conversation extends AbstractEntity implements Blockable, Comparable<Conversation>, Conversational, AvatarService.Avatarable {
+public class Conversation extends AbstractEntity
+ implements Blockable, Comparable<Conversation>, Conversational, AvatarService.Avatarable {
public static final String TABLENAME = "conversations";
public static final int STATUS_AVAILABLE = 0;
@@ -56,7 +51,8 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
public static final String ATTRIBUTE_MUTED_TILL = "muted_till";
public static final String ATTRIBUTE_ALWAYS_NOTIFY = "always_notify";
public static final String ATTRIBUTE_LAST_CLEAR_HISTORY = "last_clear_history";
- public static final String ATTRIBUTE_FORMERLY_PRIVATE_NON_ANONYMOUS = "formerly_private_non_anonymous";
+ public static final String ATTRIBUTE_FORMERLY_PRIVATE_NON_ANONYMOUS =
+ "formerly_private_non_anonymous";
public static final String ATTRIBUTE_PINNED_ON_TOP = "pinned_on_top";
static final String ATTRIBUTE_MUC_PASSWORD = "muc_password";
static final String ATTRIBUTE_MEMBERS_ONLY = "members_only";
@@ -78,7 +74,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
private int status;
private final long created;
private int mode;
- private JSONObject attributes;
+ private final JSONObject attributes;
private Jid nextCounterpart;
private transient MucOptions mucOptions = null;
private boolean messagesLeftOnServer = true;
@@ -87,17 +83,31 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
private String mFirstMamReference = null;
private String displayState = null;
- public Conversation(final String name, final Account account, final Jid contactJid,
- final int mode) {
- this(java.util.UUID.randomUUID().toString(), name, null, account
- .getUuid(), contactJid, System.currentTimeMillis(),
- STATUS_AVAILABLE, mode, "");
+ public Conversation(
+ final String name, final Account account, final Jid contactJid, final int mode) {
+ this(
+ java.util.UUID.randomUUID().toString(),
+ name,
+ null,
+ account.getUuid(),
+ contactJid,
+ System.currentTimeMillis(),
+ STATUS_AVAILABLE,
+ mode,
+ "");
this.account = account;
}
- public Conversation(final String uuid, final String name, final String contactUuid,
- final String accountUuid, final Jid contactJid, final long created, final int status,
- final int mode, final String attributes) {
+ public Conversation(
+ final String uuid,
+ final String name,
+ final String contactUuid,
+ final String accountUuid,
+ final Jid contactJid,
+ final long created,
+ final int status,
+ final int mode,
+ final String attributes) {
this.uuid = uuid;
this.name = name;
this.contactUuid = contactUuid;
@@ -106,26 +116,37 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
this.created = created;
this.status = status;
this.mode = mode;
- try {
- this.attributes = new JSONObject(attributes == null ? "" : attributes);
- } catch (JSONException e) {
- this.attributes = new JSONObject();
+ this.attributes = parseAttributes(attributes);
+ }
+
+ private static JSONObject parseAttributes(final String attributes) {
+ if (Strings.isNullOrEmpty(attributes)) {
+ return new JSONObject();
+ } else {
+ try {
+ return new JSONObject(attributes);
+ } catch (final JSONException e) {
+ return new JSONObject();
+ }
}
}
- public static Conversation fromCursor(Cursor cursor) {
- return new Conversation(cursor.getString(cursor.getColumnIndex(UUID)),
- cursor.getString(cursor.getColumnIndex(NAME)),
- cursor.getString(cursor.getColumnIndex(CONTACT)),
- cursor.getString(cursor.getColumnIndex(ACCOUNT)),
- JidHelper.parseOrFallbackToInvalid(cursor.getString(cursor.getColumnIndex(CONTACTJID))),
- cursor.getLong(cursor.getColumnIndex(CREATED)),
- cursor.getInt(cursor.getColumnIndex(STATUS)),
- cursor.getInt(cursor.getColumnIndex(MODE)),
- cursor.getString(cursor.getColumnIndex(ATTRIBUTES)));
+ public static Conversation fromCursor(final Cursor cursor) {
+ return new Conversation(
+ cursor.getString(cursor.getColumnIndexOrThrow(UUID)),
+ cursor.getString(cursor.getColumnIndexOrThrow(NAME)),
+ cursor.getString(cursor.getColumnIndexOrThrow(CONTACT)),
+ cursor.getString(cursor.getColumnIndexOrThrow(ACCOUNT)),
+ JidHelper.parseOrFallbackToInvalid(
+ cursor.getString(cursor.getColumnIndexOrThrow(CONTACTJID))),
+ cursor.getLong(cursor.getColumnIndexOrThrow(CREATED)),
+ cursor.getInt(cursor.getColumnIndexOrThrow(STATUS)),
+ cursor.getInt(cursor.getColumnIndexOrThrow(MODE)),
+ cursor.getString(cursor.getColumnIndexOrThrow(ATTRIBUTES)));
}
- public static Message getLatestMarkableMessage(final List<Message> messages, boolean isPrivateAndNonAnonymousMuc) {
+ public static Message getLatestMarkableMessage(
+ final List<Message> messages, boolean isPrivateAndNonAnonymousMuc) {
for (int i = messages.size() - 1; i >= 0; --i) {
final Message message = messages.get(i);
if (message.getStatus() <= Message.STATUS_RECEIVED
@@ -146,10 +167,13 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
}
final String contact = conversation.getJid().getDomain().toEscapedString();
final String account = conversation.getAccount().getServer();
- if (Config.OMEMO_EXCEPTIONS.matchesContactDomain(contact) || Config.OMEMO_EXCEPTIONS.ACCOUNT_DOMAINS.contains(account)) {
+ if (Config.OMEMO_EXCEPTIONS.matchesContactDomain(contact)
+ || Config.OMEMO_EXCEPTIONS.ACCOUNT_DOMAINS.contains(account)) {
return false;
}
- return conversation.isSingleOrPrivateAndNonAnonymous() || conversation.getBooleanAttribute(ATTRIBUTE_FORMERLY_PRIVATE_NON_ANONYMOUS, false);
+ return conversation.isSingleOrPrivateAndNonAnonymous()
+ || conversation.getBooleanAttribute(
+ ATTRIBUTE_FORMERLY_PRIVATE_NON_ANONYMOUS, false);
}
public boolean hasMessagesLeftOnServer() {
@@ -193,7 +217,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
public int countFailedDeliveries() {
int count = 0;
synchronized (this.messages) {
- for(final Message message : this.messages) {
+ for (final Message message : this.messages) {
if (message.getStatus() == Message.STATUS_SEND_FAILED) {
++count;
}
@@ -216,12 +240,12 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
return null;
}
-
public Message findUnsentMessageWithUuid(String uuid) {
synchronized (this.messages) {
for (final Message message : this.messages) {
final int s = message.getStatus();
- if ((s == Message.STATUS_UNSEND || s == Message.STATUS_WAITING) && message.getUuid().equals(uuid)) {
+ if ((s == Message.STATUS_UNSEND || s == Message.STATUS_WAITING)
+ && message.getUuid().equals(uuid)) {
return message;
}
}
@@ -262,10 +286,16 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
synchronized (this.messages) {
for (final Message message : this.messages) {
final Transferable transferable = message.getTransferable();
- final boolean unInitiatedButKnownSize = MessageUtils.unInitiatedButKnownSize(message);
+ final boolean unInitiatedButKnownSize =
+ MessageUtils.unInitiatedButKnownSize(message);
if (message.getUuid().equals(uuid)
&& message.getEncryption() != Message.ENCRYPTION_PGP
- && (message.isFileOrImage() || message.treatAsDownloadable() || unInitiatedButKnownSize || (transferable != null && transferable.getStatus() != Transferable.STATUS_UPLOADING))) {
+ && (message.isFileOrImage()
+ || message.treatAsDownloadable()
+ || unInitiatedButKnownSize
+ || (transferable != null
+ && transferable.getStatus()
+ != Transferable.STATUS_UPLOADING))) {
return message;
}
}
@@ -292,7 +322,8 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
if (uuids.contains(message.getUuid())) {
message.setDeleted(true);
deleted = true;
- if (message.getEncryption() == Message.ENCRYPTION_PGP && pgpDecryptionService != null) {
+ if (message.getEncryption() == Message.ENCRYPTION_PGP
+ && pgpDecryptionService != null) {
pgpDecryptionService.discard(message);
}
}
@@ -310,7 +341,9 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
if (file.uuid.toString().equals(message.getUuid())) {
message.setDeleted(file.deleted);
changed = true;
- if (file.deleted && message.getEncryption() == Message.ENCRYPTION_PGP && pgpDecryptionService != null) {
+ if (file.deleted
+ && message.getEncryption() == Message.ENCRYPTION_PGP
+ && pgpDecryptionService != null) {
pgpDecryptionService.discard(message);
}
}
@@ -338,7 +371,8 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
}
public boolean setOutgoingChatState(ChatState state) {
- if (mode == MODE_SINGLE && !getContact().isSelf() || (isPrivateAndNonAnonymous() && getNextCounterpart() == null)) {
+ if (mode == MODE_SINGLE && !getContact().isSelf()
+ || (isPrivateAndNonAnonymous() && getNextCounterpart() == null)) {
if (this.mOutgoingChatState != state) {
this.mOutgoingChatState = state;
return true;
@@ -371,7 +405,8 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
final ArrayList<Message> results = new ArrayList<>();
synchronized (this.messages) {
for (Message message : this.messages) {
- if ((message.getType() == Message.TYPE_TEXT || message.hasFileOnRemoteHost()) && message.getStatus() == Message.STATUS_UNSEND) {
+ if ((message.getType() == Message.TYPE_TEXT || message.hasFileOnRemoteHost())
+ && message.getStatus() == Message.STATUS_UNSEND) {
results.add(message);
}
}
@@ -386,7 +421,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
for (Message message : this.messages) {
if (id.equals(message.getUuid())
|| (message.getStatus() >= Message.STATUS_SEND
- && id.equals(message.getRemoteMsgId()))) {
+ && id.equals(message.getRemoteMsgId()))) {
return message;
}
}
@@ -405,7 +440,8 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
return null;
}
- public Message findMessageWithRemoteIdAndCounterpart(String id, Jid counterpart, boolean received, boolean carbon) {
+ public Message findMessageWithRemoteIdAndCounterpart(
+ String id, Jid counterpart, boolean received, boolean carbon) {
synchronized (this.messages) {
for (int i = this.messages.size() - 1; i >= 0; --i) {
final Message message = messages.get(i);
@@ -413,9 +449,12 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
if (mcp == null) {
continue;
}
- if (mcp.equals(counterpart) && ((message.getStatus() == Message.STATUS_RECEIVED) == received)
+ if (mcp.equals(counterpart)
+ && ((message.getStatus() == Message.STATUS_RECEIVED) == received)
&& (carbon == message.isCarbon() || received)) {
- if (id.equals(message.getRemoteMsgId()) && !message.isFileOrImage() && !message.treatAsDownloadable()) {
+ if (id.equals(message.getRemoteMsgId())
+ && !message.isFileOrImage()
+ && !message.treatAsDownloadable()) {
return message;
} else {
return null;
@@ -452,7 +491,8 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
public Message findReceivedWithRemoteId(final String id) {
synchronized (this.messages) {
for (final Message message : this.messages) {
- if (message.getStatus() == Message.STATUS_RECEIVED && id.equals(message.getRemoteMsgId())) {
+ if (message.getStatus() == Message.STATUS_RECEIVED
+ && id.equals(message.getRemoteMsgId())) {
return message;
}
}
@@ -487,11 +527,6 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
messages.clear();
messages.addAll(this.messages);
}
- for (Iterator<Message> iterator = messages.iterator(); iterator.hasNext(); ) {
- if (iterator.next().wasMergedIntoPrevious()) {
- iterator.remove();
- }
- }
}
@Override
@@ -548,7 +583,9 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
}
public boolean setCorrectingMessage(Message correctingMessage) {
- setAttribute(ATTRIBUTE_CORRECTING_MESSAGE, correctingMessage == null ? null : correctingMessage.getUuid());
+ setAttribute(
+ ATTRIBUTE_CORRECTING_MESSAGE,
+ correctingMessage == null ? null : correctingMessage.getUuid());
return correctingMessage == null && draftMessage != null;
}
@@ -564,7 +601,9 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
@Override
public int compareTo(@NonNull Conversation another) {
return ComparisonChain.start()
- .compareFalseFirst(another.getBooleanAttribute(ATTRIBUTE_PINNED_ON_TOP, false), getBooleanAttribute(ATTRIBUTE_PINNED_ON_TOP, false))
+ .compareFalseFirst(
+ another.getBooleanAttribute(ATTRIBUTE_PINNED_ON_TOP, false),
+ getBooleanAttribute(ATTRIBUTE_PINNED_ON_TOP, false))
.compare(another.getSortableTime(), getSortableTime())
.result();
}
@@ -589,7 +628,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
public boolean isRead() {
synchronized (this.messages) {
- for(final Message message : Lists.reverse(this.messages)) {
+ for (final Message message : Lists.reverse(this.messages)) {
if (message.isRead() && message.getType() == Message.TYPE_RTP_SESSION) {
continue;
}
@@ -628,8 +667,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
}
}
- public @NonNull
- CharSequence getName() {
+ public @NonNull CharSequence getName() {
if (getMode() == MODE_MULTI) {
final String roomName = getMucOptions().getName();
final String subject = getMucOptions().getSubject();
@@ -649,7 +687,9 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
return contactJid.getLocal() != null ? contactJid.getLocal() : contactJid;
}
}
- } else if ((QuickConversationsService.isConversations() || !Config.QUICKSY_DOMAIN.equals(contactJid.getDomain())) && isWithStranger()) {
+ } else if ((QuickConversationsService.isConversations()
+ || !Config.QUICKSY_DOMAIN.equals(contactJid.getDomain()))
+ && isWithStranger()) {
return contactJid;
} else {
return this.getContact().getDisplayName();
@@ -713,9 +753,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
this.mode = mode;
}
- /**
- * short for is Private and Non-anonymous
- */
+ /** short for is Private and Non-anonymous */
public boolean isSingleOrPrivateAndNonAnonymous() {
return mode == MODE_SINGLE || isPrivateAndNonAnonymous();
}
@@ -752,7 +790,9 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
return Message.ENCRYPTION_NONE;
}
if (OmemoSetting.isAlways()) {
- return suitableForOmemoByDefault(this) ? Message.ENCRYPTION_AXOLOTL : Message.ENCRYPTION_NONE;
+ return suitableForOmemoByDefault(this)
+ ? Message.ENCRYPTION_AXOLOTL
+ : Message.ENCRYPTION_NONE;
}
final int defaultEncryption;
if (suitableForOmemoByDefault(this)) {
@@ -777,8 +817,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
return nextMessage == null ? "" : nextMessage;
}
- public @Nullable
- Draft getDraft() {
+ public @Nullable Draft getDraft() {
long timestamp = getLongAttribute(ATTRIBUTE_NEXT_MESSAGE_TIMESTAMP, 0);
if (timestamp > getLatestMessage().getTimeSent()) {
String message = getAttribute(ATTRIBUTE_NEXT_MESSAGE);
@@ -794,7 +833,9 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
boolean changed = !getNextMessage().equals(message);
this.setAttribute(ATTRIBUTE_NEXT_MESSAGE, message);
if (changed) {
- this.setAttribute(ATTRIBUTE_NEXT_MESSAGE_TIMESTAMP, message == null ? 0 : System.currentTimeMillis());
+ this.setAttribute(
+ ATTRIBUTE_NEXT_MESSAGE_TIMESTAMP,
+ message == null ? 0 : System.currentTimeMillis());
}
return changed;
}
@@ -822,7 +863,8 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
synchronized (this.messages) {
for (int i = this.messages.size() - 1; i >= 0; --i) {
Message message = this.messages.get(i);
- if (message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_SEND) {
+ if (message.getStatus() == Message.STATUS_UNSEND
+ || message.getStatus() == Message.STATUS_SEND) {
String otherBody;
if (message.hasFileOnRemoteHost()) {
otherBody = message.getFileParams().url;
@@ -842,7 +884,9 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
synchronized (this.messages) {
for (int i = this.messages.size() - 1; i >= 0; --i) {
final Message message = this.messages.get(i);
- if ((message.getStatus() == s) && (message.getType() == Message.TYPE_RTP_SESSION) && sessionId.equals(message.getRemoteMsgId())) {
+ if ((message.getStatus() == s)
+ && (message.getType() == Message.TYPE_RTP_SESSION)
+ && sessionId.equals(message.getRemoteMsgId())) {
return message;
}
}
@@ -856,7 +900,8 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
}
synchronized (this.messages) {
for (Message message : this.messages) {
- if (serverMsgId.equals(message.getServerMsgId()) || remoteMsgId.equals(message.getRemoteMsgId())) {
+ if (serverMsgId.equals(message.getServerMsgId())
+ || remoteMsgId.equals(message.getRemoteMsgId())) {
return true;
}
}
@@ -871,10 +916,14 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
for (int i = this.messages.size() - 1; i >= 0; --i) {
final Message message = this.messages.get(i);
if (message.isPrivateMessage()) {
- continue; //it's unsafe to use private messages as anchor. They could be coming from user archive
+ continue; // it's unsafe to use private messages as anchor. They could be coming
+ // from user archive
}
- if (message.getStatus() == Message.STATUS_RECEIVED || message.isCarbon() || message.getServerMsgId() != null) {
- lastReceived = new MamReference(message.getTimeSent(), message.getServerMsgId());
+ if (message.getStatus() == Message.STATUS_RECEIVED
+ || message.isCarbon()
+ || message.getServerMsgId() != null) {
+ lastReceived =
+ new MamReference(message.getTimeSent(), message.getServerMsgId());
break;
}
}
@@ -891,7 +940,10 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
}
public boolean alwaysNotify() {
- return mode == MODE_SINGLE || getBooleanAttribute(ATTRIBUTE_ALWAYS_NOTIFY, Config.ALWAYS_NOTIFY_BY_DEFAULT || isPrivateAndNonAnonymous());
+ return mode == MODE_SINGLE
+ || getBooleanAttribute(
+ ATTRIBUTE_ALWAYS_NOTIFY,
+ Config.ALWAYS_NOTIFY_BY_DEFAULT || isPrivateAndNonAnonymous());
}
public boolean setAttribute(String key, boolean value) {
@@ -957,11 +1009,11 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
try {
list.add(Jid.of(array.getString(i)));
} catch (IllegalArgumentException e) {
- //ignored
+ // ignored
}
}
} catch (JSONException e) {
- //ignored
+ // ignored
}
}
return list;
@@ -1023,7 +1075,8 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
public void expireOldMessages(long timestamp) {
synchronized (this.messages) {
- for (ListIterator<Message> iterator = this.messages.listIterator(); iterator.hasNext(); ) {
+ for (ListIterator<Message> iterator = this.messages.listIterator();
+ iterator.hasNext(); ) {
if (iterator.next().getTimeSent() < timestamp) {
iterator.remove();
}
@@ -1034,15 +1087,17 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
public void sort() {
synchronized (this.messages) {
- Collections.sort(this.messages, (left, right) -> {
- if (left.getTimeSent() < right.getTimeSent()) {
- return -1;
- } else if (left.getTimeSent() > right.getTimeSent()) {
- return 1;
- } else {
- return 0;
- }
- });
+ Collections.sort(
+ this.messages,
+ (left, right) -> {
+ if (left.getTimeSent() < right.getTimeSent()) {
+ return -1;
+ } else if (left.getTimeSent() > right.getTimeSent()) {
+ return 1;
+ } else {
+ return 0;
+ }
+ });
untieMessages();
}
}
@@ -1056,7 +1111,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
public int unreadCount() {
synchronized (this.messages) {
int count = 0;
- for(final Message message : Lists.reverse(this.messages)) {
+ for (final Message message : Lists.reverse(this.messages)) {
if (message.isRead()) {
if (message.getType() == Message.TYPE_RTP_SESSION) {
continue;
@@ -3,26 +3,11 @@ package eu.siacs.conversations.entities;
import android.content.ContentValues;
import android.database.Cursor;
import android.graphics.Color;
-import android.text.SpannableStringBuilder;
import android.util.Log;
-
import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Longs;
-
-import org.json.JSONException;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
-
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
@@ -36,6 +21,15 @@ import eu.siacs.conversations.utils.MessageUtils;
import eu.siacs.conversations.utils.MimeUtils;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.Jid;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import org.json.JSONException;
public class Message extends AbstractEntity implements AvatarService.Avatarable {
@@ -94,7 +88,6 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
public static final String ERROR_MESSAGE_CANCELLED = "eu.siacs.conversations.cancelled";
-
public boolean markable = false;
protected String conversationUuid;
protected Jid counterpart;
@@ -140,7 +133,9 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
}
public Message(Conversational conversation, String body, int encryption, int status) {
- this(conversation, java.util.UUID.randomUUID().toString(),
+ this(
+ conversation,
+ java.util.UUID.randomUUID().toString(),
conversation.getUuid(),
conversation.getJid() == null ? null : conversation.getJid().asBareJid(),
null,
@@ -167,7 +162,9 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
}
public Message(Conversation conversation, int status, int type, final String remoteMsgId) {
- this(conversation, java.util.UUID.randomUUID().toString(),
+ this(
+ conversation,
+ java.util.UUID.randomUUID().toString(),
conversation.getUuid(),
conversation.getJid() == null ? null : conversation.getJid().asBareJid(),
null,
@@ -193,13 +190,32 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
Collections.emptyList());
}
- protected Message(final Conversational conversation, final String uuid, final String conversationUUid, final Jid counterpart,
- final Jid trueCounterpart, final String body, final long timeSent,
- final int encryption, final int status, final int type, final boolean carbon,
- final String remoteMsgId, final String relativeFilePath,
- final String serverMsgId, final String fingerprint, final boolean read,
- final String edited, final boolean oob, final String errorMessage, final Set<ReadByMarker> readByMarkers,
- final boolean markable, final boolean deleted, final String bodyLanguage, final String occupantId, final Collection<Reaction> reactions) {
+ protected Message(
+ final Conversational conversation,
+ final String uuid,
+ final String conversationUUid,
+ final Jid counterpart,
+ final Jid trueCounterpart,
+ final String body,
+ final long timeSent,
+ final int encryption,
+ final int status,
+ final int type,
+ final boolean carbon,
+ final String remoteMsgId,
+ final String relativeFilePath,
+ final String serverMsgId,
+ final String fingerprint,
+ final boolean read,
+ final String edited,
+ final boolean oob,
+ final String errorMessage,
+ final Set<ReadByMarker> readByMarkers,
+ final boolean markable,
+ final boolean deleted,
+ final String bodyLanguage,
+ final String occupantId,
+ final Collection<Reaction> reactions) {
this.conversation = conversation;
this.uuid = uuid;
this.conversationUuid = conversationUUid;
@@ -228,7 +244,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
}
public static Message fromCursor(final Cursor cursor, final Conversation conversation) {
- return new Message(conversation,
+ return new Message(
+ conversation,
cursor.getString(cursor.getColumnIndexOrThrow(UUID)),
cursor.getString(cursor.getColumnIndexOrThrow(CONVERSATION)),
fromString(cursor.getString(cursor.getColumnIndexOrThrow(COUNTERPART))),
@@ -247,14 +264,13 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
cursor.getString(cursor.getColumnIndexOrThrow(EDITED)),
cursor.getInt(cursor.getColumnIndexOrThrow(OOB)) > 0,
cursor.getString(cursor.getColumnIndexOrThrow(ERROR_MESSAGE)),
- ReadByMarker.fromJsonString(cursor.getString(cursor.getColumnIndexOrThrow(READ_BY_MARKERS))),
+ ReadByMarker.fromJsonString(
+ cursor.getString(cursor.getColumnIndexOrThrow(READ_BY_MARKERS))),
cursor.getInt(cursor.getColumnIndexOrThrow(MARKABLE)) > 0,
cursor.getInt(cursor.getColumnIndexOrThrow(DELETED)) > 0,
cursor.getString(cursor.getColumnIndexOrThrow(BODY_LANGUAGE)),
cursor.getString(cursor.getColumnIndexOrThrow(OCCUPANT_ID)),
- Reaction.fromString(cursor.getString(cursor.getColumnIndexOrThrow(REACTIONS)))
-
- );
+ Reaction.fromString(cursor.getString(cursor.getColumnIndexOrThrow(REACTIONS))));
}
private static Jid fromString(String value) {
@@ -298,7 +314,11 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
} else {
values.put(TRUE_COUNTERPART, trueCounterpart.toString());
}
- values.put(BODY, body.length() > Config.MAX_STORAGE_MESSAGE_CHARS ? body.substring(0, Config.MAX_STORAGE_MESSAGE_CHARS) : body);
+ values.put(
+ BODY,
+ body.length() > Config.MAX_STORAGE_MESSAGE_CHARS
+ ? body.substring(0, Config.MAX_STORAGE_MESSAGE_CHARS)
+ : body);
values.put(TIME_SENT, timeSent);
values.put(ENCRYPTION, encryption);
values.put(STATUS, status);
@@ -348,7 +368,9 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
if (this.trueCounterpart == null) {
return null;
} else {
- return this.conversation.getAccount().getRoster()
+ return this.conversation
+ .getAccount()
+ .getRoster()
.getContactFromContactList(this.trueCounterpart);
}
}
@@ -375,7 +397,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
public boolean sameMucUser(Message otherMessage) {
final MucOptions.User thisUser = this.user == null ? null : this.user.get();
- final MucOptions.User otherUser = otherMessage.user == null ? null : otherMessage.user.get();
+ final MucOptions.User otherUser =
+ otherMessage.user == null ? null : otherMessage.user.get();
return thisUser != null && thisUser == otherUser;
}
@@ -384,8 +407,9 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
}
public boolean setErrorMessage(String message) {
- boolean changed = (message != null && !message.equals(errorMessage))
- || (message == null && errorMessage != null);
+ boolean changed =
+ (message != null && !message.equals(errorMessage))
+ || (message == null && errorMessage != null);
this.errorMessage = message;
return changed;
}
@@ -533,7 +557,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
Iterator<ReadByMarker> iterator = this.readByMarkers.iterator();
while (iterator.hasNext()) {
ReadByMarker marker = iterator.next();
- if (marker.getRealJid() == null && readByMarker.getFullJid().equals(marker.getFullJid())) {
+ if (marker.getRealJid() == null
+ && readByMarker.getFullJid().equals(marker.getFullJid())) {
iterator.remove();
}
}
@@ -557,7 +582,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
boolean similar(Message message) {
if (!isPrivateMessage() && this.serverMsgId != null && message.getServerMsgId() != null) {
- return this.serverMsgId.equals(message.getServerMsgId()) || Edit.wasPreviouslyEditedServerMsgId(edits, message.getServerMsgId());
+ return this.serverMsgId.equals(message.getServerMsgId())
+ || Edit.wasPreviouslyEditedServerMsgId(edits, message.getServerMsgId());
} else if (Edit.wasPreviouslyEditedServerMsgId(edits, message.getServerMsgId())) {
return true;
} else if (this.body == null || this.counterpart == null) {
@@ -573,32 +599,37 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
}
final boolean matchingCounterpart = this.counterpart.equals(message.getCounterpart());
if (message.getRemoteMsgId() != null) {
- final boolean hasUuid = CryptoHelper.UUID_PATTERN.matcher(message.getRemoteMsgId()).matches();
- if (hasUuid && matchingCounterpart && Edit.wasPreviouslyEditedRemoteMsgId(edits, message.getRemoteMsgId())) {
+ final boolean hasUuid =
+ CryptoHelper.UUID_PATTERN.matcher(message.getRemoteMsgId()).matches();
+ if (hasUuid
+ && matchingCounterpart
+ && Edit.wasPreviouslyEditedRemoteMsgId(edits, message.getRemoteMsgId())) {
return true;
}
- return (message.getRemoteMsgId().equals(this.remoteMsgId) || message.getRemoteMsgId().equals(this.uuid))
+ return (message.getRemoteMsgId().equals(this.remoteMsgId)
+ || message.getRemoteMsgId().equals(this.uuid))
&& matchingCounterpart
- && (body.equals(otherBody) || (message.getEncryption() == Message.ENCRYPTION_PGP && hasUuid));
+ && (body.equals(otherBody)
+ || (message.getEncryption() == Message.ENCRYPTION_PGP && hasUuid));
} else {
return this.remoteMsgId == null
&& matchingCounterpart
&& body.equals(otherBody)
- && Math.abs(this.getTimeSent() - message.getTimeSent()) < Config.MESSAGE_MERGE_WINDOW * 1000;
+ && Math.abs(this.getTimeSent() - message.getTimeSent())
+ < Config.MESSAGE_MERGE_WINDOW * 1000;
}
}
}
public Message next() {
- if (this.conversation instanceof Conversation) {
- final Conversation conversation = (Conversation) this.conversation;
- synchronized (conversation.messages) {
+ if (this.conversation instanceof Conversation c) {
+ synchronized (c.messages) {
if (this.mNextMessage == null) {
- int index = conversation.messages.indexOf(this);
- if (index < 0 || index >= conversation.messages.size() - 1) {
+ int index = c.messages.indexOf(this);
+ if (index < 0 || index >= c.messages.size() - 1) {
this.mNextMessage = null;
} else {
- this.mNextMessage = conversation.messages.get(index + 1);
+ this.mNextMessage = c.messages.get(index + 1);
}
}
return this.mNextMessage;
@@ -609,15 +640,14 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
}
public Message prev() {
- if (this.conversation instanceof Conversation) {
- final Conversation conversation = (Conversation) this.conversation;
- synchronized (conversation.messages) {
+ if (this.conversation instanceof Conversation c) {
+ synchronized (c.messages) {
if (this.mPreviousMessage == null) {
- int index = conversation.messages.indexOf(this);
- if (index <= 0 || index > conversation.messages.size()) {
+ int index = c.messages.indexOf(this);
+ if (index <= 0 || index > c.messages.size()) {
this.mPreviousMessage = null;
} else {
- this.mPreviousMessage = conversation.messages.get(index - 1);
+ this.mPreviousMessage = c.messages.get(index - 1);
}
}
}
@@ -642,56 +672,6 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
return status != STATUS_RECEIVED && !isCarbon() && type != Message.TYPE_RTP_SESSION;
}
- public boolean mergeable(final Message message) {
- return message != null &&
- (message.getType() == Message.TYPE_TEXT &&
- this.getTransferable() == null &&
- message.getTransferable() == null &&
- message.getEncryption() != Message.ENCRYPTION_PGP &&
- message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED &&
- this.getType() == message.getType() &&
- this.isReactionsEmpty() &&
- message.isReactionsEmpty() &&
- isStatusMergeable(this.getStatus(), message.getStatus()) &&
- isEncryptionMergeable(this.getEncryption(),message.getEncryption()) &&
- this.getCounterpart() != null &&
- this.getCounterpart().equals(message.getCounterpart()) &&
- this.edited() == message.edited() &&
- (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) &&
- this.getBody().length() + message.getBody().length() <= Config.MAX_DISPLAY_MESSAGE_CHARS &&
- !message.isGeoUri() &&
- !this.isGeoUri() &&
- !message.isOOb() &&
- !this.isOOb() &&
- !message.treatAsDownloadable() &&
- !this.treatAsDownloadable() &&
- !message.hasMeCommand() &&
- !this.hasMeCommand() &&
- !this.bodyIsOnlyEmojis() &&
- !message.bodyIsOnlyEmojis() &&
- ((this.axolotlFingerprint == null && message.axolotlFingerprint == null) || this.axolotlFingerprint.equals(message.getFingerprint())) &&
- UIHelper.sameDay(message.getTimeSent(), this.getTimeSent()) &&
- this.getReadByMarkers().equals(message.getReadByMarkers()) &&
- !this.conversation.getJid().asBareJid().equals(Config.BUG_REPORTS)
- );
- }
-
- private static boolean isStatusMergeable(int a, int b) {
- return a == b || (
- (a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_UNSEND)
- || (a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_SEND)
- || (a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_WAITING)
- || (a == Message.STATUS_SEND && b == Message.STATUS_UNSEND)
- || (a == Message.STATUS_SEND && b == Message.STATUS_WAITING)
- );
- }
-
- private static boolean isEncryptionMergeable(final int a, final int b) {
- return a == b
- && Arrays.asList(ENCRYPTION_NONE, ENCRYPTION_DECRYPTED, ENCRYPTION_AXOLOTL)
- .contains(a);
- }
-
public void setCounterparts(List<MucOptions.User> counterparts) {
this.counterparts = counterparts;
}
@@ -702,7 +682,9 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
@Override
public int getAvatarBackgroundColor() {
- if (type == Message.TYPE_STATUS && getCounterparts() != null && getCounterparts().size() > 1) {
+ if (type == Message.TYPE_STATUS
+ && getCounterparts() != null
+ && getCounterparts().size() > 1) {
return Color.TRANSPARENT;
} else {
return UIHelper.getColorForName(UIHelper.getMessageDisplayName(this));
@@ -730,10 +712,6 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
return this.reactions;
}
- public boolean isReactionsEmpty() {
- return this.reactions.isEmpty();
- }
-
public Reaction.Aggregated getAggregatedReactions() {
return Reaction.aggregated(this.reactions);
}
@@ -742,75 +720,28 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
this.reactions = reactions;
}
- public static class MergeSeparator {
- }
-
- public SpannableStringBuilder getMergedBody() {
- SpannableStringBuilder body = new SpannableStringBuilder(MessageUtils.filterLtrRtl(this.body).trim());
- Message current = this;
- while (current.mergeable(current.next())) {
- current = current.next();
- if (current == null) {
- break;
- }
- body.append("\n\n");
- body.setSpan(new MergeSeparator(), body.length() - 2, body.length(),
- SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
- body.append(MessageUtils.filterLtrRtl(current.getBody()).trim());
- }
- return body;
- }
-
public boolean hasMeCommand() {
return this.body.trim().startsWith(ME_COMMAND);
}
- public int getMergedStatus() {
- int status = this.status;
- Message current = this;
- while (current.mergeable(current.next())) {
- current = current.next();
- if (current == null) {
- break;
- }
- status = current.status;
- }
- return status;
- }
-
- public long getMergedTimeSent() {
- long time = this.timeSent;
- Message current = this;
- while (current.mergeable(current.next())) {
- current = current.next();
- if (current == null) {
- break;
- }
- time = current.timeSent;
- }
- return time;
- }
-
- public boolean wasMergedIntoPrevious() {
- Message prev = this.prev();
- return prev != null && prev.mergeable(this);
- }
-
public boolean trusted() {
Contact contact = this.getContact();
- return status > STATUS_RECEIVED || (contact != null && (contact.showInContactList() || contact.isSelf()));
+ return status > STATUS_RECEIVED
+ || (contact != null && (contact.showInContactList() || contact.isSelf()));
}
public boolean fixCounterpart() {
final Presences presences = conversation.getContact().getPresences();
if (counterpart != null && presences.has(Strings.nullToEmpty(counterpart.getResource()))) {
return true;
- } else if (presences.size() >= 1) {
- counterpart = PresenceSelector.getNextCounterpart(getContact(), presences.toResourceArray()[0]);
- return true;
- } else {
+ } else if (presences.isEmpty()) {
counterpart = null;
return false;
+ } else {
+ counterpart =
+ PresenceSelector.getNextCounterpart(
+ getContact(), presences.toResourceArray()[0]);
+ return true;
}
}
@@ -930,7 +861,6 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
return type == TYPE_FILE || type == TYPE_IMAGE || type == TYPE_PRIVATE_FILE;
}
-
public boolean isTypeText() {
return type == TYPE_TEXT || type == TYPE_PRIVATE;
}
@@ -965,7 +895,10 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
public boolean isTrusted() {
final AxolotlService axolotlService = conversation.getAccount().getAxolotlService();
- final FingerprintStatus s = axolotlService != null ? axolotlService.getFingerprintTrust(axolotlFingerprint) : null;
+ final FingerprintStatus s =
+ axolotlService != null
+ ? axolotlService.getFingerprintTrust(axolotlFingerprint)
+ : null;
return s != null && s.isTrusted();
}
@@ -980,17 +913,18 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
}
private int getNextEncryption() {
- if (this.conversation instanceof Conversation) {
- Conversation conversation = (Conversation) this.conversation;
+ if (this.conversation instanceof Conversation c) {
for (Message iterator = this.next(); iterator != null; iterator = iterator.next()) {
if (iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED) {
continue;
}
return iterator.getEncryption();
}
- return conversation.getNextEncryption();
+ return c.getNextEncryption();
} else {
- throw new AssertionError("This should never be called since isInValidSession should be disabled for stubs");
+ throw new AssertionError(
+ "This should never be called since isInValidSession should be disabled for"
+ + " stubs");
}
}
@@ -998,9 +932,10 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
int pastEncryption = getCleanedEncryption(this.getPreviousEncryption());
int futureEncryption = getCleanedEncryption(this.getNextEncryption());
- boolean inUnencryptedSession = pastEncryption == ENCRYPTION_NONE
- || futureEncryption == ENCRYPTION_NONE
- || pastEncryption != futureEncryption;
+ boolean inUnencryptedSession =
+ pastEncryption == ENCRYPTION_NONE
+ || futureEncryption == ENCRYPTION_NONE
+ || pastEncryption != futureEncryption;
return inUnencryptedSession || getCleanedEncryption(this.getEncryption()) == pastEncryption;
}
@@ -1009,7 +944,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
if (encryption == ENCRYPTION_DECRYPTED || encryption == ENCRYPTION_DECRYPTION_FAILED) {
return ENCRYPTION_PGP;
}
- if (encryption == ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE || encryption == ENCRYPTION_AXOLOTL_FAILED) {
+ if (encryption == ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE
+ || encryption == ENCRYPTION_AXOLOTL_FAILED) {
return ENCRYPTION_AXOLOTL;
}
return encryption;
@@ -1047,7 +983,11 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
return configurePrivateMessage(conversation, message, counterpart, false);
}
- private static boolean configurePrivateMessage(final Conversation conversation, final Message message, final Jid counterpart, final boolean isFile) {
+ private static boolean configurePrivateMessage(
+ final Conversation conversation,
+ final Message message,
+ final Jid counterpart,
+ final boolean isFile) {
if (counterpart == null) {
return false;
}
@@ -5918,23 +5918,11 @@ public class XmppConnectionService extends Service {
}
public void resendFailedMessages(final Message message) {
- final Collection<Message> messages = new ArrayList<>();
- Message current = message;
- while (current.getStatus() == Message.STATUS_SEND_FAILED) {
- messages.add(current);
- if (current.mergeable(current.next())) {
- current = current.next();
- } else {
- break;
- }
- }
- for (final Message msg : messages) {
- msg.setTime(System.currentTimeMillis());
- markMessage(msg, Message.STATUS_WAITING);
- this.resendMessage(msg, false);
- }
- if (message.getConversation() instanceof Conversation) {
- ((Conversation) message.getConversation()).sort();
+ message.setTime(System.currentTimeMillis());
+ markMessage(message, Message.STATUS_WAITING);
+ this.resendMessage(message, false);
+ if (message.getConversation() instanceof Conversation c) {
+ c.sort();
}
updateConversationUi();
}
@@ -31,6 +31,7 @@ import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.text.Editable;
+import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.util.Log;
import android.view.ContextMenu;
@@ -55,18 +56,15 @@ import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.TextView.OnEditorActionListener;
import android.widget.Toast;
-
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.core.view.inputmethod.InputConnectionCompat;
import androidx.core.view.inputmethod.InputContentInfoCompat;
import androidx.databinding.DataBindingUtil;
-
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
-
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
@@ -128,9 +126,6 @@ import eu.siacs.conversations.xmpp.jingle.JingleFileTransferConnection;
import eu.siacs.conversations.xmpp.jingle.Media;
import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession;
import eu.siacs.conversations.xmpp.jingle.RtpCapability;
-import eu.siacs.conversations.xmpp.jingle.RtpEndUserState;
-
-
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -329,7 +324,10 @@ public class ConversationFragment extends XmppFragment
} catch (IllegalStateException e) {
Log.d(
Config.LOGTAG,
- "caught illegal state exception while updating status messages");
+ "caught illegal state"
+ + " exception while"
+ + " updating status"
+ + " messages");
}
messageListAdapter
.notifyDataSetChanged();
@@ -692,14 +690,6 @@ public class ConversationFragment extends XmppFragment
for (int i = 0; i < messages.size(); ++i) {
if (uuid.equals(messages.get(i).getUuid())) {
return i;
- } else {
- Message next = messages.get(i);
- while (next != null && next.wasMergedIntoPrevious()) {
- if (uuid.equals(next.getUuid())) {
- return i;
- }
- next = next.next();
- }
}
}
return -1;
@@ -1045,13 +1035,15 @@ public class ConversationFragment extends XmppFragment
} else if (attachment.getType() == Attachment.Type.IMAGE) {
Log.d(
Config.LOGTAG,
- "ConversationsActivity.commitAttachments() - attaching image to conversations. CHOOSE_IMAGE");
+ "ConversationsActivity.commitAttachments() - attaching image to"
+ + " conversations. CHOOSE_IMAGE");
attachImageToConversation(
conversation, attachment.getUri(), attachment.getMime());
} else {
Log.d(
Config.LOGTAG,
- "ConversationsActivity.commitAttachments() - attaching file to conversations. CHOOSE_FILE/RECORD_VOICE/RECORD_VIDEO");
+ "ConversationsActivity.commitAttachments() - attaching file to"
+ + " conversations. CHOOSE_FILE/RECORD_VOICE/RECORD_VIDEO");
attachFileToConversation(
conversation, attachment.getUri(), attachment.getMime());
}
@@ -1277,13 +1269,9 @@ public class ConversationFragment extends XmppFragment
}
}
- private void populateContextMenu(ContextMenu menu) {
+ private void populateContextMenu(final ContextMenu menu) {
final Message m = this.selectedMessage;
final Transferable t = m.getTransferable();
- Message relevantForCorrection = m;
- while (relevantForCorrection.mergeable(relevantForCorrection.next())) {
- relevantForCorrection = relevantForCorrection.next();
- }
if (m.getType() != Message.TYPE_STATUS && m.getType() != Message.TYPE_RTP_SESSION) {
if (m.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE
@@ -1329,7 +1317,8 @@ public class ConversationFragment extends XmppFragment
&& m.getErrorMessage() != null
&& !Message.ERROR_MESSAGE_CANCELLED.equals(m.getErrorMessage());
final Conversational conversational = m.getConversation();
- if (m.getStatus() == Message.STATUS_RECEIVED && conversational instanceof Conversation c) {
+ if (m.getStatus() == Message.STATUS_RECEIVED
+ && conversational instanceof Conversation c) {
final XmppConnection connection = c.getAccount().getXmppConnection();
if (c.isWithStranger()
&& m.getServerMsgId() != null
@@ -1357,7 +1346,8 @@ public class ConversationFragment extends XmppFragment
&& t == null) {
copyMessage.setVisible(true);
quoteMessage.setVisible(!showError && !MessageUtils.prepareQuote(m).isEmpty());
- final String scheme = ShareUtil.getLinkScheme(m.getMergedBody());
+ final String scheme =
+ ShareUtil.getLinkScheme(new SpannableStringBuilder(m.getBody()));
if ("xmpp".equals(scheme)) {
copyLink.setTitle(R.string.copy_jabber_id);
copyLink.setVisible(true);
@@ -1369,9 +1359,9 @@ public class ConversationFragment extends XmppFragment
retryDecryption.setVisible(true);
}
if (!showError
- && relevantForCorrection.getType() == Message.TYPE_TEXT
+ && m.getType() == Message.TYPE_TEXT
&& !m.isGeoUri()
- && relevantForCorrection.isLastCorrectableMessage()
+ && m.isLastCorrectableMessage()
&& m.getConversation() instanceof Conversation) {
correctMessage.setVisible(true);
}
@@ -1672,7 +1662,11 @@ public class ConversationFragment extends XmppFragment
}
private void triggerRtpSession(final Account account, final Jid with, final String action) {
- CallIntegrationConnectionService.placeCall(activity.xmppConnectionService, account,with,RtpSessionActivity.actionToMedia(action));
+ CallIntegrationConnectionService.placeCall(
+ activity.xmppConnectionService,
+ account,
+ with,
+ RtpSessionActivity.actionToMedia(action));
}
private void handleAttachmentSelection(MenuItem item) {
@@ -1946,7 +1940,8 @@ public class ConversationFragment extends XmppFragment
@SuppressLint("InflateParams")
protected void clearHistoryDialog(final Conversation conversation) {
- final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity());
+ final MaterialAlertDialogBuilder builder =
+ new MaterialAlertDialogBuilder(requireActivity());
builder.setTitle(R.string.clear_conversation_history);
final View dialogView =
requireActivity().getLayoutInflater().inflate(R.layout.dialog_clear_history, null);
@@ -1970,7 +1965,8 @@ public class ConversationFragment extends XmppFragment
}
protected void muteConversationDialog(final Conversation conversation) {
- final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity());
+ final MaterialAlertDialogBuilder builder =
+ new MaterialAlertDialogBuilder(requireActivity());
builder.setTitle(R.string.disable_notifications);
final int[] durations = getResources().getIntArray(R.array.mute_options_durations);
final CharSequence[] labels = new CharSequence[durations.length];
@@ -2002,7 +1998,9 @@ public class ConversationFragment extends XmppFragment
private boolean hasPermissions(int requestCode, List<String> permissions) {
final List<String> missingPermissions = new ArrayList<>();
for (String permission : permissions) {
- if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU || Config.ONLY_INTERNAL_STORAGE) && permission.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+ if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
+ || Config.ONLY_INTERNAL_STORAGE)
+ && permission.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
continue;
}
if (activity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
@@ -2012,9 +2010,7 @@ public class ConversationFragment extends XmppFragment
if (missingPermissions.size() == 0) {
return true;
} else {
- requestPermissions(
- missingPermissions.toArray(new String[0]),
- requestCode);
+ requestPermissions(missingPermissions.toArray(new String[0]), requestCode);
return false;
}
}
@@ -2119,9 +2115,6 @@ public class ConversationFragment extends XmppFragment
}
}
if (message != null) {
- while (message.next() != null && message.next().wasMergedIntoPrevious()) {
- message = message.next();
- }
return message.getUuid();
}
}
@@ -2140,12 +2133,15 @@ public class ConversationFragment extends XmppFragment
}
private void addReaction(final Message message) {
- activity.addReaction(message, reactions -> {
- if (activity.xmppConnectionService.sendReactions(message, reactions)) {
- return;
- }
- Toast.makeText(activity, R.string.could_not_add_reaction, Toast.LENGTH_LONG).show();
- });
+ activity.addReaction(
+ message,
+ reactions -> {
+ if (activity.xmppConnectionService.sendReactions(message, reactions)) {
+ return;
+ }
+ Toast.makeText(activity, R.string.could_not_add_reaction, Toast.LENGTH_LONG)
+ .show();
+ });
}
private void reportMessage(final Message message) {
@@ -2153,7 +2149,8 @@ public class ConversationFragment extends XmppFragment
}
private void showErrorMessage(final Message message) {
- final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity());
+ final MaterialAlertDialogBuilder builder =
+ new MaterialAlertDialogBuilder(requireActivity());
builder.setTitle(R.string.error_message);
final String errorMessage = message.getErrorMessage();
final String[] errorMessageParts =
@@ -2180,7 +2177,8 @@ public class ConversationFragment extends XmppFragment
}
private void deleteFile(final Message message) {
- final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity());
+ final MaterialAlertDialogBuilder builder =
+ new MaterialAlertDialogBuilder(requireActivity());
builder.setNegativeButton(R.string.cancel, null);
builder.setTitle(R.string.delete_file_dialog);
builder.setMessage(R.string.delete_file_dialog_msg);
@@ -2278,10 +2276,7 @@ public class ConversationFragment extends XmppFragment
updateEditablity();
}
- private void correctMessage(Message message) {
- while (message.mergeable(message.next())) {
- message = message.next();
- }
+ private void correctMessage(final Message message) {
this.conversation.setCorrectingMessage(message);
final Editable editable = binding.textinput.getText();
this.conversation.setDraftMessage(editable.toString());
@@ -2395,7 +2390,8 @@ public class ConversationFragment extends XmppFragment
final String uuid = pendingConversationsUuid.pop();
Log.d(
Config.LOGTAG,
- "ConversationFragment.onStart() - activity was bound but no conversation loaded. uuid="
+ "ConversationFragment.onStart() - activity was bound but no conversation"
+ + " loaded. uuid="
+ uuid);
if (uuid != null) {
findAndReInitByUuidOrArchive(uuid);
@@ -2710,7 +2706,10 @@ public class ConversationFragment extends XmppFragment
R.string.enable,
this.mEnableAccountListener);
} else if (account.getStatus() == Account.State.LOGGED_OUT) {
- showSnackbar(R.string.this_account_is_logged_out,R.string.log_in,this.mEnableAccountListener);
+ showSnackbar(
+ R.string.this_account_is_logged_out,
+ R.string.log_in,
+ this.mEnableAccountListener);
} else if (conversation.isBlocked()) {
showSnackbar(R.string.contact_blocked, R.string.unblock, this.mUnblockClickListener);
} else if (contact != null
@@ -2772,7 +2771,8 @@ public class ConversationFragment extends XmppFragment
showSnackbar(R.string.conference_kicked, R.string.join, joinMuc);
break;
case TECHNICAL_PROBLEMS:
- showSnackbar(R.string.conference_technical_problems, R.string.try_again, joinMuc);
+ showSnackbar(
+ R.string.conference_technical_problems, R.string.try_again, joinMuc);
break;
case UNKNOWN:
showSnackbar(R.string.conference_unknown_error, R.string.try_again, joinMuc);
@@ -2942,12 +2942,14 @@ public class ConversationFragment extends XmppFragment
status = Presence.Status.OFFLINE;
}
this.binding.textSendButton.setTag(action);
- this.binding.textSendButton.setIconResource(SendButtonTool.getSendButtonImageResource(action));
- this.binding.textSendButton.setIconTint(ColorStateList.valueOf(SendButtonTool.getSendButtonColor(this.binding.textSendButton, status)));
+ this.binding.textSendButton.setIconResource(
+ SendButtonTool.getSendButtonImageResource(action));
+ this.binding.textSendButton.setIconTint(
+ ColorStateList.valueOf(
+ SendButtonTool.getSendButtonColor(this.binding.textSendButton, status)));
// TODO send button color
final Activity activity = getActivity();
- if (activity != null) {
- }
+ if (activity != null) {}
}
protected void updateStatusMessages() {
@@ -3010,7 +3012,7 @@ public class ConversationFragment extends XmppFragment
if (!ReadByMarker.contains(marker, addedMarkers)) {
addedMarkers.add(
marker); // may be put outside this condition. set should do
- // dedup anyway
+ // dedup anyway
MucOptions.User user = mucOptions.findUser(marker);
if (user != null && !users.contains(user)) {
shownMarkers.add(user);
@@ -3269,8 +3271,10 @@ public class ConversationFragment extends XmppFragment
});
}
- public void showNoPGPKeyDialog(final boolean plural, final DialogInterface.OnClickListener listener) {
- final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity());
+ public void showNoPGPKeyDialog(
+ final boolean plural, final DialogInterface.OnClickListener listener) {
+ final MaterialAlertDialogBuilder builder =
+ new MaterialAlertDialogBuilder(requireActivity());
if (plural) {
builder.setTitle(getString(R.string.no_pgp_keys));
builder.setMessage(getText(R.string.contacts_have_no_pgp_keys));
@@ -3439,7 +3443,13 @@ public class ConversationFragment extends XmppFragment
try {
getActivity()
.startIntentSenderForResult(
- pendingIntent.getIntentSender(), requestCode, null, 0, 0, 0, Compatibility.pgpStartIntentSenderOptions());
+ pendingIntent.getIntentSender(),
+ requestCode,
+ null,
+ 0,
+ 0,
+ 0,
+ Compatibility.pgpStartIntentSenderOptions());
} catch (final SendIntentException ignored) {
}
}
@@ -15,18 +15,15 @@ import android.text.style.ForegroundColorSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan;
import android.util.DisplayMetrics;
-import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ArrayAdapter;
-import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
-
import androidx.annotation.AttrRes;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
@@ -35,10 +32,6 @@ import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.widget.ImageViewCompat;
-import androidx.databinding.DataBindingUtil;
-import androidx.emoji2.emojipicker.EmojiViewItem;
-import androidx.emoji2.emojipicker.RecentEmojiProvider;
-
import com.google.android.material.button.MaterialButton;
import com.google.android.material.chip.ChipGroup;
import com.google.android.material.color.MaterialColors;
@@ -47,14 +40,10 @@ import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-
import eu.siacs.conversations.AppSettings;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
-import eu.siacs.conversations.databinding.DialogAddReactionBinding;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Conversational;
@@ -89,14 +78,11 @@ import eu.siacs.conversations.utils.TimeFrameUtils;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.mam.MamReference;
-import kotlin.coroutines.Continuation;
-
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
-import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -192,7 +178,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
final Message message,
final int type,
final BubbleColor bubbleColor) {
- final int mergedStatus = message.getMergedStatus();
+ final int mergedStatus = message.getStatus();
final boolean error;
if (viewHolder.indicatorReceived != null) {
viewHolder.indicatorReceived.setVisibility(View.GONE);
@@ -285,7 +271,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
final String formattedTime =
- UIHelper.readableTimeDifferenceFull(getContext(), message.getMergedTimeSent());
+ UIHelper.readableTimeDifferenceFull(getContext(), message.getTimeSent());
final String bodyLanguage = message.getBodyLanguage();
final ImmutableList.Builder<String> timeInfoBuilder = new ImmutableList.Builder<>();
if (message.getStatus() <= Message.STATUS_RECEIVED) {
@@ -328,8 +314,8 @@ public class MessageAdapter extends ArrayAdapter<Message> {
case Message.STATUS_WAITING -> R.drawable.ic_more_horiz_24dp;
case Message.STATUS_UNSEND -> transferable == null ? null : R.drawable.ic_upload_24dp;
case Message.STATUS_SEND -> R.drawable.ic_done_24dp;
- case Message.STATUS_SEND_RECEIVED, Message.STATUS_SEND_DISPLAYED -> R.drawable
- .ic_done_all_24dp;
+ case Message.STATUS_SEND_RECEIVED, Message.STATUS_SEND_DISPLAYED ->
+ R.drawable.ic_done_all_24dp;
case Message.STATUS_SEND_FAILED -> {
final String errorMessage = message.getErrorMessage();
if (Message.ERROR_MESSAGE_CANCELLED.equals(errorMessage)) {
@@ -486,21 +472,17 @@ public class MessageAdapter extends ArrayAdapter<Message> {
if (message.getBody() != null) {
final String nick = UIHelper.getMessageDisplayName(message);
- SpannableStringBuilder body = message.getMergedBody();
- boolean hasMeCommand = message.hasMeCommand();
- if (hasMeCommand) {
- body = body.replace(0, Message.ME_COMMAND.length(), nick + " ");
- }
- if (body.length() > Config.MAX_DISPLAY_MESSAGE_CHARS) {
- body = new SpannableStringBuilder(body, 0, Config.MAX_DISPLAY_MESSAGE_CHARS);
- body.append("\u2026");
+ final boolean hasMeCommand = message.hasMeCommand();
+ final var rawBody = message.getBody();
+ final SpannableStringBuilder body;
+ if (rawBody.length() > Config.MAX_DISPLAY_MESSAGE_CHARS) {
+ body = new SpannableStringBuilder(rawBody, 0, Config.MAX_DISPLAY_MESSAGE_CHARS);
+ body.append("…");
+ } else {
+ body = new SpannableStringBuilder(rawBody);
}
- Message.MergeSeparator[] mergeSeparators =
- body.getSpans(0, body.length(), Message.MergeSeparator.class);
- for (Message.MergeSeparator mergeSeparator : mergeSeparators) {
- int start = body.getSpanStart(mergeSeparator);
- int end = body.getSpanEnd(mergeSeparator);
- body.setSpan(new DividerSpan(true), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ if (hasMeCommand) {
+ body.replace(0, Message.ME_COMMAND.length(), String.format("%s ", nick));
}
boolean startsWithQuote = handleTextQuotes(viewHolder.messageBody, body, bubbleColor);
if (!message.isPrivateMessage()) {
@@ -1211,12 +1193,15 @@ public class MessageAdapter extends ArrayAdapter<Message> {
final View view, final BubbleColor bubbleColor) {
final @AttrRes int colorAttributeResId =
switch (bubbleColor) {
- case SURFACE -> Activities.isNightMode(view.getContext())
- ? com.google.android.material.R.attr.colorSurfaceContainerHigh
- : com.google.android.material.R.attr.colorSurfaceContainerLow;
- case SURFACE_HIGH -> Activities.isNightMode(view.getContext())
- ? com.google.android.material.R.attr.colorSurfaceContainerHighest
- : com.google.android.material.R.attr.colorSurfaceContainerHigh;
+ case SURFACE ->
+ Activities.isNightMode(view.getContext())
+ ? com.google.android.material.R.attr.colorSurfaceContainerHigh
+ : com.google.android.material.R.attr.colorSurfaceContainerLow;
+ case SURFACE_HIGH ->
+ Activities.isNightMode(view.getContext())
+ ? com.google.android.material.R.attr
+ .colorSurfaceContainerHighest
+ : com.google.android.material.R.attr.colorSurfaceContainerHigh;
case PRIMARY -> com.google.android.material.R.attr.colorPrimaryContainer;
case SECONDARY -> com.google.android.material.R.attr.colorSecondaryContainer;
case TERTIARY -> com.google.android.material.R.attr.colorTertiaryContainer;
@@ -34,7 +34,6 @@ import android.content.Intent;
import android.net.Uri;
import android.text.SpannableStringBuilder;
import android.widget.Toast;
-
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
@@ -46,66 +45,82 @@ import eu.siacs.conversations.xmpp.Jid;
public class ShareUtil {
- public static void share(XmppActivity activity, Message message) {
- Intent shareIntent = new Intent();
- shareIntent.setAction(Intent.ACTION_SEND);
- if (message.isGeoUri()) {
- shareIntent.putExtra(Intent.EXTRA_TEXT, message.getBody());
- shareIntent.setType("text/plain");
- } else if (!message.isFileOrImage()) {
- shareIntent.putExtra(Intent.EXTRA_TEXT, message.getMergedBody().toString());
- shareIntent.setType("text/plain");
- shareIntent.putExtra(ConversationsActivity.EXTRA_AS_QUOTE, message.getStatus() == Message.STATUS_RECEIVED);
- } else {
- final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
- try {
- shareIntent.putExtra(Intent.EXTRA_STREAM, FileBackend.getUriForFile(activity, file));
- } catch (SecurityException e) {
- Toast.makeText(activity, activity.getString(R.string.no_permission_to_access_x, file.getAbsolutePath()), Toast.LENGTH_SHORT).show();
- return;
- }
- shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- String mime = message.getMimeType();
- if (mime == null) {
- mime = "*/*";
- }
- shareIntent.setType(mime);
- }
- try {
- activity.startActivity(Intent.createChooser(shareIntent, activity.getText(R.string.share_with)));
- } catch (ActivityNotFoundException e) {
- //This should happen only on faulty androids because normally chooser is always available
- Toast.makeText(activity, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT).show();
- }
- }
+ public static void share(XmppActivity activity, Message message) {
+ Intent shareIntent = new Intent();
+ shareIntent.setAction(Intent.ACTION_SEND);
+ if (message.isGeoUri()) {
+ shareIntent.putExtra(Intent.EXTRA_TEXT, message.getBody());
+ shareIntent.setType("text/plain");
+ } else if (!message.isFileOrImage()) {
+ shareIntent.putExtra(Intent.EXTRA_TEXT, message.getBody());
+ shareIntent.setType("text/plain");
+ shareIntent.putExtra(
+ ConversationsActivity.EXTRA_AS_QUOTE,
+ message.getStatus() == Message.STATUS_RECEIVED);
+ } else {
+ final DownloadableFile file =
+ activity.xmppConnectionService.getFileBackend().getFile(message);
+ try {
+ shareIntent.putExtra(
+ Intent.EXTRA_STREAM, FileBackend.getUriForFile(activity, file));
+ } catch (SecurityException e) {
+ Toast.makeText(
+ activity,
+ activity.getString(
+ R.string.no_permission_to_access_x, file.getAbsolutePath()),
+ Toast.LENGTH_SHORT)
+ .show();
+ return;
+ }
+ shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ String mime = message.getMimeType();
+ if (mime == null) {
+ mime = "*/*";
+ }
+ shareIntent.setType(mime);
+ }
+ try {
+ activity.startActivity(
+ Intent.createChooser(shareIntent, activity.getText(R.string.share_with)));
+ } catch (ActivityNotFoundException e) {
+ // This should happen only on faulty androids because normally chooser is always
+ // available
+ Toast.makeText(activity, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT)
+ .show();
+ }
+ }
- public static void copyToClipboard(XmppActivity activity, Message message) {
- if (activity.copyTextToClipboard(message.getMergedBody().toString(), R.string.message)) {
- Toast.makeText(activity, R.string.message_copied_to_clipboard, Toast.LENGTH_SHORT).show();
- }
- }
+ public static void copyToClipboard(XmppActivity activity, Message message) {
+ if (activity.copyTextToClipboard(message.getBody(), R.string.message)) {
+ Toast.makeText(activity, R.string.message_copied_to_clipboard, Toast.LENGTH_SHORT)
+ .show();
+ }
+ }
- public static void copyUrlToClipboard(XmppActivity activity, Message message) {
- final String url;
- final int resId;
- if (message.isGeoUri()) {
- resId = R.string.location;
- url = message.getBody();
- } else if (message.hasFileOnRemoteHost()) {
- resId = R.string.file_url;
- url = message.getFileParams().url;
- } else {
- final Message.FileParams fileParams = message.getFileParams();
- url = (fileParams != null && fileParams.url != null) ? fileParams.url : message.getBody().trim();
- resId = R.string.file_url;
- }
- if (activity.copyTextToClipboard(url, resId)) {
- Toast.makeText(activity, R.string.url_copied_to_clipboard, Toast.LENGTH_SHORT).show();
- }
- }
+ public static void copyUrlToClipboard(XmppActivity activity, Message message) {
+ final String url;
+ final int resId;
+ if (message.isGeoUri()) {
+ resId = R.string.location;
+ url = message.getBody();
+ } else if (message.hasFileOnRemoteHost()) {
+ resId = R.string.file_url;
+ url = message.getFileParams().url;
+ } else {
+ final Message.FileParams fileParams = message.getFileParams();
+ url =
+ (fileParams != null && fileParams.url != null)
+ ? fileParams.url
+ : message.getBody().trim();
+ resId = R.string.file_url;
+ }
+ if (activity.copyTextToClipboard(url, resId)) {
+ Toast.makeText(activity, R.string.url_copied_to_clipboard, Toast.LENGTH_SHORT).show();
+ }
+ }
public static void copyLinkToClipboard(final XmppActivity activity, final Message message) {
- final SpannableStringBuilder body = message.getMergedBody();
+ final SpannableStringBuilder body = new SpannableStringBuilder(message.getBody());
for (final String url : MyLinkify.extractLinks(body)) {
final Uri uri = Uri.parse(url);
if ("xmpp".equals(uri.getScheme())) {
@@ -30,16 +30,14 @@
package eu.siacs.conversations.utils;
import com.google.common.base.Strings;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.regex.Pattern;
-
import eu.siacs.conversations.entities.Conversational;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.http.AesGcmURL;
import eu.siacs.conversations.http.URL;
import eu.siacs.conversations.ui.util.QuoteHelper;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.regex.Pattern;
public class MessageUtils {
@@ -47,7 +45,7 @@ public class MessageUtils {
public static final String EMPTY_STRING = "";
- public static String prepareQuote(Message message) {
+ public static String prepareQuote(final Message message) {
final StringBuilder builder = new StringBuilder();
final String body;
if (message.hasMeCommand()) {
@@ -63,7 +61,7 @@ public class MessageUtils {
}
body = nick + " " + message.getBody().substring(Message.ME_COMMAND.length());
} else {
- body = message.getMergedBody().toString();
+ body = message.getBody();
}
for (String line : body.split("\n")) {
if (!(line.length() <= 0) && QuoteHelper.isNestedTooDeeply(line)) {
@@ -100,8 +98,12 @@ public class MessageUtils {
final String protocol = uri.getScheme();
final boolean encrypted = ref != null && AesGcmURL.IV_KEY.matcher(ref).matches();
final boolean followedByDataUri = lines.length == 2 && lines[1].startsWith("data:");
- final boolean validAesGcm = AesGcmURL.PROTOCOL_NAME.equalsIgnoreCase(protocol) && encrypted && (lines.length == 1 || followedByDataUri);
- final boolean validProtocol = "http".equalsIgnoreCase(protocol) || "https".equalsIgnoreCase(protocol);
+ final boolean validAesGcm =
+ AesGcmURL.PROTOCOL_NAME.equalsIgnoreCase(protocol)
+ && encrypted
+ && (lines.length == 1 || followedByDataUri);
+ final boolean validProtocol =
+ "http".equalsIgnoreCase(protocol) || "https".equalsIgnoreCase(protocol);
final boolean validOob = validProtocol && (oob || encrypted) && lines.length == 1;
return validAesGcm || validOob;
}
@@ -111,6 +113,10 @@ public class MessageUtils {
}
public static boolean unInitiatedButKnownSize(Message message) {
- return message.getType() == Message.TYPE_TEXT && message.getTransferable() == null && message.isOOb() && message.getFileParams().size != null && message.getFileParams().url != null;
+ return message.getType() == Message.TYPE_TEXT
+ && message.getTransferable() == null
+ && message.isOOb()
+ && message.getFileParams().size != null
+ && message.getFileParams().url != null;
}
}
@@ -29,7 +29,6 @@
package eu.siacs.conversations.utils;
-import android.content.Context;
import android.graphics.Color;
import android.graphics.Typeface;
import android.text.Editable;
@@ -45,138 +44,149 @@ import android.text.style.StyleSpan;
import android.text.style.TypefaceSpan;
import android.widget.EditText;
import android.widget.TextView;
-
import androidx.annotation.ColorInt;
-import androidx.core.content.ContextCompat;
-
import com.google.android.material.color.MaterialColors;
-
+import eu.siacs.conversations.ui.text.QuoteSpan;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.entities.Message;
-import eu.siacs.conversations.ui.text.QuoteSpan;
-
public class StylingHelper {
- private static final List<? extends Class<? extends ParcelableSpan>> SPAN_CLASSES = Arrays.asList(
- StyleSpan.class,
- StrikethroughSpan.class,
- TypefaceSpan.class,
- ForegroundColorSpan.class
- );
-
- public static void clear(final Editable editable) {
- final int end = editable.length() - 1;
- for (Class<? extends ParcelableSpan> clazz : SPAN_CLASSES) {
- for (ParcelableSpan span : editable.getSpans(0, end, clazz)) {
- editable.removeSpan(span);
- }
- }
- }
-
- public static void format(final Editable editable, int start, int end, @ColorInt int textColor) {
- for (ImStyleParser.Style style : ImStyleParser.parse(editable, start, end)) {
- final int keywordLength = style.getKeyword().length();
- editable.setSpan(createSpanForStyle(style), style.getStart() + keywordLength, style.getEnd() - keywordLength + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- makeKeywordOpaque(editable, style.getStart(), style.getStart() + keywordLength, textColor);
- makeKeywordOpaque(editable, style.getEnd() - keywordLength + 1, style.getEnd() + 1, textColor);
- }
- }
-
- public static void format(final Editable editable, @ColorInt int textColor) {
- int end = 0;
- Message.MergeSeparator[] spans = editable.getSpans(0, editable.length() - 1, Message.MergeSeparator.class);
- for (Message.MergeSeparator span : spans) {
- format(editable, end, editable.getSpanStart(span), textColor);
- end = editable.getSpanEnd(span);
- }
- format(editable, end, editable.length() - 1, textColor);
- }
-
- public static void highlight(final TextView view, final Editable editable, final List<String> needles) {
- for (final String needle : needles) {
- if (!FtsUtils.isKeyword(needle)) {
- highlight(view, editable, needle);
- }
- }
- }
-
- public static List<String> filterHighlightedWords(List<String> terms) {
- List<String> words = new ArrayList<>();
- for (String term : terms) {
- if (!FtsUtils.isKeyword(term)) {
- StringBuilder builder = new StringBuilder();
- for (int codepoint, i = 0; i < term.length(); i += Character.charCount(codepoint)) {
- codepoint = term.codePointAt(i);
- if (Character.isLetterOrDigit(codepoint)) {
- builder.append(Character.toChars(codepoint));
- } else if (builder.length() > 0) {
- words.add(builder.toString());
- builder.delete(0, builder.length());
- }
- }
- if (builder.length() > 0) {
- words.add(builder.toString());
- }
- }
- }
- return words;
- }
-
- private static void highlight(final TextView view, final Editable editable, final String needle) {
- final int length = needle.length();
- String string = editable.toString();
- int start = indexOfIgnoreCase(string, needle, 0);
- while (start != -1) {
- int end = start + length;
- editable.setSpan(new BackgroundColorSpan(MaterialColors.getColor(view, com.google.android.material.R.attr.colorPrimaryFixedDim)), start, end, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
- editable.setSpan(new ForegroundColorSpan(MaterialColors.getColor(view, com.google.android.material.R.attr.colorOnPrimaryFixed)), start, end, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
- start = indexOfIgnoreCase(string, needle, start + length);
- }
-
- }
-
- static CharSequence subSequence(CharSequence charSequence, int start, int end) {
- if (start == 0 && charSequence.length() + 1 == end) {
- return charSequence;
- }
- if (charSequence instanceof Spannable spannable) {
- Spannable sub = (Spannable) spannable.subSequence(start, end);
- for (Class<? extends ParcelableSpan> clazz : SPAN_CLASSES) {
- ParcelableSpan[] spannables = spannable.getSpans(start, end, clazz);
- for (ParcelableSpan parcelableSpan : spannables) {
- int beginSpan = spannable.getSpanStart(parcelableSpan);
- int endSpan = spannable.getSpanEnd(parcelableSpan);
- if (beginSpan >= start && endSpan <= end) {
- continue;
- }
- sub.setSpan(clone(parcelableSpan), Math.max(beginSpan - start, 0), Math.min(sub.length() - 1, endSpan), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- }
- return sub;
- } else {
- return charSequence.subSequence(start, end);
- }
- }
-
- private static ParcelableSpan clone(ParcelableSpan span) {
- if (span instanceof ForegroundColorSpan) {
- return new ForegroundColorSpan(((ForegroundColorSpan) span).getForegroundColor());
- } else if (span instanceof TypefaceSpan) {
- return new TypefaceSpan(((TypefaceSpan) span).getFamily());
- } else if (span instanceof StyleSpan) {
- return new StyleSpan(((StyleSpan) span).getStyle());
- } else if (span instanceof StrikethroughSpan) {
- return new StrikethroughSpan();
- } else {
- throw new AssertionError("Unknown Span");
- }
- }
-
- private static ParcelableSpan createSpanForStyle(final ImStyleParser.Style style) {
+ private static final List<? extends Class<? extends ParcelableSpan>> SPAN_CLASSES =
+ Arrays.asList(
+ StyleSpan.class,
+ StrikethroughSpan.class,
+ TypefaceSpan.class,
+ ForegroundColorSpan.class);
+
+ public static void clear(final Editable editable) {
+ final int end = editable.length() - 1;
+ for (Class<? extends ParcelableSpan> clazz : SPAN_CLASSES) {
+ for (ParcelableSpan span : editable.getSpans(0, end, clazz)) {
+ editable.removeSpan(span);
+ }
+ }
+ }
+
+ public static void format(
+ final Editable editable, int start, int end, @ColorInt int textColor) {
+ for (ImStyleParser.Style style : ImStyleParser.parse(editable, start, end)) {
+ final int keywordLength = style.getKeyword().length();
+ editable.setSpan(
+ createSpanForStyle(style),
+ style.getStart() + keywordLength,
+ style.getEnd() - keywordLength + 1,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ makeKeywordOpaque(
+ editable, style.getStart(), style.getStart() + keywordLength, textColor);
+ makeKeywordOpaque(
+ editable, style.getEnd() - keywordLength + 1, style.getEnd() + 1, textColor);
+ }
+ }
+
+ public static void format(final Editable editable, @ColorInt final int textColor) {
+ format(editable, 0, editable.length() - 1, textColor);
+ }
+
+ public static void highlight(
+ final TextView view, final Editable editable, final List<String> needles) {
+ for (final String needle : needles) {
+ if (!FtsUtils.isKeyword(needle)) {
+ highlight(view, editable, needle);
+ }
+ }
+ }
+
+ public static List<String> filterHighlightedWords(List<String> terms) {
+ List<String> words = new ArrayList<>();
+ for (String term : terms) {
+ if (!FtsUtils.isKeyword(term)) {
+ StringBuilder builder = new StringBuilder();
+ for (int codepoint, i = 0; i < term.length(); i += Character.charCount(codepoint)) {
+ codepoint = term.codePointAt(i);
+ if (Character.isLetterOrDigit(codepoint)) {
+ builder.append(Character.toChars(codepoint));
+ } else if (builder.length() > 0) {
+ words.add(builder.toString());
+ builder.delete(0, builder.length());
+ }
+ }
+ if (builder.length() > 0) {
+ words.add(builder.toString());
+ }
+ }
+ }
+ return words;
+ }
+
+ private static void highlight(
+ final TextView view, final Editable editable, final String needle) {
+ final int length = needle.length();
+ String string = editable.toString();
+ int start = indexOfIgnoreCase(string, needle, 0);
+ while (start != -1) {
+ int end = start + length;
+ editable.setSpan(
+ new BackgroundColorSpan(
+ MaterialColors.getColor(
+ view, com.google.android.material.R.attr.colorPrimaryFixedDim)),
+ start,
+ end,
+ SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
+ editable.setSpan(
+ new ForegroundColorSpan(
+ MaterialColors.getColor(
+ view, com.google.android.material.R.attr.colorOnPrimaryFixed)),
+ start,
+ end,
+ SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
+ start = indexOfIgnoreCase(string, needle, start + length);
+ }
+ }
+
+ static CharSequence subSequence(CharSequence charSequence, int start, int end) {
+ if (start == 0 && charSequence.length() + 1 == end) {
+ return charSequence;
+ }
+ if (charSequence instanceof Spannable spannable) {
+ Spannable sub = (Spannable) spannable.subSequence(start, end);
+ for (Class<? extends ParcelableSpan> clazz : SPAN_CLASSES) {
+ ParcelableSpan[] spannables = spannable.getSpans(start, end, clazz);
+ for (ParcelableSpan parcelableSpan : spannables) {
+ int beginSpan = spannable.getSpanStart(parcelableSpan);
+ int endSpan = spannable.getSpanEnd(parcelableSpan);
+ if (beginSpan >= start && endSpan <= end) {
+ continue;
+ }
+ sub.setSpan(
+ clone(parcelableSpan),
+ Math.max(beginSpan - start, 0),
+ Math.min(sub.length() - 1, endSpan),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ }
+ return sub;
+ } else {
+ return charSequence.subSequence(start, end);
+ }
+ }
+
+ private static ParcelableSpan clone(ParcelableSpan span) {
+ if (span instanceof ForegroundColorSpan) {
+ return new ForegroundColorSpan(((ForegroundColorSpan) span).getForegroundColor());
+ } else if (span instanceof TypefaceSpan) {
+ return new TypefaceSpan(((TypefaceSpan) span).getFamily());
+ } else if (span instanceof StyleSpan) {
+ return new StyleSpan(((StyleSpan) span).getStyle());
+ } else if (span instanceof StrikethroughSpan) {
+ return new StrikethroughSpan();
+ } else {
+ throw new AssertionError("Unknown Span");
+ }
+ }
+
+ private static ParcelableSpan createSpanForStyle(final ImStyleParser.Style style) {
return switch (style.getKeyword()) {
case "*" -> new StyleSpan(Typeface.BOLD);
case "_" -> new StyleSpan(Typeface.ITALIC);
@@ -184,62 +194,64 @@ public class StylingHelper {
case "`", "```" -> new TypefaceSpan("monospace");
default -> throw new AssertionError("Unknown Style");
};
- }
-
- private static void makeKeywordOpaque(final Editable editable, int start, int end, @ColorInt int fallbackTextColor) {
- QuoteSpan[] quoteSpans = editable.getSpans(start, end, QuoteSpan.class);
- @ColorInt int textColor = quoteSpans.length > 0 ? quoteSpans[0].getColor() : fallbackTextColor;
- @ColorInt int keywordColor = transformColor(textColor);
- editable.setSpan(new ForegroundColorSpan(keywordColor), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
-
- private static
- @ColorInt
- int transformColor(@ColorInt int c) {
- return Color.argb(Math.round(Color.alpha(c) * 0.45f), Color.red(c), Color.green(c), Color.blue(c));
- }
-
- private static int indexOfIgnoreCase(final String haystack, final String needle, final int start) {
- if (haystack == null || needle == null) {
- return -1;
- }
- final int endLimit = haystack.length() - needle.length() + 1;
- if (start > endLimit) {
- return -1;
- }
- if (needle.length() == 0) {
- return start;
- }
- for (int i = start; i < endLimit; i++) {
- if (haystack.regionMatches(true, i, needle, 0, needle.length())) {
- return i;
- }
- }
- return -1;
- }
-
- public static class MessageEditorStyler implements TextWatcher {
-
- private final EditText mEditText;
-
- public MessageEditorStyler(EditText editText) {
- this.mEditText = editText;
- }
-
- @Override
- public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
-
- }
-
- @Override
- public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
-
- }
-
- @Override
- public void afterTextChanged(Editable editable) {
- clear(editable);
- format(editable, mEditText.getCurrentTextColor());
- }
- }
+ }
+
+ private static void makeKeywordOpaque(
+ final Editable editable, int start, int end, @ColorInt int fallbackTextColor) {
+ QuoteSpan[] quoteSpans = editable.getSpans(start, end, QuoteSpan.class);
+ @ColorInt
+ int textColor = quoteSpans.length > 0 ? quoteSpans[0].getColor() : fallbackTextColor;
+ @ColorInt int keywordColor = transformColor(textColor);
+ editable.setSpan(
+ new ForegroundColorSpan(keywordColor),
+ start,
+ end,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+
+ private static @ColorInt int transformColor(@ColorInt int c) {
+ return Color.argb(
+ Math.round(Color.alpha(c) * 0.45f), Color.red(c), Color.green(c), Color.blue(c));
+ }
+
+ private static int indexOfIgnoreCase(
+ final String haystack, final String needle, final int start) {
+ if (haystack == null || needle == null) {
+ return -1;
+ }
+ final int endLimit = haystack.length() - needle.length() + 1;
+ if (start > endLimit) {
+ return -1;
+ }
+ if (needle.length() == 0) {
+ return start;
+ }
+ for (int i = start; i < endLimit; i++) {
+ if (haystack.regionMatches(true, i, needle, 0, needle.length())) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public static class MessageEditorStyler implements TextWatcher {
+
+ private final EditText mEditText;
+
+ public MessageEditorStyler(EditText editText) {
+ this.mEditText = editText;
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ clear(editable);
+ format(editable, mEditText.getCurrentTextColor());
+ }
+ }
}