diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 61c24b8748e0030518aa986982fe542d400ad315..1ea59ba83c98f0aac81cf46e11766e811e82ab95 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -69,21 +69,6 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public Iq retrieveBookmarks() { - return retrieve(Namespace.BOOKMARKS2, null); - } - - public Iq retrieveMds() { - return retrieve(Namespace.MDS_DISPLAYED, null); - } - - public Iq publishNick(String nick) { - final Element item = new Element("item"); - item.setAttribute("id", "current"); - item.addChild("nick", Namespace.NICK).setContent(nick); - return publish(Namespace.NICK, item); - } - public Iq deleteNode(final String node) { final var packet = new Iq(Iq.Type.SET); final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB_OWNER); @@ -91,45 +76,6 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public Iq deleteItem(final String node, final String id) { - final var packet = new Iq(Iq.Type.SET); - final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB); - final Element retract = pubsub.addChild("retract"); - retract.setAttribute("node", node); - retract.setAttribute("notify", "true"); - retract.addChild("item").setAttribute("id", id); - return packet; - } - - public Iq publishAvatar(Avatar avatar, Bundle options) { - final Element item = new Element("item"); - item.setAttribute("id", avatar.sha1sum); - final Element data = item.addChild("data", Namespace.AVATAR_DATA); - data.setContent(avatar.image); - return publish(Namespace.AVATAR_DATA, item, options); - } - - public Iq publishElement( - final String namespace, final Element element, String id, final Bundle options) { - final Element item = new Element("item"); - item.setAttribute("id", id); - item.addChild(element); - return publish(namespace, item, options); - } - - public Iq publishAvatarMetadata(final Avatar avatar, final Bundle options) { - final Element item = new Element("item"); - item.setAttribute("id", avatar.sha1sum); - final Element metadata = item.addChild("metadata", Namespace.AVATAR_METADATA); - final Element info = metadata.addChild("info"); - info.setAttribute("bytes", avatar.size); - info.setAttribute("id", avatar.sha1sum); - info.setAttribute("height", avatar.height); - info.setAttribute("width", avatar.height); - info.setAttribute("type", avatar.type); - return publish(Namespace.AVATAR_METADATA, item, options); - } - public Iq retrievePepAvatar(final Avatar avatar) { final Element item = new Element("item"); item.setAttribute("id", avatar.sha1sum); diff --git a/src/main/java/eu/siacs/conversations/services/AvatarService.java b/src/main/java/eu/siacs/conversations/services/AvatarService.java index 941914a92ea7b7a6abde01eda8b7615ba5aae6e6..fd2cf0099a2c8ab267713ee4ae8dbb3a4fe0b072 100644 --- a/src/main/java/eu/siacs/conversations/services/AvatarService.java +++ b/src/main/java/eu/siacs/conversations/services/AvatarService.java @@ -14,12 +14,10 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.text.TextUtils; import android.util.DisplayMetrics; -import android.util.Log; import androidx.annotation.ColorInt; import androidx.annotation.Nullable; import androidx.core.content.res.ResourcesCompat; import com.google.common.base.Strings; -import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Bookmark; @@ -33,15 +31,13 @@ import eu.siacs.conversations.entities.RawBlockable; import eu.siacs.conversations.entities.Room; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xmpp.Jid; -import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; -import eu.siacs.conversations.xmpp.XmppConnection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; -public class AvatarService implements OnAdvancedStreamFeaturesLoaded { +public class AvatarService { private static final int FG_COLOR = 0xFFFAFAFA; private static final int TRANSPARENT = 0x00000000; @@ -714,17 +710,6 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded { return true; } - @Override - public void onAdvancedStreamFeaturesAvailable(Account account) { - XmppConnection.Features features = account.getXmppConnection().getFeatures(); - if (features.pep() && !features.pepPersistent()) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": has pep but is not persistent"); - if (account.getAvatar() != null) { - mXmppConnectionService.republishAvatarIfNeeded(account); - } - } - } - private static String emptyOnNull(@Nullable Jid value) { return value == null ? "" : value.toString(); } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 8163581fad99af56fba70a828fd6b154ab769475..6898c7dc9b2ee225ade4b40091532cf3feb235ae 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -143,7 +143,6 @@ import eu.siacs.conversations.xmpp.manager.PresenceManager; import eu.siacs.conversations.xmpp.manager.PrivateStorageManager; 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; @@ -1653,7 +1652,6 @@ public class XmppConnectionService extends Service { final XmppConnection connection = new XmppConnection(account, this); connection.setOnJinglePacketReceivedListener((mJingleConnectionManager::deliverPacket)); connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService); - connection.addOnAdvancedStreamFeaturesAvailableListener(this.mAvatarService); return connection; } @@ -3561,6 +3559,7 @@ public class XmppConnectionService extends Service { updateAccountUi(); } }; + // TODO execute this via the respective Managers deleteVcardAvatar(account, onDeleted); deletePepNode(account, Namespace.AVATAR_DATA); deletePepNode(account, Namespace.AVATAR_METADATA, onDeleted); @@ -4368,15 +4367,36 @@ public class XmppConnectionService extends Service { final Bitmap.CompressFormat format = Config.AVATAR_FORMAT; final int size = Config.AVATAR_SIZE; final Avatar avatar = getFileBackend().getPepAvatar(image, size, format); - if (avatar != null) { - if (!getFileBackend().save(avatar)) { - Log.d(Config.LOGTAG, "unable to save vcard"); - callback.onAvatarPublicationFailed(R.string.error_saving_avatar); - return; - } - publishAvatar(account, avatar, open, callback); - } else { + if (avatar == null) { callback.onAvatarPublicationFailed(R.string.error_publish_avatar_converting); + return; + } + if (fileBackend.save(avatar)) { + final var connection = account.getXmppConnection(); + final var future = connection.getManager(AvatarManager.class).publish(avatar, open); + Futures.addCallback( + future, + new FutureCallback() { + @Override + public void onSuccess(Void result) { + callback.onAvatarPublicationSucceeded(); + } + + @Override + public void onFailure(@NonNull Throwable t) { + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + ": could not publish avatar", + t); + callback.onAvatarPublicationFailed( + R.string.error_publish_avatar_server_reject); + } + }, + MoreExecutors.directExecutor()); + + } else { + Log.d(Config.LOGTAG, "could not save avatar"); + callback.onAvatarPublicationFailed(R.string.error_saving_avatar); } } @@ -4430,211 +4450,6 @@ public class XmppConnectionService extends Service { }); } - public void publishAvatar( - final Account account, - final Avatar avatar, - final boolean open, - final OnAvatarPublication callback) { - final Bundle options; - if (account.getXmppConnection().getFeatures().pepPublishOptions()) { - options = open ? PublishOptions.openAccess() : PublishOptions.presenceAccess(); - } else { - options = null; - } - publishAvatar(account, avatar, options, true, callback); - } - - public void publishAvatar( - Account account, - final Avatar avatar, - final Bundle options, - final boolean retry, - final OnAvatarPublication callback) { - Log.d( - Config.LOGTAG, - account.getJid().asBareJid() + ": publishing avatar. options=" + options); - final Iq packet = this.mIqGenerator.publishAvatar(avatar, options); - this.sendIqPacket( - account, - packet, - result -> { - if (result.getType() == Iq.Type.RESULT) { - publishAvatarMetadata(account, avatar, options, true, callback); - } else if (retry && PublishOptions.preconditionNotMet(result)) { - pushNodeConfiguration( - account, - Namespace.AVATAR_DATA, - options, - new OnConfigurationPushed() { - @Override - public void onPushSucceeded() { - Log.d( - Config.LOGTAG, - account.getJid().asBareJid() - + ": changed node configuration for avatar" - + " node"); - publishAvatar(account, avatar, options, false, callback); - } - - @Override - public void onPushFailed() { - Log.d( - Config.LOGTAG, - account.getJid().asBareJid() - + ": unable to change node configuration" - + " for avatar node"); - publishAvatar(account, avatar, null, false, callback); - } - }); - } else { - Element error = result.findChild("error"); - Log.d( - Config.LOGTAG, - account.getJid().asBareJid() - + ": server rejected avatar " - + (avatar.size / 1024) - + "KiB " - + (error != null ? error.toString() : "")); - if (callback != null) { - callback.onAvatarPublicationFailed( - R.string.error_publish_avatar_server_reject); - } - } - }); - } - - public void publishAvatarMetadata( - Account account, - final Avatar avatar, - final Bundle options, - final boolean retry, - final OnAvatarPublication callback) { - final Iq packet = - XmppConnectionService.this.mIqGenerator.publishAvatarMetadata(avatar, options); - sendIqPacket( - account, - packet, - result -> { - if (result.getType() == Iq.Type.RESULT) { - if (account.setAvatar(avatar.getFilename())) { - getAvatarService().clear(account); - databaseBackend.updateAccount(account); - notifyAccountAvatarHasChanged(account); - } - Log.d( - Config.LOGTAG, - account.getJid().asBareJid() - + ": published avatar " - + (avatar.size / 1024) - + "KiB"); - if (callback != null) { - callback.onAvatarPublicationSucceeded(); - } - } else if (retry && PublishOptions.preconditionNotMet(result)) { - pushNodeConfiguration( - account, - Namespace.AVATAR_METADATA, - options, - new OnConfigurationPushed() { - @Override - public void onPushSucceeded() { - Log.d( - Config.LOGTAG, - account.getJid().asBareJid() - + ": changed node configuration for avatar" - + " meta data node"); - publishAvatarMetadata( - account, avatar, options, false, callback); - } - - @Override - public void onPushFailed() { - Log.d( - Config.LOGTAG, - account.getJid().asBareJid() - + ": unable to change node configuration" - + " for avatar meta data node"); - publishAvatarMetadata( - account, avatar, null, false, callback); - } - }); - } else { - if (callback != null) { - callback.onAvatarPublicationFailed( - R.string.error_publish_avatar_server_reject); - } - } - }); - } - - public void republishAvatarIfNeeded(final Account account) { - if (account.getAxolotlService().isPepBroken()) { - Log.d( - Config.LOGTAG, - account.getJid().asBareJid() - + ": skipping republication of avatar because pep is broken"); - return; - } - final Iq packet = this.mIqGenerator.retrieveAvatarMetaData(null); - this.sendIqPacket( - account, - packet, - new Consumer() { - - private Avatar parseAvatar(final Iq packet) { - final var pubsub = packet.getExtension(PubSub.class); - if (pubsub == null) { - return null; - } - final var items = pubsub.getItems(); - if (items == null) { - return null; - } - final var item = items.getFirstItemWithId(Metadata.class); - if (item == null) { - return null; - } - return Avatar.parseMetadata(item.getKey(), item.getValue()); - } - - private boolean errorIsItemNotFound(Iq packet) { - Element error = packet.findChild("error"); - return packet.getType() == Iq.Type.ERROR - && error != null - && error.hasChild("item-not-found"); - } - - @Override - public void accept(final Iq packet) { - if (packet.getType() == Iq.Type.RESULT || errorIsItemNotFound(packet)) { - final Avatar serverAvatar = parseAvatar(packet); - if (serverAvatar == null && account.getAvatar() != null) { - final Avatar avatar = - fileBackend.getStoredPepAvatar(account.getAvatar()); - if (avatar != null) { - Log.d( - Config.LOGTAG, - account.getJid().asBareJid() - + ": avatar on server was null. republishing"); - // publishing as 'open' - old server (that requires - // republication) likely doesn't support access models anyway - publishAvatar( - account, - fileBackend.getStoredPepAvatar(account.getAvatar()), - true, - null); - } else { - Log.e( - Config.LOGTAG, - account.getJid().asBareJid() - + ": error rereading avatar"); - } - } - } - } - }); - } - public void cancelAvatarFetches(final Account account) { synchronized (mInProgressAvatarFetches) { for (final Iterator iterator = mInProgressAvatarFetches.iterator(); diff --git a/src/main/java/eu/siacs/conversations/xmpp/manager/AvatarManager.java b/src/main/java/eu/siacs/conversations/xmpp/manager/AvatarManager.java index 7eac27a005d219363a747a00cfee1b91993dfcec..7104ead02a1dcc4105af573f35750abf330590a3 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/manager/AvatarManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/manager/AvatarManager.java @@ -1,6 +1,9 @@ package eu.siacs.conversations.xmpp.manager; import android.util.Log; +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.Contact; import eu.siacs.conversations.services.XmppConnectionService; @@ -8,6 +11,9 @@ import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.pep.Avatar; +import im.conversations.android.xmpp.NodeConfiguration; +import im.conversations.android.xmpp.model.avatar.Data; +import im.conversations.android.xmpp.model.avatar.Info; import im.conversations.android.xmpp.model.avatar.Metadata; import im.conversations.android.xmpp.model.pubsub.Items; @@ -24,31 +30,35 @@ public class AvatarManager extends AbstractManager { final var account = getAccount(); // TODO support retract final var entry = items.getFirstItemWithId(Metadata.class); + Log.d(Config.LOGTAG, "<-- " + entry + " (" + from + ")"); final var avatar = entry == null ? null : Avatar.parseMetadata(entry.getKey(), entry.getValue()); - if (avatar != null) { - avatar.owner = from.asBareJid(); - if (service.getFileBackend().isAvatarCached(avatar)) { - if (account.getJid().asBareJid().equals(from)) { - if (account.setAvatar(avatar.getFilename())) { - service.databaseBackend.updateAccount(account); - service.notifyAccountAvatarHasChanged(account); - } - service.getAvatarService().clear(account); + if (avatar == null) { + Log.d(Config.LOGTAG, "could not parse avatar metadata from " + from); + return; + } + avatar.owner = from.asBareJid(); + if (service.getFileBackend().isAvatarCached(avatar)) { + if (account.getJid().asBareJid().equals(from)) { + if (account.setAvatar(avatar.getFilename())) { + service.databaseBackend.updateAccount(account); + service.notifyAccountAvatarHasChanged(account); + } + service.getAvatarService().clear(account); + service.updateConversationUi(); + service.updateAccountUi(); + } else { + final Contact contact = account.getRoster().getContact(from); + if (contact.setAvatar(avatar)) { + connection.getManager(RosterManager.class).writeToDatabaseAsync(); + service.getAvatarService().clear(contact); service.updateConversationUi(); - service.updateAccountUi(); - } else { - final Contact contact = account.getRoster().getContact(from); - if (contact.setAvatar(avatar)) { - connection.getManager(RosterManager.class).writeToDatabaseAsync(); - service.getAvatarService().clear(contact); - service.updateConversationUi(); - service.updateRosterUi(); - } + service.updateRosterUi(); } - } else if (service.isDataSaverDisabled()) { - service.fetchAvatar(account, avatar); } + } else if (service.isDataSaverDisabled()) { + // TODO use internal mechanism to fetch PEP avatars + service.fetchAvatar(account, avatar); } } @@ -63,6 +73,29 @@ public class AvatarManager extends AbstractManager { } } + public ListenableFuture publish(final Avatar avatar, final boolean open) { + final NodeConfiguration configuration = + open ? NodeConfiguration.OPEN : NodeConfiguration.PRESENCE; + final var avatarData = new Data(); + avatarData.setContent(avatar.getImageAsBytes()); + final var future = + getManager(PepManager.class).publish(avatarData, avatar.sha1sum, configuration); + return Futures.transformAsync( + future, + v -> { + final var id = avatar.sha1sum; + final var metadata = new Metadata(); + final var info = metadata.addExtension(new Info()); + info.setBytes(avatar.size); + info.setId(avatar.sha1sum); + info.setHeight(avatar.height); + info.setWidth(avatar.width); + info.setType(avatar.type); + return getManager(PepManager.class).publish(metadata, id, configuration); + }, + MoreExecutors.directExecutor()); + } + public boolean hasPepToVCardConversion() { return getManager(DiscoManager.class).hasAccountFeature(Namespace.AVATAR_CONVERSION); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/manager/DiscoManager.java b/src/main/java/eu/siacs/conversations/xmpp/manager/DiscoManager.java index d13856f0be5b0d6551c8066c66107d241e0810f4..526b43b42228af8d60db2612f29967f7250e694f 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/manager/DiscoManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/manager/DiscoManager.java @@ -311,6 +311,7 @@ public class DiscoManager extends AbstractManager { final var appSettings = new AppSettings(context); final var account = connection.getAccount(); final ImmutableList.Builder features = ImmutableList.builder(); + features.addAll(STATIC_FEATURES); if (Config.MESSAGE_DISPLAYED_SYNCHRONIZATION) { features.add(Namespace.MDS_DISPLAYED + "+notify"); } diff --git a/src/main/java/im/conversations/android/xmpp/model/avatar/Info.java b/src/main/java/im/conversations/android/xmpp/model/avatar/Info.java index f544af72fc784e0b935bbac0c767bd3504194807..31099ff79e246d884da1b4a2c738571cb359f57a 100644 --- a/src/main/java/im/conversations/android/xmpp/model/avatar/Info.java +++ b/src/main/java/im/conversations/android/xmpp/model/avatar/Info.java @@ -34,4 +34,24 @@ public class Info extends Extension { public String getId() { return this.getAttribute("id"); } + + public void setBytes(final long size) { + this.setAttribute("bytes", size); + } + + public void setId(final String id) { + this.setAttribute("id", id); + } + + public void setHeight(final long height) { + this.setAttribute("height", height); + } + + public void setWidth(final long width) { + this.setAttribute("width", width); + } + + public void setType(final String type) { + this.setAttribute("type", type); + } }