diff options
author | steckbrief <steckbrief@chefmail.de> | 2017-01-04 19:41:20 +0100 |
---|---|---|
committer | steckbrief <steckbrief@chefmail.de> | 2017-01-04 19:41:20 +0100 |
commit | 95cb2394154a287afcd295590d741096985ff69a (patch) | |
tree | 51f0b10fa41844885fc4ec40164b80e5892e3316 /src | |
parent | b85035c2bf57de75280ae0b4eedeff6fbf87e9eb (diff) |
Added columns to fileparams table: url, original file name, key and iv
auto download of files moved from MessageParser to MessageAdapter
download and open file representation cleaned up
Diffstat (limited to 'src')
21 files changed, 577 insertions, 310 deletions
diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/FileParams.java b/src/main/java/de/thedevstack/conversationsplus/entities/FileParams.java index 1eef078f..290b87b8 100644 --- a/src/main/java/de/thedevstack/conversationsplus/entities/FileParams.java +++ b/src/main/java/de/thedevstack/conversationsplus/entities/FileParams.java @@ -9,6 +9,7 @@ import eu.siacs.conversations.utils.MimeUtils; */ public class FileParams { private String name; + private String originalFilename; private String path; private String url; private String mimeType; @@ -16,6 +17,8 @@ public class FileParams { private int width = 0; private int height = 0; private FileStatus fileStatus; + private byte[] aeskey; + private byte[] iv; public FileParams() { fileStatus = FileStatus.UNDEFINED; @@ -89,6 +92,37 @@ public class FileParams { return path; } + public void setKeyAndIv(byte[] keyIvCombo) { + if (keyIvCombo.length == 48) { + this.aeskey = new byte[32]; + this.iv = new byte[16]; + System.arraycopy(keyIvCombo, 0, this.iv, 0, 16); + System.arraycopy(keyIvCombo, 16, this.aeskey, 0, 32); + } else if (keyIvCombo.length >= 32) { + this.aeskey = new byte[32]; + System.arraycopy(keyIvCombo, 0, aeskey, 0, 32); + } else if (keyIvCombo.length >= 16) { + this.aeskey = new byte[16]; + System.arraycopy(keyIvCombo, 0, this.aeskey, 0, 16); + } + } + + public void setKey(byte[] key) { + this.aeskey = key; + } + + public void setIv(byte[] iv) { + this.iv = iv; + } + + public byte[] getKey() { + return this.aeskey; + } + + public byte[] getIv() { + return this.iv; + } + /** * Sets the path to the file. * If no file name is stored yet here - this method tries to extract the file name from the path. @@ -127,4 +161,12 @@ public class FileParams { public FileStatus getFileStatus() { return this.fileStatus; } + + public void setOriginalFilename(String originalFilename) { + this.originalFilename = originalFilename; + } + + public String getOriginalFilename() { + return this.originalFilename; + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/MessageDatabaseAccess.java b/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/MessageDatabaseAccess.java index 139dc419..0fb85fbf 100644 --- a/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/MessageDatabaseAccess.java +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/MessageDatabaseAccess.java @@ -10,6 +10,7 @@ import de.thedevstack.conversationsplus.enums.FileStatus; import de.thedevstack.conversationsplus.persistance.db.migrations.FileParamsBodyToDatabaseFieldsMigration; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.utils.CryptoHelper; /** * @@ -29,6 +30,9 @@ public class MessageDatabaseAccess extends AbstractDatabaseAccess { private static final String COLUMN_NAME_MSG_PARAMS_FILE_NAME = "file_name"; private static final String COLUMN_NAME_MSG_PARAMS_FILE_PATH = "file_path"; private static final String COLUMN_NAME_MSG_PARAMS_FILE_URL = "file_url"; + private static final String COLUMN_NAME_MSG_PARAMS_FILE_IV = "file_iv"; + private static final String COLUMN_NAME_MSG_PARAMS_FILE_KEY = "file_key"; + private static final String COLUMN_NAME_MSG_PARAMS_ORIGINAL_FILE_NAME = "original_file_name"; private static final String TABLE_ADDITIONAL_PARAMETERS_CREATE_V1 = "CREATE TABLE " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS + " (" + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + " TEXT, " @@ -46,8 +50,11 @@ public class MessageDatabaseAccess extends AbstractDatabaseAccess { + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_WIDTH + " INTEGER DEFAULT 0, " + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_STATUS + " TEXT DEFAULT 'UNDEFINED', " + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_NAME + " TEXT DEFAULT NULL, " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_ORIGINAL_FILE_NAME + " TEXT DEFAULT NULL, " + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_PATH + " TEXT DEFAULT NULL, " + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_URL + " TEXT DEFAULT NULL, " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_IV + " TEXT DEFAULT NULL, " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_KEY + " TEXT DEFAULT NULL, " + "FOREIGN KEY(" + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + ") REFERENCES " + Message.TABLENAME + "(" + Message.UUID + ") ON DELETE CASCADE)"; public static void updateMessageParameters(SQLiteDatabase db, Message message, String uuid) { @@ -66,8 +73,16 @@ public class MessageDatabaseAccess extends AbstractDatabaseAccess { additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_SIZE, fileParams.getSize()); additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_HEIGHT, fileParams.getHeight()); additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_WIDTH, fileParams.getWidth()); + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_NAME, fileParams.getName()); + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_ORIGINAL_FILE_NAME, fileParams.getOriginalFilename()); additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_PATH, fileParams.getPath()); additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_URL, fileParams.getUrl()); + if (null != fileParams.getIv()) { + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_IV, CryptoHelper.bytesToHex(fileParams.getIv())); + } + if (null != fileParams.getKey()) { + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_KEY, CryptoHelper.bytesToHex(fileParams.getKey())); + } if (null != fileParams.getFileStatus()) { additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_STATUS, fileParams.getFileStatus().name()); } @@ -96,10 +111,20 @@ public class MessageDatabaseAccess extends AbstractDatabaseAccess { fileParams.setMimeType(fileType); String name = CursorHelper.getString(cursor, COLUMN_NAME_MSG_PARAMS_FILE_NAME); fileParams.setName(name); + String originalFilename = CursorHelper.getString(cursor, COLUMN_NAME_MSG_PARAMS_ORIGINAL_FILE_NAME); + fileParams.setOriginalFilename(originalFilename); String path = CursorHelper.getString(cursor, COLUMN_NAME_MSG_PARAMS_FILE_PATH); fileParams.setPath(path); String url = CursorHelper.getString(cursor, COLUMN_NAME_MSG_PARAMS_FILE_URL); fileParams.setUrl(url); + String iv = CursorHelper.getString(cursor, COLUMN_NAME_MSG_PARAMS_FILE_IV); + if (null != iv && !iv.isEmpty()) { + fileParams.setIv(CryptoHelper.hexToBytes(iv)); + } + String key = CursorHelper.getString(cursor, COLUMN_NAME_MSG_PARAMS_FILE_KEY); + if (null != key && !key.isEmpty()) { + fileParams.setKey(CryptoHelper.hexToBytes(key)); + } long fileSize = CursorHelper.getLong(cursor, COLUMN_NAME_MSG_PARAMS_FILE_SIZE); fileParams.setSize(fileSize); int imageHeight = CursorHelper.getInt(cursor, COLUMN_NAME_MSG_PARAMS_FILE_HEIGHT); @@ -147,7 +172,11 @@ public class MessageDatabaseAccess extends AbstractDatabaseAccess { MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_WIDTH + " INTEGER DEFAULT 0", MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_STATUS + " TEXT DEFAULT 'UNDEFINED'", MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_NAME + " TEXT DEFAULT NULL", + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_ORIGINAL_FILE_NAME + " TEXT DEFAULT NULL", MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_PATH + " TEXT DEFAULT NULL", + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_URL + " TEXT DEFAULT NULL", + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_IV + " TEXT DEFAULT NULL", + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_KEY + " TEXT DEFAULT NULL", }; addNewColumns(db, MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS, columnDefinitions); @@ -167,6 +196,13 @@ public class MessageDatabaseAccess extends AbstractDatabaseAccess { parameterValues.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_SIZE, fileParams.getSize()); parameterValues.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_HEIGHT, fileParams.getHeight()); parameterValues.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_WIDTH, fileParams.getWidth()); + parameterValues.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_URL, fileParams.getUrl()); + if (null != fileParams.getIv()) { + parameterValues.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_IV, CryptoHelper.bytesToHex(fileParams.getIv())); + } + if (null != fileParams.getKey()) { + parameterValues.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_KEY, CryptoHelper.bytesToHex(fileParams.getKey())); + } db.update(MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS, parameterValues, MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + "=?", new String[] {uuid}); } diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/db/migrations/FileParamsBodyToDatabaseFieldsMigration.java b/src/main/java/de/thedevstack/conversationsplus/persistance/db/migrations/FileParamsBodyToDatabaseFieldsMigration.java index c0aa63c0..8ad94ca0 100644 --- a/src/main/java/de/thedevstack/conversationsplus/persistance/db/migrations/FileParamsBodyToDatabaseFieldsMigration.java +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/db/migrations/FileParamsBodyToDatabaseFieldsMigration.java @@ -4,6 +4,7 @@ import java.net.MalformedURLException; import java.net.URL; import de.thedevstack.conversationsplus.entities.FileParams; +import de.thedevstack.conversationsplus.utils.UrlUtil; /** * Created by steckbrief on 24.08.2016. @@ -28,6 +29,8 @@ public class FileParamsBodyToDatabaseFieldsMigration { try { URL url = new URL(parts[0]); params.setUrl(url.toString()); + byte[] ivAndKey = UrlUtil.getIvAndKeyFromURL(url); + params.setKeyAndIv(ivAndKey); } catch (MalformedURLException e1) { } } @@ -37,6 +40,8 @@ public class FileParamsBodyToDatabaseFieldsMigration { try { URL url = new URL(parts[0]); params.setUrl(url.toString()); + byte[] ivAndKey = UrlUtil.getIvAndKeyFromURL(url); + params.setKeyAndIv(ivAndKey); } catch (MalformedURLException e1) { } try { diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileTransferEntity.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileTransferEntity.java index 9ec07679..a36db4cd 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileTransferEntity.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileTransferEntity.java @@ -31,9 +31,9 @@ public class HttpFileTransferEntity extends FileTransferEntity { FileParams fileParams = this.getMessage().getFileParams(); if (null == fileParams) { fileParams = new FileParams(); + this.getMessage().setFileParams(fileParams); } fileParams.setFileStatus(FileStatus.NEEDS_UPLOAD); - this.getMessage().setFileParams(fileParams); if (Config.ENCRYPT_ON_HTTP_UPLOADED || message.getEncryption() == Message.ENCRYPTION_AXOLOTL || message.getEncryption() == Message.ENCRYPTION_OTR) { diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/MessageDetailsDialog.java b/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/MessageDetailsDialog.java index 1b44d09b..8a3de18d 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/MessageDetailsDialog.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/MessageDetailsDialog.java @@ -62,8 +62,9 @@ public class MessageDetailsDialog extends AbstractAlertDialog { view.findViewById(R.id.dlgMsgDetFileTable).setVisibility(View.VISIBLE); if (null != message.getFileParams()) { FileParams params = message.getFileParams(); - TextViewUtil.setText(view, R.id.dlgMsgDetFileSize, UIHelper.getHumanReadableFileSize(params.getSize())); + TextViewUtil.setText(view, R.id.dlgMsgDetFileSize, UIHelper.getHumanReadableDetailedFileSize(params.getSize())); TextViewUtil.setText(view, R.id.dlgMsgDetFileMimeType, params.getMimeType()); + TextViewUtil.setText(view, R.id.dlgMsgDetFileOriginalName, params.getOriginalFilename()); } TextViewUtil.setText(view, R.id.dlgMsgDetFileHttpUploaded, message.isHttpUploaded() ? R.string.cplus_yes : R.string.cplus_no); diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java index 37e39285..ed144465 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java @@ -3,6 +3,7 @@ package de.thedevstack.conversationsplus.utils; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import java.net.MalformedURLException; import java.net.URL; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -14,18 +15,80 @@ import de.thedevstack.conversationsplus.enums.FileStatus; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.persistance.FileBackend; +import eu.siacs.conversations.utils.FileUtils; +import eu.siacs.conversations.utils.MimeUtils; /** * Utility class to work with messages. */ public final class MessageUtil { + public static void extractFileParamsFromBody(Message message) { + if (null == message) { + return; + } + String body = message.getBody(); + /** + * 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 (null == body || body.isEmpty() || body.contains(" ")) { + return; + } + + FileParams fileParams = message.getFileParams(); + if (null == fileParams) { + fileParams = new FileParams(); + message.setFileParams(fileParams); + } + + try { + URL url = new URL(body); + if (!url.getProtocol().equalsIgnoreCase("http") && !url.getProtocol().equalsIgnoreCase("https")) { + message.setTreatAsDownloadable(Message.Decision.NEVER); + return; + } + if (message.isHttpUploaded()) { + fileParams.setUrl(url.toString()); + message.setTreatAsDownloadable(Message.Decision.MUST); + return; + } + String extension = FileUtils.getRelevantExtension(url); + if (extension == null) { + message.setTreatAsDownloadable(Message.Decision.NEVER); + return; + } + byte[] ivAndKey = UrlUtil.getIvAndKeyFromURL(url); + + if (null != ivAndKey) { + if (MimeUtils.guessMimeTypeFromExtension(extension) != null) { + message.setTreatAsDownloadable(Message.Decision.MUST); + fileParams.setKeyAndIv(ivAndKey); + fileParams.setUrl(url.toString()); + } else { + message.setTreatAsDownloadable(Message.Decision.NEVER); + } + } else if (Transferable.VALID_IMAGE_EXTENSIONS.contains(extension) + || Transferable.WELL_KNOWN_EXTENSIONS.contains(extension)) { + message.setTreatAsDownloadable(Message.Decision.SHOULD); + fileParams.setUrl(url.toString()); + } else { + message.setTreatAsDownloadable(Message.Decision.NEVER); + } + + } catch (MalformedURLException e) { + message.setTreatAsDownloadable(Message.Decision.NEVER); + } + } + public static boolean needsDownload(Message message) { FileStatus fileStatus = (null != message.getFileParams()) ? message.getFileParams().getFileStatus() : null; - return (null != fileStatus && (fileStatus == FileStatus.NEEDS_DOWNLOAD - || fileStatus == FileStatus.UNDEFINED)) + return (null == fileStatus + || (null != fileStatus && (fileStatus == FileStatus.NEEDS_DOWNLOAD || fileStatus == FileStatus.UNDEFINED))) && message.treatAsDownloadable() != Message.Decision.NEVER; } @@ -122,6 +185,7 @@ public final class MessageUtil { FileParams fileParams = message.getFileParams(); if (null == fileParams) { fileParams = new FileParams(); + message.setFileParams(fileParams); } fileParams.setSize(size); if (null != url) { @@ -137,7 +201,6 @@ public final class MessageUtil { if (null != relativeFilePathFromMessage && relativeFilePathFromMessage.startsWith("/")) { fileParams.setPath(relativeFilePathFromMessage); } - message.setFileParams(fileParams); } private MessageUtil() { diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/UrlUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/UrlUtil.java new file mode 100644 index 00000000..50c42288 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/utils/UrlUtil.java @@ -0,0 +1,25 @@ +package de.thedevstack.conversationsplus.utils; + +import java.net.URL; + +import eu.siacs.conversations.utils.CryptoHelper; + +/** + * This utility class provides helper methods to handle URLs. + */ + +public final class UrlUtil { + public static byte[] getIvAndKeyFromURL(URL url) { + if (null == url) { + return null; + } + String reference = url.getRef(); + boolean linkHasIvAndKey = reference != null && reference.matches("([A-Fa-f0-9]{2}){48}"); + + return linkHasIvAndKey ? CryptoHelper.hexToBytes(reference) : null; + } + + private UrlUtil() { + // Helper Class + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/upload/HttpUploadRequestSlotPacketGenerator.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/upload/HttpUploadRequestSlotPacketGenerator.java index 915cf9a7..cdb957c4 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/upload/HttpUploadRequestSlotPacketGenerator.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/upload/HttpUploadRequestSlotPacketGenerator.java @@ -33,7 +33,7 @@ public final class HttpUploadRequestSlotPacketGenerator { public static IqPacket generate(Jid host, String filename, long filesize, String mime) { SlotRequestPacket packet = new SlotRequestPacket(filename, filesize); packet.setTo(host); - packet.setMime((mime == null) ? HttpUpload.DEFAULT_MIME_TYPE : mime); + packet.setMime((mime == null || mime.isEmpty()) ? HttpUpload.DEFAULT_MIME_TYPE : mime); return packet; } diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 5abba478..f524fc58 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.net.URL; import de.thedevstack.conversationsplus.entities.FileParams; import de.thedevstack.conversationsplus.enums.FileStatus; +import de.thedevstack.conversationsplus.utils.MessageUtil; import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.utils.FileUtils; import eu.siacs.conversations.utils.MimeUtils; @@ -339,7 +340,7 @@ public class Message extends AbstractEntity { public void setType(int type) { this.type = type; - if (null != this.fileParams && (type == Message.TYPE_FILE || type == Message.TYPE_IMAGE)) { + if (null == this.fileParams && (type == Message.TYPE_FILE || type == Message.TYPE_IMAGE)) { this.setFileParams(new FileParams()); } } @@ -482,18 +483,6 @@ public class Message extends AbstractEntity { NOT_DECIDED, } - private String extractRelevantExtension(URL url) { - if (url == null) { - return null; - } - String path = url.getPath(); - return extractRelevantExtension(path); - } - - private String extractRelevantExtension(String path) { - return FileUtils.getRelevantExtension(path); - } - public String getMimeType() { // TODO: Move to fileparams if (relativeFilePath != null) { int start = relativeFilePath.lastIndexOf('.') + 1; @@ -504,7 +493,7 @@ public class Message extends AbstractEntity { } } else { try { - return MimeUtils.guessMimeTypeFromExtension(extractRelevantExtension(new URL(this.getBody()))); + return MimeUtils.guessMimeTypeFromExtension(FileUtils.getRelevantExtension(new URL(this.getBody()))); } catch (MalformedURLException e) { return null; } @@ -527,50 +516,9 @@ public class Message extends AbstractEntity { if (mTreatAsDownloadAble != Decision.NOT_DECIDED) { return mTreatAsDownloadAble; } - /** - * 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 (getBody().contains(" ")) { - mTreatAsDownloadAble = Decision.NEVER; - return mTreatAsDownloadAble; - } - try { - URL url = new URL(body); - if (!url.getProtocol().equalsIgnoreCase("http") && !url.getProtocol().equalsIgnoreCase("https")) { - mTreatAsDownloadAble = Decision.NEVER; - return mTreatAsDownloadAble; - } - String extension = extractRelevantExtension(url); - if (extension == null) { - mTreatAsDownloadAble = Decision.NEVER; - return mTreatAsDownloadAble; - } - String ref = url.getRef(); - boolean encrypted = ref != null && ref.matches("([A-Fa-f0-9]{2}){48}"); - - if (encrypted) { - if (MimeUtils.guessMimeTypeFromExtension(extension) != null) { - mTreatAsDownloadAble = Decision.MUST; - return mTreatAsDownloadAble; - } else { - mTreatAsDownloadAble = Decision.NEVER; - return mTreatAsDownloadAble; - } - } else if (Transferable.VALID_IMAGE_EXTENSIONS.contains(extension) - || Transferable.WELL_KNOWN_EXTENSIONS.contains(extension)) { - mTreatAsDownloadAble = Decision.SHOULD; - return mTreatAsDownloadAble; - } else { - mTreatAsDownloadAble = Decision.NEVER; - return mTreatAsDownloadAble; - } - } catch (MalformedURLException e) { - mTreatAsDownloadAble = Decision.NEVER; - return mTreatAsDownloadAble; - } + MessageUtil.extractFileParamsFromBody(this); + return this.mTreatAsDownloadAble; } public void untie() { diff --git a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java index a1ad3c01..e3398f93 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java @@ -19,6 +19,7 @@ import javax.net.ssl.X509TrustManager; import de.thedevstack.conversationsplus.ConversationsPlusApplication; +import de.thedevstack.conversationsplus.utils.MessageUtil; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; @@ -39,10 +40,13 @@ public class HttpConnectionManager extends AbstractConnectionManager { } public static HttpDownloadConnection createNewDownloadConnection(Message message, boolean interactive) { - HttpDownloadConnection connection = new HttpDownloadConnection(INSTANCE); - connection.init(message,interactive); - INSTANCE.downloadConnections.add(connection); - return connection; + if (MessageUtil.needsDownload(message)) { + HttpDownloadConnection connection = new HttpDownloadConnection(INSTANCE); + connection.init(message, interactive); + INSTANCE.downloadConnections.add(connection); + return connection; + } + return null; } public void finishConnection(HttpDownloadConnection connection) { diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java index 8b905fd1..77b8e333 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java @@ -18,6 +18,7 @@ import javax.net.ssl.SSLHandshakeException; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import de.thedevstack.conversationsplus.entities.FileParams; import de.thedevstack.conversationsplus.enums.FileStatus; import de.thedevstack.conversationsplus.exceptions.RemoteFileNotFoundException; import de.thedevstack.conversationsplus.utils.MessageUtil; @@ -76,8 +77,22 @@ public class HttpDownloadConnection implements Transferable { this.message = message; this.message.setTransferable(this); try { - mUrl = new URL(message.getFileParams().getUrl()); - final String sUrlFilename = mUrl.getPath().substring(mUrl.getPath().lastIndexOf('/')).toLowerCase(); + String url = (null != message && null != message.getFileParams()) ? message.getFileParams().getUrl() : null; + if (null == url) { + /* + * If this code is reached and the URL is null something went wrong. + * Try again to extract the file parameters from the message. + */ + MessageUtil.extractFileParamsFromBody(message); + url = (null != message.getFileParams()) ? message.getFileParams().getUrl() : null; + if (null == url) { + message.setTreatAsDownloadable(Message.Decision.NEVER); // TODO find sth better + this.cancel(); + return; + } + } + mUrl = new URL(url); + final String sUrlFilename = mUrl.getPath().substring(mUrl.getPath().lastIndexOf('/') + 1).toLowerCase(); final String lastPart = FileUtils.getLastExtension(sUrlFilename); if (!lastPart.isEmpty() && ("pgp".equals(lastPart) || "gpg".equals(lastPart))) { @@ -86,18 +101,25 @@ public class HttpDownloadConnection implements Transferable { && message.getEncryption() != Message.ENCRYPTION_AXOLOTL) { this.message.setEncryption(Message.ENCRYPTION_NONE); } + String extension; + String originalFilename; if (!lastPart.isEmpty() && VALID_CRYPTO_EXTENSIONS.contains(lastPart)) { extension = FileUtils.getSecondToLastExtension(sUrlFilename); + originalFilename = sUrlFilename.replace("." + lastPart, ""); } else { extension = lastPart; + originalFilename = sUrlFilename; } message.setRelativeFilePath(message.getUuid() + "." + extension); this.file = FileBackend.getFile(message, false); - String reference = mUrl.getRef(); - if (reference != null && reference.length() == 96) { - this.file.setKeyAndIv(CryptoHelper.hexToBytes(reference)); - } + + FileParams fileParams = message.getFileParams(); + if (null == fileParams) { + fileParams = new FileParams(); + message.setFileParams(fileParams); + } + fileParams.setOriginalFilename(originalFilename); if ((this.message.getEncryption() == Message.ENCRYPTION_OTR || this.message.getEncryption() == Message.ENCRYPTION_AXOLOTL) diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 571a29b4..e194f503 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -21,7 +21,6 @@ import de.thedevstack.conversationsplus.utils.AvatarUtil; import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.OtrService; import eu.siacs.conversations.crypto.axolotl.AxolotlService; -import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.axolotl.AxolotlServiceImpl; import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage; import eu.siacs.conversations.entities.Account; @@ -30,7 +29,6 @@ import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; -import eu.siacs.conversations.http.HttpConnectionManager; import eu.siacs.conversations.services.AvatarService; import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.services.XmppConnectionService; @@ -470,15 +468,16 @@ public class MessageParser extends AbstractParser implements if (message.getEncryption() == Message.ENCRYPTION_NONE || !ConversationsPlusPreferences.dontSaveEncrypted()) { mXmppConnectionService.databaseBackend.createMessage(message); } + MessageUtil.extractFileParamsFromBody(message); + FileParams fileParams = message.getFileParams(); + if (message.treatAsDownloadable() != Message.Decision.NEVER) { + fileParams.setFileStatus(FileStatus.NEEDS_DOWNLOAD); + } if (message.trusted() && message.treatAsDownloadable() != Message.Decision.NEVER && ConversationsPlusPreferences.autoAcceptFileSize() > 0 && (message.isHttpUploaded() || ConversationsPlusPreferences.autoDownloadFileLink())) { - FileParams fileParams = new FileParams(); - fileParams.setFileStatus(FileStatus.NEEDS_DOWNLOAD); - fileParams.setUrl(message.getBody()); - message.setFileParams(fileParams); - HttpConnectionManager.createNewDownloadConnection(message); + // Download this message in MessageAdapter } else { if (query == null) { 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 6f99ef55..c7cf722d 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -108,7 +108,18 @@ public class FileBackend { } public static DownloadableFile getFile(Message message, boolean decrypted) { - return new DownloadableFile(getFilePath(message, decrypted)); + DownloadableFile downloadableFile = new DownloadableFile(getFilePath(message, decrypted)); + FileParams fileParams = message.getFileParams(); + if (null != fileParams) { + if (null != fileParams.getKey()) { + downloadableFile.setKey(fileParams.getKey()); + } + if (null != fileParams.getIv()) { + downloadableFile.setIv(fileParams.getIv()); + } + } + + return downloadableFile; } protected static String getFilePath(Message message, String extension) { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 2a7432dd..4466dd22 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -62,6 +62,7 @@ import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.http.HttpConnectionManager; +import eu.siacs.conversations.http.HttpDownloadConnection; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; @@ -622,7 +623,10 @@ public class ConversationActivity extends XmppActivity Toast.makeText(this, R.string.not_connected_try_again, Toast.LENGTH_SHORT).show(); } } else if (message.treatAsDownloadable() != Message.Decision.NEVER) { - HttpConnectionManager.createNewDownloadConnection(message, true); + HttpDownloadConnection downloadConnection = HttpConnectionManager.createNewDownloadConnection(message, true); + if (null == downloadConnection) { + Toast.makeText(this, R.string.file_not_on_remote_host, Toast.LENGTH_LONG).show(); + } } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 5068af8d..08f49551 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -693,7 +693,10 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } private void downloadFile(Message message) { - HttpConnectionManager.createNewDownloadConnection(message,true); + HttpDownloadConnection downloadConnection = HttpConnectionManager.createNewDownloadConnection(message, true); + if (null == downloadConnection) { + Toast.makeText(activity, R.string.file_not_on_remote_host, Toast.LENGTH_LONG).show(); + } } private void cancelTransmission(Message message) { 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 509e3b9b..d562dd5e 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -18,9 +18,7 @@ import android.text.style.ForegroundColorSpan; import android.text.style.RelativeSizeSpan; import android.text.style.StyleSpan; import android.text.util.Linkify; -import android.util.DisplayMetrics; import android.util.Patterns; -import android.view.Gravity; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; @@ -33,7 +31,6 @@ import android.widget.TextView; import android.widget.Toast; import java.lang.ref.WeakReference; -import java.net.URL; import java.util.List; import java.util.concurrent.RejectedExecutionException; import java.util.regex.Matcher; @@ -53,6 +50,7 @@ import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Transferable; +import eu.siacs.conversations.http.HttpConnectionManager; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.providers.ConversationsPlusFileProvider; import eu.siacs.conversations.services.AvatarService; @@ -75,8 +73,6 @@ public class MessageAdapter extends ArrayAdapter<Message> { private ConversationActivity activity; - private DisplayMetrics metrics; - private OnContactPictureClicked mOnContactPictureClickedListener; private OnContactPictureLongClicked mOnContactPictureLongClickedListener; @@ -92,7 +88,6 @@ public class MessageAdapter extends ArrayAdapter<Message> { public MessageAdapter(ConversationActivity activity, List<Message> messages) { super(activity, 0, messages); this.activity = activity; - metrics = getContext().getResources().getDisplayMetrics(); } public void setOnContactPictureClicked(OnContactPictureClicked listener) { @@ -143,14 +138,9 @@ public class MessageAdapter extends ArrayAdapter<Message> { boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI && message.getStatus() <= Message.STATUS_RECEIVED; if (message.hasFileAttached() || message.getTransferable() != null) { - FileParams params = message.getFileParams(); + FileParams params = message.getFileParams(); if (null != params) { - long size = params.getSize(); - if (size > (1.5 * 1024 * 1024)) { - filesize = size / (1024 * 1024)+ " MiB"; - } else if (size > 0) { - filesize = size / 1024 + " KiB"; - } + filesize = UIHelper.getHumanReadableFileSize(params.getSize()); } if (message.getTransferable() != null && message.getTransferable().getStatus() == Transferable.STATUS_FAILED) { @@ -231,15 +221,11 @@ public class MessageAdapter extends ArrayAdapter<Message> { String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(), message.getTimeSent()); if (message.getStatus() <= Message.STATUS_RECEIVED) { - if ((filesize != null) && (info != null)) { - 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)) { - viewHolder.time.setText(formatedTime + " \u00B7 " + filesize); - } else { - viewHolder.time.setText(formatedTime); - } + StringBuilder timeText = new StringBuilder(); + timeText.append((null != formatedTime) ? formatedTime + ((null != info || null != filesize) ? " \u00B7 " : "") : ""); + timeText.append((null != filesize) ? filesize + ((null != info) ? " \u00B7 " : "") : ""); + timeText.append((null != info) ? info : ""); + viewHolder.time.setText(timeText); } else { if ((filesize != null) && (info != null)) { viewHolder.time.setText(filesize + " \u00B7 " + info); @@ -408,28 +394,60 @@ public class MessageAdapter extends ArrayAdapter<Message> { return count; } - private void displayDownloadableMessage(ViewHolder viewHolder, - final Message message, String text) { + private void displayDownloadButton(ViewHolder viewHolder, String btnText, OnClickListener onClickListener) { + viewHolder.download_button.setVisibility(View.VISIBLE); + viewHolder.download_button.setText(btnText); + viewHolder.download_button.setOnClickListener(onClickListener); + viewHolder.download_button.setOnLongClickListener(openContextMenu); + } + + private void displayFileInfoForFileMessage(final Message message, ViewHolder viewHolder) { + viewHolder.messageBody.setVisibility(View.VISIBLE); + StringBuilder fileInfos = new StringBuilder(); + String filename = UIHelper.getDisplayFilename(message); + fileInfos.append((null != filename && !filename.isEmpty()) ? (filename) : ""); + + int oldAutoLinkMask = viewHolder.messageBody.getAutoLinkMask(); + viewHolder.messageBody.setAutoLinkMask(0); + viewHolder.messageBody.setText(fileInfos); + viewHolder.messageBody.setAutoLinkMask(oldAutoLinkMask); + } + + private void displayDownloadableMessage(ViewHolder viewHolder, final Message message, int resId) { viewHolder.image.setVisibility(View.GONE); - viewHolder.messageBody.setVisibility(View.GONE); - viewHolder.download_button.setVisibility(View.VISIBLE); - viewHolder.download_button.setText(text); - viewHolder.download_button.setOnClickListener(new OnClickListener() { + FileParams fileParams = message.getFileParams(); + String btnText; + if (null != fileParams) { + this.displayFileInfoForFileMessage(message, viewHolder); + btnText = activity.getString(resId, ""); + } else { + viewHolder.messageBody.setVisibility(View.GONE); + btnText = activity.getString(resId, UIHelper.getFileDescriptionString(activity, message)); + } + + this.displayDownloadButton(viewHolder, btnText, new OnClickListener() { @Override public void onClick(View v) { activity.startDownloadable(message); } }); - viewHolder.download_button.setOnLongClickListener(openContextMenu); } private void displayOpenableMessage(ViewHolder viewHolder,final Message message) { viewHolder.image.setVisibility(View.GONE); - viewHolder.messageBody.setVisibility(View.GONE); - viewHolder.download_button.setVisibility(View.VISIBLE); - viewHolder.download_button.setText(activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity, message))); - viewHolder.download_button.setOnClickListener(new OnClickListener() { + + FileParams fileParams = message.getFileParams(); + String btnText; + if (null != fileParams) { + this.displayFileInfoForFileMessage(message, viewHolder); + btnText = activity.getString(R.string.cplus_open); + } else { + viewHolder.messageBody.setVisibility(View.GONE); + btnText = activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity, message)); + } + + this.displayDownloadButton(viewHolder, btnText, new OnClickListener() { @Override public void onClick(View v) { @@ -489,6 +507,149 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.image.setOnLongClickListener(openContextMenu); } + private View displayStatusMessage(final Message message, ViewHolder viewHolder) { + if (null != viewHolder) { + final Conversation conversation = message.getConversation(); + + viewHolder.status_message.setVisibility(View.VISIBLE); + viewHolder.contact_picture.setVisibility(View.VISIBLE); + if (conversation.getMode() == Conversation.MODE_SINGLE) { + viewHolder.contact_picture.setImageBitmap(AvatarService.getInstance().get(conversation.getContact(), + activity.getPixel(32))); + viewHolder.contact_picture.setAlpha(0.5f); + } + viewHolder.status_message.setText(message.getBody()); + } + return viewHolder.view; + } + + private void displayFileMessage(final Message message, ViewHolder viewHolder) { + final FileParams fileParams = message.getFileParams(); + String mimeType = (null != fileParams) ? fileParams.getMimeType() : null; + if ((message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) + || (fileParams != null && fileParams.getWidth() > 0) + || (null != mimeType && mimeType.startsWith("image/"))) { + displayImageMessage(viewHolder, message); + } else if ((message.getType() == Message.TYPE_FILE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) + && null != fileParams && (FileStatus.NEEDS_DOWNLOAD != fileParams.getFileStatus() || 0 == fileParams.getWidth())) { + displayOpenableMessage(viewHolder, message); + } else if (Message.Decision.NEVER == message.treatAsDownloadable()) { + displayTextMessage(viewHolder, message, getItemViewType(message) == RECEIVED && !message.isValidInSession()); + } else { + displayDownloadableMessage(viewHolder, message, R.string.download_x_file); + } + } + + + private void loadAvatar(Message message, ImageView imageView) { + if (cancelPotentialWork(message, imageView)) { + int avatarPixel = imageView.getResources().getDimensionPixelOffset(R.dimen.msg_avatar_size); + final Bitmap bm = AvatarService.getInstance().get(message, avatarPixel, true); + if (bm != null) { + imageView.setImageBitmap(bm); + imageView.setBackgroundColor(0x00000000); + } else { + imageView.setBackgroundColor(UIHelper.getColorForName(UIHelper.getMessageDisplayName(message))); + imageView.setImageDrawable(null); + final BitmapWorkerTask task = new BitmapWorkerTask(imageView); + final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task); + imageView.setImageDrawable(asyncDrawable); + try { + task.execute(message); + } catch (final RejectedExecutionException ignored) { + } + } + } + } + + private void displayAvatar(final Message message, int type, ViewHolder viewHolder) { + if (type == RECEIVED) { + ImageView imageView = viewHolder.contact_picture; + if (null != imageView) { + this.loadAvatar(message, imageView); + imageView.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + if (MessageAdapter.this.mOnContactPictureClickedListener != null) { + MessageAdapter.this.mOnContactPictureClickedListener + .onContactPictureClicked(message); + } + + } + }); + if (message.getConversation().getMode() == Conversation.MODE_MULTI) { + imageView.setOnLongClickListener(new OnLongClickListener() { + + @Override + public boolean onLongClick(View v) { + if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) { + MessageAdapter.this.mOnContactPictureLongClickedListener + .onContactPictureLongClicked(message); + return true; + } else { + return false; + } + } + }); + } + } + } + } + + private View initializeView(int type, ViewGroup parent) { + Integer viewResId = null; + + switch (type) { + case SENT: + viewResId = R.layout.message_sent; + break; + case RECEIVED: + viewResId = R.layout.message_received; + break; + case STATUS: + viewResId = R.layout.message_status; + break; + } + + return activity.getLayoutInflater().inflate(viewResId, parent, false); + } + + private ViewHolder initializeViewHolderAndView(Message message, int type, ViewGroup parent) { + View view = initializeView(type, parent); + ViewHolder viewHolder = new ViewHolder(view); + if (SENT == type + || RECEIVED == type) { + viewHolder.message_box = (LinearLayout) view.findViewById(R.id.message_box); + viewHolder.message_box.setVisibility(View.VISIBLE); + viewHolder.indicator = (ImageView) view.findViewById(R.id.security_indicator); + viewHolder.messageBody = (TextView) view.findViewById(R.id.message_body); + viewHolder.time = (TextView) view.findViewById(R.id.message_time); + viewHolder.indicatorReceived = (ImageView) view.findViewById(R.id.indicator_received); + } + if ((SENT == type + || RECEIVED == type)) { + viewHolder.download_button = (Button) view.findViewById(R.id.download_button); + viewHolder.image = (ImageView) view.findViewById(R.id.message_image); + } + if (RECEIVED == type) { // Extra block as preparation for new /me representation + viewHolder.contact_picture = (ImageView) view.findViewById(R.id.message_photo); + viewHolder.contact_picture.setVisibility(View.VISIBLE); + } + if (RECEIVED == type) { + viewHolder.encryption = (TextView) view.findViewById(R.id.message_encryption); + } + if (STATUS == type) { + viewHolder.contact_picture = (ImageView) view.findViewById(R.id.message_photo); + viewHolder.contact_picture.setVisibility(View.VISIBLE); + viewHolder.status_message = (TextView) view.findViewById(R.id.status_message); + viewHolder.status_message.setVisibility(View.VISIBLE); + } + view.setTag(viewHolder); + + return viewHolder; + } + @Override public View getView(int position, View view, ViewGroup parent) { final Message message = getItem(position); @@ -496,180 +657,81 @@ public class MessageAdapter extends ArrayAdapter<Message> { final Conversation conversation = message.getConversation(); final Account account = conversation.getAccount(); final int type = getItemViewType(position); - ViewHolder viewHolder; - if (view == null) { - viewHolder = new ViewHolder(); - switch (type) { - case SENT: - view = activity.getLayoutInflater().inflate( - R.layout.message_sent, parent, false); - viewHolder.message_box = (LinearLayout) view - .findViewById(R.id.message_box); - viewHolder.contact_picture = (ImageView) view - .findViewById(R.id.message_photo); - viewHolder.download_button = (Button) view - .findViewById(R.id.download_button); - viewHolder.indicator = (ImageView) view - .findViewById(R.id.security_indicator); - viewHolder.image = (ImageView) view - .findViewById(R.id.message_image); - viewHolder.messageBody = (TextView) view - .findViewById(R.id.message_body); - viewHolder.time = (TextView) view - .findViewById(R.id.message_time); - viewHolder.indicatorReceived = (ImageView) view - .findViewById(R.id.indicator_received); - viewHolder.remoteFileStatus = (TextView) view.findViewById(R.id.remote_file_status); - break; - case RECEIVED: - view = activity.getLayoutInflater().inflate( - R.layout.message_received, parent, false); - viewHolder.message_box = (LinearLayout) view - .findViewById(R.id.message_box); - viewHolder.contact_picture = (ImageView) view - .findViewById(R.id.message_photo); - viewHolder.download_button = (Button) view - .findViewById(R.id.download_button); - viewHolder.indicator = (ImageView) view - .findViewById(R.id.security_indicator); - viewHolder.image = (ImageView) view - .findViewById(R.id.message_image); - viewHolder.messageBody = (TextView) view - .findViewById(R.id.message_body); - viewHolder.time = (TextView) view - .findViewById(R.id.message_time); - viewHolder.indicatorReceived = (ImageView) view - .findViewById(R.id.indicator_received); - viewHolder.encryption = (TextView) view.findViewById(R.id.message_encryption); - break; - case STATUS: - view = activity.getLayoutInflater().inflate(R.layout.message_status, parent, false); - viewHolder.contact_picture = (ImageView) view.findViewById(R.id.message_photo); - viewHolder.status_message = (TextView) view.findViewById(R.id.status_message); - break; - default: - viewHolder = null; - break; - } - view.setTag(viewHolder); - } else { - viewHolder = (ViewHolder) view.getTag(); - if (viewHolder == null) { - return view; - } - } - - boolean darkBackground = (type == RECEIVED && !isInValidSession); - - if (type == STATUS) { - viewHolder.status_message.setVisibility(View.VISIBLE); - viewHolder.contact_picture.setVisibility(View.VISIBLE); - if (conversation.getMode() == Conversation.MODE_SINGLE) { - viewHolder.contact_picture.setImageBitmap(AvatarService.getInstance().get(conversation.getContact(), - activity.getPixel(32))); - viewHolder.contact_picture.setAlpha(0.5f); - } - viewHolder.status_message.setText(message.getBody()); - return view; - } else { - loadAvatar(message, viewHolder.contact_picture); - } + ViewHolder viewHolder; + if (null == view) { + viewHolder = initializeViewHolderAndView(message, type, parent); + view = viewHolder.view; + } else { + viewHolder = (ViewHolder) view.getTag(); + if (null == viewHolder) { + return view; + } + } - viewHolder.contact_picture - .setOnClickListener(new OnClickListener() { + if (type == STATUS) { + return displayStatusMessage(message, viewHolder); + } - @Override - public void onClick(View v) { - if (MessageAdapter.this.mOnContactPictureClickedListener != null) { - MessageAdapter.this.mOnContactPictureClickedListener - .onContactPictureClicked(message); - } + this.displayAvatar(message, type, viewHolder); - } - }); - viewHolder.contact_picture - .setOnLongClickListener(new OnLongClickListener() { - - @Override - public boolean onLongClick(View v) { - if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) { - MessageAdapter.this.mOnContactPictureLongClickedListener - .onContactPictureLongClicked(message); - return true; - } else { - return false; - } - } - }); + boolean darkBackground = (type == RECEIVED && !isInValidSession); + this.displayStatus(viewHolder, message, type, darkBackground); 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 (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,darkBackground); - } - } 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.getFileParams() != null && message.getFileParams().getWidth() > 0) { // TODO Use FileParams.getMimeType() - displayImageMessage(viewHolder,message); - } else { - displayOpenableMessage(viewHolder, message); - } - } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { - if (activity.hasPgp()) { - if (account.getPgpDecryptionService().isRunning()) { - displayInfoMessage(viewHolder, activity.getString(R.string.message_decrypting), darkBackground); - } else { - displayInfoMessage(viewHolder, activity.getString(R.string.pgp_message), darkBackground); - } - } else { - displayInfoMessage(viewHolder,activity.getString(R.string.install_openkeychain),darkBackground); - if (viewHolder != null) { - viewHolder.message_box - .setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - activity.showInstallPgpDialog(); - } - }); - } - } - } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { - displayDecryptionFailed(viewHolder,darkBackground); - } else if (message.hasFileAttached()) { // TODO: Move to a single block with Message.TYPE_FILE OR Message.TYPE_IMAGE - FileParams fileParams = message.getFileParams(); - String mimeType = (null != fileParams) ? fileParams.getMimeType() : null; - if (null != mimeType && mimeType.startsWith("image/")) { - displayImageMessage(viewHolder, message); + if (null != transferable) { + switch (transferable.getStatus()) { + case Transferable.STATUS_OFFER: + displayDownloadableMessage(viewHolder, message, R.string.download_x_file); + break; + case Transferable.STATUS_OFFER_CHECK_FILESIZE: + displayDownloadableMessage(viewHolder, message, R.string.check_x_filesize); + break; + case Transferable.STATUS_UPLOADING: + displayFileMessage(message, viewHolder); + break; + case Transferable.STATUS_DELETED: + case Transferable.STATUS_CHECKING: + case Transferable.STATUS_FAILED: + case Transferable.STATUS_UNKNOWN: + displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first,darkBackground); + break; + } + } else if (message.hasFileAttached()) { + if (message.trusted() + && MessageUtil.needsDownload(message) + && ConversationsPlusPreferences.autoAcceptFileSize() > 0 + && (message.isHttpUploaded() || ConversationsPlusPreferences.autoDownloadFileLink())) { + HttpConnectionManager.createNewDownloadConnection(message); + } else { + displayFileMessage(message, viewHolder); + } + } else if (GeoHelper.isGeoUri(message.getBody())) { + displayLocationMessage(viewHolder, message); + } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { + if (activity.hasPgp()) { + if (account.getPgpDecryptionService().isRunning()) { + displayInfoMessage(viewHolder, activity.getString(R.string.message_decrypting), darkBackground); + } else { + displayInfoMessage(viewHolder, activity.getString(R.string.pgp_message), darkBackground); + } } else { - displayOpenableMessage(viewHolder, message); + displayInfoMessage(viewHolder,activity.getString(R.string.install_openkeychain),darkBackground); + if (viewHolder != null) { + viewHolder.message_box + .setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + activity.showInstallPgpDialog(); + } + }); + } } + } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { + displayDecryptionFailed(viewHolder,darkBackground); } else { - if (GeoHelper.isGeoUri(message.getBody())) { - displayLocationMessage(viewHolder,message); - } else if (MessageUtil.needsDownload(message)) { - try { - URL url = new URL(message.getFileParams().getUrl()); - displayDownloadableMessage(viewHolder, - message, - activity.getString(R.string.check_x_filesize_on_host, - UIHelper.getFileDescriptionString(activity, message), - url.getHost())); - } catch (Exception e) { - displayDownloadableMessage(viewHolder, - message, - activity.getString(R.string.check_x_filesize, - UIHelper.getFileDescriptionString(activity, message))); - } - } else { - displayTextMessage(viewHolder, message, darkBackground); - } - } + displayTextMessage(viewHolder, message, darkBackground); + } if (type == RECEIVED) { if(isInValidSession) { @@ -681,8 +743,6 @@ public class MessageAdapter extends ArrayAdapter<Message> { } } - displayStatus(viewHolder, message, type, darkBackground); - return view; } @@ -750,6 +810,10 @@ public class MessageAdapter extends ArrayAdapter<Message> { } private static class ViewHolder { + protected ViewHolder(View view) { + this.view = view; + } + protected View view; protected LinearLayout message_box; protected Button download_button; @@ -774,7 +838,8 @@ public class MessageAdapter extends ArrayAdapter<Message> { @Override protected Bitmap doInBackground(Message... params) { - return AvatarService.getInstance().get(params[0], activity.getPixel(48), isCancelled()); + int avatarPixel = activity.getResources().getDimensionPixelOffset(R.dimen.msg_avatar_size); + return AvatarService.getInstance().get(params[0], avatarPixel, isCancelled()); } @Override @@ -789,26 +854,6 @@ public class MessageAdapter extends ArrayAdapter<Message> { } } - public void loadAvatar(Message message, ImageView imageView) { - if (cancelPotentialWork(message, imageView)) { - final Bitmap bm = AvatarService.getInstance().get(message, activity.getPixel(48), true); - if (bm != null) { - imageView.setImageBitmap(bm); - imageView.setBackgroundColor(0x00000000); - } else { - imageView.setBackgroundColor(UIHelper.getColorForName(UIHelper.getMessageDisplayName(message))); - imageView.setImageDrawable(null); - final BitmapWorkerTask task = new BitmapWorkerTask(imageView); - final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task); - imageView.setImageDrawable(asyncDrawable); - try { - task.execute(message); - } catch (final RejectedExecutionException ignored) { - } - } - } - } - public static boolean cancelPotentialWork(Message message, ImageView imageView) { final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); diff --git a/src/main/java/eu/siacs/conversations/utils/FileUtils.java b/src/main/java/eu/siacs/conversations/utils/FileUtils.java index 18014894..3633025f 100644 --- a/src/main/java/eu/siacs/conversations/utils/FileUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/FileUtils.java @@ -13,6 +13,7 @@ import android.provider.MediaStore; import android.provider.OpenableColumns; import java.io.File; +import java.net.URL; import java.util.List; import de.thedevstack.conversationsplus.ConversationsPlusApplication; @@ -162,6 +163,14 @@ public final class FileUtils { return "com.android.providers.media.documents".equals(uri.getAuthority()); } + public static String getRelevantExtension(URL url) { + if (url == null) { + return null; + } + String path = url.getPath(); + return getRelevantExtension(path); + } + public static String getRelevantExtension(String path) { if (path == null || path.isEmpty()) { return null; diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index a97b16a4..d9cdd34c 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -14,6 +14,7 @@ import java.util.Locale; import de.thedevstack.conversationsplus.ConversationsPlusColors; +import de.thedevstack.conversationsplus.entities.FileParams; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -211,16 +212,26 @@ public class UIHelper { } else if (mime.startsWith("image/")) { return context.getString(R.string.image); } else if (mime.contains("pdf")) { - return context.getString(R.string.pdf_document) ; + return context.getString(R.string.pdf_document); } else if (mime.contains("application/vnd.android.package-archive")) { - return context.getString(R.string.apk) ; + return context.getString(R.string.apk); } else if (mime.contains("vcard")) { - return context.getString(R.string.vcard) ; + return context.getString(R.string.vcard); } else { - return message.getRelativeFilePath(); + String filename = getDisplayFilename(message); + if (null == filename) { + return context.getString(R.string.file); + } else { + return filename; + } } } + public static String getDisplayFilename(final Message message) { + String originalFilename = (null != message.getFileParams()) ? message.getFileParams().getOriginalFilename() : null; + return (null != originalFilename) ? originalFilename : message.getRelativeFilePath(); + } + public static String getMessageDisplayName(final Message message) { final Conversation conversation = message.getConversation(); if (message.getStatus() == Message.STATUS_RECEIVED) { @@ -281,7 +292,20 @@ public class UIHelper { return LOCATION_QUESTIONS.contains(body); } - public static String getHumanReadableFileSize(long filesize) { + public static String getHumanReadableFileSize(long size) { + String filesize; + if (size > (1.5 * 1024 * 1024)) { + filesize = size / (1024 * 1024)+ " MiB"; + } else if (size > 0) { + filesize = size / 1024 + " KiB"; + } else { + filesize = null; + } + + return filesize; + } + + public static String getHumanReadableDetailedFileSize(long filesize) { if (0 > filesize) { return "?"; } diff --git a/src/main/res/layout/dialog_message_details.xml b/src/main/res/layout/dialog_message_details.xml index 7c5d92cb..f2d7af07 100644 --- a/src/main/res/layout/dialog_message_details.xml +++ b/src/main/res/layout/dialog_message_details.xml @@ -150,6 +150,28 @@ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" + android:text="@string/dlg_msg_details_original_filename" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="" + android:textAlignment="textEnd" + android:gravity="end" + android:id="@+id/dlgMsgDetFileOriginalName" /> + </TableRow> + + <TableRow + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/white" + android:layout_marginTop="0.3dp" + android:layout_marginLeft="0.3dp" + android:layout_marginRight="0.3dp"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:text="@string/dlg_msg_details_file_mime" /> <TextView diff --git a/src/main/res/values/dimens.xml b/src/main/res/values/dimens.xml index d5d4aad4..7f20b59b 100644 --- a/src/main/res/values/dimens.xml +++ b/src/main/res/values/dimens.xml @@ -9,4 +9,5 @@ <dimen name="ambilwarna_hsvWidth">240dp</dimen> <dimen name="ambilwarna_hueWidth">30dp</dimen> <dimen name="ambilwarna_spacer">8dp</dimen> + <dimen name="msg_avatar_size">48dp</dimen> </resources> diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 16494044..5200fcb9 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -679,4 +679,7 @@ <string name="remote_filestatus_delete_failed">Failed to delete remote file</string> <string name="remote_filestatus_delete_success">Remote file deleted</string> <string name="remote_filestatus_delete_inprogress">Deleting remote file...</string> + <string name="file_not_on_remote_host">No file on remote host</string> + <string name="dlg_msg_details_original_filename">Original Filename</string> + <string name="cplus_open">Open</string> </resources> |