diff --git a/app/schemas/im.conversations.android.database.ConversationsDatabase/1.json b/app/schemas/im.conversations.android.database.ConversationsDatabase/1.json index d0e218100..e9bbad705 100644 --- a/app/schemas/im.conversations.android.database.ConversationsDatabase/1.json +++ b/app/schemas/im.conversations.android.database.ConversationsDatabase/1.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "b5e8a59bbd86e133c0bc2edd303ad2a0", + "identityHash": "9620a1b63d595091a2b463e89b504eb7", "entities": [ { "tableName": "account", @@ -1009,7 +1009,7 @@ }, { "tableName": "chat", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `address` TEXT NOT NULL, `type` TEXT, `archived` INTEGER NOT NULL, FOREIGN KEY(`accountId`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `address` TEXT NOT NULL, `type` TEXT, `archived` INTEGER NOT NULL, `mucState` TEXT, `errorCondition` TEXT, FOREIGN KEY(`accountId`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", "fields": [ { "fieldPath": "id", @@ -1040,6 +1040,18 @@ "columnName": "archived", "affinity": "INTEGER", "notNull": true + }, + { + "fieldPath": "mucState", + "columnName": "mucState", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "errorCondition", + "columnName": "errorCondition", + "affinity": "TEXT", + "notNull": false } ], "primaryKey": { @@ -2007,6 +2019,60 @@ } ] }, + { + "tableName": "muc_status_code", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `chatId` INTEGER NOT NULL, `code` INTEGER NOT NULL, FOREIGN KEY(`chatId`) REFERENCES `chat`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "chatId", + "columnName": "chatId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "code", + "columnName": "code", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_muc_status_code_chatId", + "unique": false, + "columnNames": [ + "chatId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_muc_status_code_chatId` ON `${TABLE_NAME}` (`chatId`)" + } + ], + "foreignKeys": [ + { + "table": "chat", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "chatId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, { "tableName": "nick", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `address` TEXT NOT NULL, `nick` TEXT, FOREIGN KEY(`accountId`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", @@ -2528,7 +2594,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b5e8a59bbd86e133c0bc2edd303ad2a0')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9620a1b63d595091a2b463e89b504eb7')" ] } } \ No newline at end of file diff --git a/app/src/main/java/im/conversations/android/database/ConversationsDatabase.java b/app/src/main/java/im/conversations/android/database/ConversationsDatabase.java index c6abb0860..9d8690723 100644 --- a/app/src/main/java/im/conversations/android/database/ConversationsDatabase.java +++ b/app/src/main/java/im/conversations/android/database/ConversationsDatabase.java @@ -46,6 +46,7 @@ import im.conversations.android.database.entity.MessageEntity; import im.conversations.android.database.entity.MessageReactionEntity; import im.conversations.android.database.entity.MessageStateEntity; import im.conversations.android.database.entity.MessageVersionEntity; +import im.conversations.android.database.entity.MucStatusCodeEntity; import im.conversations.android.database.entity.NickEntity; import im.conversations.android.database.entity.PresenceEntity; import im.conversations.android.database.entity.RosterItemEntity; @@ -81,6 +82,7 @@ import im.conversations.android.database.entity.ServiceRecordCacheEntity; MessageStateEntity.class, MessageContentEntity.class, MessageVersionEntity.class, + MucStatusCodeEntity.class, NickEntity.class, PresenceEntity.class, MessageReactionEntity.class, diff --git a/app/src/main/java/im/conversations/android/database/dao/ChatDao.java b/app/src/main/java/im/conversations/android/database/dao/ChatDao.java index 7768978d8..665ec914d 100644 --- a/app/src/main/java/im/conversations/android/database/dao/ChatDao.java +++ b/app/src/main/java/im/conversations/android/database/dao/ChatDao.java @@ -7,13 +7,16 @@ import androidx.room.Query; import androidx.room.Transaction; import com.google.common.util.concurrent.ListenableFuture; import im.conversations.android.database.entity.ChatEntity; +import im.conversations.android.database.entity.MucStatusCodeEntity; import im.conversations.android.database.model.Account; import im.conversations.android.database.model.ChatIdentifier; import im.conversations.android.database.model.ChatType; import im.conversations.android.database.model.GroupIdentifier; +import im.conversations.android.database.model.MucState; import im.conversations.android.database.model.MucWithNick; import im.conversations.android.xmpp.model.stanza.Message; import java.util.Arrays; +import java.util.Collection; import java.util.List; import org.jxmpp.jid.Jid; import org.slf4j.Logger; @@ -59,6 +62,12 @@ public abstract class ChatDao { + " address=:address") protected abstract ChatIdentifier get(final long accountId, final Jid address); + @Query( + "SELECT id,address,type,archived FROM chat WHERE accountId=:accountId AND" + + " address=:address AND type=:chatType") + public abstract ChatIdentifier get( + final long accountId, final Jid address, final ChatType chatType); + @Insert protected abstract long insert(ChatEntity chatEntity); @@ -125,13 +134,56 @@ public abstract class ChatDao { + " chat.accountId=:account AND chat.type=:chatType AND archived=0") protected abstract List getBookmarksNotInChats(long account, ChatType chatType); - // TODO get pending only @Query( - "SELECT chat.address,bookmark.nick as nickBookmark,nick.nick as nickAccount FROM chat" - + " LEFT JOIN bookmark ON chat.accountId=bookmark.accountId AND" - + " chat.address=bookmark.address JOIN account ON account.id=chat.accountId LEFT" - + " JOIN nick ON nick.accountId=chat.accountId AND nick.address=account.address" - + " WHERE chat.accountId=:account AND chat.type=:chatType AND chat.archived=0") + "SELECT chat.id as chatId,chat.address,bookmark.nick as nickBookmark,nick.nick as" + + " nickAccount FROM chat LEFT JOIN bookmark ON chat.accountId=bookmark.accountId" + + " AND chat.address=bookmark.address JOIN account ON account.id=chat.accountId" + + " LEFT JOIN nick ON nick.accountId=chat.accountId AND" + + " nick.address=account.address WHERE chat.accountId=:account AND" + + " chat.type=:chatType AND chat.archived=0 AND chat.mucState IS NULL") public abstract ListenableFuture> getMultiUserChats( final long account, final ChatType chatType); + + @Query("UPDATE chat SET mucState=:mucState, errorCondition=:errorCondition WHERE id=:chatId") + protected abstract void setMucStateInternal( + final long chatId, final MucState mucState, final String errorCondition); + + @Transaction + public void setMucState( + final long chatId, final MucState mucState, final String errorCondition) { + setMucStateInternal(chatId, mucState, errorCondition); + deleteStatusCodes(chatId); + } + + @Transaction + public void setMucState( + final long chatId, final MucState mucState, final Collection statusCodes) { + setMucStateInternal(chatId, mucState, null); + deleteStatusCodes(chatId); + insertStatusCode(MucStatusCodeEntity.of(chatId, statusCodes)); + } + + @Transaction + public void setMucState(final long chatId, final MucState mucState) { + setMucStateInternal(chatId, mucState, null); + deleteStatusCodes(chatId); + } + + @Transaction + public void resetMucStates() { + this.nullMucStates(); + this.deleteStatusCodes(); + } + + @Insert + protected abstract void insertStatusCode(final Collection entities); + + @Query("UPDATE chat SET mucState=null,errorCondition=null") + protected abstract void nullMucStates(); + + @Query("DELETE FROM muc_status_code") + protected abstract void deleteStatusCodes(); + + @Query("DELETE FROM muc_status_code WHERE chatId=:chatId") + protected abstract void deleteStatusCodes(final long chatId); } diff --git a/app/src/main/java/im/conversations/android/database/dao/DiscoDao.java b/app/src/main/java/im/conversations/android/database/dao/DiscoDao.java index 0c91d3859..90ea69f5d 100644 --- a/app/src/main/java/im/conversations/android/database/dao/DiscoDao.java +++ b/app/src/main/java/im/conversations/android/database/dao/DiscoDao.java @@ -21,8 +21,6 @@ import im.conversations.android.xmpp.EntityCapabilities; import im.conversations.android.xmpp.EntityCapabilities2; import im.conversations.android.xmpp.model.data.Data; import im.conversations.android.xmpp.model.data.Value; -import im.conversations.android.xmpp.model.disco.info.Feature; -import im.conversations.android.xmpp.model.disco.info.Identity; import im.conversations.android.xmpp.model.disco.info.InfoQuery; import im.conversations.android.xmpp.model.disco.items.Item; import java.util.Collection; @@ -156,13 +154,11 @@ public abstract class DiscoDao { insertDiscoIdentities( Collections2.transform( - infoQuery.getExtensions(Identity.class), - i -> DiscoIdentityEntity.of(discoId, i))); + infoQuery.getIdentities(), i -> DiscoIdentityEntity.of(discoId, i))); insertDiscoFeatures( Collections2.transform( - infoQuery.getExtensions(Feature.class), - f -> DiscoFeatureEntity.of(discoId, f.getVar()))); + infoQuery.getFeatures(), f -> DiscoFeatureEntity.of(discoId, f.getVar()))); for (final Data data : infoQuery.getExtensions(Data.class)) { final var extensionId = insert(DiscoExtensionEntity.of(discoId, data.getFormType())); for (final var field : data.getFields()) { diff --git a/app/src/main/java/im/conversations/android/database/entity/ChatEntity.java b/app/src/main/java/im/conversations/android/database/entity/ChatEntity.java index 188ddf5a8..7ea15b065 100644 --- a/app/src/main/java/im/conversations/android/database/entity/ChatEntity.java +++ b/app/src/main/java/im/conversations/android/database/entity/ChatEntity.java @@ -1,11 +1,13 @@ package im.conversations.android.database.entity; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.room.Entity; import androidx.room.ForeignKey; import androidx.room.Index; import androidx.room.PrimaryKey; import im.conversations.android.database.model.ChatType; +import im.conversations.android.database.model.MucState; @Entity( tableName = "chat", @@ -32,4 +34,7 @@ public class ChatEntity { public ChatType type; public boolean archived; + + @Nullable public MucState mucState; + @Nullable public String errorCondition; } diff --git a/app/src/main/java/im/conversations/android/database/entity/MucStatusCodeEntity.java b/app/src/main/java/im/conversations/android/database/entity/MucStatusCodeEntity.java new file mode 100644 index 000000000..88c46c9ab --- /dev/null +++ b/app/src/main/java/im/conversations/android/database/entity/MucStatusCodeEntity.java @@ -0,0 +1,40 @@ +package im.conversations.android.database.entity; + +import androidx.annotation.NonNull; +import androidx.room.Entity; +import androidx.room.ForeignKey; +import androidx.room.Index; +import androidx.room.PrimaryKey; +import com.google.common.collect.Collections2; +import java.util.Collection; + +@Entity( + tableName = "muc_status_code", + foreignKeys = { + @ForeignKey( + entity = ChatEntity.class, + parentColumns = {"id"}, + childColumns = {"chatId"}, + onDelete = ForeignKey.CASCADE) + }, + indices = {@Index(value = "chatId")}) +public class MucStatusCodeEntity { + @PrimaryKey(autoGenerate = true) + public Long id; + + @NonNull public Long chatId; + + @NonNull public Integer code; + + public static Collection of( + final long chatId, final Collection codes) { + return Collections2.transform(codes, c -> of(chatId, c)); + } + + private static MucStatusCodeEntity of(final long chatId, final int code) { + final var entity = new MucStatusCodeEntity(); + entity.chatId = chatId; + entity.code = code; + return entity; + } +} diff --git a/app/src/main/java/im/conversations/android/database/model/MucState.java b/app/src/main/java/im/conversations/android/database/model/MucState.java new file mode 100644 index 000000000..7342dc470 --- /dev/null +++ b/app/src/main/java/im/conversations/android/database/model/MucState.java @@ -0,0 +1,10 @@ +package im.conversations.android.database.model; + +public enum MucState { + JOINING, + AVAILABLE, + ERROR_PRESENCE, + ERROR_IQ, + UNAVAILABLE, + NOT_A_MUC +} diff --git a/app/src/main/java/im/conversations/android/database/model/MucWithNick.java b/app/src/main/java/im/conversations/android/database/model/MucWithNick.java index aaaefdb8d..cb85f4ebf 100644 --- a/app/src/main/java/im/conversations/android/database/model/MucWithNick.java +++ b/app/src/main/java/im/conversations/android/database/model/MucWithNick.java @@ -5,12 +5,18 @@ import org.jxmpp.jid.BareJid; import org.jxmpp.jid.parts.Resourcepart; public class MucWithNick { + public final long chatId; public final BareJid address; private final String nickBookmark; private final String nickAccount; - public MucWithNick(BareJid address, String nickBookmark, String nickAccount) { + public MucWithNick( + final long chatId, + final BareJid address, + final String nickBookmark, + final String nickAccount) { + this.chatId = chatId; this.address = address; this.nickBookmark = nickBookmark; this.nickAccount = nickAccount; diff --git a/app/src/main/java/im/conversations/android/xmpp/EntityCapabilities.java b/app/src/main/java/im/conversations/android/xmpp/EntityCapabilities.java index ceb81a1fc..b282c0791 100644 --- a/app/src/main/java/im/conversations/android/xmpp/EntityCapabilities.java +++ b/app/src/main/java/im/conversations/android/xmpp/EntityCapabilities.java @@ -37,7 +37,7 @@ public final class EntityCapabilities { blankNull(a.getIdentityName()), blankNull(b.getIdentityName())) .result()) - .sortedCopy(info.getExtensions(Identity.class)); + .sortedCopy(info.getIdentities()); for (final Identity id : orderedIdentities) { s.append(blankNull(id.getCategory())) @@ -52,9 +52,7 @@ public final class EntityCapabilities { final List features = Ordering.natural() - .sortedCopy( - Collections2.transform( - info.getExtensions(Feature.class), Feature::getVar)); + .sortedCopy(Collections2.transform(info.getFeatures(), Feature::getVar)); for (final String feature : features) { s.append(clean(feature)).append("<"); } diff --git a/app/src/main/java/im/conversations/android/xmpp/EntityCapabilities2.java b/app/src/main/java/im/conversations/android/xmpp/EntityCapabilities2.java index 8541e48a3..a7f6948c8 100644 --- a/app/src/main/java/im/conversations/android/xmpp/EntityCapabilities2.java +++ b/app/src/main/java/im/conversations/android/xmpp/EntityCapabilities2.java @@ -62,8 +62,8 @@ public class EntityCapabilities2 { } private static String algorithm(final InfoQuery infoQuery) { - return features(infoQuery.getExtensions(Feature.class)) - + identities(infoQuery.getExtensions(Identity.class)) + return features(infoQuery.getFeatures()) + + identities(infoQuery.getIdentities()) + extensions(infoQuery.getExtensions(Data.class)); } diff --git a/app/src/main/java/im/conversations/android/xmpp/manager/MultiUserChatManager.java b/app/src/main/java/im/conversations/android/xmpp/manager/MultiUserChatManager.java index a8dbf9f2d..ad33c929c 100644 --- a/app/src/main/java/im/conversations/android/xmpp/manager/MultiUserChatManager.java +++ b/app/src/main/java/im/conversations/android/xmpp/manager/MultiUserChatManager.java @@ -5,12 +5,19 @@ import androidx.annotation.NonNull; import com.google.common.base.Preconditions; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; +import im.conversations.android.database.model.ChatIdentifier; import im.conversations.android.database.model.ChatType; +import im.conversations.android.database.model.MucState; import im.conversations.android.database.model.MucWithNick; +import im.conversations.android.xml.Namespace; import im.conversations.android.xmpp.Entity; +import im.conversations.android.xmpp.IqErrorException; import im.conversations.android.xmpp.XmppConnection; import im.conversations.android.xmpp.model.disco.info.InfoQuery; +import im.conversations.android.xmpp.model.error.Condition; +import im.conversations.android.xmpp.model.error.Error; import im.conversations.android.xmpp.model.muc.History; import im.conversations.android.xmpp.model.muc.MultiUserChat; import im.conversations.android.xmpp.model.muc.user.MucUser; @@ -29,47 +36,41 @@ public class MultiUserChatManager extends AbstractManager { super(context, connection); } - public void joinMultiUserChats() { - final var future = getDatabase().chatDao().getMultiUserChats(getAccount().id, ChatType.MUC); - Futures.addCallback( - future, - new FutureCallback<>() { - @Override - public void onSuccess(List result) { - for (final MucWithNick mucWithNick : result) { - enter(mucWithNick); - } - } - - @Override - public void onFailure(Throwable t) { - LOGGER.info("Could not query db", t); - } - }, + public ListenableFuture joinMultiUserChats() { + LOGGER.info("joining multi user chats. start"); + return Futures.transform( + getDatabase().chatDao().getMultiUserChats(getAccount().id, ChatType.MUC), + this::joinMultiUserChats, MoreExecutors.directExecutor()); } - public void enter(final MucWithNick mucWithNick) { + private int joinMultiUserChats(final List chats) { + LOGGER.info("joining {} chats", chats.size()); + for (final MucWithNick chat : chats) { + this.enterExisting(chat); + } + return chats.size(); + } + + public void enterExisting(final MucWithNick mucWithNick) { + getDatabase().chatDao().setMucState(mucWithNick.chatId, MucState.JOINING); final var discoInfoFuture = getManager(DiscoManager.class).info(Entity.discoItem(mucWithNick.address)); - // TODO catching Futures.addCallback( discoInfoFuture, - new FutureCallback<>() { - @Override - public void onSuccess(final InfoQuery result) { - enter(mucWithNick, result); - } - - @Override - public void onFailure(@NonNull final Throwable throwable) { - // TODO mark chat as ERROR_IQ - } - }, + new ExistingMucJoiner(mucWithNick), MoreExecutors.directExecutor()); } - public void enter(final MucWithNick mucWithNick, final InfoQuery infoQuery) { + private void enterExisting(final MucWithNick mucWithNick, final InfoQuery infoQuery) { + if (infoQuery.hasFeature(Namespace.MUC)) { + sendJoinPresence(mucWithNick); + } else { + getDatabase().chatDao().setMucState(mucWithNick.chatId, MucState.NOT_A_MUC); + } + } + + private void sendJoinPresence(final MucWithNick mucWithNick) { final var nick = mucWithNick.nick(); final Jid to; if (nick != null) { @@ -86,16 +87,106 @@ public class MultiUserChatManager extends AbstractManager { connection.sendPresencePacket(presence); } - public void handleSelfPresence(final Presence presencePacket) { + public void handleSelfPresenceAvailable(final Presence presencePacket) { final MucUser mucUser = presencePacket.getExtension(MucUser.class); Preconditions.checkArgument( mucUser.getStatus().contains(MucUser.STATUS_CODE_SELF_PRESENCE)); - // TODO flag chat as joined LOGGER.info("Received self presence for {}", presencePacket.getFrom()); + final var database = getDatabase(); + database.runInTransaction( + () -> { + final ChatIdentifier chatIdentifier = + database.chatDao() + .get( + getAccount().id, + presencePacket.getFrom().asBareJid(), + ChatType.MUC); + if (chatIdentifier == null || chatIdentifier.archived) { + LOGGER.info( + "Available presence received for archived or non existent chat"); + return; + } + // TODO set status codes + database.chatDao() + .setMucState( + chatIdentifier.id, MucState.AVAILABLE, mucUser.getStatus()); + }); + } + + public void handleSelfPresenceUnavailable(final Presence presencePacket) { + final MucUser mucUser = presencePacket.getExtension(MucUser.class); + Preconditions.checkArgument( + mucUser.getStatus().contains(MucUser.STATUS_CODE_SELF_PRESENCE)); + final var database = getDatabase(); + database.runInTransaction( + () -> { + final ChatIdentifier chatIdentifier = + database.chatDao() + .get( + getAccount().id, + presencePacket.getFrom().asBareJid(), + ChatType.MUC); + if (chatIdentifier == null) { + LOGGER.error("Unavailable presence received for non existent chat"); + } else if (chatIdentifier.archived) { + database.chatDao().setMucState(chatIdentifier.id, null); + } else { + // TODO set status codes + database.chatDao().setMucState(chatIdentifier.id, MucState.UNAVAILABLE); + } + }); } public void handleErrorPresence(final Presence presencePacket) { LOGGER.info("Received error presence from {}", presencePacket.getFrom()); + final var database = getDatabase(); + database.runInTransaction( + () -> { + final ChatIdentifier chatIdentifier = + database.chatDao() + .get( + getAccount().id, + presencePacket.getFrom().asBareJid(), + ChatType.MUC); + if (chatIdentifier == null) { + // this is fine. error is simply not for a MUC + return; + } + final Error error = presencePacket.getError(); + final Condition condition = error == null ? null : error.getCondition(); + final String errorCondition = condition == null ? null : condition.getName(); + database.chatDao() + .setMucState( + chatIdentifier.id, MucState.ERROR_PRESENCE, errorCondition); + }); + } + + private class ExistingMucJoiner implements FutureCallback { + + private final MucWithNick chat; + + private ExistingMucJoiner(final MucWithNick chat) { + this.chat = chat; + } + + @Override + public void onSuccess(final InfoQuery result) { + enterExisting(chat, result); + } + + @Override + public void onFailure(@NonNull final Throwable throwable) { + final String errorCondition; + if (throwable instanceof IqErrorException) { + final var iqErrorException = (IqErrorException) throwable; + final Error error = iqErrorException.getError(); + final Condition condition = error == null ? null : error.getCondition(); + errorCondition = condition == null ? null : condition.getName(); + } else { + errorCondition = null; + } + getDatabase().chatDao().setMucState(chat.chatId, MucState.ERROR_IQ, errorCondition); + } } } diff --git a/app/src/main/java/im/conversations/android/xmpp/model/disco/info/InfoQuery.java b/app/src/main/java/im/conversations/android/xmpp/model/disco/info/InfoQuery.java index 8b8935da8..5c75f7211 100644 --- a/app/src/main/java/im/conversations/android/xmpp/model/disco/info/InfoQuery.java +++ b/app/src/main/java/im/conversations/android/xmpp/model/disco/info/InfoQuery.java @@ -1,7 +1,9 @@ package im.conversations.android.xmpp.model.disco.info; +import com.google.common.collect.Iterables; import im.conversations.android.annotation.XmlElement; import im.conversations.android.xmpp.model.Extension; +import java.util.Collection; @XmlElement(name = "query") public class InfoQuery extends Extension { @@ -17,4 +19,16 @@ public class InfoQuery extends Extension { public String getNode() { return this.getAttribute("node"); } + + public Collection getFeatures() { + return this.getExtensions(Feature.class); + } + + public boolean hasFeature(final String feature) { + return Iterables.any(getFeatures(), f -> feature.equals(f.getVar())); + } + + public Collection getIdentities() { + return this.getExtensions(Identity.class); + } } diff --git a/app/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java b/app/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java index 161d094c7..3a1a8f0c2 100644 --- a/app/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java +++ b/app/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java @@ -29,7 +29,11 @@ public class BindProcessor extends XmppConnection.Delegate implements Consumer { + database.chatDao().resetMucStates(); + database.presenceDao().deletePresences(account.id); + }); getManager(RosterManager.class).fetch(); diff --git a/app/src/main/java/im/conversations/android/xmpp/processor/PresenceProcessor.java b/app/src/main/java/im/conversations/android/xmpp/processor/PresenceProcessor.java index 54ee32a22..29e3527a9 100644 --- a/app/src/main/java/im/conversations/android/xmpp/processor/PresenceProcessor.java +++ b/app/src/main/java/im/conversations/android/xmpp/processor/PresenceProcessor.java @@ -73,11 +73,16 @@ public class PresenceProcessor extends XmppConnection.Delegate implements Consum occupantId, muc); + final var mucManager = getManager(MultiUserChatManager.class); if (muc != null && muc.getStatus().contains(MucUser.STATUS_CODE_SELF_PRESENCE)) { - getManager(MultiUserChatManager.class).handleSelfPresence(presencePacket); + if (type == null) { + mucManager.handleSelfPresenceAvailable(presencePacket); + } else if (type == PresenceType.UNAVAILABLE) { + mucManager.handleSelfPresenceUnavailable(presencePacket); + } } if (type == PresenceType.ERROR) { - getManager(MultiUserChatManager.class).handleErrorPresence(presencePacket); + mucManager.handleErrorPresence(presencePacket); } // TODO do this only for contacts?