Implements FS#19, FS#84; Introduces ImageResizeException, MessageUtil and distinguishes between image resizing and compressing/saving

This commit is contained in:
steckbrief 2015-12-16 00:53:04 +01:00
parent 556697c47e
commit c26335f3e3
18 changed files with 386 additions and 218 deletions

View file

@ -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

View file

@ -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

View file

@ -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);
}

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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) {

View file

@ -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();
}

View file

@ -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,111 +374,34 @@ 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);
}
}
});
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 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());
}
}
}
}
});
}
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() {

View file

@ -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

View file

@ -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;
/**
@ -103,6 +110,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.

View file

@ -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
}
}

View file

@ -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);

View file

@ -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>

View file

@ -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>

View file

@ -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