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; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.DownloadableFile; import de.thedevstack.conversationsplus.entities.FileParams; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.entities.Transferable; import de.thedevstack.conversationsplus.enums.FileStatus; import de.thedevstack.conversationsplus.persistance.DatabaseBackend; import de.thedevstack.conversationsplus.persistance.FileBackend; /** * Utility class to work with messages. */ public final class MessageUtil { public static void extractFileParamsFromBody(Message message) { if (null == message) { return; } // Ensure that for every message the fileParams are set after calling this method FileParams fileParams = message.getFileParams(); if (null == fileParams) { fileParams = new FileParams(); message.setFileParams(fileParams); } 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; } try { URL url = new URL(body); if (!url.getProtocol().equalsIgnoreCase("http") && !url.getProtocol().equalsIgnoreCase("https")) { message.setTreatAsDownloadable(Message.Decision.NEVER); return; } String extension = FileUtils.getRelevantExtension(url); if (message.isHttpUploaded()) { fileParams.setUrl(url.toString()); if (null != extension && (Transferable.WELL_KNOWN_EXTENSIONS.contains(extension.toLowerCase()) || Transferable.VALID_IMAGE_EXTENSIONS.contains(extension.toLowerCase()))) { message.setTreatAsDownloadable(Message.Decision.MUST); } else { message.setTreatAsDownloadable(Message.Decision.NEVER); fileParams.setFileStatus(FileStatus.UNDEFINED); } extractFilename(message, url.toString()); return; } 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); } 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); } else { message.setTreatAsDownloadable(Message.Decision.NEVER); } if (message.treatAsDownloadable() == Message.Decision.MUST || message.treatAsDownloadable() == Message.Decision.SHOULD) { fileParams.setUrl(url.toString()); extractFilename(message, url.toString()); } } catch (MalformedURLException e) { message.setTreatAsDownloadable(Message.Decision.NEVER); } } private static void extractFilename(Message message, String url) { String originalFilename = FileUtils.getFilenameFromPath(url); final String lowerCaseFilename = originalFilename.toLowerCase(); final String lastPart = FileUtils.getLastExtension(lowerCaseFilename); detectAndSetEncryption(lastPart, message); String filenameExtension; if (!lastPart.isEmpty() && Transferable.VALID_CRYPTO_EXTENSIONS.contains(lastPart)) { filenameExtension = FileUtils.getSecondToLastExtension(lowerCaseFilename); originalFilename = originalFilename.replace("." + lastPart, ""); } else { filenameExtension = lastPart; } message.setRelativeFilePath(message.getUuid() + "." + filenameExtension); message.getFileParams().setOriginalFilename(originalFilename); } private static void detectAndSetEncryption(String lastPart, Message message) { if (!lastPart.isEmpty() && ("pgp".equals(lastPart) || "gpg".equals(lastPart))) { message.setEncryption(Message.ENCRYPTION_PGP); } else if (message.getEncryption() != Message.ENCRYPTION_OTR && message.getEncryption() != Message.ENCRYPTION_AXOLOTL) { message.setEncryption(Message.ENCRYPTION_NONE); } else if ((message.getEncryption() == Message.ENCRYPTION_OTR || message.getEncryption() == Message.ENCRYPTION_AXOLOTL) && message.getFileParams() != null && message.getFileParams().getKey() == null) { // If an encryption is set for the message, but no key given -> decryption not possible message.setEncryption(Message.ENCRYPTION_NONE); } } /** * Checks if an attached file is an image or not. * Prerequisite for calling this method: The check if a file is attached is done before. * @param message * @return */ public static boolean isAttachedFileAnImage(Message message) { final FileParams fileParams = message.getFileParams(); String mimeType = (null != fileParams) ? fileParams.getMimeType() : null; return message.getType() == Message.TYPE_IMAGE || (fileParams != null && fileParams.getWidth() > 0) || (null != mimeType && mimeType.startsWith("image/")); } public static boolean isTypeFileAndDecrypted(Message message) { return message.getType() == Message.TYPE_FILE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED; } public static boolean isDecrypted(Message message) { return message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED; } /** * Checks if the file status of an message is NOT one of: * * The file status does not guarantee that a file is really available or not. * @param message * @return */ public static boolean mayFileRemoteAvailable(Message message) { FileStatus fileStatus = (null != message.getFileParams()) ? message.getFileParams().getFileStatus() : null; return null != fileStatus && FileStatus.NOT_FOUND != fileStatus && FileStatus.DELETED != fileStatus; } public static boolean needsDownload(Message message) { FileStatus fileStatus = (null != message.getFileParams()) ? message.getFileParams().getFileStatus() : null; return (null == fileStatus || (null != fileStatus && (fileStatus == FileStatus.NEEDS_DOWNLOAD || fileStatus == FileStatus.UNDEFINED))) && message.treatAsDownloadable() != Message.Decision.NEVER; } public static boolean isMessageSent(Message message) { switch (message.getStatus()) { case Message.STATUS_SEND: case Message.STATUS_SEND_DISPLAYED: case Message.STATUS_SEND_RECEIVED: return true; default: return false; } } public static void setAndSaveFileStatus(Message message, FileStatus fileStatus) { message.getFileParams().setFileStatus(fileStatus); DatabaseBackend.getInstance().updateMessage(message); UiUpdateHelper.updateConversationUi(); } public static boolean markMessage(Conversation conversation, String uuid, int status) { if (uuid == null) { return false; } else { Message message = conversation.findSentMessageWithUuid(uuid); if (message != null) { markMessage(message, status); return true; } else { return false; } } } public static void markMessage(Message message, int status) { if (status == Message.STATUS_SEND_FAILED && (message.getStatus() == Message.STATUS_SEND_RECEIVED || message .getStatus() == Message.STATUS_SEND_DISPLAYED)) { return; } message.setStatus(status); DatabaseBackend.getInstance(ConversationsPlusApplication.getAppContext()).updateMessage(message); UiUpdateHelper.updateConversationUi(); } public static boolean wasHighlightedOrPrivate(final Message message) { final String nick = message.getConversation().getMucOptions().getActualNick(); final Pattern highlight = generateNickHighlightPattern(nick); if (message.getBody() == null || nick == null) { return false; } final Matcher m = highlight.matcher(message.getBody()); return (m.find() || message.getType() == Message.TYPE_PRIVATE); } private static Pattern generateNickHighlightPattern(final String nick) { // We expect a word boundary, i.e. space or start of string, followed by // the // nick (matched in case-insensitive manner), followed by optional // punctuation (for example "bob: i disagree" or "how are you alice?"), // followed by another word boundary. return Pattern.compile("\\b" + Pattern.quote(nick) + "\\p{Punct}?\\b", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); } public static void updateMessageWithImageDetails(Message message, String filePath, long size, int imageWidth, int imageHeight) { message.setRelativeFilePath(filePath); MessageUtil.updateMessageWithFileParams(message, null, size, imageWidth, imageHeight); } public static void updateFileParams(Message message) { updateFileParams(message, null); } public static void updateFileParams(Message message, URL url) { DownloadableFile file = FileBackend.getFile(message); int imageWidth = -1; int imageHeight = -1; if (message.getType() == Message.TYPE_IMAGE || file.getMimeType().startsWith("image/")) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; Bitmap bmp = BitmapFactory.decodeFile(file.getAbsolutePath(), options); imageHeight = options.outHeight; imageWidth = options.outWidth; if (null != bmp) { bmp.recycle(); } } MessageUtil.updateMessageWithFileParams(message, url, file.getSize(), imageWidth, imageHeight); } private static void updateMessageWithFileParams(Message message, URL url, long size, int imageWidth, int imageHeight) { FileParams fileParams = message.getFileParams(); if (null == fileParams) { fileParams = new FileParams(); message.setFileParams(fileParams); } fileParams.setSize(size); if (null != url) { fileParams.setUrl(url.toString()); } if (-1 < imageWidth) { fileParams.setWidth(imageWidth); } if (-1 < imageHeight) { fileParams.setHeight(imageHeight); } String relativeFilePathFromMessage = message.getRelativeFilePath(); if (null != relativeFilePathFromMessage && relativeFilePathFromMessage.startsWith("/")) { fileParams.setPath(relativeFilePathFromMessage); } } private MessageUtil() { // Static helper class } }