Detailed changes
@@ -20,7 +20,6 @@ import eu.siacs.conversations.crypto.sasl.HashedTokenSha512;
import eu.siacs.conversations.crypto.sasl.SaslMechanism;
import eu.siacs.conversations.http.ServiceOutageStatus;
import eu.siacs.conversations.services.AvatarService;
-import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.Resolver;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.utils.XmppUri;
@@ -96,8 +95,6 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
protected boolean online = false;
private String rosterVersion;
private String displayName = null;
- private AxolotlService axolotlService = null;
- private PgpDecryptionService pgpDecryptionService = null;
private XmppConnection xmppConnection = null;
private long mEndGracePeriod = 0L;
private final Map<Jid, Bookmark> bookmarks = new HashMap<>();
@@ -233,11 +230,11 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
}
public boolean hasPendingPgpIntent(Conversation conversation) {
- return pgpDecryptionService != null && pgpDecryptionService.hasPendingIntent(conversation);
+ return getPgpDecryptionService().hasPendingIntent(conversation);
}
public boolean isPgpDecryptionServiceConnected() {
- return pgpDecryptionService != null && pgpDecryptionService.isConnected();
+ return getPgpDecryptionService().isConnected();
}
public boolean setShowErrorNotification(boolean newValue) {
@@ -285,11 +282,12 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
final Jid prev = this.jid != null ? this.jid.asBareJid() : null;
final boolean changed = prev == null || (next != null && !prev.equals(next.asBareJid()));
if (changed) {
- final AxolotlService oldAxolotlService = this.axolotlService;
+ final AxolotlService oldAxolotlService = xmppConnection.getAxolotlService();
+ // TODO check that changing JID and recreating the AxolotlService still works
if (oldAxolotlService != null) {
oldAxolotlService.destroy();
this.jid = next;
- this.axolotlService = oldAxolotlService.makeNew();
+ xmppConnection.setAxolotlService(oldAxolotlService.makeNew());
}
}
this.jid = next;
@@ -545,18 +543,11 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
}
public AxolotlService getAxolotlService() {
- return axolotlService;
- }
-
- public void initAccountServices(final XmppConnectionService context) {
- this.xmppConnection = new XmppConnection(this, context);
- this.axolotlService = new AxolotlService(this, context);
- this.pgpDecryptionService = new PgpDecryptionService(context);
- this.xmppConnection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
+ return this.xmppConnection.getAxolotlService();
}
public PgpDecryptionService getPgpDecryptionService() {
- return this.pgpDecryptionService;
+ return this.xmppConnection.getPgpDecryptionService();
}
public XmppConnection getXmppConnection() {
@@ -739,9 +730,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
private List<XmppUri.Fingerprint> getFingerprints() {
ArrayList<XmppUri.Fingerprint> fingerprints = new ArrayList<>();
- if (axolotlService == null) {
- return fingerprints;
- }
+ final var axolotlService = getAxolotlService();
fingerprints.add(
new XmppUri.Fingerprint(
XmppUri.FingerprintType.OMEMO,
@@ -811,6 +800,10 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
return false;
}
+ public void setXmppConnection(final XmppConnection connection) {
+ this.xmppConnection = connection;
+ }
+
public enum State {
DISABLED(false, false),
LOGGED_OUT(false, false),
@@ -12,7 +12,6 @@ import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.Jid;
import im.conversations.android.xmpp.model.bookmark.Storage;
import im.conversations.android.xmpp.model.bookmark2.Conference;
-import im.conversations.android.xmpp.model.pubsub.PubSub;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashMap;
@@ -41,6 +40,7 @@ public class Bookmark extends Element implements ListItem {
public static Map<Jid, Bookmark> parseFromStorage(
final Storage storage, final Account account) {
+ // TODO refactor to use extensions. get rid of the 'old' handling
if (storage == null) {
return Collections.emptyMap();
}
@@ -61,26 +61,6 @@ public class Bookmark extends Element implements ListItem {
return bookmarks;
}
- public static Map<Jid, Bookmark> parseFromPubSub(final PubSub pubSub, final Account account) {
- if (pubSub == null) {
- return Collections.emptyMap();
- }
- final var items = pubSub.getItems();
- if (items == null || !Namespace.BOOKMARKS2.equals(items.getNode())) {
- return Collections.emptyMap();
- }
- final Map<Jid, Bookmark> bookmarks = new HashMap<>();
- for (final var item : items.getItemMap(Conference.class).entrySet()) {
- final Bookmark bookmark =
- Bookmark.parseFromItem(item.getKey(), item.getValue(), account);
- if (bookmark == null) {
- continue;
- }
- bookmarks.put(bookmark.jid, bookmark);
- }
- return bookmarks;
- }
-
public static Bookmark parse(Element element, Account account) {
Bookmark bookmark = new Bookmark(account);
bookmark.setAttributes(element.getAttributes());
@@ -103,6 +83,9 @@ public class Bookmark extends Element implements ListItem {
if (bookmark.jid == null) {
return null;
}
+
+ // TODO use proper API
+
bookmark.setBookmarkName(conference.getAttribute("name"));
bookmark.setAutojoin(conference.getAttributeAsBoolean("autojoin"));
bookmark.setNick(conference.findChildContent("nick"));
@@ -37,6 +37,7 @@ import eu.siacs.conversations.xmpp.XmppConnection;
import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
+import eu.siacs.conversations.xmpp.manager.PubSubManager;
import eu.siacs.conversations.xmpp.manager.RosterManager;
import eu.siacs.conversations.xmpp.pep.Avatar;
import im.conversations.android.xmpp.model.Extension;
@@ -321,7 +322,7 @@ public class MessageParser extends AbstractParser
}
final var storage = items.getFirstItem(Storage.class);
final Map<Jid, Bookmark> bookmarks = Bookmark.parseFromStorage(storage, account);
- mXmppConnectionService.processBookmarksInitial(account, bookmarks, true);
+ // mXmppConnectionService.processBookmarksInitial(account, bookmarks, true);
Log.d(
Config.LOGTAG,
account.getJid().asBareJid() + ": processing bookmark PEP event");
@@ -351,7 +352,7 @@ public class MessageParser extends AbstractParser
Log.d(
Config.LOGTAG,
account.getJid().asBareJid() + ": deleted bookmark for " + id);
- mXmppConnectionService.processDeletedBookmark(account, id);
+ // mXmppConnectionService.processDeletedBookmark(account, id);
mXmppConnectionService.updateConversationUi();
}
}
@@ -398,7 +399,7 @@ public class MessageParser extends AbstractParser
private void deleteAllBookmarks(final Account account) {
final var previous = account.getBookmarkedJids();
account.setBookmarks(Collections.emptyMap());
- mXmppConnectionService.processDeletedBookmarks(account, previous);
+ // mXmppConnectionService.processDeletedBookmarks(account, previous);
}
private void setNick(final Account account, final Jid user, final String nick) {
@@ -1410,6 +1411,9 @@ public class MessageParser extends AbstractParser
// end no body
}
+ if (original.hasExtension(Event.class)) {
+ getManager(PubSubManager.class).handleEvent(original);
+ }
final var event = original.getExtension(Event.class);
if (event != null && Jid.Invalid.hasValidFrom(original) && original.getFrom().isBareJid()) {
final var action = event.getAction();
@@ -50,6 +50,7 @@ public class PresenceParser extends AbstractParser
? null
: mXmppConnectionService.find(account, packet.getFrom().asBareJid());
if (conversation == null) {
+ Log.d(Config.LOGTAG, "conversation not found for parsing conference presence");
return;
}
final MucOptions mucOptions = conversation.getMucOptions();
@@ -490,6 +491,7 @@ public class PresenceParser extends AbstractParser
@Override
public void accept(final im.conversations.android.xmpp.model.stanza.Presence packet) {
+ // Log.d(Config.LOGTAG,"<--"+packet);
if (packet.hasChild("x", Namespace.MUC_USER)) {
this.parseConferencePresence(packet);
} else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) {
@@ -121,7 +121,6 @@ import eu.siacs.conversations.utils.XmppUri;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.LocalizedContent;
import eu.siacs.conversations.xml.Namespace;
-import eu.siacs.conversations.xmpp.IqErrorResponseException;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.OnContactStatusChanged;
import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
@@ -144,13 +143,12 @@ import eu.siacs.conversations.xmpp.manager.RosterManager;
import eu.siacs.conversations.xmpp.pep.Avatar;
import eu.siacs.conversations.xmpp.pep.PublishOptions;
import im.conversations.android.xmpp.Entity;
+import im.conversations.android.xmpp.IqErrorException;
import im.conversations.android.xmpp.model.avatar.Metadata;
-import im.conversations.android.xmpp.model.bookmark.Storage;
import im.conversations.android.xmpp.model.disco.info.InfoQuery;
import im.conversations.android.xmpp.model.mds.Displayed;
import im.conversations.android.xmpp.model.pubsub.PubSub;
import im.conversations.android.xmpp.model.stanza.Iq;
-import im.conversations.android.xmpp.model.storage.PrivateStorage;
import im.conversations.android.xmpp.model.up.Push;
import java.io.File;
import java.security.Security;
@@ -366,6 +364,7 @@ public class XmppConnectionService extends Service {
@Override
public void onStatusChanged(final Account account) {
+ Log.d(Config.LOGTAG, "begin onStatusChanged()");
final var status = account.getStatus();
if (ServiceOutageStatus.isPossibleOutage(status)) {
fetchServiceOutageStatus(account);
@@ -499,6 +498,7 @@ public class XmppConnectionService extends Service {
}
}
getNotificationService().updateErrorNotification();
+ Log.d(Config.LOGTAG, "end onStatusChanged()");
}
};
@@ -1843,15 +1843,13 @@ public class XmppConnectionService extends Service {
public XmppConnection createConnection(final Account account) {
final XmppConnection connection = new XmppConnection(account, this);
+ // TODO move status listener into final variable in XmppConnection
connection.setOnStatusChangedListener(this.statusListener);
connection.setOnJinglePacketReceivedListener((mJingleConnectionManager::deliverPacket));
+ // TODO move MessageAck into final Processor into XmppConnection
connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener);
connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService);
connection.addOnAdvancedStreamFeaturesAvailableListener(this.mAvatarService);
- AxolotlService axolotlService = account.getAxolotlService();
- if (axolotlService != null) {
- connection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
- }
return connection;
}
@@ -2137,44 +2135,6 @@ public class XmppConnectionService extends Service {
});
}
- public void fetchBookmarks(final Account account) {
- final Iq iqPacket = new Iq(Iq.Type.GET);
- iqPacket.addExtension(new PrivateStorage()).addExtension(new Storage());
- final Consumer<Iq> callback =
- (response) -> {
- if (response.getType() == Iq.Type.RESULT) {
- final var privateStorage = response.getExtension(PrivateStorage.class);
- if (privateStorage == null) {
- return;
- }
- final var bookmarkStorage = privateStorage.getExtension(Storage.class);
- Map<Jid, Bookmark> bookmarks =
- Bookmark.parseFromStorage(bookmarkStorage, account);
- processBookmarksInitial(account, bookmarks, false);
- } else {
- Log.d(
- Config.LOGTAG,
- account.getJid().asBareJid() + ": could not fetch bookmarks");
- }
- };
- sendIqPacket(account, iqPacket, callback);
- }
-
- public void fetchBookmarks2(final Account account) {
- final Iq retrieve = mIqGenerator.retrieveBookmarks();
- sendIqPacket(
- account,
- retrieve,
- (response) -> {
- if (response.getType() == Iq.Type.RESULT) {
- final var pubsub = response.getExtension(PubSub.class);
- final Map<Jid, Bookmark> bookmarks =
- Bookmark.parseFromPubSub(pubsub, account);
- processBookmarksInitial(account, bookmarks, true);
- }
- });
- }
-
public void fetchMessageDisplayedSynchronization(final Account account) {
Log.d(Config.LOGTAG, account.getJid() + ": retrieve mds");
final var retrieve = mIqGenerator.retrieveMds();
@@ -2252,43 +2212,7 @@ public class XmppConnectionService extends Service {
return true;
}
- public void processBookmarksInitial(
- final Account account, final Map<Jid, Bookmark> bookmarks, final boolean pep) {
- final Set<Jid> previousBookmarks = account.getBookmarkedJids();
- for (final Bookmark bookmark : bookmarks.values()) {
- previousBookmarks.remove(bookmark.getJid().asBareJid());
- processModifiedBookmark(bookmark, pep);
- }
- if (pep) {
- processDeletedBookmarks(account, previousBookmarks);
- }
- account.setBookmarks(bookmarks);
- }
-
- public void processDeletedBookmarks(final Account account, final Collection<Jid> bookmarks) {
- Log.d(
- Config.LOGTAG,
- account.getJid().asBareJid()
- + ": "
- + bookmarks.size()
- + " bookmarks have been removed");
- for (final Jid bookmark : bookmarks) {
- processDeletedBookmark(account, bookmark);
- }
- }
-
- public void processDeletedBookmark(final Account account, final Jid jid) {
- final Conversation conversation = find(account, jid);
- if (conversation == null) {
- return;
- }
- Log.d(
- Config.LOGTAG,
- account.getJid().asBareJid() + ": archiving MUC " + jid + " after PEP update");
- archiveConversation(conversation, false);
- }
-
- private void processModifiedBookmark(final Bookmark bookmark, final boolean pep) {
+ public void processModifiedBookmark(final Bookmark bookmark, final boolean pep) {
final Account account = bookmark.getAccount();
Conversation conversation = find(bookmark);
if (conversation != null) {
@@ -2407,6 +2331,7 @@ public class XmppConnectionService extends Service {
private void pushBookmarksPrivateXml(Account account) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": pushing bookmarks via private xml");
final Iq iqPacket = new Iq(Iq.Type.SET);
+ // TODO we have extensions for that
Element query = iqPacket.query("jabber:iq:private");
Element storage = query.addChild("storage", "storage:bookmarks");
for (final Bookmark bookmark : account.getBookmarks()) {
@@ -2531,8 +2456,7 @@ public class XmppConnectionService extends Service {
}
Log.d(Config.LOGTAG, "restoring roster...");
for (final Account account : accounts) {
- account.initAccountServices(
- this); // roster needs to be loaded at this stage
+ account.setXmppConnection(createConnection(account));
account.getXmppConnection().getManager(RosterManager.class).restore();
}
getBitmapCache().evictAll();
@@ -2989,7 +2913,7 @@ public class XmppConnectionService extends Service {
archiveConversation(conversation, true);
}
- private void archiveConversation(
+ public void archiveConversation(
Conversation conversation, final boolean maySynchronizeWithBookmarks) {
getNotificationService().clear(conversation);
conversation.setStatus(Conversation.STATUS_ARCHIVED);
@@ -3034,7 +2958,7 @@ public class XmppConnectionService extends Service {
}
public void createAccount(final Account account) {
- account.initAccountServices(this);
+ account.setXmppConnection(createConnection(account));
databaseBackend.createAccount(account);
if (CallIntegration.hasSystemFeature(this)) {
CallIntegrationConnectionService.togglePhoneAccountAsync(this, account);
@@ -4450,8 +4374,7 @@ public class XmppConnectionService extends Service {
account.getJid().asBareJid()
+ ": received timeout waiting for conference"
+ " configuration fetch");
- } else if (throwable
- instanceof IqErrorResponseException errorResponseException) {
+ } else if (throwable instanceof IqErrorException errorResponseException) {
if (callback != null) {
callback.onFetchFailed(
conversation,
@@ -0,0 +1,35 @@
+package im.conversations.android.xmpp;
+
+import com.google.common.base.Strings;
+import im.conversations.android.xmpp.model.error.Condition;
+import im.conversations.android.xmpp.model.error.Error;
+import im.conversations.android.xmpp.model.stanza.Iq;
+
+public class IqErrorException extends Exception {
+
+ private final Iq response;
+
+ public IqErrorException(Iq response) {
+ super(getErrorText(response));
+ this.response = response;
+ }
+
+ public Error getError() {
+ return this.response.getError();
+ }
+
+ private static String getErrorText(final Iq response) {
+ final var error = response.getError();
+ final var text = error == null ? null : error.getText();
+ final var textContent = text == null ? null : text.getContent();
+ if (Strings.isNullOrEmpty(textContent)) {
+ final var condition = error == null ? null : error.getExtension(Condition.class);
+ return condition == null ? null : condition.getName();
+ }
+ return textContent;
+ }
+
+ public Iq getResponse() {
+ return this.response;
+ }
+}
@@ -1,33 +0,0 @@
-package eu.siacs.conversations.xmpp;
-
-import im.conversations.android.xmpp.model.stanza.Iq;
-
-public class IqErrorResponseException extends Exception {
-
- private final Iq response;
-
- public IqErrorResponseException(final Iq response) {
- super(message(response));
- this.response = response;
- }
-
- public Iq getResponse() {
- return this.response;
- }
-
- public static String message(final Iq iq) {
- final var error = iq.getError();
- if (error == null) {
- return "missing error element in response";
- }
- final var text = error.getTextAsString();
- if (text != null) {
- return text;
- }
- final var condition = error.getCondition();
- if (condition != null) {
- return condition.getName();
- }
- return "no condition attached to error";
- }
-}
@@ -4,12 +4,20 @@ import com.google.common.collect.ClassToInstanceMap;
import com.google.common.collect.ImmutableClassToInstanceMap;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xmpp.manager.AbstractManager;
+import eu.siacs.conversations.xmpp.manager.AvatarManager;
+import eu.siacs.conversations.xmpp.manager.AxolotlManager;
import eu.siacs.conversations.xmpp.manager.BlockingManager;
+import eu.siacs.conversations.xmpp.manager.BookmarkManager;
import eu.siacs.conversations.xmpp.manager.CarbonsManager;
import eu.siacs.conversations.xmpp.manager.DiscoManager;
import eu.siacs.conversations.xmpp.manager.EntityTimeManager;
+import eu.siacs.conversations.xmpp.manager.LegacyBookmarkManager;
+import eu.siacs.conversations.xmpp.manager.NickManager;
+import eu.siacs.conversations.xmpp.manager.PepManager;
import eu.siacs.conversations.xmpp.manager.PingManager;
import eu.siacs.conversations.xmpp.manager.PresenceManager;
+import eu.siacs.conversations.xmpp.manager.PrivateStorageManager;
+import eu.siacs.conversations.xmpp.manager.PubSubManager;
import eu.siacs.conversations.xmpp.manager.RosterManager;
import eu.siacs.conversations.xmpp.manager.UnifiedPushManager;
@@ -22,12 +30,20 @@ public class Managers {
public static ClassToInstanceMap<AbstractManager> get(
final XmppConnectionService context, final XmppConnection connection) {
return new ImmutableClassToInstanceMap.Builder<AbstractManager>()
+ .put(AvatarManager.class, new AvatarManager(context, connection))
+ .put(AxolotlManager.class, new AxolotlManager(context, connection))
.put(BlockingManager.class, new BlockingManager(context, connection))
+ .put(BookmarkManager.class, new BookmarkManager(context, connection))
.put(CarbonsManager.class, new CarbonsManager(context, connection))
.put(DiscoManager.class, new DiscoManager(context, connection))
.put(EntityTimeManager.class, new EntityTimeManager(context, connection))
+ .put(LegacyBookmarkManager.class, new LegacyBookmarkManager(context, connection))
+ .put(NickManager.class, new NickManager(context, connection))
+ .put(PepManager.class, new PepManager(context, connection))
.put(PingManager.class, new PingManager(context, connection))
.put(PresenceManager.class, new PresenceManager(context, connection))
+ .put(PrivateStorageManager.class, new PrivateStorageManager(context, connection))
+ .put(PubSubManager.class, new PubSubManager(context, connection))
.put(RosterManager.class, new RosterManager(context, connection))
.put(UnifiedPushManager.class, new UnifiedPushManager(context, connection))
.build();
@@ -0,0 +1,16 @@
+package im.conversations.android.xmpp;
+
+import im.conversations.android.xmpp.model.pubsub.error.PubSubError;
+import im.conversations.android.xmpp.model.stanza.Iq;
+
+public class PreconditionNotMetException extends PubSubErrorException {
+
+ public PreconditionNotMetException(final Iq response) {
+ super(response);
+ if (this.pubSubError instanceof PubSubError.PreconditionNotMet) {
+ return;
+ }
+ throw new AssertionError(
+ "This exception should only be constructed for PreconditionNotMet errors");
+ }
+}
@@ -0,0 +1,19 @@
+package im.conversations.android.xmpp;
+
+import im.conversations.android.xmpp.model.pubsub.error.PubSubError;
+import im.conversations.android.xmpp.model.stanza.Iq;
+
+public class PubSubErrorException extends IqErrorException {
+
+ protected final PubSubError pubSubError;
+
+ public PubSubErrorException(Iq response) {
+ super(response);
+ final var error = response.getError();
+ final var pubSubError = error == null ? null : error.getExtension(PubSubError.class);
+ if (pubSubError == null) {
+ throw new AssertionError("This exception should only be constructed for PubSubErrors");
+ }
+ this.pubSubError = pubSubError;
+ }
+}
@@ -33,6 +33,7 @@ import eu.siacs.conversations.AppSettings;
import eu.siacs.conversations.BuildConfig;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
+import eu.siacs.conversations.crypto.PgpDecryptionService;
import eu.siacs.conversations.crypto.XmppDomainVerifier;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.crypto.sasl.ChannelBinding;
@@ -77,6 +78,7 @@ import eu.siacs.conversations.xmpp.manager.CarbonsManager;
import eu.siacs.conversations.xmpp.manager.DiscoManager;
import eu.siacs.conversations.xmpp.manager.PingManager;
import im.conversations.android.xmpp.Entity;
+import im.conversations.android.xmpp.IqErrorException;
import im.conversations.android.xmpp.model.AuthenticationFailure;
import im.conversations.android.xmpp.model.AuthenticationRequest;
import im.conversations.android.xmpp.model.AuthenticationStreamFeature;
@@ -200,6 +202,8 @@ public class XmppConnection implements Runnable {
private final Consumer<Presence> presenceListener;
private final Consumer<Iq> unregisteredIqListener;
private final Consumer<im.conversations.android.xmpp.model.stanza.Message> messageListener;
+ private AxolotlService axolotlService;
+ private final PgpDecryptionService pgpDecryptionService;
private OnStatusChanged statusListener = null;
private final Runnable bindListener;
private OnMessageAcknowledged acknowledgedListener = null;
@@ -226,6 +230,8 @@ public class XmppConnection implements Runnable {
this.messageListener = new MessageParser(service, this);
this.bindListener = new BindProcessor(service, this);
this.managers = Managers.get(service, this);
+ this.setAxolotlService(new AxolotlService(account, service));
+ this.pgpDecryptionService = new PgpDecryptionService(service);
}
private static void fixResource(final Context context, final Account account) {
@@ -277,7 +283,13 @@ public class XmppConnection implements Runnable {
}
}
if (statusListener != null) {
- statusListener.onStatusChanged(account);
+ try {
+ statusListener.onStatusChanged(account);
+ } catch (final Exception e) {
+ Log.d(Config.LOGTAG, "error executing shit", e);
+ }
+ } else {
+ Log.d(Config.LOGTAG, "status changed listener was null");
}
}
@@ -2520,7 +2532,7 @@ public class XmppConnection implements Runnable {
switch (type) {
case RESULT -> settable.set(response);
case TIMEOUT -> settable.setException(new TimeoutException());
- default -> settable.setException(new IqErrorResponseException(response));
+ default -> settable.setException(new IqErrorException(response));
}
});
return settable;
@@ -2893,6 +2905,29 @@ public class XmppConnection implements Runnable {
|| from.equals(account);
}
+ public boolean fromAccount(final Stanza stanza) {
+ final var account = getAccount().getJid();
+ final Jid from = stanza.getFrom();
+ return from == null || from.asBareJid().equals(account.asBareJid());
+ }
+
+ public AxolotlService getAxolotlService() {
+ return this.axolotlService;
+ }
+
+ public PgpDecryptionService getPgpDecryptionService() {
+ return this.pgpDecryptionService;
+ }
+
+ public void setAxolotlService(AxolotlService axolotlService) {
+ final var current = this.axolotlService;
+ if (current != null) {
+ this.advancedStreamFeaturesLoadedListeners.remove(current);
+ }
+ this.axolotlService = axolotlService;
+ this.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
+ }
+
private class MyKeyManager implements X509KeyManager {
@Override
public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) {
@@ -0,0 +1,62 @@
+package eu.siacs.conversations.xmpp.manager;
+
+import android.util.Log;
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Bookmark;
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.xmpp.Jid;
+import eu.siacs.conversations.xmpp.XmppConnection;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+public class AbstractBookmarkManager extends AbstractManager {
+
+ private final XmppConnectionService service;
+
+ protected AbstractBookmarkManager(
+ final XmppConnectionService service, final XmppConnection connection) {
+ super(service, connection);
+ this.service = service;
+ }
+
+ // TODO rename to setBookmarks?
+ public void processBookmarksInitial(final Map<Jid, Bookmark> bookmarks, final boolean pep) {
+ final var account = getAccount();
+ // TODO we can internalize this getBookmarkedJid
+ final Set<Jid> previousBookmarks = account.getBookmarkedJids();
+ for (final Bookmark bookmark : bookmarks.values()) {
+ previousBookmarks.remove(bookmark.getJid().asBareJid());
+ service.processModifiedBookmark(bookmark, pep);
+ }
+ if (pep) {
+ this.processDeletedBookmarks(account, previousBookmarks);
+ }
+ account.setBookmarks(bookmarks);
+ }
+
+ public void processDeletedBookmarks(final Account account, final Collection<Jid> bookmarks) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": "
+ + bookmarks.size()
+ + " bookmarks have been removed");
+ for (final Jid bookmark : bookmarks) {
+ processDeletedBookmark(account, bookmark);
+ }
+ }
+
+ public void processDeletedBookmark(final Account account, final Jid jid) {
+ final Conversation conversation = service.find(account, jid);
+ if (conversation == null) {
+ return;
+ }
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid() + ": archiving MUC " + jid + " after PEP update");
+ this.service.archiveConversation(conversation, false);
+ }
+}
@@ -0,0 +1,15 @@
+package eu.siacs.conversations.xmpp.manager;
+
+import android.content.Context;
+import eu.siacs.conversations.xmpp.Jid;
+import eu.siacs.conversations.xmpp.XmppConnection;
+import im.conversations.android.xmpp.model.pubsub.Items;
+
+public class AvatarManager extends AbstractManager {
+
+ public AvatarManager(Context context, XmppConnection connection) {
+ super(context, connection);
+ }
+
+ public void handleItems(Jid from, final Items items) {}
+}
@@ -0,0 +1,15 @@
+package eu.siacs.conversations.xmpp.manager;
+
+import android.content.Context;
+import eu.siacs.conversations.xmpp.Jid;
+import eu.siacs.conversations.xmpp.XmppConnection;
+import im.conversations.android.xmpp.model.pubsub.Items;
+
+public class AxolotlManager extends AbstractManager {
+
+ public AxolotlManager(Context context, XmppConnection connection) {
+ super(context, connection);
+ }
+
+ public void handleItems(Jid from, final Items items) {}
+}
@@ -0,0 +1,95 @@
+package eu.siacs.conversations.xmpp.manager;
+
+import android.util.Log;
+import androidx.annotation.NonNull;
+import com.google.common.collect.ImmutableMap;
+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 eu.siacs.conversations.Config;
+import eu.siacs.conversations.entities.Bookmark;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.xml.Namespace;
+import eu.siacs.conversations.xmpp.Jid;
+import eu.siacs.conversations.xmpp.XmppConnection;
+import im.conversations.android.xmpp.NodeConfiguration;
+import im.conversations.android.xmpp.model.bookmark2.Conference;
+import im.conversations.android.xmpp.model.bookmark2.Nick;
+import im.conversations.android.xmpp.model.pubsub.Items;
+import java.util.Map;
+
+public class BookmarkManager extends AbstractBookmarkManager {
+
+ public BookmarkManager(final XmppConnectionService service, XmppConnection connection) {
+ super(service, connection);
+ }
+
+ public void fetch() {
+ final var future = getManager(PepManager.class).fetchItems(Conference.class);
+ Futures.addCallback(
+ future,
+ new FutureCallback<>() {
+ @Override
+ public void onSuccess(final Map<String, Conference> bookmarks) {
+ final var builder = new ImmutableMap.Builder<Jid, Bookmark>();
+ for (final var entry : bookmarks.entrySet()) {
+ final Bookmark bookmark =
+ Bookmark.parseFromItem(
+ entry.getKey(), entry.getValue(), getAccount());
+ if (bookmark == null) {
+ continue;
+ }
+ builder.put(bookmark.getJid(), bookmark);
+ }
+ processBookmarksInitial(builder.buildKeepingLast(), true);
+ }
+
+ @Override
+ public void onFailure(@NonNull final Throwable throwable) {
+ Log.d(Config.LOGTAG, "Could not fetch bookmarks", throwable);
+ }
+ },
+ MoreExecutors.directExecutor());
+ }
+
+ public void handleItems(final Items items) {
+ final var retractions = items.getRetractions();
+ final var itemMap = items.getItemMap(Conference.class);
+ if (!retractions.isEmpty()) {
+ // deleteItems(retractions);
+ }
+ if (!itemMap.isEmpty()) {
+ // updateItems(itemMap);
+ }
+ }
+
+ public ListenableFuture<Void> publishBookmark(final Jid address, final boolean autoJoin) {
+ return publishBookmark(address, autoJoin, null);
+ }
+
+ public ListenableFuture<Void> publishBookmark(
+ final Jid address, final boolean autoJoin, final String nick) {
+ final var itemId = address.toString();
+ final var conference = new Conference();
+ conference.setAutoJoin(autoJoin);
+ if (nick != null) {
+ conference.addExtension(new Nick()).setContent(nick);
+ }
+ return Futures.transform(
+ getManager(PepManager.class)
+ .publish(conference, itemId, NodeConfiguration.WHITELIST_MAX_ITEMS),
+ result -> null,
+ MoreExecutors.directExecutor());
+ }
+
+ public ListenableFuture<Void> retractBookmark(final Jid address) {
+ final var itemId = address.toString();
+ return Futures.transform(
+ getManager(PepManager.class).retract(itemId, Namespace.BOOKMARKS2),
+ result -> null,
+ MoreExecutors.directExecutor());
+ }
+
+ public void deleteAllItems() {}
+}
@@ -0,0 +1,15 @@
+package eu.siacs.conversations.xmpp.manager;
+
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.xmpp.XmppConnection;
+import im.conversations.android.xmpp.model.pubsub.Items;
+
+public class LegacyBookmarkManager extends AbstractBookmarkManager {
+
+ public LegacyBookmarkManager(
+ final XmppConnectionService service, final XmppConnection connection) {
+ super(service, connection);
+ }
+
+ public void handleItems(final Items items) {}
+}
@@ -0,0 +1,31 @@
+package eu.siacs.conversations.xmpp.manager;
+
+import android.content.Context;
+import com.google.common.base.Strings;
+import com.google.common.util.concurrent.ListenableFuture;
+import eu.siacs.conversations.xmpp.Jid;
+import eu.siacs.conversations.xmpp.XmppConnection;
+import im.conversations.android.xmpp.NodeConfiguration;
+import im.conversations.android.xmpp.model.nick.Nick;
+import im.conversations.android.xmpp.model.pubsub.Items;
+
+public class NickManager extends AbstractManager {
+
+ public NickManager(Context context, XmppConnection connection) {
+ super(context, connection);
+ }
+
+ public void handleItems(final Jid from, Items items) {
+ final var item = items.getFirstItem(Nick.class);
+ final var nick = item == null ? null : item.getContent();
+ if (from == null || Strings.isNullOrEmpty(nick)) {
+ return;
+ }
+ }
+
+ public ListenableFuture<Void> publishNick(final String name) {
+ final Nick nick = new Nick();
+ nick.setContent(name);
+ return getManager(PepManager.class).publishSingleton(nick, NodeConfiguration.PRESENCE);
+ }
+}
@@ -0,0 +1,53 @@
+package eu.siacs.conversations.xmpp.manager;
+
+import android.content.Context;
+import com.google.common.util.concurrent.ListenableFuture;
+import eu.siacs.conversations.xmpp.Jid;
+import eu.siacs.conversations.xmpp.XmppConnection;
+import im.conversations.android.xmpp.NodeConfiguration;
+import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.stanza.Iq;
+import java.util.Map;
+
+public class PepManager extends AbstractManager {
+
+ public PepManager(Context context, XmppConnection connection) {
+ super(context, connection);
+ }
+
+ public <T extends Extension> ListenableFuture<Map<String, T>> fetchItems(final Class<T> clazz) {
+ return pubSubManager().fetchItems(pepService(), clazz);
+ }
+
+ public <T extends Extension> ListenableFuture<T> fetchMostRecentItem(
+ final String node, final Class<T> clazz) {
+ return pubSubManager().fetchMostRecentItem(pepService(), node, clazz);
+ }
+
+ public ListenableFuture<Void> publish(
+ Extension item, final String itemId, final NodeConfiguration nodeConfiguration) {
+ return pubSubManager().publish(pepService(), item, itemId, nodeConfiguration);
+ }
+
+ public ListenableFuture<Void> publishSingleton(
+ Extension item, final String node, final NodeConfiguration nodeConfiguration) {
+ return pubSubManager().publishSingleton(pepService(), item, node, nodeConfiguration);
+ }
+
+ public ListenableFuture<Void> publishSingleton(
+ final Extension item, final NodeConfiguration nodeConfiguration) {
+ return pubSubManager().publishSingleton(pepService(), item, nodeConfiguration);
+ }
+
+ public ListenableFuture<Iq> retract(final String itemId, final String node) {
+ return pubSubManager().retract(pepService(), itemId, node);
+ }
+
+ private PubSubManager pubSubManager() {
+ return getManager(PubSubManager.class);
+ }
+
+ private Jid pepService() {
+ return connection.getAccount().getJid().asBareJid();
+ }
+}
@@ -0,0 +1,55 @@
+package eu.siacs.conversations.xmpp.manager;
+
+import android.util.Log;
+import androidx.annotation.NonNull;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.MoreExecutors;
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.entities.Bookmark;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.xmpp.Jid;
+import eu.siacs.conversations.xmpp.XmppConnection;
+import im.conversations.android.xmpp.model.bookmark.Storage;
+import im.conversations.android.xmpp.model.stanza.Iq;
+import im.conversations.android.xmpp.model.storage.PrivateStorage;
+import java.util.Map;
+
+public class PrivateStorageManager extends AbstractBookmarkManager {
+
+ public PrivateStorageManager(final XmppConnectionService service, XmppConnection connection) {
+ super(service, connection);
+ }
+
+ public void fetchBookmarks() {
+ final var iq = new Iq(Iq.Type.GET);
+ final var privateStorage = iq.addExtension(new PrivateStorage());
+ privateStorage.addExtension(new Storage());
+ final var future = this.connection.sendIqPacket(iq);
+ Futures.addCallback(
+ future,
+ new FutureCallback<Iq>() {
+ @Override
+ public void onSuccess(Iq result) {
+ final var privateStorage = result.getExtension(PrivateStorage.class);
+ if (privateStorage == null) {
+ return;
+ }
+ final var bookmarkStorage = privateStorage.getExtension(Storage.class);
+ final Map<Jid, Bookmark> bookmarks =
+ Bookmark.parseFromStorage(bookmarkStorage, getAccount());
+ processBookmarksInitial(bookmarks, false);
+ }
+
+ @Override
+ public void onFailure(@NonNull Throwable t) {
+ Log.d(
+ Config.LOGTAG,
+ getAccount().getJid().asBareJid()
+ + ": could not fetch bookmark from private storage",
+ t);
+ }
+ },
+ MoreExecutors.directExecutor());
+ }
+}
@@ -0,0 +1,349 @@
+package eu.siacs.conversations.xmpp.manager;
+
+import android.content.Context;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import com.google.common.util.concurrent.AsyncFunction;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.xml.Namespace;
+import eu.siacs.conversations.xmpp.Jid;
+import eu.siacs.conversations.xmpp.XmppConnection;
+import im.conversations.android.xmpp.ExtensionFactory;
+import im.conversations.android.xmpp.IqErrorException;
+import im.conversations.android.xmpp.NodeConfiguration;
+import im.conversations.android.xmpp.PreconditionNotMetException;
+import im.conversations.android.xmpp.PubSubErrorException;
+import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.data.Data;
+import im.conversations.android.xmpp.model.pubsub.Items;
+import im.conversations.android.xmpp.model.pubsub.PubSub;
+import im.conversations.android.xmpp.model.pubsub.Publish;
+import im.conversations.android.xmpp.model.pubsub.PublishOptions;
+import im.conversations.android.xmpp.model.pubsub.Retract;
+import im.conversations.android.xmpp.model.pubsub.error.PubSubError;
+import im.conversations.android.xmpp.model.pubsub.event.Delete;
+import im.conversations.android.xmpp.model.pubsub.event.Event;
+import im.conversations.android.xmpp.model.pubsub.event.Purge;
+import im.conversations.android.xmpp.model.pubsub.owner.Configure;
+import im.conversations.android.xmpp.model.pubsub.owner.PubSubOwner;
+import im.conversations.android.xmpp.model.stanza.Iq;
+import im.conversations.android.xmpp.model.stanza.Message;
+import java.util.Map;
+
+public class PubSubManager extends AbstractManager {
+
+ private static final String SINGLETON_ITEM_ID = "current";
+
+ public PubSubManager(Context context, XmppConnection connection) {
+ super(context, connection);
+ }
+
+ public void handleEvent(final Message message) {
+ final var event = message.getExtension(Event.class);
+ final var action = event.getAction();
+ final var from = message.getFrom();
+
+ if (from instanceof Jid.Invalid) {
+ Log.d(
+ Config.LOGTAG,
+ getAccount().getJid().asBareJid() + ": ignoring event from invalid jid");
+ return;
+ }
+
+ if (action instanceof Purge purge) {
+ // purge is a deletion of all items in a node
+ handlePurge(message, purge);
+ } else if (action instanceof Items items) {
+ // the items wrapper contains, new and updated items as well as retractions which are
+ // deletions of individual items in a node
+ handleItems(message, items);
+ } else if (action instanceof Delete delete) {
+ // delete is the deletion of the node itself
+ handleDelete(message, delete);
+ }
+ }
+
+ public <T extends Extension> ListenableFuture<Map<String, T>> fetchItems(
+ final Jid address, final Class<T> clazz) {
+ final var id = ExtensionFactory.id(clazz);
+ if (id == null) {
+ return Futures.immediateFailedFuture(
+ new IllegalArgumentException(
+ String.format("%s is not a registered extension", clazz.getName())));
+ }
+ return fetchItems(address, id.namespace, clazz);
+ }
+
+ public <T extends Extension> ListenableFuture<Map<String, T>> fetchItems(
+ final Jid address, final String node, final Class<T> clazz) {
+ final Iq request = new Iq(Iq.Type.GET);
+ request.setTo(address);
+ final var pubSub = request.addExtension(new PubSub());
+ final var itemsWrapper = pubSub.addExtension(new PubSub.ItemsWrapper());
+ itemsWrapper.setNode(node);
+ return Futures.transform(
+ connection.sendIqPacket(request),
+ response -> {
+ final var pubSubResponse = response.getExtension(PubSub.class);
+ if (pubSubResponse == null) {
+ throw new IllegalStateException();
+ }
+ final var items = pubSubResponse.getItems();
+ if (items == null) {
+ throw new IllegalStateException();
+ }
+ return items.getItemMap(clazz);
+ },
+ MoreExecutors.directExecutor());
+ }
+
+ public <T extends Extension> ListenableFuture<T> fetchItem(
+ final Jid address, final String itemId, final Class<T> clazz) {
+ final var id = ExtensionFactory.id(clazz);
+ if (id == null) {
+ return Futures.immediateFailedFuture(
+ new IllegalArgumentException(
+ String.format("%s is not a registered extension", clazz.getName())));
+ }
+ return fetchItem(address, id.namespace, itemId, clazz);
+ }
+
+ public <T extends Extension> ListenableFuture<T> fetchItem(
+ final Jid address, final String node, final String itemId, final Class<T> clazz) {
+ final Iq request = new Iq(Iq.Type.GET);
+ request.setTo(address);
+ final var pubSub = request.addExtension(new PubSub());
+ final var itemsWrapper = pubSub.addExtension(new PubSub.ItemsWrapper());
+ itemsWrapper.setNode(node);
+ final var item = itemsWrapper.addExtension(new PubSub.Item());
+ item.setId(itemId);
+ return Futures.transform(
+ connection.sendIqPacket(request),
+ response -> {
+ final var pubSubResponse = response.getExtension(PubSub.class);
+ if (pubSubResponse == null) {
+ throw new IllegalStateException();
+ }
+ final var items = pubSubResponse.getItems();
+ if (items == null) {
+ throw new IllegalStateException();
+ }
+ return items.getItemOrThrow(itemId, clazz);
+ },
+ MoreExecutors.directExecutor());
+ }
+
+ public <T extends Extension> ListenableFuture<T> fetchMostRecentItem(
+ final Jid address, final String node, final Class<T> clazz) {
+ final Iq request = new Iq(Iq.Type.GET);
+ request.setTo(address);
+ final var pubSub = request.addExtension(new PubSub());
+ final var itemsWrapper = pubSub.addExtension(new PubSub.ItemsWrapper());
+ itemsWrapper.setNode(node);
+ itemsWrapper.setMaxItems(1);
+ return Futures.transform(
+ connection.sendIqPacket(request),
+ response -> {
+ final var pubSubResponse = response.getExtension(PubSub.class);
+ if (pubSubResponse == null) {
+ throw new IllegalStateException();
+ }
+ final var items = pubSubResponse.getItems();
+ if (items == null) {
+ throw new IllegalStateException();
+ }
+ return items.getOnlyItem(clazz);
+ },
+ MoreExecutors.directExecutor());
+ }
+
+ private void handleItems(final Message message, final Items items) {
+ final var from = message.getFrom();
+ final var isFromBare = from == null || from.isBareJid();
+ final var node = items.getNode();
+ if (connection.fromAccount(message) && Namespace.BOOKMARKS2.equals(node)) {
+ getManager(BookmarkManager.class).handleItems(items);
+ return;
+ }
+ if (connection.fromAccount(message) && Namespace.BOOKMARKS.equals(node)) {
+ getManager(LegacyBookmarkManager.class).handleItems(items);
+ return;
+ }
+ if (isFromBare && Namespace.AVATAR_METADATA.equals(node)) {
+ getManager(AvatarManager.class).handleItems(from, items);
+ return;
+ }
+ if (isFromBare && Namespace.NICK.equals(node)) {
+ getManager(NickManager.class).handleItems(from, items);
+ return;
+ }
+ if (isFromBare && Namespace.AXOLOTL_DEVICE_LIST.equals(node)) {
+ getManager(AxolotlManager.class).handleItems(from, items);
+ }
+ }
+
+ private void handlePurge(final Message message, final Purge purge) {
+ final var from = message.getFrom();
+ final var node = purge.getNode();
+ if (connection.fromAccount(message) && Namespace.BOOKMARKS2.equals(node)) {
+ getManager(BookmarkManager.class).deleteAllItems();
+ }
+ }
+
+ private void handleDelete(final Message message, final Delete delete) {}
+
+ public ListenableFuture<Void> publishSingleton(
+ Jid address, Extension item, final NodeConfiguration nodeConfiguration) {
+ final var id = ExtensionFactory.id(item.getClass());
+ return publish(address, item, SINGLETON_ITEM_ID, id.namespace, nodeConfiguration);
+ }
+
+ public ListenableFuture<Void> publishSingleton(
+ Jid address,
+ Extension item,
+ final String node,
+ final NodeConfiguration nodeConfiguration) {
+ return publish(address, item, SINGLETON_ITEM_ID, node, nodeConfiguration);
+ }
+
+ public ListenableFuture<Void> publish(
+ Jid address,
+ Extension item,
+ final String itemId,
+ final NodeConfiguration nodeConfiguration) {
+ final var id = ExtensionFactory.id(item.getClass());
+ return publish(address, item, itemId, id.namespace, nodeConfiguration);
+ }
+
+ public ListenableFuture<Void> publish(
+ final Jid address,
+ final Extension itemPayload,
+ final String itemId,
+ final String node,
+ final NodeConfiguration nodeConfiguration) {
+ final var future = publishNoRetry(address, itemPayload, itemId, node, nodeConfiguration);
+ return Futures.catchingAsync(
+ future,
+ PreconditionNotMetException.class,
+ ex -> {
+ Log.d(
+ Config.LOGTAG,
+ "Node " + node + " on " + address + " requires reconfiguration");
+ final var reconfigurationFuture =
+ reconfigureNode(address, node, nodeConfiguration);
+ return Futures.transformAsync(
+ reconfigurationFuture,
+ ignored ->
+ publishNoRetry(
+ address, itemPayload, itemId, node, nodeConfiguration),
+ MoreExecutors.directExecutor());
+ },
+ MoreExecutors.directExecutor());
+ }
+
+ private ListenableFuture<Void> publishNoRetry(
+ final Jid address,
+ final Extension itemPayload,
+ final String itemId,
+ final String node,
+ final NodeConfiguration nodeConfiguration) {
+ final var iq = new Iq(Iq.Type.SET);
+ iq.setTo(address);
+ final var pubSub = iq.addExtension(new PubSub());
+ final var publish = pubSub.addExtension(new Publish());
+ publish.setNode(node);
+ final var item = publish.addExtension(new PubSub.Item());
+ item.setId(itemId);
+ item.addExtension(itemPayload);
+ pubSub.addExtension(PublishOptions.of(nodeConfiguration));
+ final ListenableFuture<Void> iqFuture =
+ Futures.transform(
+ connection.sendIqPacket(iq),
+ result -> null,
+ MoreExecutors.directExecutor());
+ return Futures.catchingAsync(
+ iqFuture,
+ IqErrorException.class,
+ new PubSubExceptionTransformer<>(),
+ MoreExecutors.directExecutor());
+ }
+
+ private ListenableFuture<Void> reconfigureNode(
+ final Jid address, final String node, final NodeConfiguration nodeConfiguration) {
+ final Iq iq = new Iq(Iq.Type.GET);
+ iq.setTo(address);
+ final var pubSub = iq.addExtension(new PubSubOwner());
+ final var configure = pubSub.addExtension(new Configure());
+ configure.setNode(node);
+ return Futures.transformAsync(
+ connection.sendIqPacket(iq),
+ result -> {
+ final var pubSubOwnerResult = result.getExtension(PubSubOwner.class);
+ final Configure configureResult =
+ pubSubOwnerResult == null
+ ? null
+ : pubSubOwnerResult.getExtension(Configure.class);
+ if (configureResult == null) {
+ throw new IllegalStateException(
+ "No configuration found in configuration request result");
+ }
+ final var data = configureResult.getData();
+ return setNodeConfiguration(address, node, data.submit(nodeConfiguration));
+ },
+ MoreExecutors.directExecutor());
+ }
+
+ private ListenableFuture<Void> setNodeConfiguration(
+ final Jid address, final String node, final Data data) {
+ final Iq iq = new Iq(Iq.Type.SET);
+ iq.setTo(address);
+ final var pubSub = iq.addExtension(new PubSubOwner());
+ final var configure = pubSub.addExtension(new Configure());
+ configure.setNode(node);
+ configure.addExtension(data);
+ return Futures.transform(
+ connection.sendIqPacket(iq),
+ result -> {
+ Log.d(Config.LOGTAG, "Modified node configuration " + node + " on " + address);
+ return null;
+ },
+ MoreExecutors.directExecutor());
+ }
+
+ public ListenableFuture<Iq> retract(final Jid address, final String itemId, final String node) {
+ final var iq = new Iq(Iq.Type.SET);
+ iq.setTo(address);
+ final var pubSub = iq.addExtension(new PubSub());
+ final var retract = pubSub.addExtension(new Retract());
+ retract.setNode(node);
+ retract.setNotify(true);
+ final var item = retract.addExtension(new PubSub.Item());
+ item.setId(itemId);
+ return connection.sendIqPacket(iq);
+ }
+
+ private static class PubSubExceptionTransformer<V>
+ implements AsyncFunction<IqErrorException, V> {
+
+ @Override
+ @NonNull
+ public ListenableFuture<V> apply(@NonNull IqErrorException ex) {
+ final var error = ex.getError();
+ if (error == null) {
+ return Futures.immediateFailedFuture(ex);
+ }
+ final PubSubError pubSubError = error.getExtension(PubSubError.class);
+ if (pubSubError instanceof PubSubError.PreconditionNotMet) {
+ return Futures.immediateFailedFuture(
+ new PreconditionNotMetException(ex.getResponse()));
+ } else if (pubSubError != null) {
+ return Futures.immediateFailedFuture(new PubSubErrorException(ex.getResponse()));
+ } else {
+ return Futures.immediateFailedFuture(ex);
+ }
+ }
+ }
+}
@@ -7,6 +7,8 @@ import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.generator.IqGenerator;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xmpp.XmppConnection;
+import eu.siacs.conversations.xmpp.manager.BookmarkManager;
+import eu.siacs.conversations.xmpp.manager.PrivateStorageManager;
import eu.siacs.conversations.xmpp.manager.RosterManager;
import im.conversations.android.xmpp.model.stanza.Iq;
@@ -21,6 +23,7 @@ public class BindProcessor extends XmppConnection.Delegate implements Runnable {
@Override
public void run() {
+ Log.d(Config.LOGTAG, "begin onBind()");
final var account = connection.getAccount();
final var features = connection.getFeatures();
service.cancelAvatarFetches(account);
@@ -63,9 +66,10 @@ public class BindProcessor extends XmppConnection.Delegate implements Runnable {
getManager(RosterManager.class).request();
if (features.bookmarks2()) {
- service.fetchBookmarks2(account);
+ connection.getManager(BookmarkManager.class).fetch();
+ // log that we use bookmarks 1 and wait for +notify
} else if (!features.bookmarksConversion()) {
- service.fetchBookmarks(account);
+ connection.getManager(PrivateStorageManager.class).fetchBookmarks();
}
if (features.mds()) {
@@ -101,5 +105,6 @@ public class BindProcessor extends XmppConnection.Delegate implements Runnable {
connection.getManager(RosterManager.class).syncDirtyContacts();
service.getUnifiedPushBroker().renewUnifiedPushEndpointsOnBind(account);
+ Log.d(Config.LOGTAG, "end onBind()");
}
}