Detailed changes
@@ -438,15 +438,10 @@ public class Contact implements ListItem, Blockable {
}
public boolean setAvatar(final Avatar avatar) {
- return setAvatar(avatar, false);
- }
-
- public boolean setAvatar(final Avatar avatar, final boolean previouslyOmittedPepFetch) {
if (this.avatar != null && this.avatar.equals(avatar)) {
return false;
}
- if (!previouslyOmittedPepFetch
- && this.avatar != null
+ if (this.avatar != null
&& this.avatar.origin == Avatar.Origin.PEP
&& avatar.origin == Avatar.Origin.VCARD) {
return false;
@@ -57,6 +57,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
+import com.google.common.io.BaseEncoding;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@@ -142,6 +143,7 @@ import eu.siacs.conversations.xmpp.manager.NickManager;
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.manager.VCardManager;
import eu.siacs.conversations.xmpp.pep.Avatar;
import im.conversations.android.xmpp.Entity;
import im.conversations.android.xmpp.IqErrorException;
@@ -4462,23 +4464,18 @@ public class XmppConnectionService extends Service {
}
}
- public void fetchAvatar(Account account, Avatar avatar) {
- fetchAvatar(account, avatar, null);
- }
-
- public void fetchAvatar(
- Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
+ public void fetchAvatar(Account account, final Avatar avatar) {
final String KEY = generateFetchKey(account, avatar);
synchronized (this.mInProgressAvatarFetches) {
if (mInProgressAvatarFetches.add(KEY)) {
switch (avatar.origin) {
case PEP:
this.mInProgressAvatarFetches.add(KEY);
- fetchAvatarPep(account, avatar, callback);
+ fetchAvatarPep(account, avatar, null);
break;
case VCARD:
this.mInProgressAvatarFetches.add(KEY);
- fetchAvatarVcard(account, avatar, callback);
+ fetchAvatarVcard(account, avatar);
break;
}
} else if (avatar.origin == Avatar.Origin.PEP) {
@@ -4560,88 +4557,81 @@ public class XmppConnectionService extends Service {
});
}
- private void fetchAvatarVcard(
- final Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
- final Iq packet = this.mIqGenerator.retrieveVcardAvatar(avatar);
- this.sendIqPacket(
- account,
- packet,
- response -> {
- final boolean previouslyOmittedPepFetch;
- synchronized (mInProgressAvatarFetches) {
- final String KEY = generateFetchKey(account, avatar);
- mInProgressAvatarFetches.remove(KEY);
- previouslyOmittedPepFetch = mOmittedPepAvatarFetches.remove(KEY);
- }
- if (response.getType() == Iq.Type.RESULT) {
- Element vCard = response.findChild("vCard", "vcard-temp");
- Element photo = vCard != null ? vCard.findChild("PHOTO") : null;
- String image = photo != null ? photo.findChildContent("BINVAL") : null;
- if (image != null) {
- avatar.image = image;
- if (getFileBackend().save(avatar)) {
- Log.d(
- Config.LOGTAG,
- account.getJid().asBareJid()
- + ": successfully fetched vCard avatar for "
- + avatar.owner
- + " omittedPep="
- + previouslyOmittedPepFetch);
- if (avatar.owner.isBareJid()) {
- if (account.getJid().asBareJid().equals(avatar.owner)
- && account.getAvatar() == null) {
- Log.d(
- Config.LOGTAG,
- account.getJid().asBareJid()
- + ": had no avatar. replacing with vcard");
- account.setAvatar(avatar.getFilename());
- databaseBackend.updateAccount(account);
- getAvatarService().clear(account);
- updateAccountUi();
- } else {
- final Contact contact =
- account.getRoster().getContact(avatar.owner);
- contact.setAvatar(avatar, previouslyOmittedPepFetch);
- account.getXmppConnection()
- .getManager(RosterManager.class)
- .writeToDatabaseAsync();
- getAvatarService().clear(contact);
- updateRosterUi();
- }
- updateConversationUi();
- } else {
- Conversation conversation =
- find(account, avatar.owner.asBareJid());
- if (conversation != null
- && conversation.getMode() == Conversation.MODE_MULTI) {
- MucOptions.User user =
- conversation
- .getMucOptions()
- .findUserByFullJid(avatar.owner);
- if (user != null) {
- if (user.setAvatar(avatar)) {
- getAvatarService().clear(user);
- updateConversationUi();
- updateMucRosterUi();
- }
- if (user.getRealJid() != null) {
- Contact contact =
- account.getRoster()
- .getContact(user.getRealJid());
- contact.setAvatar(avatar);
- account.getXmppConnection()
- .getManager(RosterManager.class)
- .writeToDatabaseAsync();
- getAvatarService().clear(contact);
- updateRosterUi();
- }
- }
- }
- }
- }
+ private void fetchAvatarVcard(final Account account, final Avatar avatar) {
+ final var address = avatar.owner;
+ final var connection = account.getXmppConnection();
+ final var future = connection.getManager(VCardManager.class).retrievePhoto(address);
+ Futures.addCallback(
+ future,
+ new FutureCallback<>() {
+ @Override
+ public void onSuccess(byte[] result) {
+ avatar.image = BaseEncoding.base64().encode(result);
+ if (fileBackend.save(avatar)) {
+ setVCardAvatar(account, avatar);
}
}
- });
+
+ @Override
+ public void onFailure(@NonNull Throwable t) {
+ Log.d(
+ Config.LOGTAG,
+ "could not retrieve avatar from "
+ + address
+ + " ("
+ + avatar.sha1sum
+ + ")",
+ t);
+ }
+ },
+ MoreExecutors.directExecutor());
+ }
+
+ private void setVCardAvatar(final Account account, final Avatar avatar) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": successfully fetched vCard avatar for "
+ + avatar.owner);
+ if (avatar.owner.isBareJid()) {
+ if (account.getJid().asBareJid().equals(avatar.owner) && account.getAvatar() == null) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid() + ": had no avatar. replacing with vcard");
+ account.setAvatar(avatar.getFilename());
+ databaseBackend.updateAccount(account);
+ getAvatarService().clear(account);
+ updateAccountUi();
+ } else {
+ final Contact contact = account.getRoster().getContact(avatar.owner);
+ contact.setAvatar(avatar);
+ account.getXmppConnection().getManager(RosterManager.class).writeToDatabaseAsync();
+ getAvatarService().clear(contact);
+ updateRosterUi();
+ }
+ updateConversationUi();
+ } else {
+ Conversation conversation = find(account, avatar.owner.asBareJid());
+ if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
+ MucOptions.User user = conversation.getMucOptions().findUserByFullJid(avatar.owner);
+ if (user != null) {
+ if (user.setAvatar(avatar)) {
+ getAvatarService().clear(user);
+ updateConversationUi();
+ updateMucRosterUi();
+ }
+ if (user.getRealJid() != null) {
+ Contact contact = account.getRoster().getContact(user.getRealJid());
+ contact.setAvatar(avatar);
+ account.getXmppConnection()
+ .getManager(RosterManager.class)
+ .writeToDatabaseAsync();
+ getAvatarService().clear(contact);
+ updateRosterUi();
+ }
+ }
+ }
+ }
}
public void checkForAvatar(final Account account, final UiCallback<Avatar> callback) {
@@ -21,6 +21,7 @@ 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;
+import eu.siacs.conversations.xmpp.manager.VCardManager;
public class Managers {
@@ -50,6 +51,7 @@ public class Managers {
.put(PubSubManager.class, new PubSubManager(context, connection))
.put(RosterManager.class, new RosterManager(context, connection))
.put(UnifiedPushManager.class, new UnifiedPushManager(context, connection))
+ .put(VCardManager.class, new VCardManager(context, connection))
.build();
}
}
@@ -1,6 +1,9 @@
package eu.siacs.conversations.xmpp.manager;
import android.util.Log;
+import androidx.annotation.NonNull;
+import com.google.common.io.BaseEncoding;
+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;
@@ -12,6 +15,7 @@ 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.ByteContent;
import im.conversations.android.xmpp.model.avatar.Data;
import im.conversations.android.xmpp.model.avatar.Info;
import im.conversations.android.xmpp.model.avatar.Metadata;
@@ -26,11 +30,50 @@ public class AvatarManager extends AbstractManager {
this.service = service;
}
+ public ListenableFuture<byte[]> fetch(final Jid address, final String itemId) {
+ final var future = getManager(PubSubManager.class).fetchItem(address, itemId, Data.class);
+ return Futures.transform(future, ByteContent::asBytes, MoreExecutors.directExecutor());
+ }
+
+ public ListenableFuture<Void> fetchAndStore(final Avatar avatar) {
+ final var future = fetch(avatar.owner, avatar.sha1sum);
+ return Futures.transform(
+ future,
+ data -> {
+ avatar.image = BaseEncoding.base64().encode(data);
+ if (service.getFileBackend().save(avatar)) {
+ setPepAvatar(avatar);
+ return null;
+ } else {
+ throw new IllegalStateException("Could not store avatar");
+ }
+ },
+ MoreExecutors.directExecutor());
+ }
+
+ private void setPepAvatar(final Avatar avatar) {
+ final var account = getAccount();
+ if (account.getJid().asBareJid().equals(avatar.owner)) {
+ if (account.setAvatar(avatar.getFilename())) {
+ getDatabase().updateAccount(account);
+ }
+ this.service.getAvatarService().clear(account);
+ this.service.updateConversationUi();
+ this.service.updateAccountUi();
+ } else {
+ final Contact contact = account.getRoster().getContact(avatar.owner);
+ contact.setAvatar(avatar);
+ account.getXmppConnection().getManager(RosterManager.class).writeToDatabaseAsync();
+ this.service.getAvatarService().clear(contact);
+ this.service.updateConversationUi();
+ this.service.updateRosterUi();
+ }
+ }
+
public void handleItems(final Jid from, final Items items) {
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) {
@@ -57,8 +100,25 @@ public class AvatarManager extends AbstractManager {
}
}
} else if (service.isDataSaverDisabled()) {
- // TODO use internal mechanism to fetch PEP avatars
- service.fetchAvatar(account, avatar);
+ final var future = this.fetchAndStore(avatar);
+ Futures.addCallback(
+ future,
+ new FutureCallback<Void>() {
+ @Override
+ public void onSuccess(Void result) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": successfully fetched pep avatar for "
+ + avatar.owner);
+ }
+
+ @Override
+ public void onFailure(@NonNull Throwable t) {
+ Log.d(Config.LOGTAG, "could not fetch avatar", t);
+ }
+ },
+ MoreExecutors.directExecutor());
}
}
@@ -0,0 +1,50 @@
+package eu.siacs.conversations.xmpp.manager;
+
+import android.content.Context;
+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.xmpp.Jid;
+import eu.siacs.conversations.xmpp.XmppConnection;
+import im.conversations.android.xmpp.model.stanza.Iq;
+import im.conversations.android.xmpp.model.vcard.VCard;
+
+public class VCardManager extends AbstractManager {
+
+ public VCardManager(final Context context, final XmppConnection connection) {
+ super(context, connection);
+ }
+
+ public ListenableFuture<VCard> retrieve(final Jid address) {
+ final var iq = new Iq(Iq.Type.GET, new VCard());
+ iq.setTo(address);
+ return Futures.transform(
+ this.connection.sendIqPacket(iq),
+ result -> {
+ final var vCard = result.getExtension(VCard.class);
+ if (vCard == null) {
+ throw new IllegalStateException("Result did not include vCard");
+ }
+ return vCard;
+ },
+ MoreExecutors.directExecutor());
+ }
+
+ public ListenableFuture<byte[]> retrievePhoto(final Jid address) {
+ final var vCardFuture = retrieve(address);
+ return Futures.transform(
+ vCardFuture,
+ vCard -> {
+ final var photo = vCard.getPhoto();
+ if (photo == null) {
+ throw new IllegalStateException("No photo in vCard");
+ }
+ final var binaryValue = photo.getBinaryValue();
+ if (binaryValue == null) {
+ throw new IllegalStateException("Photo has no binary value");
+ }
+ return binaryValue.asBytes();
+ },
+ MoreExecutors.directExecutor());
+ }
+}
@@ -8,4 +8,8 @@ public class Photo extends Extension {
public Photo() {
super(Photo.class);
}
+
+ public BinaryValue getBinaryValue() {
+ return this.getExtension(BinaryValue.class);
+ }
}
@@ -9,4 +9,8 @@ public class VCard extends Extension {
public VCard() {
super(VCard.class);
}
+
+ public Photo getPhoto() {
+ return this.getExtension(Photo.class);
+ }
}