aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsteckbrief <steckbrief@chefmail.de>2015-12-16 00:53:04 +0100
committersteckbrief <steckbrief@chefmail.de>2015-12-16 00:53:04 +0100
commitc26335f3e366110366eb89025a42875bcb7840a9 (patch)
treef6ae254a0bf0a8ce2d9e43efd48a5eba6e454b4f
parent556697c47e74766215c17fb5f721d71494a02643 (diff)
Implements FS#19, FS#84; Introduces ImageResizeException, MessageUtil and distinguishes between image resizing and compressing/saving
-rw-r--r--Conversations-Plus-ChangeLog.md3
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java12
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java8
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java4
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/exceptions/FileCopyException.java15
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/exceptions/ImageResizeException.java16
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/exceptions/UiException.java22
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/http/HttpDownloadConnection.java3
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/http/HttpUploadConnection.java3
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java105
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java125
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java92
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java60
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java72
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java3
-rw-r--r--src/main/res/values-de/strings.xml5
-rw-r--r--src/main/res/values/strings.xml5
-rw-r--r--src/main/res/xml/preferences.xml49
18 files changed, 385 insertions, 217 deletions
diff --git a/Conversations-Plus-ChangeLog.md b/Conversations-Plus-ChangeLog.md
index 0f23c1f1..8cb7db2f 100644
--- a/Conversations-Plus-ChangeLog.md
+++ b/Conversations-Plus-ChangeLog.md
@@ -1,7 +1,9 @@
###Conversations+ ChangeLog
####Version 0.0.6
+* Fixes FS#95: NPE when opening message details for failed file transfer
* Implements FS#89: Change about information
+* Implements FS#84: Setting for location to store received pictures
* Implements FS#83: Reload from last received message
* Fixes FS#82: Strange layout in share with activity
* Fixes FS#81: Interactive message loading causes "jumps"
@@ -12,6 +14,7 @@
* Fixes FS#47: Setting "WLAN only" no longer works for received links
* Implements FS#26: Introduce dialog to choose whether to send resized picture or original picture
* Implements FS#24: Introduce setting for picture resizing
+* Implements FS#19: Received and Sent pictures are automatically stored in public picture folder
* Partially implements FS#6: Change "Report bug to developer" - Reporting conference changed to c+bugs@conference.thedevstack.de
####Version 0.0.5
diff --git a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java
index a9b09551..e4fabda5 100644
--- a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java
+++ b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java
@@ -8,6 +8,7 @@ import android.preference.PreferenceManager;
import java.io.File;
import de.thedevstack.conversationsplus.utils.ImageUtil;
+import de.thedevstack.conversationsplus.utils.SerialSingleThreadExecutor;
/**
* This class is used to provide static access to the applicationcontext.
@@ -18,6 +19,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.
*/
@@ -36,6 +40,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/crypto/PgpEngine.java b/src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java
index fe7eefdf..08aa8272 100644
--- a/src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java
+++ b/src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java
@@ -24,6 +24,8 @@ import de.thedevstack.conversationsplus.entities.Message;
import de.thedevstack.conversationsplus.http.HttpConnectionManager;
import de.thedevstack.conversationsplus.services.XmppConnectionService;
import de.thedevstack.conversationsplus.ui.UiCallback;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
+
import android.app.PendingIntent;
import android.content.Intent;
import android.net.Uri;
@@ -101,7 +103,7 @@ public class PgpEngine {
OpenPgpApi.RESULT_CODE_ERROR)) {
case OpenPgpApi.RESULT_CODE_SUCCESS:
URL url = message.getFileParams().url;
- FileBackend.updateFileParams(message, url);
+ MessageUtil.updateFileParams(message, url);
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
PgpEngine.this.mXmppConnectionService
.updateMessage(message);
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/http/HttpDownloadConnection.java b/src/main/java/de/thedevstack/conversationsplus/http/HttpDownloadConnection.java
index c76ed6d7..d8859df5 100644
--- a/src/main/java/de/thedevstack/conversationsplus/http/HttpDownloadConnection.java
+++ b/src/main/java/de/thedevstack/conversationsplus/http/HttpDownloadConnection.java
@@ -25,6 +25,7 @@ import de.thedevstack.conversationsplus.entities.Message;
import de.thedevstack.conversationsplus.persistance.FileBackend;
import de.thedevstack.conversationsplus.services.XmppConnectionService;
import de.thedevstack.conversationsplus.utils.CryptoHelper;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
public class HttpDownloadConnection implements Transferable {
@@ -236,7 +237,7 @@ public class HttpDownloadConnection implements Transferable {
private void updateImageBounds() {
message.setType(Message.TYPE_FILE);
- FileBackend.updateFileParams(message, mUrl);
+ MessageUtil.updateFileParams(message, mUrl);
mXmppConnectionService.updateMessage(message);
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/http/HttpUploadConnection.java b/src/main/java/de/thedevstack/conversationsplus/http/HttpUploadConnection.java
index d5d7ad02..58488c99 100644
--- a/src/main/java/de/thedevstack/conversationsplus/http/HttpUploadConnection.java
+++ b/src/main/java/de/thedevstack/conversationsplus/http/HttpUploadConnection.java
@@ -21,6 +21,7 @@ import de.thedevstack.conversationsplus.persistance.FileBackend;
import de.thedevstack.conversationsplus.services.XmppConnectionService;
import de.thedevstack.conversationsplus.ui.UiCallback;
import de.thedevstack.conversationsplus.utils.CryptoHelper;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
import de.thedevstack.conversationsplus.utils.StreamUtil;
import de.thedevstack.conversationsplus.utils.Xmlns;
import de.thedevstack.conversationsplus.xml.Element;
@@ -164,7 +165,7 @@ public class HttpUploadConnection implements Transferable {
if (key != null) {
mGetUrl = new URL(mGetUrl.toString() + "#" + CryptoHelper.bytesToHex(key));
}
- FileBackend.updateFileParams(message, mGetUrl);
+ MessageUtil.updateFileParams(message, mGetUrl);
message.setTransferable(null);
message.setCounterpart(message.getConversation().getJid().toBareJid());
if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java b/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java
index d442abe2..0a118ccb 100644
--- a/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java
+++ b/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java
@@ -21,18 +21,18 @@ import android.webkit.MimeTypeMap;
import de.thedevstack.android.logcat.Logging;
import de.thedevstack.conversationsplus.Config;
import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
import de.thedevstack.conversationsplus.R;
import de.thedevstack.conversationsplus.entities.Transferable;
import de.thedevstack.conversationsplus.entities.DownloadableFile;
import de.thedevstack.conversationsplus.entities.Message;
import de.thedevstack.conversationsplus.exceptions.FileCopyException;
import de.thedevstack.conversationsplus.utils.ImageUtil;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
import de.thedevstack.conversationsplus.utils.StreamUtil;
public final class FileBackend {
- private static int IMAGE_SIZE = 1920;
-
private static final SimpleDateFormat imageDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
public static DownloadableFile getFile(Message message) {
@@ -63,29 +63,35 @@ public final class FileBackend {
return new DownloadableFile(path);
} else {
if (Arrays.asList(Transferable.VALID_IMAGE_EXTENSIONS).contains(extension)) {
- return new DownloadableFile(getConversationsFileDirectory() + path);
- } else {
return new DownloadableFile(getConversationsImageDirectory() + path);
+ } else {
+ return new DownloadableFile(getConversationsFileDirectory() + path);
}
}
}
}
public static String getConversationsFileDirectory() {
- return Environment.getExternalStorageDirectory().getAbsolutePath()+"/Conversations/";
+ return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + ConversationsPlusPreferences.fileTransferFolder() + File.separator;
}
public static String getConversationsImageDirectory() {
- return Environment.getExternalStoragePublicDirectory(
- Environment.DIRECTORY_PICTURES).getAbsolutePath()
- + "/Conversations/";
+ return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath() + File.separator + ConversationsPlusPreferences.imgTransferFolder() + File.separator;
}
+ private static String getPrivateFileDirectoryPath() {
+ return ConversationsPlusApplication.getPrivateFilesDir().getAbsolutePath();
+ }
+
+ private static String getPrivateImageDirectoryPath() {
+ return FileBackend.getPrivateFileDirectoryPath() + File.separator + "Images" + File.separator;
+ }
+
public static DownloadableFile copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException {
Logging.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage");
String mime = ConversationsPlusApplication.getInstance().getContentResolver().getType(uri);
String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime);
- message.setRelativeFilePath(message.getUuid() + "." + extension);
+ message.setRelativeFilePath(FileBackend.getPrivateFileDirectoryPath() + message.getUuid() + "." + extension);
DownloadableFile file = getFile(message);
file.getParentFile().mkdirs();
OutputStream os = null;
@@ -113,68 +119,30 @@ public final class FileBackend {
return file;
}
- public static DownloadableFile copyImageToPrivateStorage(Message message, Uri image)
- throws FileCopyException {
- return copyImageToPrivateStorage(message, image, 0);
- }
-
- private static DownloadableFile copyImageToPrivateStorage(Message message,
- Uri image, int sampleSize) throws FileCopyException {
+ public static DownloadableFile compressImageAndCopyToPrivateStorage(Message message, Bitmap scaledBitmap) throws FileCopyException {
+ message.setRelativeFilePath(FileBackend.getPrivateImageDirectoryPath() + message.getUuid() + ".webp");
DownloadableFile file = getFile(message);
file.getParentFile().mkdirs();
- InputStream is = null;
OutputStream os = null;
try {
file.createNewFile();
- is = StreamUtil.openInputStreamFromContentResolver(image);
- os = new FileOutputStream(file);
-
- 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(is, null, options);
- is.close();
- if (originalBitmap == null) {
- throw new FileCopyException(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);
- }
+ os = new FileOutputStream(file);
boolean success = scaledBitmap.compress(Bitmap.CompressFormat.WEBP, 75, os);
if (!success) {
throw new FileCopyException(R.string.error_compressing_image);
}
os.flush();
- long size = file.getSize();
- int width = scaledBitmap.getWidth();
- int height = scaledBitmap.getHeight();
- message.setBody(Long.toString(size) + '|' + width + '|' + height);
- return file;
- } catch (FileNotFoundException e) {
- throw new FileCopyException(R.string.error_file_not_found);
- } catch (IOException e) {
- e.printStackTrace();
- throw new FileCopyException(R.string.error_io_exception);
+ } catch (IOException e) {
+ throw new FileCopyException(R.string.error_io_exception, e);
} catch (SecurityException e) {
- throw new FileCopyException(R.string.error_security_exception_during_image_copy);
- } catch (OutOfMemoryError e) {
- ++sampleSize;
- if (sampleSize <= 3) {
- return copyImageToPrivateStorage(message, image, sampleSize);
- } else {
- throw new FileCopyException(R.string.error_out_of_memory);
- }
- } catch (NullPointerException e) {
+ throw new FileCopyException(R.string.error_security_exception_during_image_copy);
+ } catch (NullPointerException e) {
throw new FileCopyException(R.string.error_io_exception);
} finally {
StreamUtil.close(os);
- StreamUtil.close(is);
}
+ return file;
}
public static Uri getTakePhotoUri() {
@@ -195,33 +163,6 @@ public final class FileBackend {
return Uri.parse("file://" + file.getAbsolutePath());
}
- public static void updateFileParams(Message message) {
- updateFileParams(message,null);
- }
-
- public static void updateFileParams(Message message, URL url) {
- DownloadableFile file = getFile(message);
- if (message.getType() == Message.TYPE_IMAGE || file.getMimeType().startsWith("image/")) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(file.getAbsolutePath(), options);
- int imageHeight = options.outHeight;
- int imageWidth = options.outWidth;
- if (url == null) {
- message.setBody(Long.toString(file.getSize()) + '|' + imageWidth + '|' + imageHeight);
- } else {
- message.setBody(url.toString()+"|"+Long.toString(file.getSize()) + '|' + imageWidth + '|' + imageHeight);
- }
- } else {
- if (url != null) {
- message.setBody(url.toString()+"|"+Long.toString(file.getSize()));
- } else {
- message.setBody(Long.toString(file.getSize()));
- }
- }
-
- }
-
public static boolean isFileAvailable(Message message) {
return getFile(message).exists();
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java b/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java
index 159fcf44..7472ad21 100644
--- a/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java
+++ b/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java
@@ -53,10 +53,13 @@ import de.duenndns.ssl.MemorizingTrustManager;
import de.thedevstack.android.logcat.Logging;
import de.thedevstack.conversationsplus.ConversationsPlusApplication;
import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.entities.DownloadableFile;
import de.thedevstack.conversationsplus.exceptions.FileCopyException;
+import de.thedevstack.conversationsplus.exceptions.UiException;
import de.thedevstack.conversationsplus.utils.AvatarUtil;
import de.thedevstack.conversationsplus.utils.FileHelper;
import de.thedevstack.conversationsplus.utils.ImageUtil;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
import de.tzur.conversations.Settings;
import de.thedevstack.conversationsplus.Config;
import de.thedevstack.conversationsplus.R;
@@ -86,7 +89,6 @@ import de.thedevstack.conversationsplus.utils.ExceptionHelper;
import de.thedevstack.conversationsplus.utils.OnPhoneContactsLoadedListener;
import de.thedevstack.conversationsplus.utils.PRNGFixes;
import de.thedevstack.conversationsplus.utils.PhoneHelper;
-import de.thedevstack.conversationsplus.utils.SerialSingleThreadExecutor;
import de.thedevstack.conversationsplus.utils.Xmlns;
import de.thedevstack.conversationsplus.xml.Element;
import de.thedevstack.conversationsplus.xmpp.OnBindListener;
@@ -130,9 +132,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
};
- private final SerialSingleThreadExecutor mFileAddingExecutor = new SerialSingleThreadExecutor();
- private final SerialSingleThreadExecutor mDatabaseExecutor = new SerialSingleThreadExecutor();
-
private final IBinder mBinder = new XmppConnectionBinder();
private final List<Conversation> conversations = new CopyOnWriteArrayList<>();
private final FileObserver fileObserver = new FileObserver(
@@ -375,110 +374,33 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
message.setCounterpart(conversation.getNextCounterpart());
message.setType(Message.TYPE_FILE);
String path = FileHelper.getRealPathFromUri(uri);
- if (path!=null) {
+ if (path != null) {
message.setRelativeFilePath(path);
- FileBackend.updateFileParams(message);
+ MessageUtil.updateFileParams(message);
if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
getPgpEngine().encrypt(message, callback);
} else {
callback.success(message);
}
} else {
- mFileAddingExecutor.execute(new Runnable() {
- @Override
- public void run() {
- try {
- FileBackend.copyFileToPrivateStorage(message, uri);
- FileBackend.updateFileParams(message);
- if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
- getPgpEngine().encrypt(message, callback);
- } else {
- callback.success(message);
- }
- } catch (FileCopyException e) {
- callback.error(e.getResId(), message);
- }
- }
- });
- }
- }
-
- public void attachImageToConversationWithoutResizing(final Conversation conversation, final Uri uri, final UiCallback<Message> 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);
- mFileAddingExecutor.execute(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;
- message.setRelativeFilePath(FileHelper.getRealPathFromUri(uri));
- message.setBody(Long.toString(imageSize) + '|' + imageWidth + '|' + imageHeight);
- callback.success(message);
- } catch (FileNotFoundException e) {
- Logging.e("pictureresize", "File not found to send not resized. " + e.getMessage());
- callback.error(R.string.error_file_not_found, message);
- } catch (IOException e) {
- Logging.e("pictureresize", "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("pictureresize", "Error while closing stream for sending not resized picture. " + e.getMessage());
+ ConversationsPlusApplication.executeFileAdding(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ FileBackend.copyFileToPrivateStorage(message, uri);
+ MessageUtil.updateFileParams(message);
+ if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
+ getPgpEngine().encrypt(message, callback);
+ } else {
+ callback.success(message);
}
+ } catch (FileCopyException e) {
+ callback.error(e.getResId(), message);
}
}
- }
- });
- }
-
- public void attachImageToConversation(final Conversation conversation,
- final Uri uri, final UiCallback<Message> 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);
- mFileAddingExecutor.execute(new Runnable() {
-
- @Override
- public void run() {
- try {
- FileBackend.copyImageToPrivateStorage(message, uri);
- if (conversation.getNextEncryption(forceEncryption) == Message.ENCRYPTION_PGP) {
- getPgpEngine().encrypt(message, callback);
- } else {
- callback.success(message);
- }
- } catch (final FileCopyException e) {
- callback.error(e.getResId(), message);
- }
- }
- });
- }
+ });
+ }
+ }
public Conversation find(Bookmark bookmark) {
return find(bookmark.getAccount(), bookmark.getJid());
@@ -1019,7 +941,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
updateConversationUi();
}
};
- mDatabaseExecutor.execute(runnable);
+ ConversationsPlusApplication.executeDatabaseOperation(runnable);
}
}
@@ -1123,7 +1045,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
};
- mDatabaseExecutor.execute(runnable);
+ ConversationsPlusApplication.executeDatabaseOperation(runnable);
}
public List<Account> getAccounts() {
@@ -2390,8 +2312,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
databaseBackend.writeRoster(account.getRoster());
}
};
- mDatabaseExecutor.execute(runnable);
-
+ ConversationsPlusApplication.executeDatabaseOperation(runnable);
}
public List<String> getKnownHosts() {
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 ae2b3b79..a9c245ed 100644
--- a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java
@@ -1,18 +1,31 @@
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.R;
import de.thedevstack.conversationsplus.entities.Conversation;
+import de.thedevstack.conversationsplus.entities.DownloadableFile;
import de.thedevstack.conversationsplus.entities.Message;
import de.thedevstack.conversationsplus.enums.UserDecision;
+import de.thedevstack.conversationsplus.exceptions.UiException;
+import de.thedevstack.conversationsplus.persistance.FileBackend;
import de.thedevstack.conversationsplus.services.XmppConnectionService;
import de.thedevstack.conversationsplus.ui.UiCallback;
import de.thedevstack.conversationsplus.ui.XmppActivity;
+import de.thedevstack.conversationsplus.utils.FileHelper;
+import de.thedevstack.conversationsplus.utils.ImageUtil;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
/**
* Created by tzur on 31.10.2015.
@@ -81,13 +94,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 7e8cacd0..9250c432 100644
--- a/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java
+++ b/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java
@@ -11,17 +11,24 @@ 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.Config;
+import de.thedevstack.conversationsplus.R;
import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.exceptions.FileCopyException;
+import de.thedevstack.conversationsplus.exceptions.ImageResizeException;
import de.thedevstack.conversationsplus.persistance.FileBackend;
/**
* This util provides
*/
public final class ImageUtil {
+
+ private static int IMAGE_SIZE = 1920;
private static LruCache<String, Bitmap> BITMAP_CACHE;
/**
@@ -104,6 +111,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..c04ebdb8
--- /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 de.thedevstack.conversationsplus.entities.DownloadableFile;
+import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.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
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java
index 6ddf99b8..7885cc0f 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java
@@ -23,6 +23,7 @@ import de.thedevstack.conversationsplus.entities.TransferablePlaceholder;
import de.thedevstack.conversationsplus.entities.Message;
import de.thedevstack.conversationsplus.persistance.FileBackend;
import de.thedevstack.conversationsplus.services.XmppConnectionService;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
import de.thedevstack.conversationsplus.xml.Element;
import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived;
import de.thedevstack.conversationsplus.xmpp.jid.Jid;
@@ -92,7 +93,7 @@ public class JingleConnection implements Transferable {
JingleConnection.this.mXmppConnectionService
.getNotificationService().push(message);
}
- FileBackend.updateFileParams(message);
+ MessageUtil.updateFileParams(message);
mXmppConnectionService.databaseBackend.createMessage(message);
mXmppConnectionService.markMessage(message,
Message.STATUS_RECEIVED);
diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml
index 29b2ec88..e6f6273e 100644
--- a/src/main/res/values-de/strings.xml
+++ b/src/main/res/values-de/strings.xml
@@ -505,4 +505,9 @@
<string name="cplus_copied_to_clipboard">In Zwischenablage kopiert</string>
<string name="pref_show_logcat_title">Zeige logcat Ausgabe</string>
<string name="pref_show_logcat_summary">Zeigt die Ausgabe von logcat an. Hilfreich für die Fehlersuche.</string>
+ <string name="pref_file_transfer">Ordnername, um eingehnde Datein zu speichern</string>
+ <string name="pref_file_transfer_folder_summary">Unterordner des globalen Dateiordners, um eingehende Dateien zu speichern.</string>
+ <string name="pref_img_file_transfer">Ordnername, um eingehende Bilder zu speichern</string>
+ <string name="pref_img_file_transfer_summary">Unterordner des globalen Bilderordners, um eingehende Bilder zu speichern.</string>
+ <string name="pref_file_transfer_category">Dateiübertragung</string>
</resources>
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 96a89c9c..3431e831 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -538,4 +538,9 @@
<string name="pref_show_logcat_title">Show logcat output</string>
<string name="pref_show_logcat_summary">Shows the output of logcat. This is useful for debugging.</string>
<string name="cplus_bugreport_jabberid">c+bugs@conference.thedevstack.de</string>
+ <string name="pref_file_transfer">Folder to save incoming files</string>
+ <string name="pref_file_transfer_folder_summary">This is the subdirectory for incoming files.</string>
+ <string name="pref_img_file_transfer">Folder to save incoming pictures</string>
+ <string name="pref_img_file_transfer_summary">This is the subdirectory in the pictures directory for incoming files.</string>
+ <string name="pref_file_transfer_category">File Transfer</string>
</resources>
diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml
index a90385bc..0232fe02 100644
--- a/src/main/res/xml/preferences.xml
+++ b/src/main/res/xml/preferences.xml
@@ -17,6 +17,26 @@
android:summary="@string/pref_xmpp_resource_summary"
android:title="@string/pref_xmpp_resource" />
+ <ListPreference
+ android:defaultValue="2"
+ android:entries="@array/confirm_strings"
+ android:entryValues="@array/confirm_values"
+ android:key="confirm_messages_list"
+ android:summary="@string/pref_confirm_messages_summary"
+ android:title="@string/pref_confirm_messages" />
+
+ <CheckBoxPreference
+ android:defaultValue="false"
+ android:key="chat_states"
+ android:summary="@string/pref_chat_states_summary"
+ android:title="@string/pref_chat_states" />
+ <CheckBoxPreference
+ android:defaultValue="true"
+ android:key="parse_emoticons"
+ android:summary="@string/pref_parse_emoticons_summary"
+ android:title="@string/pref_parse_emoticons"/>
+ </PreferenceCategory>
+ <PreferenceCategory android:title="@string/pref_file_transfer_category">
<PreferenceScreen
android:summary="@string/pref_accept_files_summary"
android:title="@string/pref_accept_files">
@@ -42,6 +62,16 @@
android:title="@string/pref_accept_files_download" />
</PreferenceScreen>
+ <EditTextPreference
+ android:title="@string/pref_img_file_transfer"
+ android:summary="@string/pref_file_transfer_folder_summary"
+ android:key="file_transfer_folder"
+ android:defaultValue="Conversations+"/>
+ <EditTextPreference
+ android:title="@string/pref_file_transfer"
+ android:summary="@string/pref_img_file_transfer_summary"
+ android:key="img_transfer_folder"
+ android:defaultValue="Conversations+"/>
<ListPreference
android:defaultValue="ASK"
@@ -50,25 +80,6 @@
android:key="resize_picture"
android:summary="@string/pref_resize_picture_summary"
android:title="@string/pref_resize_picture"/>
-
- <ListPreference
- android:defaultValue="2"
- android:entries="@array/confirm_strings"
- android:entryValues="@array/confirm_values"
- android:key="confirm_messages_list"
- android:summary="@string/pref_confirm_messages_summary"
- android:title="@string/pref_confirm_messages" />
-
- <CheckBoxPreference
- android:defaultValue="false"
- android:key="chat_states"
- android:summary="@string/pref_chat_states_summary"
- android:title="@string/pref_chat_states" />
- <CheckBoxPreference
- android:defaultValue="true"
- android:key="parse_emoticons"
- android:summary="@string/pref_parse_emoticons_summary"
- android:title="@string/pref_parse_emoticons"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_notification_settings" >
<PreferenceScreen