apply message corrections
This commit is contained in:
parent
be3a8dc5e1
commit
a69b4b14a5
|
@ -70,6 +70,7 @@ public final class Namespace {
|
||||||
public static final String MUC = "http://jabber.org/protocol/muc";
|
public static final String MUC = "http://jabber.org/protocol/muc";
|
||||||
public static final String MUC_USER = MUC + "#user";
|
public static final String MUC_USER = MUC + "#user";
|
||||||
public static final String NICK = "http://jabber.org/protocol/nick";
|
public static final String NICK = "http://jabber.org/protocol/nick";
|
||||||
|
public static final String OCCUPANT_ID = "urn:xmpp:occupant-id:0";
|
||||||
public static final String OMEMO_DTLS_SRTP_VERIFICATION =
|
public static final String OMEMO_DTLS_SRTP_VERIFICATION =
|
||||||
"http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification";
|
"http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification";
|
||||||
public static final String OOB = "jabber:x:oob";
|
public static final String OOB = "jabber:x:oob";
|
||||||
|
|
|
@ -57,6 +57,10 @@ public abstract class MessageDao {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this method returns a MessageIdentifier (message + version) used to create ORIGINAL messages
|
||||||
|
// it might return something that was previously a stub (message that only has reactions or
|
||||||
|
// corrections but no original content). but in the process of invoking this method the stub
|
||||||
|
// will be upgraded to an original message (missing information filled in)
|
||||||
@Transaction
|
@Transaction
|
||||||
public MessageIdentifier getOrCreateMessage(
|
public MessageIdentifier getOrCreateMessage(
|
||||||
ChatIdentifier chatIdentifier, final Transformation transformation) {
|
ChatIdentifier chatIdentifier, final Transformation transformation) {
|
||||||
|
@ -96,6 +100,96 @@ public abstract class MessageDao {
|
||||||
messageVersionId);
|
messageVersionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this gets either a message or a stub.
|
||||||
|
// stubs are recognized by latestVersion=NULL
|
||||||
|
// when found by stanzaId the stanzaId must either by verified or belonging to a stub
|
||||||
|
// when found by messageId the from must either match (for corrections) or not be set (null) and
|
||||||
|
// we only look up stubs
|
||||||
|
// TODO the from matcher should be in the outer condition
|
||||||
|
@Query(
|
||||||
|
"SELECT id,stanzaId,messageId,fromBare,latestVersion as version FROM message WHERE"
|
||||||
|
+ " chatId=:chatId AND (fromBare=:fromBare OR fromBare=NULL) AND ((stanzaId !="
|
||||||
|
+ " NULL AND stanzaId=:stanzaId AND (stanzaIdVerified=1 OR latestVersion=NULL)) OR"
|
||||||
|
+ " (stanzaId = NULL AND messageId=:messageId AND latestVersion = NULL))")
|
||||||
|
abstract MessageIdentifier get(long chatId, Jid fromBare, String stanzaId, String messageId);
|
||||||
|
|
||||||
|
public MessageIdentifier getOrCreateVersion(
|
||||||
|
ChatIdentifier chat,
|
||||||
|
Transformation transformation,
|
||||||
|
final String messageId,
|
||||||
|
final Modification modification) {
|
||||||
|
Preconditions.checkArgument(
|
||||||
|
messageId != null, "A modification must reference a message id");
|
||||||
|
final MessageIdentifier messageIdentifier;
|
||||||
|
if (transformation.occupantId == null) {
|
||||||
|
messageIdentifier = getByMessageId(chat.id, transformation.fromBare(), messageId);
|
||||||
|
} else {
|
||||||
|
messageIdentifier =
|
||||||
|
getByOccupantIdAndMessageId(
|
||||||
|
chat.id,
|
||||||
|
transformation.fromBare(),
|
||||||
|
transformation.occupantId,
|
||||||
|
messageId);
|
||||||
|
}
|
||||||
|
if (messageIdentifier == null) {
|
||||||
|
LOGGER.info(
|
||||||
|
"Create stub for {} because we could not find anything with id {} from {}",
|
||||||
|
modification,
|
||||||
|
messageId,
|
||||||
|
transformation.fromBare());
|
||||||
|
final var messageEntity = MessageEntity.stub(chat.id, messageId, transformation);
|
||||||
|
final long messageEntityId = insert(messageEntity);
|
||||||
|
final long messageVersionId =
|
||||||
|
insert(MessageVersionEntity.of(messageEntityId, modification, transformation));
|
||||||
|
// we do not point latestVersion to this newly created versions. We've only created a
|
||||||
|
// stub and are waiting for the original message to arrive
|
||||||
|
return new MessageIdentifier(
|
||||||
|
messageEntityId, null, null, transformation.fromBare(), messageVersionId);
|
||||||
|
}
|
||||||
|
if (hasVersionWithMessageId(messageIdentifier.id, transformation.messageId)) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
String.format(
|
||||||
|
"A modification with messageId %s has already been applied",
|
||||||
|
messageId));
|
||||||
|
}
|
||||||
|
final long messageVersionId =
|
||||||
|
insert(MessageVersionEntity.of(messageIdentifier.id, modification, transformation));
|
||||||
|
if (messageIdentifier.version != null) {
|
||||||
|
// if the existing message was not a stub we retarget the version
|
||||||
|
final long latestVersion = getLatestVersion(messageIdentifier.id);
|
||||||
|
setLatestMessageId(messageIdentifier.id, latestVersion);
|
||||||
|
}
|
||||||
|
return new MessageIdentifier(
|
||||||
|
messageIdentifier.id,
|
||||||
|
messageIdentifier.stanzaId,
|
||||||
|
messageIdentifier.messageId,
|
||||||
|
messageIdentifier.fromBare,
|
||||||
|
messageVersionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"SELECT id,stanzaId,messageId,fromBare,latestVersion as version FROM message WHERE"
|
||||||
|
+ " chatId=:chatId AND (fromBare=:fromBare OR fromBare IS NULL) AND"
|
||||||
|
+ " (occupantId=:occupantId OR occupantId IS NULL) AND messageId=:messageId")
|
||||||
|
abstract MessageIdentifier getByOccupantIdAndMessageId(
|
||||||
|
long chatId, Jid fromBare, String occupantId, String messageId);
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"SELECT id,stanzaId,messageId,fromBare,latestVersion as version FROM message WHERE"
|
||||||
|
+ " chatId=:chatId AND (fromBare=:fromBare OR fromBare IS NULL) AND"
|
||||||
|
+ " messageId=:messageId")
|
||||||
|
abstract MessageIdentifier getByMessageId(long chatId, Jid fromBare, String messageId);
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"SELECT id FROM message_version WHERE messageEntityId=:messageEntityId ORDER BY (CASE"
|
||||||
|
+ " modification WHEN 'ORIGINAL' THEN 0 ELSE 1 END),receivedAt DESC LIMIT 1")
|
||||||
|
abstract Long getLatestVersion(long messageEntityId);
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"SELECT EXISTS (SELECT id FROM message_version WHERE messageEntityId=:messageEntityId"
|
||||||
|
+ " AND messageId=:messageId)")
|
||||||
|
abstract boolean hasVersionWithMessageId(long messageEntityId, String messageId);
|
||||||
|
|
||||||
@Insert
|
@Insert
|
||||||
protected abstract long insert(MessageEntity messageEntity);
|
protected abstract long insert(MessageEntity messageEntity);
|
||||||
|
|
||||||
|
@ -114,19 +208,6 @@ public abstract class MessageDao {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this gets either a message or a stub.
|
|
||||||
// stubs are recognized by latestVersion=NULL
|
|
||||||
// when found by stanzaId the stanzaId must either by verified or belonging to a stub
|
|
||||||
// when found by messageId the from must either match (for corrections) or not be set (null) and
|
|
||||||
// we only look up stubs
|
|
||||||
// TODO the from matcher should be in the outer condition
|
|
||||||
@Query(
|
|
||||||
"SELECT id,stanzaId,messageId,fromBare,latestVersion as version FROM message WHERE"
|
|
||||||
+ " chatId=:chatId AND (fromBare=:fromBare OR fromBare=NULL) AND ((stanzaId !="
|
|
||||||
+ " NULL AND stanzaId=:stanzaId AND (stanzaIdVerified=1 OR latestVersion=NULL)) OR"
|
|
||||||
+ " (stanzaId = NULL AND messageId=:messageId AND latestVersion = NULL))")
|
|
||||||
abstract MessageIdentifier get(long chatId, Jid fromBare, String stanzaId, String messageId);
|
|
||||||
|
|
||||||
public void insertMessageContent(Long latestVersion, List<MessageContent> contents) {
|
public void insertMessageContent(Long latestVersion, List<MessageContent> contents) {
|
||||||
Preconditions.checkNotNull(
|
Preconditions.checkNotNull(
|
||||||
latestVersion, "Contents can only be inserted for a specific version");
|
latestVersion, "Contents can only be inserted for a specific version");
|
||||||
|
|
|
@ -65,9 +65,20 @@ public class MessageEntity {
|
||||||
entity.toResource = transformation.toResource();
|
entity.toResource = transformation.toResource();
|
||||||
entity.fromBare = transformation.fromBare();
|
entity.fromBare = transformation.fromBare();
|
||||||
entity.fromResource = transformation.fromResource();
|
entity.fromResource = transformation.fromResource();
|
||||||
|
entity.occupantId = transformation.occupantId;
|
||||||
entity.messageId = transformation.messageId;
|
entity.messageId = transformation.messageId;
|
||||||
entity.stanzaId = transformation.stanzaId;
|
entity.stanzaId = transformation.stanzaId;
|
||||||
entity.stanzaIdVerified = Objects.nonNull(transformation.stanzaId);
|
entity.stanzaIdVerified = Objects.nonNull(transformation.stanzaId);
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static MessageEntity stub(
|
||||||
|
final long chatId, String messageId, Transformation transformation) {
|
||||||
|
final var entity = new MessageEntity();
|
||||||
|
entity.chatId = chatId;
|
||||||
|
entity.fromBare = transformation.fromBare();
|
||||||
|
entity.messageId = messageId;
|
||||||
|
entity.stanzaIdVerified = false;
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ import im.conversations.android.xmpp.model.DeliveryReceipt;
|
||||||
import im.conversations.android.xmpp.model.DeliveryReceiptRequest;
|
import im.conversations.android.xmpp.model.DeliveryReceiptRequest;
|
||||||
import im.conversations.android.xmpp.model.Extension;
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
import im.conversations.android.xmpp.model.axolotl.Encrypted;
|
import im.conversations.android.xmpp.model.axolotl.Encrypted;
|
||||||
|
import im.conversations.android.xmpp.model.correction.Replace;
|
||||||
|
import im.conversations.android.xmpp.model.error.Error;
|
||||||
import im.conversations.android.xmpp.model.jabber.Body;
|
import im.conversations.android.xmpp.model.jabber.Body;
|
||||||
import im.conversations.android.xmpp.model.jabber.Thread;
|
import im.conversations.android.xmpp.model.jabber.Thread;
|
||||||
import im.conversations.android.xmpp.model.markers.Displayed;
|
import im.conversations.android.xmpp.model.markers.Displayed;
|
||||||
|
@ -31,7 +33,8 @@ public class Transformation {
|
||||||
OutOfBandData.class,
|
OutOfBandData.class,
|
||||||
DeliveryReceipt.class,
|
DeliveryReceipt.class,
|
||||||
MultiUserChat.class,
|
MultiUserChat.class,
|
||||||
Displayed.class);
|
Displayed.class,
|
||||||
|
Replace.class);
|
||||||
|
|
||||||
public final Instant receivedAt;
|
public final Instant receivedAt;
|
||||||
public final Jid to;
|
public final Jid to;
|
||||||
|
@ -41,6 +44,8 @@ public class Transformation {
|
||||||
public final String messageId;
|
public final String messageId;
|
||||||
public final String stanzaId;
|
public final String stanzaId;
|
||||||
|
|
||||||
|
public final String occupantId;
|
||||||
|
|
||||||
private final List<Extension> extensions;
|
private final List<Extension> extensions;
|
||||||
|
|
||||||
public final Collection<DeliveryReceiptRequest> deliveryReceiptRequests;
|
public final Collection<DeliveryReceiptRequest> deliveryReceiptRequests;
|
||||||
|
@ -53,6 +58,7 @@ public class Transformation {
|
||||||
final Message.Type type,
|
final Message.Type type,
|
||||||
final String messageId,
|
final String messageId,
|
||||||
final String stanzaId,
|
final String stanzaId,
|
||||||
|
final String occupantId,
|
||||||
final List<Extension> extensions,
|
final List<Extension> extensions,
|
||||||
final Collection<DeliveryReceiptRequest> deliveryReceiptRequests) {
|
final Collection<DeliveryReceiptRequest> deliveryReceiptRequests) {
|
||||||
this.receivedAt = receivedAt;
|
this.receivedAt = receivedAt;
|
||||||
|
@ -62,6 +68,7 @@ public class Transformation {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.messageId = messageId;
|
this.messageId = messageId;
|
||||||
this.stanzaId = stanzaId;
|
this.stanzaId = stanzaId;
|
||||||
|
this.occupantId = occupantId;
|
||||||
this.extensions = extensions;
|
this.extensions = extensions;
|
||||||
this.deliveryReceiptRequests = deliveryReceiptRequests;
|
this.deliveryReceiptRequests = deliveryReceiptRequests;
|
||||||
}
|
}
|
||||||
|
@ -97,11 +104,21 @@ public class Transformation {
|
||||||
}
|
}
|
||||||
|
|
||||||
public <E extends Extension> E getExtension(final Class<E> clazz) {
|
public <E extends Extension> E getExtension(final Class<E> clazz) {
|
||||||
|
checkArgument(clazz);
|
||||||
final var extension = Iterables.find(this.extensions, clazz::isInstance, null);
|
final var extension = Iterables.find(this.extensions, clazz::isInstance, null);
|
||||||
return extension == null ? null : clazz.cast(extension);
|
return extension == null ? null : clazz.cast(extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkArgument(final Class<? extends Extension> clazz) {
|
||||||
|
if (EXTENSION_FOR_TRANSFORMATION.contains(clazz) || clazz == Error.class) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("%s has not been registered for transformation", clazz.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
public <E extends Extension> Collection<E> getExtensions(final Class<E> clazz) {
|
public <E extends Extension> Collection<E> getExtensions(final Class<E> clazz) {
|
||||||
|
checkArgument(clazz);
|
||||||
return Collections2.transform(
|
return Collections2.transform(
|
||||||
Collections2.filter(this.extensions, clazz::isInstance), clazz::cast);
|
Collections2.filter(this.extensions, clazz::isInstance), clazz::cast);
|
||||||
}
|
}
|
||||||
|
@ -110,7 +127,8 @@ public class Transformation {
|
||||||
@NonNull final Message message,
|
@NonNull final Message message,
|
||||||
@NonNull final Instant receivedAt,
|
@NonNull final Instant receivedAt,
|
||||||
@NonNull final Jid remote,
|
@NonNull final Jid remote,
|
||||||
final String stanzaId) {
|
final String stanzaId,
|
||||||
|
final String occupantId) {
|
||||||
final var to = message.getTo();
|
final var to = message.getTo();
|
||||||
final var from = message.getFrom();
|
final var from = message.getFrom();
|
||||||
final var type = message.getType();
|
final var type = message.getType();
|
||||||
|
@ -134,6 +152,7 @@ public class Transformation {
|
||||||
type,
|
type,
|
||||||
messageId,
|
messageId,
|
||||||
stanzaId,
|
stanzaId,
|
||||||
|
occupantId,
|
||||||
extensionListBuilder.build(),
|
extensionListBuilder.build(),
|
||||||
requests);
|
requests);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
package im.conversations.android.transformer;
|
package im.conversations.android.transformer;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import im.conversations.android.xmpp.XmppConnection;
|
import im.conversations.android.xmpp.XmppConnection;
|
||||||
|
import im.conversations.android.xmpp.manager.DiscoManager;
|
||||||
|
import im.conversations.android.xmpp.model.occupant.OccupantId;
|
||||||
import im.conversations.android.xmpp.model.stanza.Message;
|
import im.conversations.android.xmpp.model.stanza.Message;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
|
@ -27,7 +30,18 @@ public class TransformationFactory extends XmppConnection.Delegate {
|
||||||
} else {
|
} else {
|
||||||
remote = from;
|
remote = from;
|
||||||
}
|
}
|
||||||
// TODO parse occupant on group chats
|
final String occupantId;
|
||||||
return Transformation.of(message, receivedAt, remote, stanzaId);
|
if (message.getType() == Message.Type.GROUPCHAT && message.hasExtension(OccupantId.class)) {
|
||||||
|
if (from != null
|
||||||
|
&& getManager(DiscoManager.class)
|
||||||
|
.hasFeature(from.asBareJid(), Namespace.OCCUPANT_ID)) {
|
||||||
|
occupantId = message.getExtension(OccupantId.class).getId();
|
||||||
|
} else {
|
||||||
|
occupantId = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
occupantId = null;
|
||||||
|
}
|
||||||
|
return Transformation.of(message, receivedAt, remote, stanzaId, occupantId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,9 @@ import im.conversations.android.database.ConversationsDatabase;
|
||||||
import im.conversations.android.database.model.Account;
|
import im.conversations.android.database.model.Account;
|
||||||
import im.conversations.android.database.model.ChatIdentifier;
|
import im.conversations.android.database.model.ChatIdentifier;
|
||||||
import im.conversations.android.database.model.MessageContent;
|
import im.conversations.android.database.model.MessageContent;
|
||||||
|
import im.conversations.android.database.model.MessageIdentifier;
|
||||||
import im.conversations.android.database.model.MessageState;
|
import im.conversations.android.database.model.MessageState;
|
||||||
|
import im.conversations.android.database.model.Modification;
|
||||||
import im.conversations.android.xmpp.model.DeliveryReceipt;
|
import im.conversations.android.xmpp.model.DeliveryReceipt;
|
||||||
import im.conversations.android.xmpp.model.axolotl.Encrypted;
|
import im.conversations.android.xmpp.model.axolotl.Encrypted;
|
||||||
import im.conversations.android.xmpp.model.correction.Replace;
|
import im.conversations.android.xmpp.model.correction.Replace;
|
||||||
|
@ -17,6 +19,7 @@ import im.conversations.android.xmpp.model.markers.Displayed;
|
||||||
import im.conversations.android.xmpp.model.muc.user.MultiUserChat;
|
import im.conversations.android.xmpp.model.muc.user.MultiUserChat;
|
||||||
import im.conversations.android.xmpp.model.oob.OutOfBandData;
|
import im.conversations.android.xmpp.model.oob.OutOfBandData;
|
||||||
import im.conversations.android.xmpp.model.stanza.Message;
|
import im.conversations.android.xmpp.model.stanza.Message;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
@ -66,31 +69,43 @@ public class Transformer {
|
||||||
chat, transformation.messageId, MessageState.error(transformation));
|
chat, transformation.messageId, MessageState.error(transformation));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final Replace lastMessageCorrection = transformation.getExtension(Replace.class);
|
final Replace messageCorrection = transformation.getExtension(Replace.class);
|
||||||
final List<MessageContent> contents = parseContent(transformation);
|
final List<MessageContent> contents = parseContent(transformation);
|
||||||
|
|
||||||
// TODO this also needs to be true for retractions once we support those (anything that
|
final boolean identifiableSender =
|
||||||
// creates a new message version
|
Arrays.asList(Message.Type.NORMAL, Message.Type.CHAT).contains(messageType)
|
||||||
// TODO a type=groupchat message correction is only valid with an occupant id
|
|| Objects.nonNull(transformation.occupantId);
|
||||||
final boolean versionModification = Objects.nonNull(lastMessageCorrection);
|
final boolean isMessageCorrection =
|
||||||
|
Objects.nonNull(messageCorrection)
|
||||||
|
&& messageCorrection.getId() != null
|
||||||
|
&& identifiableSender;
|
||||||
|
|
||||||
if (contents.isEmpty()) {
|
if (contents.isEmpty()) {
|
||||||
LOGGER.info("Received message from {} w/o contents", transformation.from);
|
LOGGER.info("Received message from {} w/o contents", transformation.from);
|
||||||
transformMessageState(chat, transformation);
|
transformMessageState(chat, transformation);
|
||||||
// TODO apply reactions
|
// TODO apply reactions
|
||||||
} else {
|
} else {
|
||||||
if (versionModification) {
|
final MessageIdentifier messageIdentifier;
|
||||||
// TODO use getOrStub
|
try {
|
||||||
// TODO check if versionModification has already been applied
|
if (isMessageCorrection) {
|
||||||
|
messageIdentifier =
|
||||||
|
database.messageDao()
|
||||||
|
.getOrCreateVersion(
|
||||||
|
chat,
|
||||||
|
transformation,
|
||||||
|
messageCorrection.getId(),
|
||||||
|
Modification.EDIT);
|
||||||
|
|
||||||
// TODO for replaced message create a new version; re-target latestVersion
|
} else {
|
||||||
|
messageIdentifier =
|
||||||
} else {
|
database.messageDao().getOrCreateMessage(chat, transformation);
|
||||||
final var messageIdentifier =
|
}
|
||||||
database.messageDao().getOrCreateMessage(chat, transformation);
|
} catch (final IllegalStateException e) {
|
||||||
database.messageDao().insertMessageContent(messageIdentifier.version, contents);
|
LOGGER.warn("Could not get message identifier", e);
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
database.messageDao().insertMessageContent(messageIdentifier.version, contents);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,18 @@
|
||||||
package im.conversations.android.xmpp.model.correction;
|
package im.conversations.android.xmpp.model.correction;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
import im.conversations.android.xmpp.model.Extension;
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement(namespace = Namespace.LAST_MESSAGE_CORRECTION)
|
||||||
public class Replace extends Extension {
|
public class Replace extends Extension {
|
||||||
|
|
||||||
public Replace() {
|
public Replace() {
|
||||||
super(Replace.class);
|
super(Replace.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return Strings.emptyToNull(this.getAttribute("id"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package im.conversations.android.xmpp.model.occupant;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement(namespace = Namespace.OCCUPANT_ID)
|
||||||
|
public class OccupantId extends Extension {
|
||||||
|
|
||||||
|
public OccupantId() {
|
||||||
|
super(OccupantId.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return Strings.emptyToNull(this.getAttribute("id"));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue