package eu.siacs.conversations.persistance; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.Locale; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Environment; import android.webkit.MimeTypeMap; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.exceptions.FileCopyException; import de.thedevstack.conversationsplus.utils.ImageUtil; import de.thedevstack.conversationsplus.utils.StreamUtil; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; 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) { return getFile(message, true); } public static DownloadableFile getFile(Message message, boolean decrypted) { String path = message.getRelativeFilePath(); String extension; if (path != null && !path.isEmpty()) { String[] parts = path.split("\\."); extension = "."+parts[parts.length - 1]; } else { if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_TEXT) { extension = ".webp"; } else { extension = ""; } path = message.getUuid()+extension; } final boolean encrypted = !decrypted && (message.getEncryption() == Message.ENCRYPTION_PGP || message.getEncryption() == Message.ENCRYPTION_DECRYPTED); if (encrypted) { return new DownloadableFile(getConversationsFileDirectory()+message.getUuid()+extension+".pgp"); } else { if (path.startsWith("/")) { 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); } } } } public static String getConversationsFileDirectory() { return Environment.getExternalStorageDirectory().getAbsolutePath()+"/Conversations/"; } public static String getConversationsImageDirectory() { return Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES).getAbsolutePath() + "/Conversations/"; } 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); DownloadableFile file = getFile(message); file.getParentFile().mkdirs(); OutputStream os = null; InputStream is = null; try { file.createNewFile(); os = new FileOutputStream(file); is = StreamUtil.openInputStreamFromContentResolver(uri); byte[] buffer = new byte[1024]; int length; while ((length = is.read(buffer)) > 0) { os.write(buffer, 0, length); } os.flush(); } 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); } finally { StreamUtil.close(os); StreamUtil.close(is); } Logging.d(Config.LOGTAG, "output file name " + file); 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 { 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); } 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 (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_io_exception); } finally { StreamUtil.close(os); StreamUtil.close(is); } } public static Uri getTakePhotoUri() { StringBuilder pathBuilder = new StringBuilder(); pathBuilder.append(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)); pathBuilder.append('/'); pathBuilder.append("Camera"); pathBuilder.append('/'); pathBuilder.append("IMG_" + imageDateFormat.format(new Date()) + ".jpg"); Uri uri = Uri.parse("file://" + pathBuilder.toString()); File file = new File(uri.toString()); file.getParentFile().mkdirs(); return uri; } public static Uri getJingleFileUri(Message message) { File file = getFile(message); 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(); } private FileBackend() { // Static helper class } }