package eu.siacs.conversations.persistance; import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; import android.os.Environment; import android.util.Log; import android.webkit.MimeTypeMap; 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.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.entities.FileParams; import de.thedevstack.conversationsplus.exceptions.FileCopyException; import de.thedevstack.conversationsplus.persistance.observers.FileDeletionObserver; import de.thedevstack.conversationsplus.utils.StreamUtil; import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.services.XmppConnectionService; public class FileBackend { private static final SimpleDateFormat imageDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); private static FileBackend INSTANCE; private FileDeletionObserver privateFilesDirectoryObserver; private FileDeletionObserver privateFilesImageDirectoryObserver; private FileDeletionObserver conversationsFilesDirectoryObserver; private FileDeletionObserver conversationsImagesDirectoryObserver; public static void init() { if (null == INSTANCE) { INSTANCE = new FileBackend(); } INSTANCE.initFileObservers(); INSTANCE.createNoMedia(); } private void initFileObservers() { this.privateFilesDirectoryObserver = new FileDeletionObserver(FileBackend.getPrivateFileDirectoryPath()); this.privateFilesImageDirectoryObserver = new FileDeletionObserver(FileBackend.getConversationsFileDirectory()); this.conversationsFilesDirectoryObserver = new FileDeletionObserver(FileBackend.getConversationsImageDirectory()); this.conversationsImagesDirectoryObserver = new FileDeletionObserver(FileBackend.getPrivateImageDirectoryPath()); this.privateFilesDirectoryObserver.startWatching(); this.privateFilesImageDirectoryObserver.startWatching(); this.conversationsFilesDirectoryObserver.startWatching(); this.conversationsImagesDirectoryObserver.startWatching(); } private void createNoMedia() { final File nomedia = new File(getConversationsFileDirectory()+".nomedia"); if (!nomedia.exists()) { try { nomedia.createNewFile(); } catch (Exception e) { Log.d(Config.LOGTAG, "could not create nomedia file"); } } } public static void onFileTransferFolderChanged() { INSTANCE.conversationsFilesDirectoryObserver.stopWatching(); INSTANCE.conversationsFilesDirectoryObserver = new FileDeletionObserver(FileBackend.getConversationsFileDirectory()); INSTANCE.conversationsFilesDirectoryObserver.startWatching(); INSTANCE.createNoMedia(); } public static void onImageTransferFolderChanged() { INSTANCE.conversationsImagesDirectoryObserver.stopWatching(); INSTANCE.conversationsImagesDirectoryObserver = new FileDeletionObserver(FileBackend.getConversationsImageDirectory()); INSTANCE.conversationsImagesDirectoryObserver.startWatching(); } public static void updateMediaScanner(File file, XmppConnectionService xmppConnectionService) { if (file.getAbsolutePath().startsWith(getConversationsImageDirectory())) { Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); intent.setData(Uri.fromFile(file)); xmppConnectionService.sendBroadcast(intent); } } public static boolean deleteFile(Message message) { File file = getFile(message); if (file.delete()) { updateMediaScanner(file, XmppConnectionServiceAccessor.xmppConnectionService); return true; } else { return false; } } public static DownloadableFile getFile(Message message) { return getFile(message, true); } public static DownloadableFile getFile(Message message, boolean decrypted) { DownloadableFile downloadableFile = new DownloadableFile(getFilePath(message, decrypted)); FileParams fileParams = message.getFileParams(); if (null != fileParams) { if (null != fileParams.getKey()) { downloadableFile.setKey(fileParams.getKey()); } if (null != fileParams.getIv()) { downloadableFile.setIv(fileParams.getIv()); } } return downloadableFile; } protected static String getFilePath(Message message, String extension) { String path = FileBackend.getFilePath(message, true); if (!path.endsWith(extension)) { path += "." + extension; } return path; } protected static String getFilePath(Message message, Uri uri) { String mime = ConversationsPlusApplication.getInstance().getContentResolver().getType(uri); String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime); return getFilePath(message, extension); } protected static String getFilePath(Message message, boolean decrypted) { final boolean encrypted = !decrypted && (message.getEncryption() == Message.ENCRYPTION_PGP || message.getEncryption() == Message.ENCRYPTION_DECRYPTED); FileParams fileParams = message.getFileParams(); if (null == fileParams) { fileParams = new FileParams(); message.setFileParams(fileParams); } String path = fileParams.getPath(); if (null == path) { // File does not yet exist path = message.getUuid(); String mime = message.getMimeType(); if (mime != null && mime.startsWith("image")) { // TODO: Check if this can be determined in a better way path = getConversationsImageDirectory() + path; } else { path = getConversationsFileDirectory() + path; } String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime); path += "." + extension; fileParams.setPath(path); } if (encrypted) { path += ".pgp"; } return path; } public static String getConversationsFileDirectory() { return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + ConversationsPlusPreferences.fileTransferFolder() + File.separator; } public static String getConversationsImageDirectory() { return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath() + File.separator + ConversationsPlusPreferences.imgTransferFolder() + File.separator; } public static String getPrivateFileDirectoryPath() { return ConversationsPlusApplication.getPrivateFilesDir().getAbsolutePath(); } private static String getPrivateImageDirectoryPath() { return FileBackend.getPrivateFileDirectoryPath() + File.separator + "Images" + File.separator; } public static void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException { 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); } public static void copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException { Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage"); String path = getFilePath(message, uri); message.getFileParams().setPath(path); message.setRelativeFilePath(path); // TODO: Remove copyFileToPrivateStorage(getFile(message), uri); } public static DownloadableFile compressImageAndCopyToPrivateStorage(Message message, Bitmap scaledBitmap) throws FileCopyException { String path = getFilePath(message, "jpg"); message.getFileParams().setPath(path); message.setRelativeFilePath(path); // TODO: Remove DownloadableFile file = getFile(message); file.getParentFile().mkdirs(); OutputStream os = null; try { file.createNewFile(); os = new FileOutputStream(file); boolean success = scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 75, os); if (!success) { throw new FileCopyException(R.string.error_compressing_image); } os.flush(); } 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 (NullPointerException e) { throw new FileCopyException(R.string.error_io_exception); } finally { StreamUtil.close(os); } return file; } 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 boolean isFileAvailable(Message message) { return getFile(message).exists(); } private FileBackend() { // Static helper class } }