diff --git a/src/main/java/eu/siacs/conversations/entities/Bookmark.java b/src/main/java/eu/siacs/conversations/entities/Bookmark.java index eb8f3328dd64c8d9f8240f011f69320b2f1d94f9..72abd718d5dcf637f16be479ecf2e56ee32a08f6 100644 --- a/src/main/java/eu/siacs/conversations/entities/Bookmark.java +++ b/src/main/java/eu/siacs/conversations/entities/Bookmark.java @@ -9,7 +9,6 @@ import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import java.lang.ref.WeakReference; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -61,11 +60,11 @@ public class Bookmark extends Element implements ListItem { return bookmarks; } - public static Map parseFromPubsub(Element pubsub, Account account) { - if (pubsub == null) { + public static Map parseFromPubSub(final Element pubSub, final Account account) { + if (pubSub == null) { return Collections.emptyMap(); } - final Element items = pubsub.findChild("items"); + final Element items = pubSub.findChild("items"); if (items != null && Namespace.BOOKMARKS2.equals(items.getAttribute("node"))) { final Map bookmarks = new HashMap<>(); for(Element item : items.getChildren()) { @@ -99,6 +98,7 @@ public class Bookmark extends Element implements ListItem { } final Bookmark bookmark = new Bookmark(account); bookmark.jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("id")); + // TODO verify that we only use bare jids and ignore full jids if (bookmark.jid == null) { return null; } diff --git a/src/main/java/eu/siacs/conversations/entities/IndividualMessage.java b/src/main/java/eu/siacs/conversations/entities/IndividualMessage.java index 8f416a301aae5799ca52ab585e6f3092d48ce37b..f13d0e25458d4c495d4a745494856af9cc37b60b 100644 --- a/src/main/java/eu/siacs/conversations/entities/IndividualMessage.java +++ b/src/main/java/eu/siacs/conversations/entities/IndividualMessage.java @@ -31,6 +31,7 @@ package eu.siacs.conversations.entities; import android.database.Cursor; +import java.util.Collection; import java.util.Set; import eu.siacs.conversations.ui.adapter.MessageAdapter; @@ -43,8 +44,8 @@ public class IndividualMessage extends Message { super(conversation); } - private IndividualMessage(Conversational conversation, String uuid, String conversationUUid, Jid counterpart, Jid trueCounterpart, String body, long timeSent, int encryption, int status, int type, boolean carbon, String remoteMsgId, String relativeFilePath, String serverMsgId, String fingerprint, boolean read, String edited, boolean oob, String errorMessage, Set readByMarkers, boolean markable, boolean deleted, String bodyLanguage) { - super(conversation, uuid, conversationUUid, counterpart, trueCounterpart, body, timeSent, encryption, status, type, carbon, remoteMsgId, relativeFilePath, serverMsgId, fingerprint, read, edited, oob, errorMessage, readByMarkers, markable, deleted, bodyLanguage); + private IndividualMessage(Conversational conversation, String uuid, String conversationUUid, Jid counterpart, Jid trueCounterpart, String body, long timeSent, int encryption, int status, int type, boolean carbon, String remoteMsgId, String relativeFilePath, String serverMsgId, String fingerprint, boolean read, String edited, boolean oob, String errorMessage, Set readByMarkers, boolean markable, boolean deleted, String bodyLanguage, String occupantId, Collection reactions) { + super(conversation, uuid, conversationUUid, counterpart, trueCounterpart, body, timeSent, encryption, status, type, carbon, remoteMsgId, relativeFilePath, serverMsgId, fingerprint, read, edited, oob, errorMessage, readByMarkers, markable, deleted, bodyLanguage, occupantId, reactions); } @Override @@ -73,7 +74,7 @@ public class IndividualMessage extends Message { public static Message fromCursor(Cursor cursor, Conversational conversation) { Jid jid; try { - String value = cursor.getString(cursor.getColumnIndex(COUNTERPART)); + String value = cursor.getString(cursor.getColumnIndexOrThrow(COUNTERPART)); if (value != null) { jid = Jid.of(value); } else { @@ -86,7 +87,7 @@ public class IndividualMessage extends Message { } Jid trueCounterpart; try { - String value = cursor.getString(cursor.getColumnIndex(TRUE_COUNTERPART)); + String value = cursor.getString(cursor.getColumnIndexOrThrow(TRUE_COUNTERPART)); if (value != null) { trueCounterpart = Jid.of(value); } else { @@ -96,28 +97,30 @@ public class IndividualMessage extends Message { trueCounterpart = null; } return new IndividualMessage(conversation, - cursor.getString(cursor.getColumnIndex(UUID)), - cursor.getString(cursor.getColumnIndex(CONVERSATION)), + cursor.getString(cursor.getColumnIndexOrThrow(UUID)), + cursor.getString(cursor.getColumnIndexOrThrow(CONVERSATION)), jid, trueCounterpart, - cursor.getString(cursor.getColumnIndex(BODY)), - cursor.getLong(cursor.getColumnIndex(TIME_SENT)), - cursor.getInt(cursor.getColumnIndex(ENCRYPTION)), - cursor.getInt(cursor.getColumnIndex(STATUS)), - cursor.getInt(cursor.getColumnIndex(TYPE)), - cursor.getInt(cursor.getColumnIndex(CARBON)) > 0, - cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)), - cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH)), - cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID)), - cursor.getString(cursor.getColumnIndex(FINGERPRINT)), - cursor.getInt(cursor.getColumnIndex(READ)) > 0, - cursor.getString(cursor.getColumnIndex(EDITED)), - cursor.getInt(cursor.getColumnIndex(OOB)) > 0, - cursor.getString(cursor.getColumnIndex(ERROR_MESSAGE)), - ReadByMarker.fromJsonString(cursor.getString(cursor.getColumnIndex(READ_BY_MARKERS))), - cursor.getInt(cursor.getColumnIndex(MARKABLE)) > 0, - cursor.getInt(cursor.getColumnIndex(DELETED)) > 0, - cursor.getString(cursor.getColumnIndex(BODY_LANGUAGE)) + cursor.getString(cursor.getColumnIndexOrThrow(BODY)), + cursor.getLong(cursor.getColumnIndexOrThrow(TIME_SENT)), + cursor.getInt(cursor.getColumnIndexOrThrow(ENCRYPTION)), + cursor.getInt(cursor.getColumnIndexOrThrow(STATUS)), + cursor.getInt(cursor.getColumnIndexOrThrow(TYPE)), + cursor.getInt(cursor.getColumnIndexOrThrow(CARBON)) > 0, + cursor.getString(cursor.getColumnIndexOrThrow(REMOTE_MSG_ID)), + cursor.getString(cursor.getColumnIndexOrThrow(RELATIVE_FILE_PATH)), + cursor.getString(cursor.getColumnIndexOrThrow(SERVER_MSG_ID)), + cursor.getString(cursor.getColumnIndexOrThrow(FINGERPRINT)), + cursor.getInt(cursor.getColumnIndexOrThrow(READ)) > 0, + 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))), + 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))) ); } } diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index a359cf34c2cff3970879992301bae33d9539294d..372165b70e5a73404e1840f1016eeb23988c25a0 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -16,6 +16,8 @@ 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; @@ -86,6 +88,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable public static final String READ_BY_MARKERS = "readByMarkers"; public static final String MARKABLE = "markable"; public static final String DELETED = "deleted"; + public static final String OCCUPANT_ID = "occupantId"; + public static final String REACTIONS = "reactions"; public static final String ME_COMMAND = "/me "; public static final String ERROR_MESSAGE_CANCELLED = "eu.siacs.conversations.cancelled"; @@ -117,6 +121,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable private String axolotlFingerprint = null; private String errorMessage = null; private Set readByMarkers = new CopyOnWriteArraySet<>(); + private String occupantId; + private Collection reactions = Collections.emptyList(); private Boolean isGeoUri = null; private Boolean isEmojisOnly = null; @@ -155,7 +161,9 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable null, false, false, - null); + null, + null, + Collections.emptyList()); } public Message(Conversation conversation, int status, int type, final String remoteMsgId) { @@ -180,7 +188,9 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable null, false, false, - null); + null, + null, + Collections.emptyList()); } protected Message(final Conversational conversation, final String uuid, final String conversationUUid, final Jid counterpart, @@ -189,7 +199,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable 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 readByMarkers, - final boolean markable, final boolean deleted, final String bodyLanguage) { + final boolean markable, final boolean deleted, final String bodyLanguage, final String occupantId, final Collection reactions) { this.conversation = conversation; this.uuid = uuid; this.conversationUuid = conversationUUid; @@ -213,32 +223,37 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable this.markable = markable; this.deleted = deleted; this.bodyLanguage = bodyLanguage; + this.occupantId = occupantId; + this.reactions = reactions; } public static Message fromCursor(Cursor cursor, Conversation conversation) { return new Message(conversation, - cursor.getString(cursor.getColumnIndex(UUID)), - cursor.getString(cursor.getColumnIndex(CONVERSATION)), - fromString(cursor.getString(cursor.getColumnIndex(COUNTERPART))), - fromString(cursor.getString(cursor.getColumnIndex(TRUE_COUNTERPART))), - cursor.getString(cursor.getColumnIndex(BODY)), - cursor.getLong(cursor.getColumnIndex(TIME_SENT)), - cursor.getInt(cursor.getColumnIndex(ENCRYPTION)), - cursor.getInt(cursor.getColumnIndex(STATUS)), - cursor.getInt(cursor.getColumnIndex(TYPE)), - cursor.getInt(cursor.getColumnIndex(CARBON)) > 0, - cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)), - cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH)), - cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID)), - cursor.getString(cursor.getColumnIndex(FINGERPRINT)), - cursor.getInt(cursor.getColumnIndex(READ)) > 0, - cursor.getString(cursor.getColumnIndex(EDITED)), - cursor.getInt(cursor.getColumnIndex(OOB)) > 0, - cursor.getString(cursor.getColumnIndex(ERROR_MESSAGE)), - ReadByMarker.fromJsonString(cursor.getString(cursor.getColumnIndex(READ_BY_MARKERS))), - cursor.getInt(cursor.getColumnIndex(MARKABLE)) > 0, - cursor.getInt(cursor.getColumnIndex(DELETED)) > 0, - cursor.getString(cursor.getColumnIndex(BODY_LANGUAGE)) + cursor.getString(cursor.getColumnIndexOrThrow(UUID)), + cursor.getString(cursor.getColumnIndexOrThrow(CONVERSATION)), + fromString(cursor.getString(cursor.getColumnIndexOrThrow(COUNTERPART))), + fromString(cursor.getString(cursor.getColumnIndexOrThrow(TRUE_COUNTERPART))), + cursor.getString(cursor.getColumnIndexOrThrow(BODY)), + cursor.getLong(cursor.getColumnIndexOrThrow(TIME_SENT)), + cursor.getInt(cursor.getColumnIndexOrThrow(ENCRYPTION)), + cursor.getInt(cursor.getColumnIndexOrThrow(STATUS)), + cursor.getInt(cursor.getColumnIndexOrThrow(TYPE)), + cursor.getInt(cursor.getColumnIndexOrThrow(CARBON)) > 0, + cursor.getString(cursor.getColumnIndexOrThrow(REMOTE_MSG_ID)), + cursor.getString(cursor.getColumnIndexOrThrow(RELATIVE_FILE_PATH)), + cursor.getString(cursor.getColumnIndexOrThrow(SERVER_MSG_ID)), + cursor.getString(cursor.getColumnIndexOrThrow(FINGERPRINT)), + cursor.getInt(cursor.getColumnIndexOrThrow(READ)) > 0, + 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))), + 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))) + ); } @@ -270,7 +285,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable @Override public ContentValues getContentValues() { - ContentValues values = new ContentValues(); + final var values = new ContentValues(); values.put(UUID, uuid); values.put(CONVERSATION, conversationUuid); if (counterpart == null) { @@ -305,6 +320,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable values.put(MARKABLE, markable ? 1 : 0); values.put(DELETED, deleted ? 1 : 0); values.put(BODY_LANGUAGE, bodyLanguage); + values.put(OCCUPANT_ID, occupantId); + values.put(REACTIONS, Reaction.toString(this.reactions)); return values; } @@ -708,6 +725,10 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable return oob; } + public void setOccupantId(final String id) { + this.occupantId = id; + } + public static class MergeSeparator { } diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index 0f6580005da1e6fe8e261b20b572026285c27958..7c0c5a8741f6c218ccccae90b78b64143d54a6b7 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -11,6 +11,7 @@ import eu.siacs.conversations.services.AvatarService; import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.utils.JidHelper; import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.chatstate.ChatState; import eu.siacs.conversations.xmpp.forms.Data; @@ -226,6 +227,11 @@ public class MucOptions { return getFeatures().contains("http://jabber.org/protocol/muc#stable_id"); } + public boolean occupantId() { + final var features = getFeatures(); + return features.contains(Namespace.OCCUPANT_ID); + } + public User deleteUser(Jid jid) { User user = findUserByFullJid(jid); if (user != null) { diff --git a/src/main/java/eu/siacs/conversations/entities/Reaction.java b/src/main/java/eu/siacs/conversations/entities/Reaction.java new file mode 100644 index 0000000000000000000000000000000000000000..c8b1f8b9bef5b66ccb85aff02c34e6a9f14db613 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/entities/Reaction.java @@ -0,0 +1,43 @@ +package eu.siacs.conversations.entities; + +import com.google.common.base.Strings; +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; + +import eu.siacs.conversations.xmpp.Jid; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + +public class Reaction { + + private static final Gson GSON = new Gson(); + + public final String reaction; + public final Jid jid; + public final String occupantId; + + public Reaction(final String reaction, final Jid jid, final String occupantId) { + this.reaction = reaction; + this.jid = jid; + this.occupantId = occupantId; + } + + + public static String toString(final Collection reactions) { + return (reactions == null || reactions.isEmpty()) ? null : GSON.toJson(reactions); + } + + public static Collection fromString(final String asString) { + if ( Strings.isNullOrEmpty(asString)) { + return Collections.emptyList(); + } + try { + return GSON.fromJson(asString,new TypeToken>(){}.getType()); + } catch (final JsonSyntaxException e) { + return Collections.emptyList(); + } + } +} diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 3f40fde3306dee22787764f9a80cdf1359b700cb..fc741e26fce95484f1aeedb4ad416377da4b87fc 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -54,6 +54,7 @@ import im.conversations.android.xmpp.model.Extension; import im.conversations.android.xmpp.model.carbons.Received; import im.conversations.android.xmpp.model.carbons.Sent; import im.conversations.android.xmpp.model.forward.Forwarded; +import im.conversations.android.xmpp.model.occupant.OccupantId; public class MessageParser extends AbstractParser implements Consumer { @@ -631,8 +632,14 @@ public class MessageParser extends AbstractParser implements Consumer= 52) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.OCCUPANT_ID + " TEXT"); + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.REACTIONS + " TEXT"); + } } private void canonicalizeJids(SQLiteDatabase db) { diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index b7935c3b5362027c51f4251a716df9c25a96d71a..44d84438ed559d19e8b3a6874b1bd8b0afbce3ca 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -155,7 +155,6 @@ import eu.siacs.conversations.xml.LocalizedContent; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; -import eu.siacs.conversations.xmpp.OnBindListener; import eu.siacs.conversations.xmpp.OnContactStatusChanged; import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.OnMessageAcknowledged; @@ -1836,7 +1835,7 @@ public class XmppConnectionService extends Service { sendIqPacket(account, retrieve, (response) -> { if (response.getType() == Iq.Type.RESULT) { final Element pubsub = response.findChild("pubsub", Namespace.PUBSUB); - final Map bookmarks = Bookmark.parseFromPubsub(pubsub, account); + final Map bookmarks = Bookmark.parseFromPubSub(pubsub, account); processBookmarksInitial(account, bookmarks, true); } });