diff --git a/CHANGELOG.md b/CHANGELOG.md index 61980b618..1170e5a25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ ###Changelog +####Version 1.4.5 +* fixes to message parser to not display some ejabberd muc status messages + +####Version 1.4.4 +* added unread count badges on supported devices +* rewrote message parser + ####Version 1.4.0 * send button turns into quick action button to offer faster access to take photo, send location or record audio * visually seperate merged messages diff --git a/build.gradle b/build.gradle index e93f3946a..ca78ddeac 100644 --- a/build.gradle +++ b/build.gradle @@ -45,8 +45,8 @@ android { defaultConfig { minSdkVersion 14 targetSdkVersion 21 - versionCode 75 - versionName "1.4.7" + versionCode 76 + versionName "1.5.0-beta" } compileOptions { diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 779cbbe86..ce91244d8 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -32,6 +32,8 @@ public final class Config { public static final boolean EXTENDED_SM_LOGGING = true; // log stanza counts public static final boolean RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE = true; //setting to true might increase power consumption + public static final boolean ENCRYPT_ON_HTTP_UPLOADED = false; + public static final long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000; public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY / 2; public static final int MAM_MAX_MESSAGES = 500; diff --git a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java index ad5a41323..505c4b0f1 100644 --- a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java +++ b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java @@ -59,9 +59,9 @@ public class PgpEngine { message.setEncryption(Message.ENCRYPTION_DECRYPTED); final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager(); if (message.trusted() - && message.bodyContainsDownloadable() + && message.treatAsDownloadable() != Message.Decision.NEVER && manager.getAutoAcceptFileSize() > 0) { - manager.createNewConnection(message); + manager.createNewDownloadConnection(message); } callback.success(message); } @@ -98,7 +98,7 @@ public class PgpEngine { switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { case OpenPgpApi.RESULT_CODE_SUCCESS: - URL url = message.getImageParams().url; + URL url = message.getFileParams().url; mXmppConnectionService.getFileBackend().updateFileParams(message,url); message.setEncryption(Message.ENCRYPTION_DECRYPTED); PgpEngine.this.mXmppConnectionService @@ -143,11 +143,15 @@ public class PgpEngine { params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message .getConversation().getAccount().getJid().toBareJid().toString()); - if (message.getType() == Message.TYPE_TEXT) { + if (!message.needsUploading()) { params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); - - InputStream is = new ByteArrayInputStream(message.getBody() - .getBytes()); + String body; + if (message.hasFileOnRemoteHost()) { + body = message.getFileParams().url.toString(); + } else { + body = message.getBody(); + } + InputStream is = new ByteArrayInputStream(body.getBytes()); final OutputStream os = new ByteArrayOutputStream(); api.executeApiAsync(params, is, os, new IOpenPgpCallback() { @@ -184,7 +188,7 @@ public class PgpEngine { } } }); - } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) { + } else { try { DownloadableFile inputFile = this.mXmppConnectionService .getFileBackend().getFile(message, true); diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java index 10cd3167e..c95a62df3 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java @@ -185,7 +185,7 @@ public class ScramSha1 extends SaslMechanism { case RESPONSE_SENT: final String clientCalculatedServerFinalMessage = "v=" + Base64.encodeToString(serverSignature, Base64.NO_WRAP); - if (!clientCalculatedServerFinalMessage.equals(new String(Base64.decode(challenge, Base64.DEFAULT)))) { + if (challenge == null || !clientCalculatedServerFinalMessage.equals(new String(Base64.decode(challenge, Base64.DEFAULT)))) { throw new AuthenticationException("Server final message does not match calculated final message"); } state = State.VALID_SERVER_RESPONSE; diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java index 6e6dcb6a0..f472361f2 100644 --- a/src/main/java/eu/siacs/conversations/entities/Account.java +++ b/src/main/java/eu/siacs/conversations/entities/Account.java @@ -44,6 +44,10 @@ public class Account extends AbstractEntity { public static final int OPTION_REGISTER = 2; public static final int OPTION_USECOMPRESSION = 3; + public boolean httpUploadAvailable() { + return xmppConnection != null && xmppConnection.getFeatures().httpUpload(); + } + public static enum State { DISABLED, OFFLINE, diff --git a/src/main/java/eu/siacs/conversations/entities/Downloadable.java b/src/main/java/eu/siacs/conversations/entities/Downloadable.java deleted file mode 100644 index d25bf93a8..000000000 --- a/src/main/java/eu/siacs/conversations/entities/Downloadable.java +++ /dev/null @@ -1,28 +0,0 @@ -package eu.siacs.conversations.entities; - -public interface Downloadable { - - public final String[] VALID_IMAGE_EXTENSIONS = {"webp", "jpeg", "jpg", "png", "jpe"}; - public final String[] VALID_CRYPTO_EXTENSIONS = {"pgp", "gpg", "otr"}; - - public static final int STATUS_UNKNOWN = 0x200; - public static final int STATUS_CHECKING = 0x201; - public static final int STATUS_FAILED = 0x202; - public static final int STATUS_OFFER = 0x203; - public static final int STATUS_DOWNLOADING = 0x204; - public static final int STATUS_DELETED = 0x205; - public static final int STATUS_OFFER_CHECK_FILESIZE = 0x206; - public static final int STATUS_UPLOADING = 0x207; - - public boolean start(); - - public int getStatus(); - - public long getFileSize(); - - public int getProgress(); - - public String getMimeType(); - - public void cancel(); -} diff --git a/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java b/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java index 7c8f95d14..ae9ba1f1d 100644 --- a/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java +++ b/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java @@ -20,6 +20,8 @@ import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import eu.siacs.conversations.Config; +import eu.siacs.conversations.utils.MimeUtils; + import android.util.Log; public class DownloadableFile extends File { @@ -56,16 +58,11 @@ public class DownloadableFile extends File { public String getMimeType() { String path = this.getAbsolutePath(); - try { - String mime = URLConnection.guessContentTypeFromName(path.replace("#","")); - if (mime != null) { - return mime; - } else if (mime == null && path.endsWith(".webp")) { - return "image/webp"; - } else { - return ""; - } - } catch (final StringIndexOutOfBoundsException e) { + int start = path.lastIndexOf('.') + 1; + if (start < path.length()) { + String mime = MimeUtils.guessMimeTypeFromExtension(path.substring(start)); + return mime == null ? "" : mime; + } else { return ""; } } diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index a63d033d2..28ef89df1 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -9,6 +9,7 @@ import java.util.Arrays; import eu.siacs.conversations.Config; import eu.siacs.conversations.utils.GeoHelper; +import eu.siacs.conversations.utils.MimeUtils; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; @@ -69,7 +70,7 @@ public class Message extends AbstractEntity { protected String remoteMsgId = null; protected String serverMsgId = null; protected Conversation conversation = null; - protected Downloadable downloadable = null; + protected Transferable transferable = null; private Message mNextMessage = null; private Message mPreviousMessage = null; @@ -307,12 +308,12 @@ public class Message extends AbstractEntity { this.trueCounterpart = trueCounterpart; } - public Downloadable getDownloadable() { - return this.downloadable; + public Transferable getTransferable() { + return this.transferable; } - public void setDownloadable(Downloadable downloadable) { - this.downloadable = downloadable; + public void setTransferable(Transferable transferable) { + this.transferable = transferable; } public boolean equals(Message message) { @@ -363,8 +364,8 @@ public class Message extends AbstractEntity { public boolean mergeable(final Message message) { return message != null && (message.getType() == Message.TYPE_TEXT && - this.getDownloadable() == null && - message.getDownloadable() == null && + this.getTransferable() == null && + message.getTransferable() == null && message.getEncryption() != Message.ENCRYPTION_PGP && this.getType() == message.getType() && //this.getStatus() == message.getStatus() && @@ -375,8 +376,8 @@ public class Message extends AbstractEntity { (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) && !GeoHelper.isGeoUri(message.getBody()) && !GeoHelper.isGeoUri(this.body) && - !message.bodyContainsDownloadable() && - !this.bodyContainsDownloadable() && + message.treatAsDownloadable() == Decision.NEVER && + this.treatAsDownloadable() == Decision.NEVER && !message.getBody().startsWith(ME_COMMAND) && !this.getBody().startsWith(ME_COMMAND) && !this.bodyIsHeart() && @@ -434,48 +435,97 @@ public class Message extends AbstractEntity { return (status > STATUS_RECEIVED || (contact != null && contact.trusted())); } - public boolean bodyContainsDownloadable() { - /** - * there are a few cases where spaces result in an unwanted behavior, e.g. - * "http://example.com/image.jpg text that will not be shown /abc.png" - * or more than one image link in one message. - */ - if (body.trim().contains(" ")) { + public boolean fixCounterpart() { + Presences presences = conversation.getContact().getPresences(); + if (counterpart != null && presences.has(counterpart.getResourcepart())) { + return true; + } else if (presences.size() >= 1) { + try { + counterpart = Jid.fromParts(conversation.getJid().getLocalpart(), + conversation.getJid().getDomainpart(), + presences.asStringArray()[0]); + return true; + } catch (InvalidJidException e) { + counterpart = null; + return false; + } + } else { + counterpart = null; return false; } + } + + public enum Decision { + MUST, + SHOULD, + NEVER, + } + + private static String extractRelevantExtension(URL url) { + String path = url.getPath(); + if (path == null || path.isEmpty()) { + return null; + } + String filename = path.substring(path.lastIndexOf('/') + 1).toLowerCase(); + String[] extensionParts = filename.split("\\."); + if (extensionParts.length == 2) { + return extensionParts[extensionParts.length - 1]; + } else if (extensionParts.length == 3 && Arrays + .asList(Transferable.VALID_CRYPTO_EXTENSIONS) + .contains(extensionParts[extensionParts.length - 1])) { + return extensionParts[extensionParts.length -2]; + } + return null; + } + + public String getMimeType() { + if (relativeFilePath != null) { + int start = relativeFilePath.lastIndexOf('.') + 1; + if (start < relativeFilePath.length()) { + return MimeUtils.guessMimeTypeFromExtension(relativeFilePath.substring(start)); + } else { + return null; + } + } else { + try { + return MimeUtils.guessMimeTypeFromExtension(extractRelevantExtension(new URL(body.trim()))); + } catch (MalformedURLException e) { + return null; + } + } + } + + public Decision treatAsDownloadable() { + if (body.trim().contains(" ")) { + return Decision.NEVER; + } try { URL url = new URL(body); - if (!url.getProtocol().equalsIgnoreCase("http") - && !url.getProtocol().equalsIgnoreCase("https")) { - return false; + if (!url.getProtocol().equalsIgnoreCase("http") && !url.getProtocol().equalsIgnoreCase("https")) { + return Decision.NEVER; } - - String sUrlPath = url.getPath(); - if (sUrlPath == null || sUrlPath.isEmpty()) { - return false; + String extension = extractRelevantExtension(url); + if (extension == null) { + return Decision.NEVER; } + String ref = url.getRef(); + boolean encrypted = ref != null && ref.matches("([A-Fa-f0-9]{2}){48}"); - int iSlashIndex = sUrlPath.lastIndexOf('/') + 1; - - String sLastUrlPath = sUrlPath.substring(iSlashIndex).toLowerCase(); - - String[] extensionParts = sLastUrlPath.split("\\."); - if (extensionParts.length == 2 - && Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains( - extensionParts[extensionParts.length - 1])) { - return true; - } else if (extensionParts.length == 3 - && Arrays - .asList(Downloadable.VALID_CRYPTO_EXTENSIONS) - .contains(extensionParts[extensionParts.length - 1]) - && Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains( - extensionParts[extensionParts.length - 2])) { - return true; + if (encrypted) { + if (MimeUtils.guessMimeTypeFromExtension(extension) != null) { + return Decision.MUST; + } else { + return Decision.NEVER; + } + } else if (Arrays.asList(Transferable.VALID_IMAGE_EXTENSIONS).contains(extension) + || Arrays.asList(Transferable.WELL_KNOWN_EXTENSIONS).contains(extension)) { + return Decision.SHOULD; } else { - return false; + return Decision.NEVER; } + } catch (MalformedURLException e) { - return false; + return Decision.NEVER; } } @@ -483,74 +533,77 @@ public class Message extends AbstractEntity { return body != null && UIHelper.HEARTS.contains(body.trim()); } - public ImageParams getImageParams() { - ImageParams params = getLegacyImageParams(); + public FileParams getFileParams() { + FileParams params = getLegacyFileParams(); if (params != null) { return params; } - params = new ImageParams(); - if (this.downloadable != null) { - params.size = this.downloadable.getFileSize(); + params = new FileParams(); + if (this.transferable != null) { + params.size = this.transferable.getFileSize(); } if (body == null) { return params; } String parts[] = body.split("\\|"); - if (parts.length == 1) { - try { - params.size = Long.parseLong(parts[0]); - } catch (NumberFormatException e) { - params.origin = parts[0]; + switch (parts.length) { + case 1: + try { + params.size = Long.parseLong(parts[0]); + } catch (NumberFormatException e) { + try { + params.url = new URL(parts[0]); + } catch (MalformedURLException e1) { + params.url = null; + } + } + break; + case 2: + case 4: try { params.url = new URL(parts[0]); } catch (MalformedURLException e1) { params.url = null; } - } - } else if (parts.length == 3) { - try { - params.size = Long.parseLong(parts[0]); - } catch (NumberFormatException e) { - params.size = 0; - } - try { - params.width = Integer.parseInt(parts[1]); - } catch (NumberFormatException e) { - params.width = 0; - } - try { - params.height = Integer.parseInt(parts[2]); - } catch (NumberFormatException e) { - params.height = 0; - } - } else if (parts.length == 4) { - params.origin = parts[0]; - try { - params.url = new URL(parts[0]); - } catch (MalformedURLException e1) { - params.url = null; - } - try { - params.size = Long.parseLong(parts[1]); - } catch (NumberFormatException e) { - params.size = 0; - } - try { - params.width = Integer.parseInt(parts[2]); - } catch (NumberFormatException e) { - params.width = 0; - } - try { - params.height = Integer.parseInt(parts[3]); - } catch (NumberFormatException e) { - params.height = 0; - } + try { + params.size = Long.parseLong(parts[1]); + } catch (NumberFormatException e) { + params.size = 0; + } + try { + params.width = Integer.parseInt(parts[2]); + } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { + params.width = 0; + } + try { + params.height = Integer.parseInt(parts[3]); + } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { + params.height = 0; + } + break; + case 3: + try { + params.size = Long.parseLong(parts[0]); + } catch (NumberFormatException e) { + params.size = 0; + } + try { + params.width = Integer.parseInt(parts[1]); + } catch (NumberFormatException e) { + params.width = 0; + } + try { + params.height = Integer.parseInt(parts[2]); + } catch (NumberFormatException e) { + params.height = 0; + } + break; } return params; } - public ImageParams getLegacyImageParams() { - ImageParams params = new ImageParams(); + public FileParams getLegacyFileParams() { + FileParams params = new FileParams(); if (body == null) { return params; } @@ -586,11 +639,18 @@ public class Message extends AbstractEntity { return type == TYPE_FILE || type == TYPE_IMAGE; } - public class ImageParams { + public boolean hasFileOnRemoteHost() { + return isFileOrImage() && getFileParams().url != null; + } + + public boolean needsUploading() { + return isFileOrImage() && getFileParams().url == null; + } + + public class FileParams { public URL url; public long size = 0; public int width = 0; public int height = 0; - public String origin; } } diff --git a/src/main/java/eu/siacs/conversations/entities/Transferable.java b/src/main/java/eu/siacs/conversations/entities/Transferable.java new file mode 100644 index 000000000..2db6e3c98 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/entities/Transferable.java @@ -0,0 +1,28 @@ +package eu.siacs.conversations.entities; + +public interface Transferable { + + String[] VALID_IMAGE_EXTENSIONS = {"webp", "jpeg", "jpg", "png", "jpe"}; + String[] VALID_CRYPTO_EXTENSIONS = {"pgp", "gpg", "otr"}; + String[] WELL_KNOWN_EXTENSIONS = {"pdf","m4a"}; + + int STATUS_UNKNOWN = 0x200; + int STATUS_CHECKING = 0x201; + int STATUS_FAILED = 0x202; + int STATUS_OFFER = 0x203; + int STATUS_DOWNLOADING = 0x204; + int STATUS_DELETED = 0x205; + int STATUS_OFFER_CHECK_FILESIZE = 0x206; + int STATUS_UPLOADING = 0x207; + + + boolean start(); + + int getStatus(); + + long getFileSize(); + + int getProgress(); + + void cancel(); +} diff --git a/src/main/java/eu/siacs/conversations/entities/DownloadablePlaceholder.java b/src/main/java/eu/siacs/conversations/entities/TransferablePlaceholder.java similarity index 68% rename from src/main/java/eu/siacs/conversations/entities/DownloadablePlaceholder.java rename to src/main/java/eu/siacs/conversations/entities/TransferablePlaceholder.java index 03fceceb7..e065953ef 100644 --- a/src/main/java/eu/siacs/conversations/entities/DownloadablePlaceholder.java +++ b/src/main/java/eu/siacs/conversations/entities/TransferablePlaceholder.java @@ -1,10 +1,10 @@ package eu.siacs.conversations.entities; -public class DownloadablePlaceholder implements Downloadable { +public class TransferablePlaceholder implements Transferable { private int status; - public DownloadablePlaceholder(int status) { + public TransferablePlaceholder(int status) { this.status = status; } @Override @@ -27,11 +27,6 @@ public class DownloadablePlaceholder implements Downloadable { return 0; } - @Override - public String getMimeType() { - return ""; - } - @Override public void cancel() { diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index d7366daa8..47915e3fb 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -6,6 +6,7 @@ import java.util.List; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.PhoneHelper; @@ -102,7 +103,7 @@ public class IqGenerator extends AbstractGenerator { public IqPacket retrieveVcardAvatar(final Avatar avatar) { final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); packet.setTo(avatar.owner); - packet.addChild("vCard","vcard-temp"); + packet.addChild("vCard", "vcard-temp"); return packet; } @@ -194,4 +195,13 @@ public class IqGenerator extends AbstractGenerator { item.setAttribute("role", role); return packet; } + + public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file) { + IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + packet.setTo(host); + Element request = packet.addChild("request",Xmlns.HTTP_UPLOAD); + request.addChild("filename").setContent(file.getName()); + request.addChild("size").setContent(String.valueOf(file.getExpectedSize())); + return packet; + } } diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index 474a3e1dc..bc1148d9a 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -73,7 +73,13 @@ public class MessageGenerator extends AbstractGenerator { packet.addChild("no-copy", "urn:xmpp:hints"); packet.addChild("no-permanent-store", "urn:xmpp:hints"); try { - packet.setBody(otrSession.transformSending(message.getBody())[0]); + String content; + if (message.hasFileOnRemoteHost()) { + content = message.getFileParams().url.toString(); + } else { + content = message.getBody(); + } + packet.setBody(otrSession.transformSending(content)[0]); return packet; } catch (OtrException e) { return null; @@ -86,7 +92,11 @@ public class MessageGenerator extends AbstractGenerator { public MessagePacket generateChat(Message message, boolean addDelay) { MessagePacket packet = preparePacket(message, addDelay); - packet.setBody(message.getBody()); + if (message.hasFileOnRemoteHost()) { + packet.setBody(message.getFileParams().url.toString()); + } else { + packet.setBody(message.getBody()); + } return packet; } @@ -96,13 +106,11 @@ public class MessageGenerator extends AbstractGenerator { public MessagePacket generatePgpChat(Message message, boolean addDelay) { MessagePacket packet = preparePacket(message, addDelay); - packet.setBody("This is an XEP-0027 encryted message"); + packet.setBody("This is an XEP-0027 encrypted message"); if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { - packet.addChild("x", "jabber:x:encrypted").setContent( - message.getEncryptedBody()); + packet.addChild("x", "jabber:x:encrypted").setContent(message.getEncryptedBody()); } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { - packet.addChild("x", "jabber:x:encrypted").setContent( - message.getBody()); + packet.addChild("x", "jabber:x:encrypted").setContent(message.getBody()); } return packet; } diff --git a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java index 9a2a24052..58a6d1e37 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java @@ -1,11 +1,22 @@ package eu.siacs.conversations.http; +import org.apache.http.conn.ssl.StrictHostnameVerifier; + +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.X509TrustManager; + import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.utils.CryptoHelper; public class HttpConnectionManager extends AbstractConnectionManager { @@ -13,16 +24,67 @@ public class HttpConnectionManager extends AbstractConnectionManager { super(service); } - private List connections = new CopyOnWriteArrayList(); + private List downloadConnections = new CopyOnWriteArrayList<>(); + private List uploadConnections = new CopyOnWriteArrayList<>(); - public HttpConnection createNewConnection(Message message) { - HttpConnection connection = new HttpConnection(this); - connection.init(message); - this.connections.add(connection); + public HttpDownloadConnection createNewDownloadConnection(Message message) { + return this.createNewDownloadConnection(message, false); + } + + public HttpDownloadConnection createNewDownloadConnection(Message message, boolean interactive) { + HttpDownloadConnection connection = new HttpDownloadConnection(this); + connection.init(message,interactive); + this.downloadConnections.add(connection); return connection; } - public void finishConnection(HttpConnection connection) { - this.connections.remove(connection); + public HttpUploadConnection createNewUploadConnection(Message message) { + HttpUploadConnection connection = new HttpUploadConnection(this); + connection.init(message); + this.uploadConnections.add(connection); + return connection; + } + + public void finishConnection(HttpDownloadConnection connection) { + this.downloadConnections.remove(connection); + } + + public void finishUploadConnection(HttpUploadConnection httpUploadConnection) { + this.uploadConnections.remove(httpUploadConnection); + } + + public void setupTrustManager(final HttpsURLConnection connection, final boolean interactive) { + final X509TrustManager trustManager; + final HostnameVerifier hostnameVerifier; + if (interactive) { + trustManager = mXmppConnectionService.getMemorizingTrustManager(); + hostnameVerifier = mXmppConnectionService + .getMemorizingTrustManager().wrapHostnameVerifier( + new StrictHostnameVerifier()); + } else { + trustManager = mXmppConnectionService.getMemorizingTrustManager() + .getNonInteractive(); + hostnameVerifier = mXmppConnectionService + .getMemorizingTrustManager() + .wrapHostnameVerifierNonInteractive( + new StrictHostnameVerifier()); + } + try { + final SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(null, new X509TrustManager[]{trustManager}, + mXmppConnectionService.getRNG()); + + final SSLSocketFactory sf = sc.getSocketFactory(); + final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites( + sf.getSupportedCipherSuites()); + if (cipherSuites.length > 0) { + sc.getDefaultSSLParameters().setCipherSuites(cipherSuites); + + } + + connection.setSSLSocketFactory(sf); + connection.setHostnameVerifier(hostnameVerifier); + } catch (final KeyManagementException | NoSuchAlgorithmException ignored) { + } } } diff --git a/src/main/java/eu/siacs/conversations/http/HttpConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java similarity index 63% rename from src/main/java/eu/siacs/conversations/http/HttpConnection.java rename to src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java index e7d309190..6fcd8c870 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java @@ -3,8 +3,7 @@ package eu.siacs.conversations.http; import android.content.Intent; import android.net.Uri; import android.os.SystemClock; - -import org.apache.http.conn.ssl.StrictHostnameVerifier; +import android.util.Log; import java.io.BufferedInputStream; import java.io.IOException; @@ -12,25 +11,20 @@ import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; import java.util.Arrays; -import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; import javax.net.ssl.SSLHandshakeException; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.X509TrustManager; import eu.siacs.conversations.Config; -import eu.siacs.conversations.entities.Downloadable; +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; -public class HttpConnection implements Downloadable { +public class HttpDownloadConnection implements Transferable { private HttpConnectionManager mHttpConnectionManager; private XmppConnectionService mXmppConnectionService; @@ -38,12 +32,12 @@ public class HttpConnection implements Downloadable { private URL mUrl; private Message message; private DownloadableFile file; - private int mStatus = Downloadable.STATUS_UNKNOWN; + private int mStatus = Transferable.STATUS_UNKNOWN; private boolean acceptedAutomatically = false; private int mProgress = 0; private long mLastGuiRefresh = 0; - public HttpConnection(HttpConnectionManager manager) { + public HttpDownloadConnection(HttpConnectionManager manager) { this.mHttpConnectionManager = manager; this.mXmppConnectionService = manager.getXmppConnectionService(); } @@ -63,8 +57,12 @@ public class HttpConnection implements Downloadable { } public void init(Message message) { + init(message, false); + } + + public void init(Message message, boolean interactive) { this.message = message; - this.message.setDownloadable(this); + this.message.setTransferable(this); try { mUrl = new URL(message.getBody()); String[] parts = mUrl.getPath().toLowerCase().split("\\."); @@ -92,7 +90,7 @@ public class HttpConnection implements Downloadable { && this.file.getKey() == null) { this.message.setEncryption(Message.ENCRYPTION_NONE); } - checkFileSize(false); + checkFileSize(true); } catch (MalformedURLException e) { this.cancel(); } @@ -104,7 +102,7 @@ public class HttpConnection implements Downloadable { public void cancel() { mHttpConnectionManager.finishConnection(this); - message.setDownloadable(null); + message.setTransferable(null); mXmppConnectionService.updateConversationUi(); } @@ -112,7 +110,7 @@ public class HttpConnection implements Downloadable { Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); intent.setData(Uri.fromFile(file)); mXmppConnectionService.sendBroadcast(intent); - message.setDownloadable(null); + message.setTransferable(null); mHttpConnectionManager.finishConnection(this); mXmppConnectionService.updateConversationUi(); if (acceptedAutomatically) { @@ -125,42 +123,6 @@ public class HttpConnection implements Downloadable { mXmppConnectionService.updateConversationUi(); } - private void setupTrustManager(final HttpsURLConnection connection, - final boolean interactive) { - final X509TrustManager trustManager; - final HostnameVerifier hostnameVerifier; - if (interactive) { - trustManager = mXmppConnectionService.getMemorizingTrustManager(); - hostnameVerifier = mXmppConnectionService - .getMemorizingTrustManager().wrapHostnameVerifier( - new StrictHostnameVerifier()); - } else { - trustManager = mXmppConnectionService.getMemorizingTrustManager() - .getNonInteractive(); - hostnameVerifier = mXmppConnectionService - .getMemorizingTrustManager() - .wrapHostnameVerifierNonInteractive( - new StrictHostnameVerifier()); - } - try { - final SSLContext sc = SSLContext.getInstance("TLS"); - sc.init(null, new X509TrustManager[]{trustManager}, - mXmppConnectionService.getRNG()); - - final SSLSocketFactory sf = sc.getSocketFactory(); - final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites( - sf.getSupportedCipherSuites()); - if (cipherSuites.length > 0) { - sc.getDefaultSSLParameters().setCipherSuites(cipherSuites); - - } - - connection.setSSLSocketFactory(sf); - connection.setHostnameVerifier(hostnameVerifier); - } catch (final KeyManagementException | NoSuchAlgorithmException ignored) { - } - } - private class FileSizeChecker implements Runnable { private boolean interactive = false; @@ -176,43 +138,46 @@ public class HttpConnection implements Downloadable { size = retrieveFileSize(); } catch (SSLHandshakeException e) { changeStatus(STATUS_OFFER_CHECK_FILESIZE); - HttpConnection.this.acceptedAutomatically = false; - HttpConnection.this.mXmppConnectionService.getNotificationService().push(message); + HttpDownloadConnection.this.acceptedAutomatically = false; + HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message); return; } catch (IOException e) { + Log.d(Config.LOGTAG, "io exception in http file size checker: " + e.getMessage()); + if (interactive) { + mXmppConnectionService.showErrorToastInUi(R.string.file_not_found_on_remote_host); + } cancel(); return; } file.setExpectedSize(size); if (size <= mHttpConnectionManager.getAutoAcceptFileSize()) { - HttpConnection.this.acceptedAutomatically = true; + HttpDownloadConnection.this.acceptedAutomatically = true; new Thread(new FileDownloader(interactive)).start(); } else { changeStatus(STATUS_OFFER); - HttpConnection.this.acceptedAutomatically = false; - HttpConnection.this.mXmppConnectionService.getNotificationService().push(message); + HttpDownloadConnection.this.acceptedAutomatically = false; + HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message); } } - private long retrieveFileSize() throws IOException, - SSLHandshakeException { - changeStatus(STATUS_CHECKING); - HttpURLConnection connection = (HttpURLConnection) mUrl - .openConnection(); - connection.setRequestMethod("HEAD"); - if (connection instanceof HttpsURLConnection) { - setupTrustManager((HttpsURLConnection) connection, interactive); - } - connection.connect(); - String contentLength = connection.getHeaderField("Content-Length"); - if (contentLength == null) { - throw new IOException(); - } - try { - return Long.parseLong(contentLength, 10); - } catch (NumberFormatException e) { - throw new IOException(); - } + private long retrieveFileSize() throws IOException { + Log.d(Config.LOGTAG,"retrieve file size. interactive:"+String.valueOf(interactive)); + changeStatus(STATUS_CHECKING); + HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection(); + connection.setRequestMethod("HEAD"); + if (connection instanceof HttpsURLConnection) { + mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive); + } + connection.connect(); + String contentLength = connection.getHeaderField("Content-Length"); + if (contentLength == null) { + throw new IOException(); + } + try { + return Long.parseLong(contentLength, 10); + } catch (NumberFormatException e) { + throw new IOException(); + } } } @@ -235,19 +200,18 @@ public class HttpConnection implements Downloadable { } catch (SSLHandshakeException e) { changeStatus(STATUS_OFFER); } catch (IOException e) { + mXmppConnectionService.showErrorToastInUi(R.string.file_not_found_on_remote_host); cancel(); } } private void download() throws SSLHandshakeException, IOException { - HttpURLConnection connection = (HttpURLConnection) mUrl - .openConnection(); + HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection(); if (connection instanceof HttpsURLConnection) { - setupTrustManager((HttpsURLConnection) connection, interactive); + mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive); } connection.connect(); - BufferedInputStream is = new BufferedInputStream( - connection.getInputStream()); + BufferedInputStream is = new BufferedInputStream(connection.getInputStream()); file.getParentFile().mkdirs(); file.createNewFile(); OutputStream os = file.createOutputStream(); @@ -269,8 +233,8 @@ public class HttpConnection implements Downloadable { } private void updateImageBounds() { - message.setType(Message.TYPE_IMAGE); - mXmppConnectionService.getFileBackend().updateFileParams(message,mUrl); + message.setType(Message.TYPE_FILE); + mXmppConnectionService.getFileBackend().updateFileParams(message, mUrl); mXmppConnectionService.updateMessage(message); } @@ -302,9 +266,4 @@ public class HttpConnection implements Downloadable { public int getProgress() { return this.mProgress; } - - @Override - public String getMimeType() { - return ""; - } } diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java new file mode 100644 index 000000000..dd8427541 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -0,0 +1,204 @@ +package eu.siacs.conversations.http; + +import android.app.PendingIntent; +import android.util.Log; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; + +import javax.net.ssl.HttpsURLConnection; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Transferable; +import eu.siacs.conversations.entities.DownloadableFile; +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.persistance.FileBackend; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.ui.UiCallback; +import eu.siacs.conversations.utils.CryptoHelper; +import eu.siacs.conversations.utils.Xmlns; +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.OnIqPacketReceived; +import eu.siacs.conversations.xmpp.jid.Jid; +import eu.siacs.conversations.xmpp.stanzas.IqPacket; + +public class HttpUploadConnection implements Transferable { + + private HttpConnectionManager mHttpConnectionManager; + private XmppConnectionService mXmppConnectionService; + + private boolean canceled = false; + private Account account; + private DownloadableFile file; + private Message message; + private URL mGetUrl; + private URL mPutUrl; + + private byte[] key = null; + + private long transmitted = 0; + private long expected = 1; + + public HttpUploadConnection(HttpConnectionManager httpConnectionManager) { + this.mHttpConnectionManager = httpConnectionManager; + this.mXmppConnectionService = httpConnectionManager.getXmppConnectionService(); + } + + @Override + public boolean start() { + return false; + } + + @Override + public int getStatus() { + return STATUS_UPLOADING; + } + + @Override + public long getFileSize() { + return this.file.getExpectedSize(); + } + + @Override + public int getProgress() { + return (int) ((((double) transmitted) / expected) * 100); + } + + @Override + public void cancel() { + this.canceled = true; + } + + private void fail() { + mHttpConnectionManager.finishUploadConnection(this); + message.setTransferable(null); + mXmppConnectionService.markMessage(message,Message.STATUS_SEND_FAILED); + } + + public void init(Message message) { + this.message = message; + message.setTransferable(this); + mXmppConnectionService.markMessage(message,Message.STATUS_UNSEND); + this.account = message.getConversation().getAccount(); + this.file = mXmppConnectionService.getFileBackend().getFile(message, false); + this.file.setExpectedSize(this.file.getSize()); + + if (Config.ENCRYPT_ON_HTTP_UPLOADED) { + this.key = new byte[48]; + mXmppConnectionService.getRNG().nextBytes(this.key); + this.file.setKey(this.key); + } + + Jid host = account.getXmppConnection().findDiscoItemByFeature(Xmlns.HTTP_UPLOAD); + IqPacket request = mXmppConnectionService.getIqGenerator().requestHttpUploadSlot(host,file); + mXmppConnectionService.sendIqPacket(account, request, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + Element slot = packet.findChild("slot",Xmlns.HTTP_UPLOAD); + if (slot != null) { + try { + mGetUrl = new URL(slot.findChildContent("get")); + mPutUrl = new URL(slot.findChildContent("put")); + if (!canceled) { + new Thread(new FileUploader()).start(); + } + } catch (MalformedURLException e) { + fail(); + } + } else { + fail(); + } + } else { + fail(); + } + } + }); + } + + private class FileUploader implements Runnable { + + @Override + public void run() { + this.upload(); + } + + private void upload() { + OutputStream os = null; + InputStream is = null; + HttpURLConnection connection = null; + try { + Log.d(Config.LOGTAG, "uploading to " + mPutUrl.toString()); + connection = (HttpURLConnection) mPutUrl.openConnection(); + if (connection instanceof HttpsURLConnection) { + mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true); + } + connection.setRequestMethod("PUT"); + connection.setFixedLengthStreamingMode((int) file.getExpectedSize()); + connection.setDoOutput(true); + connection.connect(); + os = connection.getOutputStream(); + is = file.createInputStream(); + transmitted = 0; + expected = file.getExpectedSize(); + int count = -1; + byte[] buffer = new byte[4096]; + while (((count = is.read(buffer)) != -1) && !canceled) { + transmitted += count; + os.write(buffer, 0, count); + mXmppConnectionService.updateConversationUi(); + } + os.flush(); + os.close(); + is.close(); + int code = connection.getResponseCode(); + if (code == 200) { + Log.d(Config.LOGTAG, "finished uploading file"); + Message.FileParams params = message.getFileParams(); + if (key != null) { + mGetUrl = new URL(mGetUrl.toString() + "#" + CryptoHelper.bytesToHex(key)); + } + mXmppConnectionService.getFileBackend().updateFileParams(message, mGetUrl); + message.setTransferable(null); + message.setCounterpart(message.getConversation().getJid().toBareJid()); + if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { + mXmppConnectionService.getPgpEngine().encrypt(message, new UiCallback() { + @Override + public void success(Message message) { + mXmppConnectionService.resendMessage(message); + } + + @Override + public void error(int errorCode, Message object) { + fail(); + } + + @Override + public void userInputRequried(PendingIntent pi, Message object) { + fail(); + } + }); + } else { + mXmppConnectionService.resendMessage(message); + } + } else { + fail(); + } + } catch (IOException e) { + Log.d(Config.LOGTAG, e.getMessage()); + fail(); + } finally { + FileBackend.close(is); + FileBackend.close(os); + if (connection != null) { + connection.disconnect(); + } + } + } + } +} diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 864d0667e..d46ff1950 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -361,8 +361,8 @@ public class MessageParser extends AbstractParser implements mXmppConnectionService.databaseBackend.createMessage(message); } final HttpConnectionManager manager = this.mXmppConnectionService.getHttpConnectionManager(); - if (message.trusted() && message.bodyContainsDownloadable() && manager.getAutoAcceptFileSize() > 0) { - manager.createNewConnection(message); + if (message.trusted() && message.treatAsDownloadable() != Message.Decision.NEVER && manager.getAutoAcceptFileSize() > 0) { + manager.createNewDownloadConnection(message); } else if (!message.isRead()) { mXmppConnectionService.getNotificationService().push(message); } diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index e120adbd1..ee53e90e8 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -13,6 +13,7 @@ import java.security.DigestOutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Date; import java.util.Locale; @@ -32,6 +33,7 @@ import android.webkit.MimeTypeMap; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.services.XmppConnectionService; @@ -78,10 +80,10 @@ public class FileBackend { if (path.startsWith("/")) { return new DownloadableFile(path); } else { - if (message.getType() == Message.TYPE_FILE) { + if (Arrays.asList(Transferable.VALID_IMAGE_EXTENSIONS).contains(extension)) { return new DownloadableFile(getConversationsFileDirectory() + path); } else { - return new DownloadableFile(getConversationsImageDirectory()+path); + return new DownloadableFile(getConversationsImageDirectory() + path); } } } @@ -217,7 +219,7 @@ public class FileBackend { long size = file.getSize(); int width = scaledBitmap.getWidth(); int height = scaledBitmap.getHeight(); - message.setBody(Long.toString(size) + ',' + width + ',' + height); + message.setBody(Long.toString(size) + '|' + width + '|' + height); return file; } catch (FileNotFoundException e) { throw new FileCopyException(R.string.error_file_not_found); @@ -497,7 +499,11 @@ public class FileBackend { message.setBody(url.toString()+"|"+Long.toString(file.getSize()) + '|' + imageWidth + '|' + imageHeight); } } else { - message.setBody(Long.toString(file.getSize())); + if (url != null) { + message.setBody(url.toString()+"|"+Long.toString(file.getSize())); + } else { + message.setBody(Long.toString(file.getSize())); + } } } diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index 49543eebc..2b59e4734 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -18,7 +18,6 @@ import android.support.v4.app.NotificationCompat.Builder; import android.support.v4.app.TaskStackBuilder; import android.text.Html; import android.util.DisplayMetrics; -import android.util.Log; import org.json.JSONArray; import org.json.JSONObject; @@ -42,7 +41,6 @@ import eu.siacs.conversations.ui.ManageAccountActivity; import eu.siacs.conversations.ui.TimePreference; import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.UIHelper; -import eu.siacs.conversations.xmpp.XmppConnection; public class NotificationService { @@ -303,7 +301,7 @@ public class NotificationService { final ArrayList tmp = new ArrayList<>(); for (final Message msg : messages) { if (msg.getType() == Message.TYPE_TEXT - && msg.getDownloadable() == null) { + && msg.getTransferable() == null) { tmp.add(msg); } } @@ -335,7 +333,7 @@ public class NotificationService { private Message getImage(final Iterable messages) { for (final Message message : messages) { if (message.getType() == Message.TYPE_IMAGE - && message.getDownloadable() == null + && message.getTransferable() == null && message.getEncryption() != Message.ENCRYPTION_PGP) { return message; } @@ -346,7 +344,7 @@ public class NotificationService { private Message getFirstDownloadableMessage(final Iterable messages) { for (final Message message : messages) { if ((message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) && - message.getDownloadable() != null) { + message.getTransferable() != null) { return message; } } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 17c344c20..2dd0f04cd 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -28,6 +28,7 @@ import android.util.LruCache; import net.java.otr4j.OtrException; import net.java.otr4j.session.Session; import net.java.otr4j.session.SessionID; +import net.java.otr4j.session.SessionImpl; import net.java.otr4j.session.SessionStatus; import org.openintents.openpgp.util.OpenPgpApi; @@ -56,12 +57,11 @@ import eu.siacs.conversations.entities.Blockable; import eu.siacs.conversations.entities.Bookmark; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; -import eu.siacs.conversations.entities.Downloadable; -import eu.siacs.conversations.entities.DownloadablePlaceholder; +import eu.siacs.conversations.entities.Transferable; +import eu.siacs.conversations.entities.TransferablePlaceholder; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.MucOptions.OnRenameListener; -import eu.siacs.conversations.entities.Presences; import eu.siacs.conversations.generator.IqGenerator; import eu.siacs.conversations.generator.MessageGenerator; import eu.siacs.conversations.generator.PresenceGenerator; @@ -233,6 +233,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this); private OnConversationUpdate mOnConversationUpdate = null; private int convChangedListenerCount = 0; + private OnShowErrorToast mOnShowErrorToast = null; + private int showErrorToastListenerCount = 0; private int unreadCount = -1; private OnAccountUpdate mOnAccountUpdate = null; private OnStatusChanged statusListener = new OnStatusChanged() { @@ -390,7 +392,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa callback.success(message); } } catch (FileBackend.FileCopyException e) { - callback.error(e.getResId(),message); + callback.error(e.getResId(), message); } } }); @@ -630,7 +632,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } Context context = getApplicationContext(); AlarmManager alarmManager = (AlarmManager) context - .getSystemService(Context.ALARM_SERVICE); + .getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(context, EventReceiver.class); alarmManager.cancel(PendingIntent.getBroadcast(context, 0, intent, 0)); Log.d(Config.LOGTAG, "good bye"); @@ -672,114 +674,138 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } - public void sendMessage(final Message message) { + private void sendFileMessage(final Message message) { + Log.d(Config.LOGTAG, "send file message"); final Account account = message.getConversation().getAccount(); + final XmppConnection connection = account.getXmppConnection(); + if (connection != null && connection.getFeatures().httpUpload()) { + mHttpConnectionManager.createNewUploadConnection(message); + } else { + mJingleConnectionManager.createNewConnection(message); + } + } + + public void sendMessage(final Message message) { + sendMessage(message, false); + } + + private void sendMessage(final Message message, final boolean resend) { + final Account account = message.getConversation().getAccount(); + final Conversation conversation = message.getConversation(); account.deactivateGracePeriod(); - final Conversation conv = message.getConversation(); MessagePacket packet = null; boolean saveInDb = true; - boolean send = false; - if (account.getStatus() == Account.State.ONLINE - && account.getXmppConnection() != null) { - if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) { - if (message.getCounterpart() != null) { - if (message.getEncryption() == Message.ENCRYPTION_OTR) { - if (!conv.hasValidOtrSession()) { - conv.startOtrSession(message.getCounterpart().getResourcepart(),true); - message.setStatus(Message.STATUS_WAITING); - } else if (conv.hasValidOtrSession() - && conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) { - mJingleConnectionManager - .createNewConnection(message); - } - } else { - mJingleConnectionManager.createNewConnection(message); - } - } else { - if (message.getEncryption() == Message.ENCRYPTION_OTR) { - conv.startOtrIfNeeded(); - } - message.setStatus(Message.STATUS_WAITING); - } - } else { - if (message.getEncryption() == Message.ENCRYPTION_OTR) { - if (!conv.hasValidOtrSession() && (message.getCounterpart() != null)) { - conv.startOtrSession(message.getCounterpart().getResourcepart(), true); - message.setStatus(Message.STATUS_WAITING); - } else if (conv.hasValidOtrSession()) { - if (conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) { - packet = mMessageGenerator.generateOtrChat(message); - send = true; - } else { - message.setStatus(Message.STATUS_WAITING); - conv.startOtrIfNeeded(); - } - } else { - message.setStatus(Message.STATUS_WAITING); - } - } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { - message.getConversation().endOtrIfNeeded(); - message.getConversation().findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() { - @Override - public void onMessageFound(Message message) { - markMessage(message,Message.STATUS_SEND_FAILED); - } - }); - packet = mMessageGenerator.generatePgpChat(message); - send = true; - } else { - message.getConversation().endOtrIfNeeded(); - message.getConversation().findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() { - @Override - public void onMessageFound(Message message) { - markMessage(message,Message.STATUS_SEND_FAILED); - } - }); - packet = mMessageGenerator.generateChat(message); - send = true; - } - } - if (!account.getXmppConnection().getFeatures().sm() - && conv.getMode() != Conversation.MODE_MULTI) { - message.setStatus(Message.STATUS_SEND); - } - } else { - message.setStatus(Message.STATUS_WAITING); - if (message.getType() == Message.TYPE_TEXT) { - if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { - String pgpBody = message.getEncryptedBody(); - String decryptedBody = message.getBody(); - message.setBody(pgpBody); - message.setEncryption(Message.ENCRYPTION_PGP); - databaseBackend.createMessage(message); - saveInDb = false; - message.setBody(decryptedBody); - message.setEncryption(Message.ENCRYPTION_DECRYPTED); - } else if (message.getEncryption() == Message.ENCRYPTION_OTR) { - if (!conv.hasValidOtrSession() - && message.getCounterpart() != null) { - conv.startOtrSession(message.getCounterpart().getResourcepart(), false); - } - } - } + message.setStatus(Message.STATUS_WAITING); + if (!resend && message.getEncryption() != Message.ENCRYPTION_OTR) { + message.getConversation().endOtrIfNeeded(); + message.getConversation().findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() { + @Override + public void onMessageFound(Message message) { + markMessage(message,Message.STATUS_SEND_FAILED); + } + }); } - conv.add(message); - if (saveInDb) { - if (message.getEncryption() == Message.ENCRYPTION_NONE - || saveEncryptedMessages()) { - databaseBackend.createMessage(message); + + if (account.isOnlineAndConnected()) { + switch (message.getEncryption()) { + case Message.ENCRYPTION_NONE: + if (message.needsUploading()) { + if (account.httpUploadAvailable() || message.fixCounterpart()) { + this.sendFileMessage(message); + } else { + break; + } + } else { + packet = mMessageGenerator.generateChat(message,resend); } + break; + case Message.ENCRYPTION_PGP: + case Message.ENCRYPTION_DECRYPTED: + if (message.needsUploading()) { + if (account.httpUploadAvailable() || message.fixCounterpart()) { + this.sendFileMessage(message); + } else { + break; + } + } else { + packet = mMessageGenerator.generatePgpChat(message,resend); + } + break; + case Message.ENCRYPTION_OTR: + SessionImpl otrSession = conversation.getOtrSession(); + if (otrSession != null && otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) { + try { + message.setCounterpart(Jid.fromSessionID(otrSession.getSessionID())); + } catch (InvalidJidException e) { + break; + } + if (message.needsUploading()) { + mJingleConnectionManager.createNewConnection(message); + } else { + packet = mMessageGenerator.generateOtrChat(message,resend); + } + } else if (otrSession == null) { + if (message.fixCounterpart()) { + conversation.startOtrSession(message.getCounterpart().getResourcepart(), true); + } else { + break; + } + } + break; + } + if (packet != null) { + if (account.getXmppConnection().getFeatures().sm() || conversation.getMode() == Conversation.MODE_MULTI) { + message.setStatus(Message.STATUS_UNSEND); + } else { + message.setStatus(Message.STATUS_SEND); + } + } + } else { + switch(message.getEncryption()) { + case Message.ENCRYPTION_DECRYPTED: + if (!message.needsUploading()) { + String pgpBody = message.getEncryptedBody(); + String decryptedBody = message.getBody(); + message.setBody(pgpBody); + message.setEncryption(Message.ENCRYPTION_PGP); + databaseBackend.createMessage(message); + saveInDb = false; + message.setBody(decryptedBody); + message.setEncryption(Message.ENCRYPTION_DECRYPTED); + } + break; + case Message.ENCRYPTION_OTR: + if (!conversation.hasValidOtrSession() && message.getCounterpart() != null) { + conversation.startOtrSession(message.getCounterpart().getResourcepart(), false); + } + break; + } } - if ((send) && (packet != null)) { - if (conv.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) { + + if (resend) { + if (packet != null) { + if (account.getXmppConnection().getFeatures().sm() || conversation.getMode() == Conversation.MODE_MULTI) { + markMessage(message,Message.STATUS_UNSEND); + } else { + markMessage(message,Message.STATUS_SEND); + } + } + } else { + conversation.add(message); + if (saveInDb && (message.getEncryption() == Message.ENCRYPTION_NONE || saveEncryptedMessages())) { + databaseBackend.createMessage(message); + } + updateConversationUi(); + } + if (packet != null) { + if (conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) { if (this.sendChatStates()) { - packet.addChild(ChatState.toElement(conv.getOutgoingChatState())); + packet.addChild(ChatState.toElement(conversation.getOutgoingChatState())); } } sendMessagePacket(account, packet); } - updateConversationUi(); } private void sendUnsentMessages(final Conversation conversation) { @@ -792,77 +818,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa }); } - private void resendMessage(final Message message) { - Account account = message.getConversation().getAccount(); - MessagePacket packet = null; - if (message.getEncryption() == Message.ENCRYPTION_OTR) { - Presences presences = message.getConversation().getContact() - .getPresences(); - if (!message.getConversation().hasValidOtrSession()) { - if ((message.getCounterpart() != null) - && (presences.has(message.getCounterpart().getResourcepart()))) { - message.getConversation().startOtrSession(message.getCounterpart().getResourcepart(), true); - } else { - if (presences.size() == 1) { - String presence = presences.asStringArray()[0]; - message.getConversation().startOtrSession(presence, true); - } - } - } else { - if (message.getConversation().getOtrSession() - .getSessionStatus() == SessionStatus.ENCRYPTED) { - try { - message.setCounterpart(Jid.fromSessionID(message.getConversation().getOtrSession().getSessionID())); - if (message.getType() == Message.TYPE_TEXT) { - packet = mMessageGenerator.generateOtrChat(message, - true); - } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) { - mJingleConnectionManager.createNewConnection(message); - } - } catch (final InvalidJidException ignored) { - - } - } - } - } else if (message.getType() == Message.TYPE_TEXT) { - if (message.getEncryption() == Message.ENCRYPTION_NONE) { - packet = mMessageGenerator.generateChat(message, true); - } else if ((message.getEncryption() == Message.ENCRYPTION_DECRYPTED) - || (message.getEncryption() == Message.ENCRYPTION_PGP)) { - packet = mMessageGenerator.generatePgpChat(message, true); - } - } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) { - Contact contact = message.getConversation().getContact(); - Presences presences = contact.getPresences(); - if ((message.getCounterpart() != null) - && (presences.has(message.getCounterpart().getResourcepart()))) { - mJingleConnectionManager.createNewConnection(message); - } else { - if (presences.size() == 1) { - String presence = presences.asStringArray()[0]; - try { - message.setCounterpart(Jid.fromParts(contact.getJid().getLocalpart(), contact.getJid().getDomainpart(), presence)); - } catch (InvalidJidException e) { - return; - } - mJingleConnectionManager.createNewConnection(message); - } - } - } - if (packet != null) { - if (!account.getXmppConnection().getFeatures().sm() - && message.getConversation().getMode() != Conversation.MODE_MULTI) { - markMessage(message, Message.STATUS_SEND); - } else { - markMessage(message, Message.STATUS_UNSEND); - } - if (message.getConversation().setOutgoingChatState(Config.DEFAULT_CHATSTATE)) { - if (this.sendChatStates()) { - packet.addChild(ChatState.toElement(message.getConversation().getOutgoingChatState())); - } - } - sendMessagePacket(account, packet); - } + public void resendMessage(final Message message) { + sendMessage(message, true); } public void fetchRosterFromServer(final Account account) { @@ -1017,7 +974,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa @Override public void onMessageFound(Message message) { if (!getFileBackend().isFileAvailable(message)) { - message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_DELETED)); + message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); } } }); @@ -1028,7 +985,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa Message message = conversation.findMessageWithFileAndUuid(uuid); if (message != null) { if (!getFileBackend().isFileAvailable(message)) { - message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_DELETED)); + message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); updateConversationUi(); } return; @@ -1040,13 +997,14 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa populateWithOrderedConversations(list, true); } - public void populateWithOrderedConversations(final List list, boolean includeConferences) { + public void populateWithOrderedConversations(final List list, boolean includeNoFileUpload) { list.clear(); - if (includeConferences) { + if (includeNoFileUpload) { list.addAll(getConversations()); } else { for (Conversation conversation : getConversations()) { - if (conversation.getMode() == Conversation.MODE_SINGLE) { + if (conversation.getMode() == Conversation.MODE_SINGLE + || conversation.getAccount().httpUploadAvailable()) { list.add(conversation); } } @@ -1284,6 +1242,32 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } + public void setOnShowErrorToastListener(OnShowErrorToast onShowErrorToast) { + synchronized (this) { + if (checkListeners()) { + switchToForeground(); + } + this.mOnShowErrorToast = onShowErrorToast; + if (this.showErrorToastListenerCount < 2) { + this.showErrorToastListenerCount++; + } + } + this.mOnShowErrorToast = onShowErrorToast; + } + + public void removeOnShowErrorToastListener() { + synchronized (this) { + this.showErrorToastListenerCount--; + if (this.showErrorToastListenerCount <= 0) { + this.showErrorToastListenerCount = 0; + this.mOnShowErrorToast = null; + if (checkListeners()) { + switchToBackground(); + } + } + } + } + public void setOnAccountListChangedListener(OnAccountUpdate listener) { synchronized (this) { if (checkListeners()) { @@ -1388,7 +1372,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return (this.mOnAccountUpdate == null && this.mOnConversationUpdate == null && this.mOnRosterUpdate == null - && this.mOnUpdateBlocklist == null); + && this.mOnUpdateBlocklist == null + && this.mOnShowErrorToast == null); } private void switchToForeground() { @@ -1810,15 +1795,15 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } catch (InvalidJidException e) { return; } - if (message.getType() == Message.TYPE_TEXT) { + if (message.needsUploading()) { + mJingleConnectionManager.createNewConnection(message); + } else { MessagePacket outPacket = mMessageGenerator.generateOtrChat(message, true); if (outPacket != null) { message.setStatus(Message.STATUS_SEND); databaseBackend.updateMessage(message); sendMessagePacket(account, outPacket); } - } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) { - mJingleConnectionManager.createNewConnection(message); } updateConversationUi(); } @@ -2239,6 +2224,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return count; } + + public void showErrorToastInUi(int resId) { + if (mOnShowErrorToast != null) { + mOnShowErrorToast.onShowErrorToast(resId); + } + } + public void updateConversationUi() { if (mOnConversationUpdate != null) { mOnConversationUpdate.onConversationUpdate(); @@ -2572,6 +2564,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public void onPushFailed(); } + public interface OnShowErrorToast { + void onShowErrorToast(int resId); + } + public class XmppConnectionBinder extends Binder { public XmppConnectionService getService() { return XmppConnectionService.this; diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index c48b5865c..a507a5fe5 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -35,10 +35,12 @@ import java.util.Iterator; import java.util.List; import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Blockable; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdate; import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate; @@ -47,7 +49,7 @@ import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; public class ConversationActivity extends XmppActivity - implements OnAccountUpdate, OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist { + implements OnAccountUpdate, OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast { public static final String ACTION_DOWNLOAD = "eu.siacs.conversations.action.DOWNLOAD"; @@ -382,7 +384,7 @@ public class ConversationActivity extends XmppActivity } if (this.getSelectedConversation().getMode() == Conversation.MODE_MULTI) { menuContactDetails.setVisible(false); - menuAttach.setVisible(false); + menuAttach.setVisible(getSelectedConversation().getAccount().httpUploadAvailable()); menuInviteContact.setVisible(getSelectedConversation().getMucOptions().canInvite()); } else { menuMucDetails.setVisible(false); @@ -398,6 +400,8 @@ public class ConversationActivity extends XmppActivity } private void selectPresenceToAttachFile(final int attachmentChoice, final int encryption) { + final Conversation conversation = getSelectedConversation(); + final Account account = conversation.getAccount(); final OnPresenceSelected callback = new OnPresenceSelected() { @Override @@ -449,11 +453,11 @@ public class ConversationActivity extends XmppActivity } } }; - if (attachmentChoice == ATTACHMENT_CHOICE_LOCATION && encryption != Message.ENCRYPTION_OTR) { - getSelectedConversation().setNextCounterpart(null); + if ((account.httpUploadAvailable() || attachmentChoice == ATTACHMENT_CHOICE_LOCATION) && encryption != Message.ENCRYPTION_OTR) { + conversation.setNextCounterpart(null); callback.onPresenceSelected(); } else { - selectPresence(getSelectedConversation(),callback); + selectPresence(conversation,callback); } } @@ -1268,4 +1272,14 @@ public class ConversationActivity extends XmppActivity public boolean enterIsSend() { return getPreferences().getBoolean("enter_is_send",false); } + + @Override + public void onShowErrorToast(final int resId) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(ConversationActivity.this,resId,Toast.LENGTH_SHORT).show(); + } + }); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index a817b27bf..7983fdca7 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -34,7 +34,6 @@ import android.widget.Toast; import net.java.otr4j.session.SessionStatus; -import java.net.URLConnection; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; @@ -46,9 +45,9 @@ import eu.siacs.conversations.crypto.PgpEngine; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; -import eu.siacs.conversations.entities.Downloadable; +import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.DownloadableFile; -import eu.siacs.conversations.entities.DownloadablePlaceholder; +import eu.siacs.conversations.entities.TransferablePlaceholder; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.Presences; @@ -437,34 +436,36 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa MenuItem shareWith = menu.findItem(R.id.share_with); MenuItem sendAgain = menu.findItem(R.id.send_again); MenuItem copyUrl = menu.findItem(R.id.copy_url); - MenuItem downloadImage = menu.findItem(R.id.download_image); + MenuItem downloadFile = menu.findItem(R.id.download_file); MenuItem cancelTransmission = menu.findItem(R.id.cancel_transmission); - if ((m.getType() != Message.TYPE_TEXT && m.getType() != Message.TYPE_PRIVATE) - || m.getDownloadable() != null || GeoHelper.isGeoUri(m.getBody())) { - copyText.setVisible(false); + if ((m.getType() == Message.TYPE_TEXT || m.getType() == Message.TYPE_PRIVATE) + && m.getTransferable() == null + && !GeoHelper.isGeoUri(m.getBody()) + && m.treatAsDownloadable() != Message.Decision.MUST) { + copyText.setVisible(true); } - if ((m.getType() == Message.TYPE_TEXT - || m.getType() == Message.TYPE_PRIVATE - || m.getDownloadable() != null) - && (!GeoHelper.isGeoUri(m.getBody()))) { - shareWith.setVisible(false); + if ((m.getType() != Message.TYPE_TEXT + && m.getType() != Message.TYPE_PRIVATE + && m.getTransferable() == null) + || (GeoHelper.isGeoUri(m.getBody()))) { + shareWith.setVisible(true); } - if (m.getStatus() != Message.STATUS_SEND_FAILED) { - sendAgain.setVisible(false); + if (m.getStatus() == Message.STATUS_SEND_FAILED) { + sendAgain.setVisible(true); } - if (((m.getType() != Message.TYPE_IMAGE && m.getDownloadable() == null) - || m.getImageParams().url == null) && !GeoHelper.isGeoUri(m.getBody())) { - copyUrl.setVisible(false); + if (m.hasFileOnRemoteHost() + || GeoHelper.isGeoUri(m.getBody()) + || m.treatAsDownloadable() == Message.Decision.MUST) { + copyUrl.setVisible(true); } - if (m.getType() != Message.TYPE_TEXT - || m.getDownloadable() != null - || !m.bodyContainsDownloadable()) { - downloadImage.setVisible(false); + if (m.getType() == Message.TYPE_TEXT && m.getTransferable() == null && m.treatAsDownloadable() != Message.Decision.NEVER) { + downloadFile.setVisible(true); + downloadFile.setTitle(activity.getString(R.string.download_x_file,UIHelper.getFileDescriptionString(activity, m))); } - if (!((m.getDownloadable() != null && !(m.getDownloadable() instanceof DownloadablePlaceholder)) + if ((m.getTransferable() != null && !(m.getTransferable() instanceof TransferablePlaceholder)) || (m.isFileOrImage() && (m.getStatus() == Message.STATUS_WAITING - || m.getStatus() == Message.STATUS_OFFERED)))) { - cancelTransmission.setVisible(false); + || m.getStatus() == Message.STATUS_OFFERED))) { + cancelTransmission.setVisible(true); } } } @@ -484,8 +485,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa case R.id.copy_url: copyUrl(selectedMessage); return true; - case R.id.download_image: - downloadImage(selectedMessage); + case R.id.download_file: + downloadFile(selectedMessage); return true; case R.id.cancel_transmission: cancelTransmission(selectedMessage); @@ -506,8 +507,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa activity.xmppConnectionService.getFileBackend() .getJingleFileUri(message)); shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - String path = message.getRelativeFilePath(); - String mime = path == null ? null : URLConnection.guessContentTypeFromName(path); + String mime = message.getMimeType(); if (mime == null) { mime = "image/webp"; } @@ -529,7 +529,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message); if (!file.exists()) { Toast.makeText(activity, R.string.file_deleted, Toast.LENGTH_SHORT).show(); - message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_DELETED)); + message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); return; } } @@ -542,9 +542,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (GeoHelper.isGeoUri(message.getBody())) { resId = R.string.location; url = message.getBody(); + } else if (message.hasFileOnRemoteHost()) { + resId = R.string.file_url; + url = message.getFileParams().url.toString(); } else { - resId = R.string.image_url; - url = message.getImageParams().url.toString(); + url = message.getBody().trim(); + resId = R.string.file_url; } if (activity.copyTextToClipboard(url, resId)) { Toast.makeText(activity, R.string.url_copied_to_clipboard, @@ -552,15 +555,15 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } } - private void downloadImage(Message message) { + private void downloadFile(Message message) { activity.xmppConnectionService.getHttpConnectionManager() - .createNewConnection(message); + .createNewDownloadConnection(message); } private void cancelTransmission(Message message) { - Downloadable downloadable = message.getDownloadable(); - if (downloadable != null) { - downloadable.cancel(); + Transferable transferable = message.getTransferable(); + if (transferable != null) { + transferable.cancel(); } else { activity.xmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); } @@ -754,7 +757,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (message.getEncryption() == Message.ENCRYPTION_PGP && (message.getStatus() == Message.STATUS_RECEIVED || message .getStatus() >= Message.STATUS_SEND) - && message.getDownloadable() == null) { + && message.getTransferable() == null) { if (!mEncryptedMessages.contains(message)) { mEncryptedMessages.add(message); } @@ -912,7 +915,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa final SendButtonAction action; final int status; final boolean empty = this.mEditMessage == null || this.mEditMessage.getText().length() == 0; - if (c.getMode() == Conversation.MODE_MULTI) { + final boolean conference = c.getMode() == Conversation.MODE_MULTI; + if (conference && !c.getAccount().httpUploadAvailable()) { if (empty && c.getNextCounterpart() != null) { action = SendButtonAction.CANCEL; } else { @@ -920,28 +924,32 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } } else { if (empty) { - String setting = activity.getPreferences().getString("quick_action","recent"); - if (!setting.equals("none") && UIHelper.receivedLocationQuestion(conversation.getLatestMessage())) { - setting = "location"; - } else if (setting.equals("recent")) { - setting = activity.getPreferences().getString("recently_used_quick_action","text"); - } - switch (setting) { - case "photo": - action = SendButtonAction.TAKE_PHOTO; - break; - case "location": - action = SendButtonAction.SEND_LOCATION; - break; - case "voice": - action = SendButtonAction.RECORD_VOICE; - break; - case "picture": - action = SendButtonAction.CHOOSE_PICTURE; - break; - default: - action = SendButtonAction.TEXT; - break; + if (conference && c.getNextCounterpart() != null) { + action = SendButtonAction.CANCEL; + } else { + String setting = activity.getPreferences().getString("quick_action", "recent"); + if (!setting.equals("none") && UIHelper.receivedLocationQuestion(conversation.getLatestMessage())) { + setting = "location"; + } else if (setting.equals("recent")) { + setting = activity.getPreferences().getString("recently_used_quick_action", "text"); + } + switch (setting) { + case "photo": + action = SendButtonAction.TAKE_PHOTO; + break; + case "location": + action = SendButtonAction.SEND_LOCATION; + break; + case "voice": + action = SendButtonAction.RECORD_VOICE; + break; + case "picture": + action = SendButtonAction.CHOOSE_PICTURE; + break; + default: + action = SendButtonAction.TEXT; + break; + } } } else { action = SendButtonAction.TEXT; diff --git a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java index 200a577e7..351f1dfc2 100644 --- a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java @@ -4,7 +4,6 @@ import android.app.PendingIntent; import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -13,10 +12,7 @@ import android.widget.AdapterView.OnItemClickListener; import android.widget.ListView; import android.widget.Toast; -import java.io.UnsupportedEncodingException; import java.net.URLConnection; -import java.net.URLDecoder; -import java.nio.charset.UnsupportedCharsetException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -66,18 +62,17 @@ public class ShareWithActivity extends XmppActivity { } }; - protected void onActivityResult(int requestCode, int resultCode, - final Intent data) { + protected void onActivityResult(int requestCode, int resultCode, final Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_START_NEW_CONVERSATION && resultCode == RESULT_OK) { share.contact = data.getStringExtra("contact"); share.account = data.getStringExtra("account"); - Log.d(Config.LOGTAG, "contact: " + share.contact + " account:" - + share.account); } - if (xmppConnectionServiceBound && share != null - && share.contact != null && share.account != null) { + if (xmppConnectionServiceBound + && share != null + && share.contact != null + && share.account != null) { share(); } } @@ -101,13 +96,8 @@ public class ShareWithActivity extends XmppActivity { mListView.setOnItemClickListener(new OnItemClickListener() { @Override - public void onItemClick(AdapterView arg0, View arg1, - int position, long arg3) { - Conversation conversation = mConversations.get(position); - if (conversation.getMode() == Conversation.MODE_SINGLE - || share.uris.size() == 0) { - share(mConversations.get(position)); - } + public void onItemClick(AdapterView arg0, View arg1, int position, long arg3) { + share(mConversations.get(position)); } }); @@ -123,11 +113,10 @@ public class ShareWithActivity extends XmppActivity { @Override public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { - case R.id.action_add: - final Intent intent = new Intent(getApplicationContext(), - ChooseContactActivity.class); - startActivityForResult(intent, REQUEST_START_NEW_CONVERSATION); - return true; + case R.id.action_add: + final Intent intent = new Intent(getApplicationContext(), ChooseContactActivity.class); + startActivityForResult(intent, REQUEST_START_NEW_CONVERSATION); + return true; } return super.onOptionsItemSelected(item); } @@ -157,7 +146,7 @@ public class ShareWithActivity extends XmppActivity { this.share.uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); } if (xmppConnectionServiceBound) { - xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.image); + xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uris.size() == 0); } } @@ -183,28 +172,28 @@ public class ShareWithActivity extends XmppActivity { } private void share() { - Account account; - try { - account = xmppConnectionService.findAccountByJid(Jid.fromString(share.account)); - } catch (final InvalidJidException e) { - account = null; - } - if (account == null) { + Account account; + try { + account = xmppConnectionService.findAccountByJid(Jid.fromString(share.account)); + } catch (final InvalidJidException e) { + account = null; + } + if (account == null) { return; } - final Conversation conversation; - try { - conversation = xmppConnectionService - .findOrCreateConversation(account, Jid.fromString(share.contact), false); - } catch (final InvalidJidException e) { - return; - } - share(conversation); + final Conversation conversation; + try { + conversation = xmppConnectionService + .findOrCreateConversation(account, Jid.fromString(share.contact), false); + } catch (final InvalidJidException e) { + return; + } + share(conversation); } private void share(final Conversation conversation) { if (share.uris.size() != 0) { - selectPresence(conversation, new OnPresenceSelected() { + OnPresenceSelected callback = new OnPresenceSelected() { @Override public void onPresenceSelected() { if (share.image) { @@ -227,7 +216,12 @@ public class ShareWithActivity extends XmppActivity { switchToConversation(conversation, null, true); finish(); } - }); + }; + if (conversation.getAccount().httpUploadAvailable()) { + callback.onPresenceSelected(); + } else { + selectPresence(conversation, callback); + } } else { switchToConversation(conversation, this.share.text, true); finish(); diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 934c696f0..ddad1e309 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -284,6 +284,9 @@ public abstract class XmppActivity extends Activity { if (this instanceof OnUpdateBlocklist) { this.xmppConnectionService.setOnUpdateBlocklistListener((OnUpdateBlocklist) this); } + if (this instanceof XmppConnectionService.OnShowErrorToast) { + this.xmppConnectionService.setOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this); + } } protected void unregisterListeners() { @@ -302,6 +305,9 @@ public abstract class XmppActivity extends Activity { if (this instanceof OnUpdateBlocklist) { this.xmppConnectionService.removeOnUpdateBlocklistListener(); } + if (this instanceof XmppConnectionService.OnShowErrorToast) { + this.xmppConnectionService.removeOnShowErrorToastListener(); + } } @Override @@ -447,14 +453,11 @@ public abstract class XmppActivity extends Activity { @Override public void success(Account account) { - xmppConnectionService.databaseBackend - .updateAccount(account); + xmppConnectionService.databaseBackend.updateAccount(account); xmppConnectionService.sendPresence(account); if (conversation != null) { - conversation - .setNextEncryption(Message.ENCRYPTION_PGP); - xmppConnectionService.databaseBackend - .updateConversation(conversation); + conversation.setNextEncryption(Message.ENCRYPTION_PGP); + xmppConnectionService.databaseBackend.updateConversation(conversation); } } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java index d5b7e4c05..bfe44326e 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java @@ -3,7 +3,6 @@ package eu.siacs.conversations.ui.adapter; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.Color; import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; @@ -22,7 +21,7 @@ import java.util.concurrent.RejectedExecutionException; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Conversation; -import eu.siacs.conversations.entities.Downloadable; +import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.ui.ConversationActivity; import eu.siacs.conversations.ui.XmppActivity; @@ -69,9 +68,9 @@ public class ConversationAdapter extends ArrayAdapter { convName.setTypeface(null, Typeface.NORMAL); } - if (message.getImageParams().width > 0 - && (message.getDownloadable() == null - || message.getDownloadable().getStatus() != Downloadable.STATUS_DELETED)) { + if (message.getFileParams().width > 0 + && (message.getTransferable() == null + || message.getTransferable().getStatus() != Transferable.STATUS_DELETED)) { mLastMessage.setVisibility(View.GONE); imagePreview.setVisibility(View.VISIBLE); activity.loadBitmap(message, imagePreview); diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 29dfced2e..167f3f02f 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -29,10 +29,10 @@ import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; -import eu.siacs.conversations.entities.Downloadable; +import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; -import eu.siacs.conversations.entities.Message.ImageParams; +import eu.siacs.conversations.entities.Message.FileParams; import eu.siacs.conversations.ui.ConversationActivity; import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.UIHelper; @@ -99,14 +99,14 @@ public class MessageAdapter extends ArrayAdapter { } boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI && message.getMergedStatus() <= Message.STATUS_RECEIVED; - if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getDownloadable() != null) { - ImageParams params = message.getImageParams(); + if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getTransferable() != null) { + FileParams params = message.getFileParams(); if (params.size > (1.5 * 1024 * 1024)) { filesize = params.size / (1024 * 1024)+ " MiB"; } else if (params.size > 0) { filesize = params.size / 1024 + " KiB"; } - if (message.getDownloadable() != null && message.getDownloadable().getStatus() == Downloadable.STATUS_FAILED) { + if (message.getTransferable() != null && message.getTransferable().getStatus() == Transferable.STATUS_FAILED) { error = true; } } @@ -115,7 +115,7 @@ public class MessageAdapter extends ArrayAdapter { info = getContext().getString(R.string.waiting); break; case Message.STATUS_UNSEND: - Downloadable d = message.getDownloadable(); + Transferable d = message.getTransferable(); if (d!=null) { info = getContext().getString(R.string.sending_file,d.getProgress()); } else { @@ -160,7 +160,7 @@ public class MessageAdapter extends ArrayAdapter { message.getMergedTimeSent()); if (message.getStatus() <= Message.STATUS_RECEIVED) { if ((filesize != null) && (info != null)) { - viewHolder.time.setText(filesize + " \u00B7 " + info); + viewHolder.time.setText(formatedTime + " \u00B7 " + filesize +" \u00B7 " + info); } else if ((filesize == null) && (info != null)) { viewHolder.time.setText(formatedTime + " \u00B7 " + info); } else if ((filesize != null) && (info == null)) { @@ -339,7 +339,7 @@ public class MessageAdapter extends ArrayAdapter { } viewHolder.messageBody.setVisibility(View.GONE); viewHolder.image.setVisibility(View.VISIBLE); - ImageParams params = message.getImageParams(); + FileParams params = message.getFileParams(); double target = metrics.density * 288; int scalledW; int scalledH; @@ -482,19 +482,19 @@ public class MessageAdapter extends ArrayAdapter { } }); - final Downloadable downloadable = message.getDownloadable(); - if (downloadable != null && downloadable.getStatus() != Downloadable.STATUS_UPLOADING) { - if (downloadable.getStatus() == Downloadable.STATUS_OFFER) { + final Transferable transferable = message.getTransferable(); + if (transferable != null && transferable.getStatus() != Transferable.STATUS_UPLOADING) { + if (transferable.getStatus() == Transferable.STATUS_OFFER) { displayDownloadableMessage(viewHolder,message,activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, message))); - } else if (downloadable.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) { - displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_image_filesize)); + } else if (transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) { + displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message))); } else { displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first); } } else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) { displayImageMessage(viewHolder, message); } else if (message.getType() == Message.TYPE_FILE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) { - if (message.getImageParams().width > 0) { + if (message.getFileParams().width > 0) { displayImageMessage(viewHolder,message); } else { displayOpenableMessage(viewHolder, message); @@ -521,12 +521,12 @@ public class MessageAdapter extends ArrayAdapter { } else { if (GeoHelper.isGeoUri(message.getBody())) { displayLocationMessage(viewHolder,message); + } else if (message.bodyIsHeart()) { + displayHeartMessage(viewHolder, message.getBody().trim()); + } else if (message.treatAsDownloadable() == Message.Decision.MUST) { + displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message))); } else { - if (message.bodyIsHeart()) { - displayHeartMessage(viewHolder, message.getBody().trim()); - } else { - displayTextMessage(viewHolder, message); - } + displayTextMessage(viewHolder, message); } } @@ -536,12 +536,14 @@ public class MessageAdapter extends ArrayAdapter { } public void startDownloadable(Message message) { - Downloadable downloadable = message.getDownloadable(); - if (downloadable != null) { - if (!downloadable.start()) { + Transferable transferable = message.getTransferable(); + if (transferable != null) { + if (!transferable.start()) { Toast.makeText(activity, R.string.not_connected_try_again, Toast.LENGTH_SHORT).show(); } + } else if (message.treatAsDownloadable() != Message.Decision.NEVER) { + activity.xmppConnectionService.getHttpConnectionManager().createNewDownloadConnection(message); } } diff --git a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java index 064263405..5a47bb3c3 100644 --- a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java @@ -132,7 +132,7 @@ public class DNSHelper { } catch (SocketTimeoutException e) { bundle.putString("error", "timeout"); } catch (Exception e) { - Log.d(Config.LOGTAG,e.getMessage()); + e.printStackTrace(); bundle.putString("error", "unhandled"); } return bundle; diff --git a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java new file mode 100644 index 000000000..a9e89d1b7 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java @@ -0,0 +1,487 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package eu.siacs.conversations.utils; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +/** + * Utilities for dealing with MIME types. + * Used to implement java.net.URLConnection and android.webkit.MimeTypeMap. + */ +public final class MimeUtils { + private static final Map mimeTypeToExtensionMap = new HashMap(); + private static final Map extensionToMimeTypeMap = new HashMap(); + static { + // The following table is based on /etc/mime.types data minus + // chemical/* MIME types and MIME types that don't map to any + // file extensions. We also exclude top-level domain names to + // deal with cases like: + // + // mail.google.com/a/google.com + // + // and "active" MIME types (due to potential security issues). + // Note that this list is _not_ in alphabetical order and must not be sorted. + // The "most popular" extension must come first, so that it's the one returned + // by guessExtensionFromMimeType. + add("application/andrew-inset", "ez"); + add("application/dsptype", "tsp"); + add("application/hta", "hta"); + add("application/mac-binhex40", "hqx"); + add("application/mathematica", "nb"); + add("application/msaccess", "mdb"); + add("application/oda", "oda"); + add("application/ogg", "ogg"); + add("application/ogg", "oga"); + add("application/pdf", "pdf"); + add("application/pgp-keys", "key"); + add("application/pgp-signature", "pgp"); + add("application/pics-rules", "prf"); + add("application/pkix-cert", "cer"); + add("application/rar", "rar"); + add("application/rdf+xml", "rdf"); + add("application/rss+xml", "rss"); + add("application/zip", "zip"); + add("application/vnd.android.package-archive", "apk"); + add("application/vnd.cinderella", "cdy"); + add("application/vnd.ms-pki.stl", "stl"); + add("application/vnd.oasis.opendocument.database", "odb"); + add("application/vnd.oasis.opendocument.formula", "odf"); + add("application/vnd.oasis.opendocument.graphics", "odg"); + add("application/vnd.oasis.opendocument.graphics-template", "otg"); + add("application/vnd.oasis.opendocument.image", "odi"); + add("application/vnd.oasis.opendocument.spreadsheet", "ods"); + add("application/vnd.oasis.opendocument.spreadsheet-template", "ots"); + add("application/vnd.oasis.opendocument.text", "odt"); + add("application/vnd.oasis.opendocument.text-master", "odm"); + add("application/vnd.oasis.opendocument.text-template", "ott"); + add("application/vnd.oasis.opendocument.text-web", "oth"); + add("application/vnd.google-earth.kml+xml", "kml"); + add("application/vnd.google-earth.kmz", "kmz"); + add("application/msword", "doc"); + add("application/msword", "dot"); + add("application/vnd.openxmlformats-officedocument.wordprocessingml.document", "docx"); + add("application/vnd.openxmlformats-officedocument.wordprocessingml.template", "dotx"); + add("application/vnd.ms-excel", "xls"); + add("application/vnd.ms-excel", "xlt"); + add("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx"); + add("application/vnd.openxmlformats-officedocument.spreadsheetml.template", "xltx"); + add("application/vnd.ms-powerpoint", "ppt"); + add("application/vnd.ms-powerpoint", "pot"); + add("application/vnd.ms-powerpoint", "pps"); + add("application/vnd.openxmlformats-officedocument.presentationml.presentation", "pptx"); + add("application/vnd.openxmlformats-officedocument.presentationml.template", "potx"); + add("application/vnd.openxmlformats-officedocument.presentationml.slideshow", "ppsx"); + add("application/vnd.rim.cod", "cod"); + add("application/vnd.smaf", "mmf"); + add("application/vnd.stardivision.calc", "sdc"); + add("application/vnd.stardivision.draw", "sda"); + add("application/vnd.stardivision.impress", "sdd"); + add("application/vnd.stardivision.impress", "sdp"); + add("application/vnd.stardivision.math", "smf"); + add("application/vnd.stardivision.writer", "sdw"); + add("application/vnd.stardivision.writer", "vor"); + add("application/vnd.stardivision.writer-global", "sgl"); + add("application/vnd.sun.xml.calc", "sxc"); + add("application/vnd.sun.xml.calc.template", "stc"); + add("application/vnd.sun.xml.draw", "sxd"); + add("application/vnd.sun.xml.draw.template", "std"); + add("application/vnd.sun.xml.impress", "sxi"); + add("application/vnd.sun.xml.impress.template", "sti"); + add("application/vnd.sun.xml.math", "sxm"); + add("application/vnd.sun.xml.writer", "sxw"); + add("application/vnd.sun.xml.writer.global", "sxg"); + add("application/vnd.sun.xml.writer.template", "stw"); + add("application/vnd.visio", "vsd"); + add("application/x-abiword", "abw"); + add("application/x-apple-diskimage", "dmg"); + add("application/x-bcpio", "bcpio"); + add("application/x-bittorrent", "torrent"); + add("application/x-cdf", "cdf"); + add("application/x-cdlink", "vcd"); + add("application/x-chess-pgn", "pgn"); + add("application/x-cpio", "cpio"); + add("application/x-debian-package", "deb"); + add("application/x-debian-package", "udeb"); + add("application/x-director", "dcr"); + add("application/x-director", "dir"); + add("application/x-director", "dxr"); + add("application/x-dms", "dms"); + add("application/x-doom", "wad"); + add("application/x-dvi", "dvi"); + add("application/x-font", "pfa"); + add("application/x-font", "pfb"); + add("application/x-font", "gsf"); + add("application/x-font", "pcf"); + add("application/x-font", "pcf.Z"); + add("application/x-freemind", "mm"); + // application/futuresplash isn't IANA, so application/x-futuresplash should come first. + add("application/x-futuresplash", "spl"); + add("application/futuresplash", "spl"); + add("application/x-gnumeric", "gnumeric"); + add("application/x-go-sgf", "sgf"); + add("application/x-graphing-calculator", "gcf"); + add("application/x-gtar", "tgz"); + add("application/x-gtar", "gtar"); + add("application/x-gtar", "taz"); + add("application/x-hdf", "hdf"); + add("application/x-ica", "ica"); + add("application/x-internet-signup", "ins"); + add("application/x-internet-signup", "isp"); + add("application/x-iphone", "iii"); + add("application/x-iso9660-image", "iso"); + add("application/x-jmol", "jmz"); + add("application/x-kchart", "chrt"); + add("application/x-killustrator", "kil"); + add("application/x-koan", "skp"); + add("application/x-koan", "skd"); + add("application/x-koan", "skt"); + add("application/x-koan", "skm"); + add("application/x-kpresenter", "kpr"); + add("application/x-kpresenter", "kpt"); + add("application/x-kspread", "ksp"); + add("application/x-kword", "kwd"); + add("application/x-kword", "kwt"); + add("application/x-latex", "latex"); + add("application/x-lha", "lha"); + add("application/x-lzh", "lzh"); + add("application/x-lzx", "lzx"); + add("application/x-maker", "frm"); + add("application/x-maker", "maker"); + add("application/x-maker", "frame"); + add("application/x-maker", "fb"); + add("application/x-maker", "book"); + add("application/x-maker", "fbdoc"); + add("application/x-mif", "mif"); + add("application/x-ms-wmd", "wmd"); + add("application/x-ms-wmz", "wmz"); + add("application/x-msi", "msi"); + add("application/x-ns-proxy-autoconfig", "pac"); + add("application/x-nwc", "nwc"); + add("application/x-object", "o"); + add("application/x-oz-application", "oza"); + add("application/x-pem-file", "pem"); + add("application/x-pkcs12", "p12"); + add("application/x-pkcs12", "pfx"); + add("application/x-pkcs7-certreqresp", "p7r"); + add("application/x-pkcs7-crl", "crl"); + add("application/x-quicktimeplayer", "qtl"); + add("application/x-shar", "shar"); + add("application/x-shockwave-flash", "swf"); + add("application/x-stuffit", "sit"); + add("application/x-sv4cpio", "sv4cpio"); + add("application/x-sv4crc", "sv4crc"); + add("application/x-tar", "tar"); + add("application/x-texinfo", "texinfo"); + add("application/x-texinfo", "texi"); + add("application/x-troff", "t"); + add("application/x-troff", "roff"); + add("application/x-troff-man", "man"); + add("application/x-ustar", "ustar"); + add("application/x-wais-source", "src"); + add("application/x-wingz", "wz"); + add("application/x-webarchive", "webarchive"); + add("application/x-webarchive-xml", "webarchivexml"); + add("application/x-x509-ca-cert", "crt"); + add("application/x-x509-user-cert", "crt"); + add("application/x-x509-server-cert", "crt"); + add("application/x-xcf", "xcf"); + add("application/x-xfig", "fig"); + add("application/xhtml+xml", "xhtml"); + add("audio/3gpp", "3gpp"); + add("audio/aac", "aac"); + add("audio/aac-adts", "aac"); + add("audio/amr", "amr"); + add("audio/amr-wb", "awb"); + add("audio/basic", "snd"); + add("audio/flac", "flac"); + add("application/x-flac", "flac"); + add("audio/imelody", "imy"); + add("audio/midi", "mid"); + add("audio/midi", "midi"); + add("audio/midi", "ota"); + add("audio/midi", "kar"); + add("audio/midi", "rtttl"); + add("audio/midi", "xmf"); + add("audio/mobile-xmf", "mxmf"); + // add ".mp3" first so it will be the default for guessExtensionFromMimeType + add("audio/mpeg", "mp3"); + add("audio/mpeg", "mpga"); + add("audio/mpeg", "mpega"); + add("audio/mpeg", "mp2"); + add("audio/mpeg", "m4a"); + add("audio/mpegurl", "m3u"); + add("audio/prs.sid", "sid"); + add("audio/x-aiff", "aif"); + add("audio/x-aiff", "aiff"); + add("audio/x-aiff", "aifc"); + add("audio/x-gsm", "gsm"); + add("audio/x-matroska", "mka"); + add("audio/x-mpegurl", "m3u"); + add("audio/x-ms-wma", "wma"); + add("audio/x-ms-wax", "wax"); + add("audio/x-pn-realaudio", "ra"); + add("audio/x-pn-realaudio", "rm"); + add("audio/x-pn-realaudio", "ram"); + add("audio/x-realaudio", "ra"); + add("audio/x-scpls", "pls"); + add("audio/x-sd2", "sd2"); + add("audio/x-wav", "wav"); + // image/bmp isn't IANA, so image/x-ms-bmp should come first. + add("image/x-ms-bmp", "bmp"); + add("image/bmp", "bmp"); + add("image/gif", "gif"); + // image/ico isn't IANA, so image/x-icon should come first. + add("image/x-icon", "ico"); + add("image/ico", "cur"); + add("image/ico", "ico"); + add("image/ief", "ief"); + // add ".jpg" first so it will be the default for guessExtensionFromMimeType + add("image/jpeg", "jpg"); + add("image/jpeg", "jpeg"); + add("image/jpeg", "jpe"); + add("image/pcx", "pcx"); + add("image/png", "png"); + add("image/svg+xml", "svg"); + add("image/svg+xml", "svgz"); + add("image/tiff", "tiff"); + add("image/tiff", "tif"); + add("image/vnd.djvu", "djvu"); + add("image/vnd.djvu", "djv"); + add("image/vnd.wap.wbmp", "wbmp"); + add("image/webp", "webp"); + add("image/x-cmu-raster", "ras"); + add("image/x-coreldraw", "cdr"); + add("image/x-coreldrawpattern", "pat"); + add("image/x-coreldrawtemplate", "cdt"); + add("image/x-corelphotopaint", "cpt"); + add("image/x-jg", "art"); + add("image/x-jng", "jng"); + add("image/x-photoshop", "psd"); + add("image/x-portable-anymap", "pnm"); + add("image/x-portable-bitmap", "pbm"); + add("image/x-portable-graymap", "pgm"); + add("image/x-portable-pixmap", "ppm"); + add("image/x-rgb", "rgb"); + add("image/x-xbitmap", "xbm"); + add("image/x-xpixmap", "xpm"); + add("image/x-xwindowdump", "xwd"); + add("model/iges", "igs"); + add("model/iges", "iges"); + add("model/mesh", "msh"); + add("model/mesh", "mesh"); + add("model/mesh", "silo"); + add("text/calendar", "ics"); + add("text/calendar", "icz"); + add("text/comma-separated-values", "csv"); + add("text/css", "css"); + add("text/html", "htm"); + add("text/html", "html"); + add("text/h323", "323"); + add("text/iuls", "uls"); + add("text/mathml", "mml"); + // add ".txt" first so it will be the default for guessExtensionFromMimeType + add("text/plain", "txt"); + add("text/plain", "asc"); + add("text/plain", "text"); + add("text/plain", "diff"); + add("text/plain", "po"); // reserve "pot" for vnd.ms-powerpoint + add("text/richtext", "rtx"); + add("text/rtf", "rtf"); + add("text/text", "phps"); + add("text/tab-separated-values", "tsv"); + add("text/xml", "xml"); + add("text/x-bibtex", "bib"); + add("text/x-boo", "boo"); + add("text/x-c++hdr", "hpp"); + add("text/x-c++hdr", "h++"); + add("text/x-c++hdr", "hxx"); + add("text/x-c++hdr", "hh"); + add("text/x-c++src", "cpp"); + add("text/x-c++src", "c++"); + add("text/x-c++src", "cc"); + add("text/x-c++src", "cxx"); + add("text/x-chdr", "h"); + add("text/x-component", "htc"); + add("text/x-csh", "csh"); + add("text/x-csrc", "c"); + add("text/x-dsrc", "d"); + add("text/x-haskell", "hs"); + add("text/x-java", "java"); + add("text/x-literate-haskell", "lhs"); + add("text/x-moc", "moc"); + add("text/x-pascal", "p"); + add("text/x-pascal", "pas"); + add("text/x-pcs-gcd", "gcd"); + add("text/x-setext", "etx"); + add("text/x-tcl", "tcl"); + add("text/x-tex", "tex"); + add("text/x-tex", "ltx"); + add("text/x-tex", "sty"); + add("text/x-tex", "cls"); + add("text/x-vcalendar", "vcs"); + add("text/x-vcard", "vcf"); + add("video/3gpp", "3gpp"); + add("video/3gpp", "3gp"); + add("video/3gpp2", "3gpp2"); + add("video/3gpp2", "3g2"); + add("video/avi", "avi"); + add("video/dl", "dl"); + add("video/dv", "dif"); + add("video/dv", "dv"); + add("video/fli", "fli"); + add("video/m4v", "m4v"); + add("video/mp2ts", "ts"); + add("video/mpeg", "mpeg"); + add("video/mpeg", "mpg"); + add("video/mpeg", "mpe"); + add("video/mp4", "mp4"); + add("video/mpeg", "VOB"); + add("video/quicktime", "qt"); + add("video/quicktime", "mov"); + add("video/vnd.mpegurl", "mxu"); + add("video/webm", "webm"); + add("video/x-la-asf", "lsf"); + add("video/x-la-asf", "lsx"); + add("video/x-matroska", "mkv"); + add("video/x-mng", "mng"); + add("video/x-ms-asf", "asf"); + add("video/x-ms-asf", "asx"); + add("video/x-ms-wm", "wm"); + add("video/x-ms-wmv", "wmv"); + add("video/x-ms-wmx", "wmx"); + add("video/x-ms-wvx", "wvx"); + add("video/x-sgi-movie", "movie"); + add("video/x-webex", "wrf"); + add("x-conference/x-cooltalk", "ice"); + add("x-epoc/x-sisx-app", "sisx"); + applyOverrides(); + } + private static void add(String mimeType, String extension) { + // If we have an existing x -> y mapping, we do not want to + // override it with another mapping x -> y2. + // If a mime type maps to several extensions + // the first extension added is considered the most popular + // so we do not want to overwrite it later. + if (!mimeTypeToExtensionMap.containsKey(mimeType)) { + mimeTypeToExtensionMap.put(mimeType, extension); + } + if (!extensionToMimeTypeMap.containsKey(extension)) { + extensionToMimeTypeMap.put(extension, mimeType); + } + } + private static InputStream getContentTypesPropertiesStream() { + // User override? + String userTable = System.getProperty("content.types.user.table"); + if (userTable != null) { + File f = new File(userTable); + if (f.exists()) { + try { + return new FileInputStream(f); + } catch (IOException ignored) { + } + } + } + // Standard location? + File f = new File(System.getProperty("java.home"), "lib" + File.separator + "content-types.properties"); + if (f.exists()) { + try { + return new FileInputStream(f); + } catch (IOException ignored) { + } + } + return null; + } + /** + * This isn't what the RI does. The RI doesn't have hard-coded defaults, so supplying your + * own "content.types.user.table" means you don't get any of the built-ins, and the built-ins + * come from "$JAVA_HOME/lib/content-types.properties". + */ + private static void applyOverrides() { + // Get the appropriate InputStream to read overrides from, if any. + InputStream stream = getContentTypesPropertiesStream(); + if (stream == null) { + return; + } + try { + try { + // Read the properties file... + Properties overrides = new Properties(); + overrides.load(stream); + // And translate its mapping to ours... + for (Map.Entry entry : overrides.entrySet()) { + String extension = (String) entry.getKey(); + String mimeType = (String) entry.getValue(); + add(mimeType, extension); + } + } finally { + stream.close(); + } + } catch (IOException ignored) { + } + } + private MimeUtils() { + } + /** + * Returns true if the given MIME type has an entry in the map. + * @param mimeType A MIME type (i.e. text/plain) + * @return True iff there is a mimeType entry in the map. + */ + public static boolean hasMimeType(String mimeType) { + if (mimeType == null || mimeType.isEmpty()) { + return false; + } + return mimeTypeToExtensionMap.containsKey(mimeType); + } + /** + * Returns the MIME type for the given extension. + * @param extension A file extension without the leading '.' + * @return The MIME type for the given extension or null iff there is none. + */ + public static String guessMimeTypeFromExtension(String extension) { + if (extension == null || extension.isEmpty()) { + return null; + } + return extensionToMimeTypeMap.get(extension); + } + /** + * Returns true if the given extension has a registered MIME type. + * @param extension A file extension without the leading '.' + * @return True iff there is an extension entry in the map. + */ + public static boolean hasExtension(String extension) { + if (extension == null || extension.isEmpty()) { + return false; + } + return extensionToMimeTypeMap.containsKey(extension); + } + /** + * Returns the registered extension for the given MIME type. Note that some + * MIME types map to multiple extensions. This call will return the most + * common extension for the given MIME type. + * @param mimeType A MIME type (i.e. text/plain) + * @return The extension for the given MIME type or null iff there is none. + */ + public static String guessExtensionFromMimeType(String mimeType) { + if (mimeType == null || mimeType.isEmpty()) { + return null; + } + return mimeTypeToExtensionMap.get(mimeType); + } +} diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index 2f96a83a9..8047993a3 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -1,6 +1,5 @@ package eu.siacs.conversations.utils; -import java.net.URLConnection; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -10,7 +9,7 @@ import java.util.Locale; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; -import eu.siacs.conversations.entities.Downloadable; +import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.xmpp.jid.Jid; @@ -142,24 +141,24 @@ public class UIHelper { } public static Pair getMessagePreview(final Context context, final Message message) { - final Downloadable d = message.getDownloadable(); + final Transferable d = message.getTransferable(); if (d != null ) { switch (d.getStatus()) { - case Downloadable.STATUS_CHECKING: + case Transferable.STATUS_CHECKING: return new Pair<>(context.getString(R.string.checking_image),true); - case Downloadable.STATUS_DOWNLOADING: + case Transferable.STATUS_DOWNLOADING: return new Pair<>(context.getString(R.string.receiving_x_file, getFileDescriptionString(context,message), d.getProgress()),true); - case Downloadable.STATUS_OFFER: - case Downloadable.STATUS_OFFER_CHECK_FILESIZE: + case Transferable.STATUS_OFFER: + case Transferable.STATUS_OFFER_CHECK_FILESIZE: return new Pair<>(context.getString(R.string.x_file_offered_for_download, getFileDescriptionString(context,message)),true); - case Downloadable.STATUS_DELETED: + case Transferable.STATUS_DELETED: return new Pair<>(context.getString(R.string.file_deleted),true); - case Downloadable.STATUS_FAILED: + case Transferable.STATUS_FAILED: return new Pair<>(context.getString(R.string.file_transmission_failed),true); - case Downloadable.STATUS_UPLOADING: + case Transferable.STATUS_UPLOADING: if (message.getStatus() == Message.STATUS_OFFERED) { return new Pair<>(context.getString(R.string.offering_x_file, getFileDescriptionString(context, message)), true); @@ -199,16 +198,7 @@ public class UIHelper { if (message.getType() == Message.TYPE_IMAGE) { return context.getString(R.string.image); } - final String path = message.getRelativeFilePath(); - if (path == null) { - return ""; - } - final String mime; - try { - mime = URLConnection.guessContentTypeFromName(path.replace("#","")); - } catch (final StringIndexOutOfBoundsException ignored) { - return context.getString(R.string.file); - } + final String mime = message.getMimeType(); if (mime == null) { return context.getString(R.string.file); } else if (mime.startsWith("audio/")) { @@ -230,10 +220,14 @@ public class UIHelper { public static String getMessageDisplayName(final Message message) { if (message.getStatus() == Message.STATUS_RECEIVED) { + final Contact contact = message.getContact(); if (message.getConversation().getMode() == Conversation.MODE_MULTI) { - return getDisplayedMucCounterpart(message.getCounterpart()); + if (contact != null) { + return contact.getDisplayName(); + } else { + return getDisplayedMucCounterpart(message.getCounterpart()); + } } else { - final Contact contact = message.getContact(); return contact != null ? contact.getDisplayName() : ""; } } else { diff --git a/src/main/java/eu/siacs/conversations/utils/Xmlns.java b/src/main/java/eu/siacs/conversations/utils/Xmlns.java index 17fd2d261..868566d96 100644 --- a/src/main/java/eu/siacs/conversations/utils/Xmlns.java +++ b/src/main/java/eu/siacs/conversations/utils/Xmlns.java @@ -5,4 +5,5 @@ public final class Xmlns { public static final String ROSTER = "jabber:iq:roster"; public static final String REGISTER = "jabber:iq:register"; public static final String BYTE_STREAMS = "http://jabber.org/protocol/bytestreams"; + public static final String HTTP_UPLOAD = "eu:siacs:conversations:http:upload"; } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 8a4389065..35c89b45f 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -366,17 +366,21 @@ public class XmppConnection implements Runnable { } else if (nextTag.isStart("a")) { final Element ack = tagReader.readElement(nextTag); lastPacketReceived = SystemClock.elapsedRealtime(); - final int serverSequence = Integer.parseInt(ack.getAttribute("h")); - if (Config.EXTENDED_SM_LOGGING) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server acknowledged stanza #" + serverSequence); - } - final String msgId = this.messageReceipts.get(serverSequence); - if (msgId != null) { - if (this.acknowledgedListener != null) { - this.acknowledgedListener.onMessageAcknowledged( - account, msgId); + try { + final int serverSequence = Integer.parseInt(ack.getAttribute("h")); + if (Config.EXTENDED_SM_LOGGING) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server acknowledged stanza #" + serverSequence); } - this.messageReceipts.remove(serverSequence); + final String msgId = this.messageReceipts.get(serverSequence); + if (msgId != null) { + if (this.acknowledgedListener != null) { + this.acknowledgedListener.onMessageAcknowledged( + account, msgId); + } + this.messageReceipts.remove(serverSequence); + } + } catch (NumberFormatException e) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": server send ack without sequence number"); } } else if (nextTag.isStart("failed")) { tagReader.readElement(nextTag); @@ -1025,18 +1029,18 @@ public class XmppConnection implements Runnable { this.streamId = null; } - public List findDiscoItemsByFeature(final String feature) { - final List items = new ArrayList<>(); + public List findDiscoItemsByFeature(final String feature) { + final List items = new ArrayList<>(); for (final Entry cursor : disco.entrySet()) { if (cursor.getValue().features.contains(feature)) { - items.add(cursor.getKey().toString()); + items.add(cursor.getKey()); } } return items; } - public String findDiscoItemByFeature(final String feature) { - final List items = findDiscoItemsByFeature(feature); + public Jid findDiscoItemByFeature(final String feature) { + final List items = findDiscoItemsByFeature(feature); if (items.size() >= 1) { return items.get(0); } @@ -1191,6 +1195,10 @@ public class XmppConnection implements Runnable { public void setBlockListRequested(boolean value) { this.blockListRequested = value; } + + public boolean httpUpload() { + return findDiscoItemsByFeature(Xmlns.HTTP_UPLOAD).size() > 0; + } } private IqGenerator getIqGenerator() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java index c9bb9c93b..335347246 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java @@ -1,6 +1,5 @@ package eu.siacs.conversations.xmpp.jingle; -import java.net.URLConnection; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; @@ -16,9 +15,9 @@ import android.util.Log; import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; -import eu.siacs.conversations.entities.Downloadable; +import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.DownloadableFile; -import eu.siacs.conversations.entities.DownloadablePlaceholder; +import eu.siacs.conversations.entities.TransferablePlaceholder; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; @@ -29,7 +28,7 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; import eu.siacs.conversations.xmpp.stanzas.IqPacket; -public class JingleConnection implements Downloadable { +public class JingleConnection implements Transferable { private JingleConnectionManager mJingleConnectionManager; private XmppConnectionService mXmppConnectionService; @@ -43,7 +42,7 @@ public class JingleConnection implements Downloadable { private int ibbBlockSize = 4096; private int mJingleStatus = -1; - private int mStatus = Downloadable.STATUS_UNKNOWN; + private int mStatus = Transferable.STATUS_UNKNOWN; private Message message; private String sessionId; private Account account; @@ -199,8 +198,8 @@ public class JingleConnection implements Downloadable { this.contentCreator = "initiator"; this.contentName = this.mJingleConnectionManager.nextRandomId(); this.message = message; - this.message.setDownloadable(this); - this.mStatus = Downloadable.STATUS_UPLOADING; + this.message.setTransferable(this); + this.mStatus = Transferable.STATUS_UPLOADING; this.account = message.getConversation().getAccount(); this.initiator = this.account.getJid(); this.responder = this.message.getCounterpart(); @@ -256,8 +255,8 @@ public class JingleConnection implements Downloadable { packet.getFrom().toBareJid(), false); this.message = new Message(conversation, "", Message.ENCRYPTION_NONE); this.message.setStatus(Message.STATUS_RECEIVED); - this.mStatus = Downloadable.STATUS_OFFER; - this.message.setDownloadable(this); + this.mStatus = Transferable.STATUS_OFFER; + this.message.setTransferable(this); final Jid from = packet.getFrom(); this.message.setCounterpart(from); this.account = account; @@ -408,7 +407,7 @@ public class JingleConnection implements Downloadable { private void sendAccept() { mJingleStatus = JINGLE_STATUS_ACCEPTED; - this.mStatus = Downloadable.STATUS_DOWNLOADING; + this.mStatus = Transferable.STATUS_DOWNLOADING; mXmppConnectionService.updateConversationUi(); this.mJingleConnectionManager.getPrimaryCandidate(this.account, new OnPrimaryCandidateFound() { @Override @@ -639,7 +638,7 @@ public class JingleConnection implements Downloadable { this.disconnectSocks5Connections(); this.mJingleStatus = JINGLE_STATUS_FINISHED; this.message.setStatus(Message.STATUS_RECEIVED); - this.message.setDownloadable(null); + this.message.setTransferable(null); this.mXmppConnectionService.updateMessage(message); this.mJingleConnectionManager.finishConnection(this); } @@ -716,7 +715,7 @@ public class JingleConnection implements Downloadable { if (this.transport != null && this.transport instanceof JingleInbandTransport) { this.transport.disconnect(); } - this.message.setDownloadable(null); + this.message.setTransferable(null); this.mJingleConnectionManager.finishConnection(this); } @@ -728,7 +727,7 @@ public class JingleConnection implements Downloadable { this.sendCancel(); this.mJingleConnectionManager.finishConnection(this); if (this.responder.equals(account.getJid())) { - this.message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_FAILED)); + this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED)); if (this.file!=null) { file.delete(); } @@ -736,7 +735,7 @@ public class JingleConnection implements Downloadable { } else { this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND_FAILED); - this.message.setDownloadable(null); + this.message.setTransferable(null); } } @@ -748,7 +747,7 @@ public class JingleConnection implements Downloadable { } if (this.message != null) { if (this.responder.equals(account.getJid())) { - this.message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_FAILED)); + this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED)); if (this.file!=null) { file.delete(); } @@ -756,7 +755,7 @@ public class JingleConnection implements Downloadable { } else { this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND_FAILED); - this.message.setDownloadable(null); + this.message.setTransferable(null); } } this.mJingleConnectionManager.finishConnection(this); @@ -954,24 +953,4 @@ public class JingleConnection implements Downloadable { public int getProgress() { return this.mProgress; } - - @Override - public String getMimeType() { - if (this.message.getType() == Message.TYPE_FILE) { - String mime = null; - String path = this.message.getRelativeFilePath(); - if (path != null && !this.message.getRelativeFilePath().isEmpty()) { - mime = URLConnection.guessContentTypeFromName(this.message.getRelativeFilePath()); - if (mime!=null) { - return mime; - } else { - return ""; - } - } else { - return ""; - } - } else { - return "image/webp"; - } - } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index c19dd04c7..cadf9df39 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -9,14 +9,13 @@ import android.annotation.SuppressLint; import android.util.Log; import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.entities.Downloadable; +import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.Xmlns; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.OnIqPacketReceived; -import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; import eu.siacs.conversations.xmpp.stanzas.IqPacket; @@ -59,7 +58,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { } public JingleConnection createNewConnection(Message message) { - Downloadable old = message.getDownloadable(); + Transferable old = message.getTransferable(); if (old != null) { old.cancel(); } @@ -87,10 +86,10 @@ public class JingleConnectionManager extends AbstractConnectionManager { return; } if (!this.primaryCandidates.containsKey(account.getJid().toBareJid())) { - final String proxy = account.getXmppConnection().findDiscoItemByFeature(Xmlns.BYTE_STREAMS); + final Jid proxy = account.getXmppConnection().findDiscoItemByFeature(Xmlns.BYTE_STREAMS); if (proxy != null) { IqPacket iq = new IqPacket(IqPacket.TYPE.GET); - iq.setAttribute("to", proxy); + iq.setTo(proxy); iq.query(Xmlns.BYTE_STREAMS); account.getXmppConnection().sendIqPacket(iq,new OnIqPacketReceived() { @@ -105,11 +104,11 @@ public class JingleConnectionManager extends AbstractConnectionManager { candidate.setHost(host); candidate.setPort(Integer.parseInt(port)); candidate.setType(JingleCandidate.TYPE_PROXY); - candidate.setJid(Jid.fromString(proxy)); + candidate.setJid(proxy); candidate.setPriority(655360 + 65535); primaryCandidates.put(account.getJid().toBareJid(),candidate); listener.onPrimaryCandidateFound(true,candidate); - } catch (final NumberFormatException | InvalidJidException e) { + } catch (final NumberFormatException e) { listener.onPrimaryCandidateFound(false,null); return; } diff --git a/src/main/res/menu/message_context.xml b/src/main/res/menu/message_context.xml index 21659266f..4e1316d39 100644 --- a/src/main/res/menu/message_context.xml +++ b/src/main/res/menu/message_context.xml @@ -3,21 +3,27 @@ + android:title="@string/copy_text" + android:visible="false"/> + android:title="@string/share_with" + android:visible="false"/> + android:title="@string/copy_original_url" + android:visible="false"/> + android:title="@string/send_again" + android:visible="false"/> + android:id="@+id/download_file" + android:title="@string/download_x_file" + android:visible="false"/> + android:title="@string/cancel_transmission" + android:visible="false"/> \ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 4631bd60c..1af335878 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -82,7 +82,6 @@ Send OTR encrypted message Send OpenPGP encrypted message Your nickname has been changed - Download Image Send unencrypted Decryption failed. Maybe you don’t have the proper private key. OpenKeychain @@ -320,12 +319,12 @@ Checking image on HTTP host The image file has been deleted You are not connected. Try again later - Check image file size + Check %s size Message options Copy text Copy original URL Send again - Image URL + File URL Message text URL copied to clipboard Message copied to clipboard @@ -479,4 +478,5 @@ None Most recently used Choose quick action + File not found on remote server