diff options
Diffstat (limited to 'src/main/java/de/thedevstack/conversationsplus')
9 files changed, 291 insertions, 11 deletions
diff --git a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java index 4a67aa3e..9c217e35 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java +++ b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java @@ -8,6 +8,8 @@ import android.preference.PreferenceManager; import java.io.File; import de.thedevstack.conversationsplus.utils.ImageUtil; +import eu.siacs.conversations.utils.SerialSingleThreadExecutor; + import eu.siacs.conversations.R; /** @@ -19,6 +21,9 @@ public class ConversationsPlusApplication extends Application { */ private static ConversationsPlusApplication instance; + private final SerialSingleThreadExecutor mFileAddingExecutor = new SerialSingleThreadExecutor(); + private final SerialSingleThreadExecutor mDatabaseExecutor = new SerialSingleThreadExecutor(); + /** * Initializes the application and saves its instance. */ @@ -37,6 +42,14 @@ public class ConversationsPlusApplication extends Application { return ConversationsPlusApplication.instance; } + public static void executeFileAdding(Runnable r) { + getInstance().mFileAddingExecutor.execute(r); + } + + public static void executeDatabaseOperation(Runnable r) { + getInstance().mDatabaseExecutor.execute(r); + } + /** * Returns the application's context. * @return Context the application's context diff --git a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java index b7b7fe47..17829998 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java +++ b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java @@ -14,6 +14,14 @@ public class ConversationsPlusPreferences extends Settings { private static ConversationsPlusPreferences instance; private final SharedPreferences sharedPreferences; + public static String imgTransferFolder() { + return getString("img_transfer_folder", getString("app_name", "Conversations+")); + } + + public static String fileTransferFolder() { + return getString("file_transfer_folder", getString("app_name", "Conversations+")); + } + public static UserDecision resizePicture() { return getEnumFromStringPref("resize_picture", UserDecision.ASK); } diff --git a/src/main/java/de/thedevstack/conversationsplus/exceptions/FileCopyException.java b/src/main/java/de/thedevstack/conversationsplus/exceptions/FileCopyException.java index 58363c0f..858b4563 100644 --- a/src/main/java/de/thedevstack/conversationsplus/exceptions/FileCopyException.java +++ b/src/main/java/de/thedevstack/conversationsplus/exceptions/FileCopyException.java @@ -1,14 +1,13 @@ package de.thedevstack.conversationsplus.exceptions; -public class FileCopyException extends Exception { +public class FileCopyException extends UiException { private static final long serialVersionUID = -1010013599132881427L; - private int resId; - public FileCopyException(int resId) { - this.resId = resId; - } + public FileCopyException(int resId) { + super(resId); + } - public int getResId() { - return resId; - } + public FileCopyException(int resId, Throwable e) { + super(resId, e); + } }
\ No newline at end of file diff --git a/src/main/java/de/thedevstack/conversationsplus/exceptions/ImageResizeException.java b/src/main/java/de/thedevstack/conversationsplus/exceptions/ImageResizeException.java new file mode 100644 index 00000000..b5786990 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/exceptions/ImageResizeException.java @@ -0,0 +1,16 @@ +package de.thedevstack.conversationsplus.exceptions; + +/** + * Created by tzur on 15.12.2015. + */ +public class ImageResizeException extends UiException { + private static final long serialVersionUID = -1010013599112881427L; + + public ImageResizeException(int resId) { + super(resId); + } + + public ImageResizeException(int resId, Throwable e) { + super(resId, e); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/exceptions/UiException.java b/src/main/java/de/thedevstack/conversationsplus/exceptions/UiException.java new file mode 100644 index 00000000..b05c5025 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/exceptions/UiException.java @@ -0,0 +1,22 @@ +package de.thedevstack.conversationsplus.exceptions; + +/** + * Exception to be shown in UI. + */ +public class UiException extends Exception { + private static final long serialVersionUID = -1010015239132881427L; + private int resId; + + public UiException(int resId) { + this.resId = resId; + } + + public UiException(int resId, Throwable e) { + super(e); + this.resId = resId; + } + + public int getResId() { + return resId; + } +} 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 b1b2298b..af875251 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/MessageDetailsDialog.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/MessageDetailsDialog.java @@ -55,6 +55,7 @@ public class MessageDetailsDialog extends AbstractAlertDialog { */ protected void displayFileInfo(View view, Message message) { if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getTransferable() != null) { + Logging.d("messagedetailsfile", "File is stored in path: " + message.getRelativeFilePath()); view.findViewById(R.id.dlgMsgDetFileTable).setVisibility(View.VISIBLE); if (null != message.getFileParams()) { Message.FileParams params = message.getFileParams(); @@ -140,7 +141,7 @@ public class MessageDetailsDialog extends AbstractAlertDialog { // Get own resource name -> What about msg written on other client? String me = conversation.getAccount().getJid().getResourcepart(); // Get resource name of chat partner, if available - String other = (message.getCounterpart().isBareJid()) ? "" : message.getCounterpart().getResourcepart(); + String other = (null == message.getCounterpart() || message.getCounterpart().isBareJid()) ? "" : message.getCounterpart().getResourcepart(); Logging.d("MesageDialog", "Me: " + me + ", other: " + other); TextView sender = (TextView) view.findViewById(R.id.dlgMsgDetSender); TextView receipient = (TextView) view.findViewById(R.id.dlgMsgDetReceipient); diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java index 135d6faa..ac58e698 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java @@ -1,15 +1,29 @@ package de.thedevstack.conversationsplus.ui.listeners; import android.app.PendingIntent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.net.Uri; import android.widget.Toast; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.enums.UserDecision; +import de.thedevstack.conversationsplus.exceptions.UiException; +import de.thedevstack.conversationsplus.utils.FileHelper; +import de.thedevstack.conversationsplus.utils.ImageUtil; +import de.thedevstack.conversationsplus.utils.MessageUtil; + import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Conversation; +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.ui.XmppActivity; @@ -81,13 +95,88 @@ public class ResizePictureUserDecisionListener implements UserDecisionListener { @Override public void onYes() { this.showPrepareFileToast(); - xmppConnectionService.attachImageToConversation(this.conversation, this.uri, this.callback); + final Message message; + final boolean forceEncryption = ConversationsPlusPreferences.forceEncryption(); + if (conversation.getNextEncryption(forceEncryption) == Message.ENCRYPTION_PGP) { + message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED); + } else { + message = new Message(conversation, "", conversation.getNextEncryption(forceEncryption)); + } + message.setCounterpart(conversation.getNextCounterpart()); + message.setType(Message.TYPE_IMAGE); + ConversationsPlusApplication.executeFileAdding(new Runnable() { + + @Override + public void run() { + try { + Bitmap resizedAndRotatedImage = ImageUtil.resizeAndRotateImage(uri); + DownloadableFile file = FileBackend.compressImageAndCopyToPrivateStorage(message, resizedAndRotatedImage); + String filePath = file.getAbsolutePath(); + long imageSize = file.getSize(); + int imageWidth = resizedAndRotatedImage.getWidth(); + int imageHeight = resizedAndRotatedImage.getHeight(); + MessageUtil.updateMessageWithImageDetails(message, filePath, imageSize, imageWidth, imageHeight); + if (conversation.getNextEncryption(forceEncryption) == Message.ENCRYPTION_PGP) { + xmppConnectionService.getPgpEngine().encrypt(message, callback); + } else { + callback.success(message); + } + } catch (final UiException e) { + Logging.e("pictureresizesending", "Error while sending resized picture. " + e.getMessage()); + callback.error(e.getResId(), message); + } + } + }); } @Override public void onNo() { this.showPrepareFileToast(); - xmppConnectionService.attachImageToConversationWithoutResizing(this.conversation, this.uri, this.callback); + final Message message; + final boolean forceEncryption = ConversationsPlusPreferences.forceEncryption(); + if (conversation.getNextEncryption(forceEncryption) == Message.ENCRYPTION_PGP) { + message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED); + } else { + message = new Message(conversation, "", conversation.getNextEncryption(forceEncryption)); + } + message.setCounterpart(conversation.getNextCounterpart()); + message.setType(Message.TYPE_IMAGE); + ConversationsPlusApplication.executeFileAdding(new Runnable() { + @Override + public void run() { + InputStream is = null; + try { + is = ConversationsPlusApplication.getInstance().getContentResolver().openInputStream(uri); + long imageSize = is.available(); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeStream(is, null, options); + int imageHeight = options.outHeight; + int imageWidth = options.outWidth; + String filePath = FileHelper.getRealPathFromUri(uri); + MessageUtil.updateMessageWithImageDetails(message, filePath, imageSize, imageWidth, imageHeight); + if (conversation.getNextEncryption(forceEncryption) == Message.ENCRYPTION_PGP) { + xmppConnectionService.getPgpEngine().encrypt(message, callback); + } else { + callback.success(message); + } + } catch (FileNotFoundException e) { + Logging.e("picturesending", "File not found to send not resized. " + e.getMessage()); + callback.error(R.string.error_file_not_found, message); + } catch (IOException e) { + Logging.e("picturesending", "Error while sending not resized picture. " + e.getMessage()); + callback.error(R.string.error_io_exception, message); + } finally { + if (null != is) { + try { + is.close(); + } catch (IOException e) { + Logging.w("picturesending", "Error while closing stream for sending not resized picture. " + e.getMessage()); + } + } + } + } + }); } @Override diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java index 204f56c4..f51ca7ea 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java @@ -11,11 +11,16 @@ import android.util.LruCache; import java.io.File; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.exceptions.FileCopyException; +import de.thedevstack.conversationsplus.exceptions.ImageResizeException; +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.utils.ExifHelper; @@ -24,6 +29,8 @@ import eu.siacs.conversations.utils.ExifHelper; * This util provides */ public final class ImageUtil { + + private static int IMAGE_SIZE = 1920; private static LruCache<String, Bitmap> BITMAP_CACHE; /** @@ -106,6 +113,59 @@ public final class ImageUtil { } /** + * Resizes and rotates an image given by uri and returns the bitmap. + * @param image the uri of the image to be resized and rotated + * @return resized and rotated bitmap + * @throws ImageResizeException + */ + public static Bitmap resizeAndRotateImage(Uri image) throws ImageResizeException { + return ImageUtil.resizeAndRotateImage(image, 0); + } + + /** + * Resizes and rotates an image given by uri and returns the bitmap. + * @param image the uri of the image to be resized and rotated + * @return resized and rotated bitmap + * @throws ImageResizeException + */ + private static Bitmap resizeAndRotateImage(Uri image, int sampleSize) throws ImageResizeException { + InputStream imageInputStream = null; + try { + imageInputStream = StreamUtil.openInputStreamFromContentResolver(image); + Bitmap originalBitmap; + BitmapFactory.Options options = new BitmapFactory.Options(); + int inSampleSize = (int) Math.pow(2, sampleSize); + Logging.d(Config.LOGTAG, "reading bitmap with sample size " + inSampleSize); + options.inSampleSize = inSampleSize; + originalBitmap = BitmapFactory.decodeStream(imageInputStream, null, options); + imageInputStream.close(); + if (originalBitmap == null) { + throw new ImageResizeException(R.string.error_not_an_image_file); + } + Bitmap scaledBitmap = ImageUtil.resize(originalBitmap, IMAGE_SIZE); + int rotation = ImageUtil.getRotation(image); + if (rotation > 0) { + scaledBitmap = ImageUtil.rotate(scaledBitmap, rotation); + } + + return scaledBitmap; + } catch (FileNotFoundException e) { + throw new ImageResizeException(R.string.error_file_not_found); + } catch (IOException e) { + throw new ImageResizeException(R.string.error_io_exception); + } catch (OutOfMemoryError e) { + ++sampleSize; + if (sampleSize <= 3) { + return resizeAndRotateImage(image, sampleSize); + } else { + throw new ImageResizeException(R.string.error_out_of_memory); + } + } finally { + StreamUtil.close(imageInputStream); + } + } + + /** * Returns the rotation from the exif information of an image identified with the given uri. * The orientation is retrieved by parsing the stream of the image. * FileNotFoundException is silently ignored. diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java new file mode 100644 index 00000000..9fedada6 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java @@ -0,0 +1,72 @@ +package de.thedevstack.conversationsplus.utils; + +import android.graphics.BitmapFactory; + +import java.net.URL; + +import eu.siacs.conversations.entities.DownloadableFile; +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.persistance.FileBackend; + +/** + * Created by tzur on 15.12.2015. + */ +public final class MessageUtil { + public static void updateMessageWithImageDetails(Message message, String filePath, long size, int imageWidth, int imageHeight) { + message.setRelativeFilePath(filePath); + MessageUtil.updateMessageBodyWithImageParams(message, 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; + BitmapFactory.decodeFile(file.getAbsolutePath(), options); + imageHeight = options.outHeight; + imageWidth = options.outWidth; + } + + MessageUtil.updateMessageBodyWithFileParams(message, url, file.getSize(), imageWidth, imageHeight); + } + + private static void updateMessageBodyWithFileParams(Message message, URL url, long fileSize, int imageWidth, int imageHeight) { + message.setBody(MessageUtil.getMessageBodyWithImageParams(url, fileSize, imageWidth, imageHeight)); + } + + private static void updateMessageBodyWithImageParams(Message message, long size, int imageWidth, int imageHeight) { + MessageUtil.updateMessageBodyWithImageParams(message, null, size, imageWidth, imageHeight); + } + + private static void updateMessageBodyWithImageParams(Message message, URL url, long size, int imageWidth, int imageHeight) { + message.setBody(MessageUtil.getMessageBodyWithImageParams(url, size, imageWidth, imageHeight)); + } + + private static String getMessageBodyWithImageParams(URL url, long size, int imageWidth, int imageHeight) { + StringBuilder sb = new StringBuilder(); + if (null != url) { + sb.append(url.toString()); + sb.append('|'); + } + sb.append(size); + if (-1 < imageWidth) { + sb.append('|'); + sb.append(imageWidth); + } + if (-1 < imageHeight) { + sb.append('|'); + sb.append(imageHeight); + } + return sb.toString(); + } + + private MessageUtil() { + // Static helper class + } +} |