M src/cheogram/java/com/cheogram/android/BobTransfer.java => src/cheogram/java/com/cheogram/android/BobTransfer.java +1 -0
@@ 33,6 33,7 @@ public class BobTransfer implements Transferable {
protected XmppConnectionService xmppConnectionService;
public static Cid cid(URI uri) {
+ if (!uri.getScheme().equals("cid")) return null;
String bobCid = uri.getSchemeSpecificPart();
if (!bobCid.contains("@") || !bobCid.contains("+")) return null;
String[] cidParts = bobCid.split("@")[0].split("\\+");
A src/cheogram/java/com/cheogram/android/GetThumbnailForCid.java => src/cheogram/java/com/cheogram/android/GetThumbnailForCid.java +9 -0
@@ 0,0 1,9 @@
+package com.cheogram.android;
+
+import android.graphics.drawable.Drawable;
+
+import io.ipfs.cid.Cid;
+
+public interface GetThumbnailForCid {
+ public Drawable getThumbnail(Cid cid);
+}
M src/main/java/eu/siacs/conversations/entities/Message.java => src/main/java/eu/siacs/conversations/entities/Message.java +55 -2
@@ 2,10 2,15 @@ package eu.siacs.conversations.entities;
import android.content.ContentValues;
import android.database.Cursor;
+import android.graphics.drawable.Drawable;
import android.graphics.Color;
+import android.text.Html;
import android.text.SpannableStringBuilder;
import android.util.Log;
+import com.cheogram.android.BobTransfer;
+import com.cheogram.android.GetThumbnailForCid;
+
import com.google.common.io.ByteSource;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
@@ 25,6 30,8 @@ import java.util.Set;
import java.util.stream.Collectors;
import java.util.concurrent.CopyOnWriteArraySet;
+import io.ipfs.cid.Cid;
+
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
@@ 762,8 769,42 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
public static class MergeSeparator {
}
+ public SpannableStringBuilder getSpannableBody(GetThumbnailForCid thumbnailer, Drawable fallbackImg) {
+ final Element html = getHtml();
+ if (html == null) {
+ return new SpannableStringBuilder(MessageUtils.filterLtrRtl(getBody()).trim());
+ } else {
+ SpannableStringBuilder spannable = new SpannableStringBuilder(Html.fromHtml(
+ MessageUtils.filterLtrRtl(html.toString()).trim(),
+ Html.FROM_HTML_MODE_COMPACT,
+ (source) -> {
+ try {
+ if (thumbnailer == null) return fallbackImg;
+ Cid cid = BobTransfer.cid(new URI(source));
+ if (cid == null) return fallbackImg;
+ Drawable thumbnail = thumbnailer.getThumbnail(cid);
+ if (thumbnail == null) return fallbackImg;
+ return thumbnail;
+ } catch (final URISyntaxException e) {
+ return fallbackImg;
+ }
+ },
+ (opening, tag, output, xmlReader) -> {}
+ ));
+
+ // https://stackoverflow.com/a/10187511/8611
+ int i = spannable.length();
+ while(--i >= 0 && Character.isWhitespace(spannable.charAt(i))) { }
+ return (SpannableStringBuilder) spannable.subSequence(0, i+1);
+ }
+ }
+
public SpannableStringBuilder getMergedBody() {
- SpannableStringBuilder body = new SpannableStringBuilder(MessageUtils.filterLtrRtl(getBody()).trim());
+ return getMergedBody(null, null);
+ }
+
+ public SpannableStringBuilder getMergedBody(GetThumbnailForCid thumbnailer, Drawable fallbackImg) {
+ SpannableStringBuilder body = getSpannableBody(thumbnailer, fallbackImg);
Message current = this;
while (current.mergeable(current.next())) {
current = current.next();
@@ 773,7 814,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
body.append("\n\n");
body.setSpan(new MergeSeparator(), body.length() - 2, body.length(),
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
- body.append(MessageUtils.filterLtrRtl(current.getBody()).trim());
+ body.append(current.getSpannableBody(thumbnailer, fallbackImg));
}
return body;
}
@@ 868,6 909,18 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
this.payloads.add(el);
}
+ public Element getHtml() {
+ if (this.payloads == null) return null;
+
+ for (Element el : this.payloads) {
+ if (el.getName().equals("html") && el.getNamespace().equals("http://jabber.org/protocol/xhtml-im")) {
+ return el.getChildren().get(0);
+ }
+ }
+
+ return null;
+ }
+
public List<Element> getCommands() {
if (this.payloads == null) return null;
M src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java => src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java +1 -0
@@ 112,6 112,7 @@ public abstract class AbstractGenerator {
public List<String> getFeatures(Account account) {
final XmppConnection connection = account.getXmppConnection();
final ArrayList<String> features = new ArrayList<>(Arrays.asList(FEATURES));
+ features.add("http://jabber.org/protocol/xhtml-im");
if (mXmppConnectionService.confirmMessages()) {
features.addAll(Arrays.asList(MESSAGE_CONFIRMATION_FEATURES));
}
M src/main/java/eu/siacs/conversations/parser/MessageParser.java => src/main/java/eu/siacs/conversations/parser/MessageParser.java +9 -3
@@ 434,6 434,11 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
}
boolean notify = false;
+ Element html = original.findChild("html", "http://jabber.org/protocol/xhtml-im");
+ if (html != null && html.findChild("body", "http://www.w3.org/1999/xhtml") == null) {
+ html = null;
+ }
+
if (from == null || !InvalidJid.isValid(from) || !InvalidJid.isValid(to)) {
Log.e(Config.LOGTAG, "encountered invalid message from='" + from + "' to='" + to + "'");
return;
@@ 472,7 477,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
}
}
- if ((body != null || pgpEncrypted != null || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload")) || oobUrl != null) && !isMucStatusMessage) {
+ if ((body != null || pgpEncrypted != null || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload")) || oobUrl != null || html != null) && !isMucStatusMessage) {
final boolean conversationIsProbablyMuc = isTypeGroupChat || mucUserElement != null || account.getXmppConnection().getMucServersWithholdAccount().contains(counterpart.getDomain().toEscapedString());
final Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.asBareJid(), conversationIsProbablyMuc, false, query, false);
final boolean conversationMultiMode = conversation.getMode() == Conversation.MODE_MULTI;
@@ 577,12 582,13 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
}
} else {
- message = new Message(conversation, body.content, Message.ENCRYPTION_NONE, status);
- if (body.count > 1) {
+ message = new Message(conversation, body == null ? "HTML-only message" : body.content, Message.ENCRYPTION_NONE, status);
+ if (body != null && body.count > 1) {
message.setBodyLanguage(body.language);
}
}
+ if (html != null) message.addPayload(html);
message.setSubject(original.findChildContent("subject"));
message.setCounterpart(counterpart);
message.setRemoteMsgId(remoteMsgId);