M build.gradle => build.gradle +1 -0
@@ 100,6 100,7 @@ dependencies {
implementation 'me.saket:better-link-movement-method:2.2.0'
implementation 'com.github.singpolyma:android-identicons:master-SNAPSHOT'
implementation 'org.snikket:webrtc-android:107.0.0'
+ implementation 'com.github.woltapp:blurhash:master'
// INSERT
}
M src/cheogram/java/com/cheogram/android/BobTransfer.java => src/cheogram/java/com/cheogram/android/BobTransfer.java +6 -0
@@ 1,5 1,6 @@
package com.cheogram.android;
+import android.net.Uri;
import android.util.Base64;
import android.util.Log;
@@ 35,6 36,11 @@ public class BobTransfer implements Transferable {
protected Jid to;
protected XmppConnectionService xmppConnectionService;
+ public static Cid cid(Uri uri) {
+ if (!uri.getScheme().equals("cid")) return null;
+ return cid(uri.getSchemeSpecificPart());
+ }
+
public static Cid cid(URI uri) {
if (!uri.getScheme().equals("cid")) return null;
return cid(uri.getSchemeSpecificPart());
M src/main/java/eu/siacs/conversations/entities/Message.java => src/main/java/eu/siacs/conversations/entities/Message.java +124 -5
@@ 7,6 7,7 @@ import android.graphics.Color;
import android.os.Build;
import android.text.Html;
import android.text.SpannableStringBuilder;
+import android.util.Base64;
import android.util.Log;
import com.cheogram.android.BobTransfer;
@@ 23,6 24,8 @@ import java.lang.ref.WeakReference;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.time.Duration;
+import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
@@ 242,8 245,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
this.bodyLanguage = bodyLanguage;
this.timeReceived = timeReceived;
this.subject = subject;
- if (fileParams != null) this.fileParams = new FileParams(fileParams);
if (payloads != null) this.payloads = payloads;
+ if (fileParams != null && getSims().isEmpty()) this.fileParams = new FileParams(fileParams);
}
public static Message fromCursor(Cursor cursor, Conversation conversation) throws IOException {
@@ 319,6 322,14 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
values.put(UUID, uuid);
values.put("subject", subject);
values.put("fileParams", fileParams == null ? null : fileParams.toString());
+ if (fileParams != null) {
+ List<Element> sims = getSims();
+ if (sims.isEmpty()) {
+ addPayload(fileParams.toSims());
+ } else {
+ sims.get(0).replaceChildren(fileParams.toSims().getChildren());
+ }
+ }
values.put("payloads", payloads.size() < 1 ? null : payloads.stream().map(Object::toString).collect(Collectors.joining()));
return values;
}
@@ 996,21 1007,33 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
return isGeoUri;
}
+ protected List<Element> getSims() {
+ return payloads.stream().filter(el ->
+ el.getName().equals("reference") && el.getNamespace().equals("urn:xmpp:reference:0") &&
+ el.findChild("media-sharing", "urn:xmpp:sims:1") != null
+ ).collect(Collectors.toList());
+ }
+
public synchronized void resetFileParams() {
this.fileParams = null;
}
public synchronized void setFileParams(FileParams fileParams) {
+ if (this.fileParams != null && this.fileParams.sims != null && fileParams.sims == null) {
+ fileParams.sims = this.fileParams.sims;
+ }
this.fileParams = fileParams;
}
public synchronized FileParams getFileParams() {
if (fileParams == null) {
- fileParams = new FileParams(oob ? this.body : "");
+ List<Element> sims = getSims();
+ fileParams = sims.isEmpty() ? new FileParams(oob ? this.body : "") : new FileParams(sims.get(0));
if (this.transferable != null) {
fileParams.size = this.transferable.getFileSize();
}
}
+
return fileParams;
}
@@ 1054,6 1077,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
public int width = 0;
public int height = 0;
public int runtime = 0;
+ public Element sims = null;
public FileParams() { }
@@ 1062,6 1086,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
this.url = el.findChildContent("url", Namespace.OOB);
}
if (el.getName().equals("reference") && el.getNamespace().equals("urn:xmpp:reference:0")) {
+ sims = el;
final String refUri = el.getAttribute("uri");
if (refUri != null) url = refUri;
final Element mediaSharing = el.findChild("media-sharing", "urn:xmpp:sims:1");
@@ 1070,10 1095,14 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
if (file == null) file = mediaSharing.findChild("file", "urn:xmpp:jingle:apps:file-transfer:4");
if (file == null) file = mediaSharing.findChild("file", "urn:xmpp:jingle:apps:file-transfer:3");
if (file != null) {
- String sizeS = file.findChildContent("size", "urn:xmpp:jingle:apps:file-transfer:5");
- if (sizeS == null) sizeS = file.findChildContent("size", "urn:xmpp:jingle:apps:file-transfer:4");
- if (sizeS == null) sizeS = file.findChildContent("size", "urn:xmpp:jingle:apps:file-transfer:3");
+ String sizeS = file.findChildContent("size", file.getNamespace());
if (sizeS != null) size = new Long(sizeS);
+ String widthS = file.findChildContent("width", "https://schema.org/");
+ if (widthS != null) width = parseInt(widthS);
+ String heightS = file.findChildContent("height", "https://schema.org/");
+ if (heightS != null) height = parseInt(heightS);
+ String durationS = file.findChildContent("duration", "https://schema.org/");
+ if (durationS != null) runtime = (int)(Duration.parse(durationS).toMillis() / 1000L);
}
final Element sources = mediaSharing.findChild("sources", "urn:xmpp:sims:1");
@@ 1116,6 1145,86 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
return size == null ? 0 : size;
}
+ public Element toSims() {
+ if (sims == null) sims = new Element("reference", "urn:xmpp:reference:0");
+ sims.setAttribute("type", "data");
+ Element mediaSharing = sims.findChild("media-sharing", "urn:xmpp:sims:1");
+ if (mediaSharing == null) mediaSharing = sims.addChild("media-sharing", "urn:xmpp:sims:1");
+
+ Element file = mediaSharing.findChild("file", "urn:xmpp:jingle:apps:file-transfer:5");
+ if (file == null) file = mediaSharing.findChild("file", "urn:xmpp:jingle:apps:file-transfer:4");
+ if (file == null) file = mediaSharing.findChild("file", "urn:xmpp:jingle:apps:file-transfer:3");
+ if (file == null) file = mediaSharing.addChild("file", "urn:xmpp:jingle:apps:file-transfer:5");
+
+ file.removeChild(file.findChild("size", file.getNamespace()));
+ if (size != null) file.addChild("size", file.getNamespace()).setContent(size.toString());
+
+ file.removeChild(file.findChild("width", "https://schema.org/"));
+ if (width > 0) file.addChild("width", "https://schema.org/").setContent(String.valueOf(width));
+
+ file.removeChild(file.findChild("height", "https://schema.org/"));
+ if (height > 0) file.addChild("height", "https://schema.org/").setContent(String.valueOf(height));
+
+ file.removeChild(file.findChild("duration", "https://schema.org/"));
+ if (runtime > 0) file.addChild("duration", "https://schema.org/").setContent("PT" + runtime + "S");
+
+ if (url != null) {
+ Element sources = mediaSharing.findChild("sources", mediaSharing.getNamespace());
+ if (sources == null) sources = mediaSharing.addChild("sources", mediaSharing.getNamespace());
+
+ Element source = sources.findChild("reference", "urn:xmpp:reference:0");
+ if (source == null) source = sources.addChild("reference", "urn:xmpp:reference:0");
+ source.setAttribute("type", "data");
+ source.setAttribute("uri", url);
+ }
+
+ return sims;
+ }
+
+ protected Element getFileElement() {
+ Element file = null;
+ if (sims == null) return file;
+
+ Element mediaSharing = sims.findChild("media-sharing", "urn:xmpp:sims:1");
+ if (mediaSharing == null) return file;
+ file = mediaSharing.findChild("file", "urn:xmpp:jingle:apps:file-transfer:5");
+ if (file == null) file = mediaSharing.findChild("file", "urn:xmpp:jingle:apps:file-transfer:4");
+ if (file == null) file = mediaSharing.findChild("file", "urn:xmpp:jingle:apps:file-transfer:3");
+ return file;
+ }
+
+ public List<Cid> getCids() {
+ List<Cid> cids = new ArrayList<>();
+ Element file = getFileElement();
+ if (file == null) return cids;
+
+ for (Element child : file.getChildren()) {
+ if (child.getName().equals("hash") && child.getNamespace().equals("urn:xmpp:hashes:2")) {
+ try {
+ cids.add(CryptoHelper.cid(Base64.decode(child.getContent(), Base64.DEFAULT), child.getAttribute("algo")));
+ } catch (final NoSuchAlgorithmException | IllegalStateException e) { }
+ }
+ }
+
+ cids.sort((x, y) -> y.getType().compareTo(x.getType()));
+
+ return cids;
+ }
+
+ public List<Element> getThumbnails() {
+ List<Element> thumbs = new ArrayList<>();
+ Element file = getFileElement();
+ if (file == null) return thumbs;
+
+ for (Element child : file.getChildren()) {
+ if (child.getName().equals("thumbnail") && child.getNamespace().equals("urn:xmpp:thumbs:1")) {
+ thumbs.add(child);
+ }
+ }
+
+ return thumbs;
+ }
+
public String toString() {
final StringBuilder builder = new StringBuilder();
if (url != null) builder.append(url);
@@ 1125,6 1234,16 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
if (runtime > 0) builder.append('|').append(runtime);
return builder.toString();
}
+
+ public boolean equals(Object o) {
+ if (!(o instanceof FileParams)) return false;
+
+ return url.equals(((FileParams) o).url);
+ }
+
+ public int hashCode() {
+ return url.hashCode();
+ }
}
public void setFingerprint(String fingerprint) {
M src/main/java/eu/siacs/conversations/parser/MessageParser.java => src/main/java/eu/siacs/conversations/parser/MessageParser.java +33 -11
@@ 5,18 5,22 @@ import android.util.Pair;
import com.cheogram.android.BobTransfer;
+import java.io.File;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
+import io.ipfs.cid.Cid;
+
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
@@ 410,13 414,19 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
final Element mucUserElement = packet.findChild("x", Namespace.MUC_USER);
final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted");
final Element replaceElement = packet.findChild("replace", "urn:xmpp:message-correct:0");
- Element oob = packet.findChild("x", Namespace.OOB);
- if (oob != null && oob.findChildContent("url") == null) {
- oob = null;
+ Set<Message.FileParams> attachments = new LinkedHashSet<>();
+ for (Element child : packet.getChildren()) {
+ // SIMS first so they get preference in the set
+ if (child.getName().equals("reference") && child.getNamespace().equals("urn:xmpp:reference:0")) {
+ if (child.findChild("media-sharing", "urn:xmpp:sims:1") != null) {
+ attachments.add(new Message.FileParams(child));
+ }
+ }
}
- final Element reference = packet.findChild("reference", "urn:xmpp:reference:0");
- if (reference != null && reference.findChild("media-sharing", "urn:xmpp:sims:1") != null) {
- oob = reference;
+ for (Element child : packet.getChildren()) {
+ if (child.getName().equals("x") && child.getNamespace().equals(Namespace.OOB)) {
+ attachments.add(new Message.FileParams(child));
+ }
}
String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id");
if (replacementId == null) {
@@ 485,7 495,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
}
}
- if ((body != null || pgpEncrypted != null || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload")) || oob != null || html != null) && !isMucStatusMessage) {
+ if ((body != null || pgpEncrypted != null || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload")) || !attachments.isEmpty() || 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;
@@ 583,7 593,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
if (conversationMultiMode) {
message.setTrueCounterpart(origin);
}
- } else if (body == null && oob != null) {
+ } else if (body == null && !attachments.isEmpty()) {
message = new Message(conversation, "", Message.ENCRYPTION_NONE, status);
} else {
message = new Message(conversation, body == null ? "HTML-only message" : body.content, Message.ENCRYPTION_NONE, status);
@@ 599,8 609,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
message.setServerMsgId(serverMsgId);
message.setCarbon(isCarbon);
message.setTime(timestamp);
- if (oob != null) {
- message.setFileParams(new Message.FileParams(oob));
+ if (!attachments.isEmpty()) {
+ message.setFileParams(attachments.iterator().next());
if (CryptoHelper.isPgpEncryptedUrl(message.getFileParams().url)) {
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
}
@@ 768,9 778,21 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
processMessageReceipts(account, packet, remoteMsgId, query);
}
+ if (message.getFileParams() != null) {
+ for (Cid cid : message.getFileParams().getCids()) {
+ File f = mXmppConnectionService.getFileForCid(cid);
+ if (f != null && f.canRead()) {
+ message.setRelativeFilePath(f.getAbsolutePath());
+ mXmppConnectionService.getFileBackend().updateFileParams(message, null, false);
+ break;
+ }
+ }
+ }
+
mXmppConnectionService.databaseBackend.createMessage(message);
+
final HttpConnectionManager manager = this.mXmppConnectionService.getHttpConnectionManager();
- if (message.trusted() && message.treatAsDownloadable() && manager.getAutoAcceptFileSize() > 0) {
+ if (message.getRelativeFilePath() == null && message.trusted() && message.treatAsDownloadable() && manager.getAutoAcceptFileSize() > 0) {
if (message.getOob() != null && message.getOob().getScheme().equalsIgnoreCase("cid")) {
try {
BobTransfer transfer = new BobTransfer.ForMessage(message, mXmppConnectionService);
M src/main/java/eu/siacs/conversations/persistance/FileBackend.java => src/main/java/eu/siacs/conversations/persistance/FileBackend.java +91 -2
@@ 37,10 37,14 @@ import androidx.annotation.StringRes;
import androidx.core.content.FileProvider;
import androidx.exifinterface.media.ExifInterface;
+import com.cheogram.android.BobTransfer;
+
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
+import com.wolt.blurhashkt.BlurHashDecoder;
+
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
@@ 53,11 57,13 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
+import java.nio.ByteBuffer;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
@@ 78,6 84,7 @@ import eu.siacs.conversations.utils.FileUtils;
import eu.siacs.conversations.utils.FileWriterException;
import eu.siacs.conversations.utils.MimeUtils;
import eu.siacs.conversations.xmpp.pep.Avatar;
+import eu.siacs.conversations.xml.Element;
public class FileBackend {
@@ 1000,8 1007,89 @@ public class FileBackend {
}
}
+ public BitmapDrawable getFallbackThumbnail(final Message message, int size) {
+ List<Element> thumbs = message.getFileParams() != null ? message.getFileParams().getThumbnails() : null;
+ if (thumbs != null && !thumbs.isEmpty()) {
+ for (Element thumb : thumbs) {
+ Uri uri = Uri.parse(thumb.getAttribute("uri"));
+ if (uri.getScheme().equals("data")) {
+ String[] parts = uri.getSchemeSpecificPart().split(",", 2);
+ if (parts[0].equals("image/blurhash")) {
+ final LruCache<String, Drawable> cache = mXmppConnectionService.getDrawableCache();
+ BitmapDrawable cached = (BitmapDrawable) cache.get(parts[1]);
+ if (cached != null) return cached;
+
+ int width = message.getFileParams().width;
+ if (width < 1 && thumb.getAttribute("width") != null) width = Integer.parseInt(thumb.getAttribute("width"));
+ if (width < 1) width = 1920;
+
+ int height = message.getFileParams().height;
+ if (height < 1 && thumb.getAttribute("height") != null) height = Integer.parseInt(thumb.getAttribute("height"));
+ if (height < 1) height = 1080;
+ Rect r = rectForSize(width, height, size);
+
+ Bitmap blurhash = BlurHashDecoder.INSTANCE.decode(parts[1], r.width(), r.height(), 1.0f, false);
+ if (blurhash != null) {
+ cached = new BitmapDrawable(blurhash);
+ cache.put(parts[1], cached);
+ return cached;
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
public Drawable getThumbnail(Message message, Resources res, int size, boolean cacheOnly) throws IOException {
- return getThumbnail(getFile(message), res, size, cacheOnly);
+ final LruCache<String, Drawable> cache = mXmppConnectionService.getDrawableCache();
+ DownloadableFile file = getFile(message);
+ Drawable thumbnail = cache.get(file.getAbsolutePath());
+ if (thumbnail != null) return thumbnail;
+
+ if ((thumbnail == null) && (!cacheOnly)) {
+ synchronized (THUMBNAIL_LOCK) {
+ List<Element> thumbs = message.getFileParams() != null ? message.getFileParams().getThumbnails() : null;
+ if (thumbs != null && !thumbs.isEmpty()) {
+ for (Element thumb : thumbs) {
+ Uri uri = Uri.parse(thumb.getAttribute("uri"));
+ if (uri.getScheme().equals("data")) {
+ if (android.os.Build.VERSION.SDK_INT < 28) continue;
+ String[] parts = uri.getSchemeSpecificPart().split(",", 2);
+ byte[] data;
+ if (Arrays.asList(parts[0].split(";")).contains("base64")) {
+ data = Base64.decode(parts[1], 0);
+ } else {
+ data = parts[1].getBytes("UTF-8");
+ }
+
+ ImageDecoder.Source source = ImageDecoder.createSource(ByteBuffer.wrap(data));
+ thumbnail = ImageDecoder.decodeDrawable(source, (decoder, info, src) -> {
+ int w = info.getSize().getWidth();
+ int h = info.getSize().getHeight();
+ Rect r = rectForSize(w, h, size);
+ decoder.setTargetSize(r.width(), r.height());
+ });
+
+ if (thumbnail != null) {
+ cache.put(file.getAbsolutePath(), thumbnail);
+ return thumbnail;
+ }
+ } else if (uri.getScheme().equals("cid")) {
+ Cid cid = BobTransfer.cid(uri);
+ if (cid == null) continue;
+ DownloadableFile f = mXmppConnectionService.getFileForCid(cid);
+ if (f != null && f.canRead()) {
+ return getThumbnail(f, res, size, cacheOnly);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return getThumbnail(file, res, size, cacheOnly);
}
public Drawable getThumbnail(DownloadableFile file, Resources res, int size, boolean cacheOnly) throws IOException {
@@ 1605,7 1693,8 @@ public class FileBackend {
final boolean image =
message.getType() == Message.TYPE_IMAGE
|| (mime != null && mime.startsWith("image/"));
- Message.FileParams fileParams = new Message.FileParams();
+ Message.FileParams fileParams = message.getFileParams();
+ if (fileParams == null) fileParams = new Message.FileParams();
if (url != null) {
fileParams.url = url;
}
M src/main/java/eu/siacs/conversations/ui/XmppActivity.java => src/main/java/eu/siacs/conversations/ui/XmppActivity.java +8 -2
@@ 916,8 916,9 @@ public abstract class XmppActivity extends ActionBarActivity {
imageView.setBackgroundColor(0xff333333);
imageView.setImageDrawable(null);
final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
+ final BitmapDrawable fallbackThumb = xmppConnectionService.getFileBackend().getFallbackThumbnail(message, (int) (metrics.density * 288));
final AsyncDrawable asyncDrawable = new AsyncDrawable(
- getResources(), null, task);
+ getResources(), fallbackThumb != null ? fallbackThumb.getBitmap() : null, task);
imageView.setImageDrawable(asyncDrawable);
try {
task.execute(message);
@@ 995,7 996,12 @@ public abstract class XmppActivity extends ActionBarActivity {
if (!isCancelled()) {
final ImageView imageView = imageViewReference.get();
if (imageView != null) {
- imageView.setImageDrawable(drawable);
+ Drawable old = imageView.getDrawable();
+ if (drawable == null && old instanceof AsyncDrawable) {
+ imageView.setImageDrawable(new BitmapDrawable(((AsyncDrawable) old).getBitmap()));
+ } else {
+ imageView.setImageDrawable(drawable);
+ }
imageView.setBackgroundColor(drawable == null ? 0xff333333 : 0x00000000);
if (Build.VERSION.SDK_INT >= 28 && drawable instanceof AnimatedImageDrawable) {
((AnimatedImageDrawable) drawable).start();
M src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java => src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +64 -13
@@ 341,6 341,13 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
}
+ private void displayInfoMessage(ViewHolder viewHolder, CharSequence text, boolean darkBackground, final Message message, int type) {
+ displayDownloadableMessage(viewHolder, message, "", darkBackground, type);
+ int imageVisibility = viewHolder.image.getVisibility();
+ displayInfoMessage(viewHolder, text, darkBackground);
+ viewHolder.image.setVisibility(imageVisibility);
+ }
+
private void displayInfoMessage(ViewHolder viewHolder, CharSequence text, boolean darkBackground) {
viewHolder.download_button.setVisibility(View.GONE);
viewHolder.audioPlayer.setVisibility(View.GONE);
@@ 586,6 593,46 @@ public class MessageAdapter extends ArrayAdapter<Message> {
private void displayDownloadableMessage(ViewHolder viewHolder, final Message message, String text, final boolean darkBackground, final int type) {
displayTextMessage(viewHolder, message, darkBackground, type);
viewHolder.image.setVisibility(View.GONE);
+ List<Element> thumbs = message.getFileParams() != null ? message.getFileParams().getThumbnails() : null;
+ if (thumbs != null && !thumbs.isEmpty()) {
+ for (Element thumb : thumbs) {
+ Uri uri = Uri.parse(thumb.getAttribute("uri"));
+ if (uri.getScheme().equals("data")) {
+ String[] parts = uri.getSchemeSpecificPart().split(",", 2);
+ parts = parts[0].split(";");
+ if (!parts[0].equals("image/blurhash") && !parts[0].equals("image/jpeg") && !parts[0].equals("image/png") && !parts[0].equals("image/webp") && !parts[0].equals("image/gif")) continue;
+ } else if (uri.getScheme().equals("cid")) {
+ Cid cid = BobTransfer.cid(uri);
+ if (cid == null) continue;
+ DownloadableFile f = activity.xmppConnectionService.getFileForCid(cid);
+ if (f == null || !f.canRead()) {
+ if (!message.trusted() && !message.getConversation().canInferPresence()) continue;
+
+ try {
+ new BobTransfer(BobTransfer.uri(cid), message.getConversation().getAccount(), message.getCounterpart(), activity.xmppConnectionService).start();
+ } catch (final NoSuchAlgorithmException | URISyntaxException e) { }
+ continue;
+ }
+ } else {
+ continue;
+ }
+
+ int width = message.getFileParams().width;
+ if (width < 1 && thumb.getAttribute("width") != null) width = Integer.parseInt(thumb.getAttribute("width"));
+ if (width < 1) width = 1920;
+
+ int height = message.getFileParams().height;
+ if (height < 1 && thumb.getAttribute("height") != null) height = Integer.parseInt(thumb.getAttribute("height"));
+ if (height < 1) height = 1080;
+
+ viewHolder.image.setVisibility(View.VISIBLE);
+ imagePreviewLayout(width, height, viewHolder.image);
+ activity.loadBitmap(message, viewHolder.image);
+ viewHolder.image.setOnClickListener(v -> ConversationFragment.downloadFile(activity, message));
+
+ break;
+ }
+ }
viewHolder.audioPlayer.setVisibility(View.GONE);
viewHolder.download_button.setVisibility(View.VISIBLE);
viewHolder.download_button.setText(text);
@@ 626,27 673,31 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.audioPlayer.setVisibility(View.GONE);
viewHolder.image.setVisibility(View.VISIBLE);
final FileParams params = message.getFileParams();
+ imagePreviewLayout(params.width, params.height, viewHolder.image);
+ activity.loadBitmap(message, viewHolder.image);
+ viewHolder.image.setOnClickListener(v -> openDownloadable(message));
+ }
+
+ private void imagePreviewLayout(int w, int h, ImageView image) {
final float target = activity.getResources().getDimension(R.dimen.image_preview_width);
final int scaledW;
final int scaledH;
- if (Math.max(params.height, params.width) * metrics.density <= target) {
- scaledW = (int) (params.width * metrics.density);
- scaledH = (int) (params.height * metrics.density);
- } else if (Math.max(params.height, params.width) <= target) {
- scaledW = params.width;
- scaledH = params.height;
- } else if (params.width <= params.height) {
- scaledW = (int) (params.width / ((double) params.height / target));
+ if (Math.max(h, w) * metrics.density <= target) {
+ scaledW = (int) (w * metrics.density);
+ scaledH = (int) (h * metrics.density);
+ } else if (Math.max(h, w) <= target) {
+ scaledW = w;
+ scaledH = h;
+ } else if (w <= h) {
+ scaledW = (int) (w / ((double) h / target));
scaledH = (int) target;
} else {
scaledW = (int) target;
- scaledH = (int) (params.height / ((double) params.width / target));
+ scaledH = (int) (h / ((double) w / target));
}
final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(scaledW, scaledH);
layoutParams.setMargins(0, (int) (metrics.density * 4), 0, (int) (metrics.density * 4));
- viewHolder.image.setLayoutParams(layoutParams);
- activity.loadBitmap(message, viewHolder.image);
- viewHolder.image.setOnClickListener(v -> openDownloadable(message));
+ image.setLayoutParams(layoutParams);
}
private void toggleWhisperInfo(ViewHolder viewHolder, final Message message, final boolean darkBackground) {
@@ 881,7 932,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
} else if (transferable != null && transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) {
displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message)), darkBackground, type);
} else {
- displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first, darkBackground);
+ displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first, darkBackground, message, type);
}
} else if (message.isFileOrImage() && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
if (message.getFileParams().width > 0 && message.getFileParams().height > 0) {
M src/main/java/eu/siacs/conversations/xml/Element.java => src/main/java/eu/siacs/conversations/xml/Element.java +5 -2
@@ 1,6 1,7 @@
package eu.siacs.conversations.xml;
import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Ints;
import org.jetbrains.annotations.NotNull;
@@ 70,6 71,8 @@ public class Element implements Node {
}
public void removeChild(Node child) {
+ if (child == null) return;
+
this.childNodes.remove(child);
if (child instanceof Element) this.children.remove(child);
}
@@ 134,13 137,13 @@ public class Element implements Node {
}
public final List<Element> getChildren() {
- return this.children;
+ return ImmutableList.copyOf(this.children);
}
// Deprecated: you probably want bindTo or replaceChildren
public Element setChildren(List<Element> children) {
this.childNodes = new ArrayList(children);
- this.children = children;
+ this.children = new ArrayList(children);
return this;
}