Implements FS#19, FS#84; Introduces ImageResizeException, MessageUtil and distinguishes between image resizing and compressing/saving
This commit is contained in:
parent
556697c47e
commit
c26335f3e3
18 changed files with 386 additions and 218 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue