rename method that expand filename

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/entities/DownloadableFile.java |   4 
src/main/java/eu/siacs/conversations/persistance/FileBackend.java   | 441 
src/main/java/eu/siacs/conversations/ui/RecordingActivity.java      |   6 
3 files changed, 310 insertions(+), 141 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/persistance/FileBackend.java 🔗

@@ -33,6 +33,7 @@ import androidx.annotation.StringRes;
 import androidx.core.content.FileProvider;
 import androidx.exifinterface.media.ExifInterface;
 
+import com.google.common.base.Strings;
 import com.google.common.io.ByteStreams;
 
 import java.io.ByteArrayOutputStream;
@@ -76,7 +77,8 @@ public class FileBackend {
 
     private static final Object THUMBNAIL_LOCK = new Object();
 
-    private static final SimpleDateFormat IMAGE_DATE_FORMAT = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US);
+    private static final SimpleDateFormat IMAGE_DATE_FORMAT =
+            new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US);
 
     private static final String FILE_PROVIDER = ".files";
     private static final float IGNORE_PADDING = 0.15f;
@@ -91,8 +93,8 @@ public class FileBackend {
     }
 
     public static boolean isInDirectoryThatShouldNotBeScanned(Context context, String path) {
-        for (String type : new String[]{RecordingActivity.STORAGE_DIRECTORY_TYPE_NAME, "Files"}) {
-            if (path.startsWith(getConversationsDirectory(context, type))) {
+        for (String type : new String[] {RecordingActivity.STORAGE_DIRECTORY_TYPE_NAME, "Files"}) {
+            if (path.startsWith(getLegacyStorageLocation(context, type).getAbsolutePath())) {
                 return true;
             }
         }
@@ -114,11 +116,14 @@ public class FileBackend {
         }
     }
 
-    public static boolean allFilesUnderSize(Context context, List<Attachment> attachments, long max) {
-        final boolean compressVideo = !AttachFileToConversationRunnable.getVideoCompression(context).equals("uncompressed");
+    public static boolean allFilesUnderSize(
+            Context context, List<Attachment> attachments, long max) {
+        final boolean compressVideo =
+                !AttachFileToConversationRunnable.getVideoCompression(context)
+                        .equals("uncompressed");
         if (max <= 0) {
             Log.d(Config.LOGTAG, "server did not report max file size for http upload");
-            return true; //exception to be compatible with HTTP Upload < v0.2
+            return true; // exception to be compatible with HTTP Upload < v0.2
         }
         for (Attachment attachment : attachments) {
             if (attachment.getType() != Attachment.Type.FILE) {
@@ -127,33 +132,50 @@ public class FileBackend {
             String mime = attachment.getMime();
             if (mime != null && mime.startsWith("video/") && compressVideo) {
                 try {
-                    Dimensions dimensions = FileBackend.getVideoDimensions(context, attachment.getUri());
+                    Dimensions dimensions =
+                            FileBackend.getVideoDimensions(context, attachment.getUri());
                     if (dimensions.getMin() > 720) {
-                        Log.d(Config.LOGTAG, "do not consider video file with min width larger than 720 for size check");
+                        Log.d(
+                                Config.LOGTAG,
+                                "do not consider video file with min width larger than 720 for size check");
                         continue;
                     }
                 } catch (NotAVideoFile notAVideoFile) {
-                    //ignore and fall through
+                    // ignore and fall through
                 }
             }
             if (FileBackend.getFileSize(context, attachment.getUri()) > max) {
-                Log.d(Config.LOGTAG, "not all files are under " + max + " bytes. suggesting falling back to jingle");
+                Log.d(
+                        Config.LOGTAG,
+                        "not all files are under "
+                                + max
+                                + " bytes. suggesting falling back to jingle");
                 return false;
             }
         }
         return true;
     }
 
-    public static String getConversationsDirectory(Context context, final String type) {
+    public static File getLegacyStorageLocation(Context context, final String type) {
         if (Config.ONLY_INTERNAL_STORAGE) {
-            return context.getFilesDir().getAbsolutePath() + "/" + type + "/";
+            return new File(context.getFilesDir(), type);
         } else {
-            return getAppMediaDirectory(context) + context.getString(R.string.app_name) + " " + type + "/";
+            final File appDirectory =
+                    new File(
+                            Environment.getExternalStorageDirectory(),
+                            context.getString(R.string.app_name));
+            final File appMediaDirectory = new File(appDirectory, "Media");
+            final String locationName =
+                    String.format("%s %s", context.getString(R.string.app_name), type);
+            return new File(appMediaDirectory, locationName);
         }
     }
 
-    public static String getAppMediaDirectory(Context context) {
-        return Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + context.getString(R.string.app_name) + "/Media/";
+    private static String getAppMediaDirectory(Context context) {
+        return Environment.getExternalStorageDirectory().getAbsolutePath()
+                + "/"
+                + context.getString(R.string.app_name)
+                + "/Media/";
     }
 
     public static String getBackupDirectory(Context context) {
@@ -180,7 +202,8 @@ public class FileBackend {
     }
 
     public static boolean isPathBlacklisted(String path) {
-        final String androidDataPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/";
+        final String androidDataPath =
+                Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/";
         return path.startsWith(androidDataPath);
     }
 
@@ -193,7 +216,8 @@ public class FileBackend {
     }
 
     private static String getTakePhotoPath() {
-        return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) + "/Camera/";
+        return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
+                + "/Camera/";
     }
 
     public static Uri getUriForUri(Context context, Uri uri) {
@@ -246,7 +270,6 @@ public class FileBackend {
         return calcSampleSize(options, size);
     }
 
-
     private static int calcSampleSize(BitmapFactory.Options options, int size) {
         int height = options.outHeight;
         int width = options.outWidth;
@@ -256,8 +279,7 @@ public class FileBackend {
             int halfHeight = height / 2;
             int halfWidth = width / 2;
 
-            while ((halfHeight / inSampleSize) > size
-                    && (halfWidth / inSampleSize) > size) {
+            while ((halfHeight / inSampleSize) > size && (halfWidth / inSampleSize) > size) {
                 inSampleSize *= 2;
             }
         }
@@ -274,7 +296,8 @@ public class FileBackend {
         return getVideoDimensions(mediaMetadataRetriever);
     }
 
-    private static Dimensions getVideoDimensionsOfFrame(MediaMetadataRetriever mediaMetadataRetriever) {
+    private static Dimensions getVideoDimensionsOfFrame(
+            MediaMetadataRetriever mediaMetadataRetriever) {
         Bitmap bitmap = null;
         try {
             bitmap = mediaMetadataRetriever.getFrameAtTime();
@@ -288,8 +311,10 @@ public class FileBackend {
         }
     }
 
-    private static Dimensions getVideoDimensions(MediaMetadataRetriever metadataRetriever) throws NotAVideoFile {
-        String hasVideo = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
+    private static Dimensions getVideoDimensions(MediaMetadataRetriever metadataRetriever)
+            throws NotAVideoFile {
+        String hasVideo =
+                metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
         if (hasVideo == null) {
             throw new NotAVideoFile();
         }
@@ -301,14 +326,18 @@ public class FileBackend {
         boolean rotated = rotation == 90 || rotation == 270;
         int height;
         try {
-            String h = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
+            String h =
+                    metadataRetriever.extractMetadata(
+                            MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
             height = Integer.parseInt(h);
         } catch (Exception e) {
             height = -1;
         }
         int width;
         try {
-            String w = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
+            String w =
+                    metadataRetriever.extractMetadata(
+                            MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
             width = Integer.parseInt(w);
         } catch (Exception e) {
             width = -1;
@@ -319,7 +348,9 @@ public class FileBackend {
     }
 
     private static int extractRotationFromMediaRetriever(MediaMetadataRetriever metadataRetriever) {
-        String r = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
+        String r =
+                metadataRetriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
         try {
             return Integer.parseInt(r);
         } catch (Exception e) {
@@ -368,9 +399,8 @@ public class FileBackend {
     }
 
     /**
-     * This is more than hacky but probably way better than doing nothing
-     * Further 'optimizations' might contain to get the parents of CacheDir and NoBackupDir
-     * and check against those as well
+     * This is more than hacky but probably way better than doing nothing Further 'optimizations'
+     * might contain to get the parents of CacheDir and NoBackupDir and check against those as well
      */
     private static boolean fileIsInFilesDir(Context context, Uri uri) {
         try {
@@ -386,7 +416,9 @@ public class FileBackend {
     private static boolean weOwnFileLollipop(Uri uri) {
         try {
             File file = new File(uri.getPath());
-            FileDescriptor fd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY).getFileDescriptor();
+            FileDescriptor fd =
+                    ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
+                            .getFileDescriptor();
             StructStat st = Os.fstat(fd);
             return st.st_uid == android.os.Process.myUid();
         } catch (FileNotFoundException e) {
@@ -400,18 +432,22 @@ public class FileBackend {
         final String filePath = file.getAbsolutePath();
         final Cursor cursor;
         try {
-            cursor = context.getContentResolver().query(
-                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
-                    new String[]{MediaStore.Images.Media._ID},
-                    MediaStore.Images.Media.DATA + "=? ",
-                    new String[]{filePath}, null);
+            cursor =
+                    context.getContentResolver()
+                            .query(
+                                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+                                    new String[] {MediaStore.Images.Media._ID},
+                                    MediaStore.Images.Media.DATA + "=? ",
+                                    new String[] {filePath},
+                                    null);
         } catch (SecurityException e) {
             return null;
         }
         if (cursor != null && cursor.moveToFirst()) {
             final int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
             cursor.close();
-            return Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, String.valueOf(id));
+            return Uri.withAppendedPath(
+                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI, String.valueOf(id));
         } else {
             return null;
         }
@@ -433,15 +469,30 @@ public class FileBackend {
         final String mime = attachment.getMime();
         if ("application/pdf".equals(mime) && Compatibility.runsTwentyOne()) {
             bitmap = cropCenterSquarePdf(attachment.getUri(), size);
-            drawOverlay(bitmap, paintOverlayBlackPdf(bitmap) ? R.drawable.open_pdf_black : R.drawable.open_pdf_white, 0.75f);
+            drawOverlay(
+                    bitmap,
+                    paintOverlayBlackPdf(bitmap)
+                            ? R.drawable.open_pdf_black
+                            : R.drawable.open_pdf_white,
+                    0.75f);
         } else if (mime != null && mime.startsWith("video/")) {
             bitmap = cropCenterSquareVideo(attachment.getUri(), size);
-            drawOverlay(bitmap, paintOverlayBlack(bitmap) ? R.drawable.play_video_black : R.drawable.play_video_white, 0.75f);
+            drawOverlay(
+                    bitmap,
+                    paintOverlayBlack(bitmap)
+                            ? R.drawable.play_video_black
+                            : R.drawable.play_video_white,
+                    0.75f);
         } else {
             bitmap = cropCenterSquare(attachment.getUri(), size);
             if (bitmap != null && "image/gif".equals(mime)) {
                 Bitmap withGifOverlay = bitmap.copy(Bitmap.Config.ARGB_8888, true);
-                drawOverlay(withGifOverlay, paintOverlayBlack(withGifOverlay) ? R.drawable.play_gif_black : R.drawable.play_gif_white, 1.0f);
+                drawOverlay(
+                        withGifOverlay,
+                        paintOverlayBlack(withGifOverlay)
+                                ? R.drawable.play_gif_black
+                                : R.drawable.play_gif_white,
+                        1.0f);
                 bitmap.recycle();
                 bitmap = withGifOverlay;
             }
@@ -471,29 +522,32 @@ public class FileBackend {
 
     public void updateMediaScanner(File file, final Runnable callback) {
         if (!isInDirectoryThatShouldNotBeScanned(mXmppConnectionService, file)) {
-            MediaScannerConnection.scanFile(mXmppConnectionService, new String[]{file.getAbsolutePath()}, null, new MediaScannerConnection.MediaScannerConnectionClient() {
-                @Override
-                public void onMediaScannerConnected() {
-
-                }
-
-                @Override
-                public void onScanCompleted(String path, Uri uri) {
-                    if (callback != null && file.getAbsolutePath().equals(path)) {
-                        callback.run();
-                    } else {
-                        Log.d(Config.LOGTAG, "media scanner scanned wrong file");
-                        if (callback != null) {
-                            callback.run();
+            MediaScannerConnection.scanFile(
+                    mXmppConnectionService,
+                    new String[] {file.getAbsolutePath()},
+                    null,
+                    new MediaScannerConnection.MediaScannerConnectionClient() {
+                        @Override
+                        public void onMediaScannerConnected() {}
+
+                        @Override
+                        public void onScanCompleted(String path, Uri uri) {
+                            if (callback != null && file.getAbsolutePath().equals(path)) {
+                                callback.run();
+                            } else {
+                                Log.d(Config.LOGTAG, "media scanner scanned wrong file");
+                                if (callback != null) {
+                                    callback.run();
+                                }
+                            }
                         }
-                    }
-                }
-            });
+                    });
             return;
             /*Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
             intent.setData(Uri.fromFile(file));
             mXmppConnectionService.sendBroadcast(intent);*/
-        } else if (file.getAbsolutePath().startsWith(getAppMediaDirectory(mXmppConnectionService))) {
+        } else if (file.getAbsolutePath()
+                .startsWith(getAppMediaDirectory(mXmppConnectionService))) {
             createNoMedia(file.getParentFile());
         }
         if (callback != null) {
@@ -515,25 +569,30 @@ public class FileBackend {
         return getFile(message, true);
     }
 
-
     public DownloadableFile getFileForPath(String path) {
-        return getFileForPath(path, MimeUtils.guessMimeTypeFromExtension(MimeUtils.extractRelevantExtension(path)));
+        return getFileForPath(
+                path,
+                MimeUtils.guessMimeTypeFromExtension(MimeUtils.extractRelevantExtension(path)));
     }
 
-    public DownloadableFile getFileForPath(String path, String mime) {
-        final DownloadableFile file;
+    public DownloadableFile getFileForPath(final String path, final String mime) {
         if (path.startsWith("/")) {
-            file = new DownloadableFile(path);
+            return new DownloadableFile(path);
         } else {
-            if (mime != null && mime.startsWith("image/")) {
-                file = new DownloadableFile(getConversationsDirectory("Images") + path);
-            } else if (mime != null && mime.startsWith("video/")) {
-                file = new DownloadableFile(getConversationsDirectory("Videos") + path);
-            } else {
-                file = new DownloadableFile(getConversationsDirectory("Files") + path);
-            }
+            return getLegacyFileForFilename(path, mime);
+        }
+    }
+
+    public DownloadableFile getLegacyFileForFilename(final String filename, final String mime) {
+        if (Strings.isNullOrEmpty(mime)) {
+            return new DownloadableFile(getLegacyStorageLocation("Files"), filename);
+        } else if (mime.startsWith("image/")) {
+            return new DownloadableFile(getLegacyStorageLocation("Images"), filename);
+        } else if (mime.startsWith("video/")) {
+            return new DownloadableFile(getLegacyStorageLocation("Videos"), filename);
+        } else {
+            return new DownloadableFile(getLegacyStorageLocation("Files"), filename);
         }
-        return file;
     }
 
     public boolean isInternalFile(final File file) {
@@ -542,33 +601,37 @@ public class FileBackend {
     }
 
     public DownloadableFile getFile(Message message, boolean decrypted) {
-        final boolean encrypted = !decrypted
-                && (message.getEncryption() == Message.ENCRYPTION_PGP
-                || message.getEncryption() == Message.ENCRYPTION_DECRYPTED);
+        final boolean encrypted =
+                !decrypted
+                        && (message.getEncryption() == Message.ENCRYPTION_PGP
+                                || message.getEncryption() == Message.ENCRYPTION_DECRYPTED);
         String path = message.getRelativeFilePath();
         if (path == null) {
             path = message.getUuid();
         }
         final DownloadableFile file = getFileForPath(path, message.getMimeType());
         if (encrypted) {
-            return new DownloadableFile(getConversationsDirectory("Files") + file.getName() + ".pgp");
+            return new DownloadableFile(getLegacyStorageLocation("Files"), file.getName() + ".pgp");
         } else {
             return file;
         }
     }
 
     public List<Attachment> convertToAttachments(List<DatabaseBackend.FilePath> relativeFilePaths) {
-        List<Attachment> attachments = new ArrayList<>();
-        for (DatabaseBackend.FilePath relativeFilePath : relativeFilePaths) {
-            final String mime = MimeUtils.guessMimeTypeFromExtension(MimeUtils.extractRelevantExtension(relativeFilePath.path));
+        final List<Attachment> attachments = new ArrayList<>();
+        for (final DatabaseBackend.FilePath relativeFilePath : relativeFilePaths) {
+            final String mime =
+                    MimeUtils.guessMimeTypeFromExtension(
+                            MimeUtils.extractRelevantExtension(relativeFilePath.path));
             final File file = getFileForPath(relativeFilePath.path, mime);
             attachments.add(Attachment.of(relativeFilePath.uuid, file, mime));
         }
         return attachments;
     }
 
-    private String getConversationsDirectory(final String type) {
-        return getConversationsDirectory(mXmppConnectionService, type);
+    // TODO remove static method. use direct instance access
+    private File getLegacyStorageLocation(final String type) {
+        return getLegacyStorageLocation(mXmppConnectionService, type);
     }
 
     private Bitmap resize(final Bitmap originalBitmap, int size) throws IOException {
@@ -586,7 +649,8 @@ public class FileBackend {
                 scalledW = size;
                 scalledH = Math.max((int) (h / ((double) w / size)), 1);
             }
-            final Bitmap result = Bitmap.createScaledBitmap(originalBitmap, scalledW, scalledH, true);
+            final Bitmap result =
+                    Bitmap.createScaledBitmap(originalBitmap, scalledW, scalledH, true);
             if (!originalBitmap.isRecycled()) {
                 originalBitmap.recycle();
             }
@@ -603,19 +667,26 @@ public class FileBackend {
         }
         final File file = new File(path);
         long size = file.length();
-        if (size == 0 || size >= mXmppConnectionService.getResources().getInteger(R.integer.auto_accept_filesize)) {
+        if (size == 0
+                || size
+                        >= mXmppConnectionService
+                                .getResources()
+                                .getInteger(R.integer.auto_accept_filesize)) {
             return false;
         }
         BitmapFactory.Options options = new BitmapFactory.Options();
         options.inJustDecodeBounds = true;
         try {
-            final InputStream inputStream = mXmppConnectionService.getContentResolver().openInputStream(uri);
+            final InputStream inputStream =
+                    mXmppConnectionService.getContentResolver().openInputStream(uri);
             BitmapFactory.decodeStream(inputStream, null, options);
             close(inputStream);
             if (options.outMimeType == null || options.outHeight <= 0 || options.outWidth <= 0) {
                 return false;
             }
-            return (options.outWidth <= Config.IMAGE_SIZE && options.outHeight <= Config.IMAGE_SIZE && options.outMimeType.contains(Config.IMAGE_FORMAT.name().toLowerCase()));
+            return (options.outWidth <= Config.IMAGE_SIZE
+                    && options.outHeight <= Config.IMAGE_SIZE
+                    && options.outMimeType.contains(Config.IMAGE_FORMAT.name().toLowerCase()));
         } catch (FileNotFoundException e) {
             Log.d(Config.LOGTAG, "unable to get image dimensions", e);
             return false;
@@ -627,7 +698,9 @@ public class FileBackend {
     }
 
     private void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException {
-        Log.d(Config.LOGTAG, "copy file (" + uri.toString() + ") to private storage " + file.getAbsolutePath());
+        Log.d(
+                Config.LOGTAG,
+                "copy file (" + uri.toString() + ") to private storage " + file.getAbsolutePath());
         file.getParentFile().mkdirs();
         try {
             file.createNewFile();
@@ -635,7 +708,8 @@ public class FileBackend {
             throw new FileCopyException(R.string.error_unable_to_create_temporary_file);
         }
         try (final OutputStream os = new FileOutputStream(file);
-             final InputStream is = mXmppConnectionService.getContentResolver().openInputStream(uri)) {
+                final InputStream is =
+                        mXmppConnectionService.getContentResolver().openInputStream(uri)) {
             if (is == null) {
                 throw new FileCopyException(R.string.error_file_not_found);
             }
@@ -664,7 +738,8 @@ public class FileBackend {
         }
     }
 
-    public void copyFileToPrivateStorage(Message message, Uri uri, String type) throws FileCopyException {
+    public void copyFileToPrivateStorage(Message message, Uri uri, String type)
+            throws FileCopyException {
         String mime = MimeUtils.guessMimeTypeFromUriAndMime(mXmppConnectionService, uri, type);
         Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage (mime=" + mime + ")");
         String extension = MimeUtils.guessExtensionFromMimeType(mime);
@@ -684,7 +759,10 @@ public class FileBackend {
         String filename = null;
         Cursor cursor;
         try {
-            cursor = mXmppConnectionService.getContentResolver().query(uri, projection, null, null, null);
+            cursor =
+                    mXmppConnectionService
+                            .getContentResolver()
+                            .query(uri, projection, null, null, null);
         } catch (IllegalArgumentException e) {
             cursor = null;
         }
@@ -709,7 +787,8 @@ public class FileBackend {
         return pos > 0 ? filename.substring(pos + 1) : null;
     }
 
-    private void copyImageToPrivateStorage(File file, Uri image, int sampleSize) throws FileCopyException, ImageCompressionException {
+    private void copyImageToPrivateStorage(File file, Uri image, int sampleSize)
+            throws FileCopyException, ImageCompressionException {
         final File parent = file.getParentFile();
         if (parent != null && parent.mkdirs()) {
             Log.d(Config.LOGTAG, "created parent directory");
@@ -743,7 +822,10 @@ public class FileBackend {
             scaledBitmap = rotate(scaledBitmap, rotation);
             boolean targetSizeReached = false;
             int quality = Config.IMAGE_QUALITY;
-            final int imageMaxSize = mXmppConnectionService.getResources().getInteger(R.integer.auto_accept_filesize);
+            final int imageMaxSize =
+                    mXmppConnectionService
+                            .getResources()
+                            .getInteger(R.integer.auto_accept_filesize);
             while (!targetSizeReached) {
                 os = new FileOutputStream(file);
                 Log.d(Config.LOGTAG, "compressing image with quality " + quality);
@@ -788,12 +870,19 @@ public class FileBackend {
         }
     }
 
-    public void copyImageToPrivateStorage(File file, Uri image) throws FileCopyException, ImageCompressionException {
-        Log.d(Config.LOGTAG, "copy image (" + image.toString() + ") to private storage " + file.getAbsolutePath());
+    public void copyImageToPrivateStorage(File file, Uri image)
+            throws FileCopyException, ImageCompressionException {
+        Log.d(
+                Config.LOGTAG,
+                "copy image ("
+                        + image.toString()
+                        + ") to private storage "
+                        + file.getAbsolutePath());
         copyImageToPrivateStorage(file, image, 0);
     }
 
-    public void copyImageToPrivateStorage(Message message, Uri image) throws FileCopyException, ImageCompressionException {
+    public void copyImageToPrivateStorage(Message message, Uri image)
+            throws FileCopyException, ImageCompressionException {
         switch (Config.IMAGE_FORMAT) {
             case JPEG:
                 message.setRelativeFilePath(message.getUuid() + ".jpg");
@@ -813,7 +902,8 @@ public class FileBackend {
         try {
             final BitmapFactory.Options options = new BitmapFactory.Options();
             options.inJustDecodeBounds = true;
-            final InputStream inputStream = mXmppConnectionService.getContentResolver().openInputStream(image);
+            final InputStream inputStream =
+                    mXmppConnectionService.getContentResolver().openInputStream(image);
             BitmapFactory.decodeStream(inputStream, null, options);
             close(inputStream);
             float ratio = (float) options.outHeight / options.outWidth;
@@ -833,7 +923,8 @@ public class FileBackend {
     }
 
     private int getRotation(final Uri image) {
-        try (final InputStream is = mXmppConnectionService.getContentResolver().openInputStream(image)) {
+        try (final InputStream is =
+                mXmppConnectionService.getContentResolver().openInputStream(image)) {
             return is == null ? 0 : getRotation(is);
         } catch (final Exception e) {
             return 0;
@@ -842,7 +933,9 @@ public class FileBackend {
 
     private static int getRotation(final InputStream inputStream) throws IOException {
         final ExifInterface exif = new ExifInterface(inputStream);
-        final int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
+        final int orientation =
+                exif.getAttributeInt(
+                        ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
         switch (orientation) {
             case ExifInterface.ORIENTATION_ROTATE_180:
                 return 180;
@@ -880,7 +973,12 @@ public class FileBackend {
                     thumbnail = rotate(thumbnail, getRotation(file));
                     if (mime.equals("image/gif")) {
                         Bitmap withGifOverlay = thumbnail.copy(Bitmap.Config.ARGB_8888, true);
-                        drawOverlay(withGifOverlay, paintOverlayBlack(withGifOverlay) ? R.drawable.play_gif_black : R.drawable.play_gif_white, 1.0f);
+                        drawOverlay(
+                                withGifOverlay,
+                                paintOverlayBlack(withGifOverlay)
+                                        ? R.drawable.play_gif_black
+                                        : R.drawable.play_gif_white,
+                                1.0f);
                         thumbnail.recycle();
                         thumbnail = withGifOverlay;
                     }
@@ -903,27 +1001,36 @@ public class FileBackend {
     }
 
     private void drawOverlay(Bitmap bitmap, int resource, float factor) {
-        Bitmap overlay = BitmapFactory.decodeResource(mXmppConnectionService.getResources(), resource);
+        Bitmap overlay =
+                BitmapFactory.decodeResource(mXmppConnectionService.getResources(), resource);
         Canvas canvas = new Canvas(bitmap);
         float targetSize = Math.min(canvas.getWidth(), canvas.getHeight()) * factor;
-        Log.d(Config.LOGTAG, "target size overlay: " + targetSize + " overlay bitmap size was " + overlay.getHeight());
+        Log.d(
+                Config.LOGTAG,
+                "target size overlay: "
+                        + targetSize
+                        + " overlay bitmap size was "
+                        + overlay.getHeight());
         float left = (canvas.getWidth() - targetSize) / 2.0f;
         float top = (canvas.getHeight() - targetSize) / 2.0f;
         RectF dst = new RectF(left, top, left + targetSize - 1, top + targetSize - 1);
         canvas.drawBitmap(overlay, null, dst, createAntiAliasingPaint());
     }
 
-    /**
-     * https://stackoverflow.com/a/3943023/210897
-     */
+    /** https://stackoverflow.com/a/3943023/210897 */
     private boolean paintOverlayBlack(final Bitmap bitmap) {
         final int h = bitmap.getHeight();
         final int w = bitmap.getWidth();
         int record = 0;
         for (int y = Math.round(h * IGNORE_PADDING); y < h - Math.round(h * IGNORE_PADDING); ++y) {
-            for (int x = Math.round(w * IGNORE_PADDING); x < w - Math.round(w * IGNORE_PADDING); ++x) {
+            for (int x = Math.round(w * IGNORE_PADDING);
+                    x < w - Math.round(w * IGNORE_PADDING);
+                    ++x) {
                 int pixel = bitmap.getPixel(x, y);
-                if ((Color.red(pixel) * 0.299 + Color.green(pixel) * 0.587 + Color.blue(pixel) * 0.114) > 186) {
+                if ((Color.red(pixel) * 0.299
+                                + Color.green(pixel) * 0.587
+                                + Color.blue(pixel) * 0.114)
+                        > 186) {
                     --record;
                 } else {
                     ++record;
@@ -940,7 +1047,10 @@ public class FileBackend {
         for (int y = 0; y < h; ++y) {
             for (int x = 0; x < w; ++x) {
                 int pixel = bitmap.getPixel(x, y);
-                if ((Color.red(pixel) * 0.299 + Color.green(pixel) * 0.587 + Color.blue(pixel) * 0.114) > 186) {
+                if ((Color.red(pixel) * 0.299
+                                + Color.green(pixel) * 0.587
+                                + Color.blue(pixel) * 0.114)
+                        > 186) {
                     white++;
                 }
             }
@@ -975,16 +1085,27 @@ public class FileBackend {
             frame = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
             frame.eraseColor(0xff000000);
         }
-        drawOverlay(frame, paintOverlayBlack(frame) ? R.drawable.play_video_black : R.drawable.play_video_white, 0.75f);
+        drawOverlay(
+                frame,
+                paintOverlayBlack(frame)
+                        ? R.drawable.play_video_black
+                        : R.drawable.play_video_white,
+                0.75f);
         return frame;
     }
 
     @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
     private Bitmap getPdfDocumentPreview(final File file, final int size) {
         try {
-            final ParcelFileDescriptor fileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+            final ParcelFileDescriptor fileDescriptor =
+                    ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
             final Bitmap rendered = renderPdfDocument(fileDescriptor, size, true);
-            drawOverlay(rendered, paintOverlayBlackPdf(rendered) ? R.drawable.open_pdf_black : R.drawable.open_pdf_white, 0.75f);
+            drawOverlay(
+                    rendered,
+                    paintOverlayBlackPdf(rendered)
+                            ? R.drawable.open_pdf_black
+                            : R.drawable.open_pdf_white,
+                    0.75f);
             return rendered;
         } catch (final IOException | SecurityException e) {
             Log.d(Config.LOGTAG, "unable to render PDF document preview", e);
@@ -994,11 +1115,11 @@ public class FileBackend {
         }
     }
 
-
     @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
     private Bitmap cropCenterSquarePdf(final Uri uri, final int size) {
         try {
-            ParcelFileDescriptor fileDescriptor = mXmppConnectionService.getContentResolver().openFileDescriptor(uri, "r");
+            ParcelFileDescriptor fileDescriptor =
+                    mXmppConnectionService.getContentResolver().openFileDescriptor(uri, "r");
             final Bitmap bitmap = renderPdfDocument(fileDescriptor, size, false);
             return cropCenterSquare(bitmap, size);
         } catch (Exception e) {
@@ -1009,11 +1130,15 @@ public class FileBackend {
     }
 
     @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
-    private Bitmap renderPdfDocument(ParcelFileDescriptor fileDescriptor, int targetSize, boolean fit) throws IOException {
+    private Bitmap renderPdfDocument(
+            ParcelFileDescriptor fileDescriptor, int targetSize, boolean fit) throws IOException {
         final PdfRenderer pdfRenderer = new PdfRenderer(fileDescriptor);
         final PdfRenderer.Page page = pdfRenderer.openPage(0);
-        final Dimensions dimensions = scalePdfDimensions(new Dimensions(page.getHeight(), page.getWidth()), targetSize, fit);
-        final Bitmap rendered = Bitmap.createBitmap(dimensions.width, dimensions.height, Bitmap.Config.ARGB_8888);
+        final Dimensions dimensions =
+                scalePdfDimensions(
+                        new Dimensions(page.getHeight(), page.getWidth()), targetSize, fit);
+        final Bitmap rendered =
+                Bitmap.createBitmap(dimensions.width, dimensions.height, Bitmap.Config.ARGB_8888);
         rendered.eraseColor(0xffffffff);
         page.render(rendered, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
         page.close();
@@ -1025,9 +1150,17 @@ public class FileBackend {
     public Uri getTakePhotoUri() {
         File file;
         if (Config.ONLY_INTERNAL_STORAGE) {
-            file = new File(mXmppConnectionService.getCacheDir().getAbsolutePath(), "Camera/IMG_" + IMAGE_DATE_FORMAT.format(new Date()) + ".jpg");
+            file =
+                    new File(
+                            mXmppConnectionService.getCacheDir().getAbsolutePath(),
+                            "Camera/IMG_" + IMAGE_DATE_FORMAT.format(new Date()) + ".jpg");
         } else {
-            file = new File(getTakePhotoPath() + "IMG_" + IMAGE_DATE_FORMAT.format(new Date()) + ".jpg");
+            file =
+                    new File(
+                            getTakePhotoPath()
+                                    + "IMG_"
+                                    + IMAGE_DATE_FORMAT.format(new Date())
+                                    + ".jpg");
         }
         file.getParentFile().mkdirs();
         return getUriForFile(mXmppConnectionService, file);
@@ -1036,11 +1169,15 @@ public class FileBackend {
     public Avatar getPepAvatar(Uri image, int size, Bitmap.CompressFormat format) {
 
         final Avatar uncompressAvatar = getUncompressedAvatar(image);
-        if (uncompressAvatar != null && uncompressAvatar.image.length() <= Config.AVATAR_CHAR_LIMIT) {
+        if (uncompressAvatar != null
+                && uncompressAvatar.image.length() <= Config.AVATAR_CHAR_LIMIT) {
             return uncompressAvatar;
         }
         if (uncompressAvatar != null) {
-            Log.d(Config.LOGTAG, "uncompressed avatar exceeded char limit by " + (uncompressAvatar.image.length() - Config.AVATAR_CHAR_LIMIT));
+            Log.d(
+                    Config.LOGTAG,
+                    "uncompressed avatar exceeded char limit by "
+                            + (uncompressAvatar.image.length() - Config.AVATAR_CHAR_LIMIT));
         }
 
         Bitmap bm = cropCenterSquare(image, size);
@@ -1059,7 +1196,9 @@ public class FileBackend {
     private Avatar getUncompressedAvatar(Uri uri) {
         Bitmap bitmap = null;
         try {
-            bitmap = BitmapFactory.decodeStream(mXmppConnectionService.getContentResolver().openInputStream(uri));
+            bitmap =
+                    BitmapFactory.decodeStream(
+                            mXmppConnectionService.getContentResolver().openInputStream(uri));
             return getPepAvatar(bitmap, Bitmap.CompressFormat.PNG, 100);
         } catch (Exception e) {
             return null;
@@ -1073,18 +1212,24 @@ public class FileBackend {
     private Avatar getPepAvatar(Bitmap bitmap, Bitmap.CompressFormat format, int quality) {
         try {
             ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream();
-            Base64OutputStream mBase64OutputStream = new Base64OutputStream(mByteArrayOutputStream, Base64.DEFAULT);
+            Base64OutputStream mBase64OutputStream =
+                    new Base64OutputStream(mByteArrayOutputStream, Base64.DEFAULT);
             MessageDigest digest = MessageDigest.getInstance("SHA-1");
-            DigestOutputStream mDigestOutputStream = new DigestOutputStream(mBase64OutputStream, digest);
+            DigestOutputStream mDigestOutputStream =
+                    new DigestOutputStream(mBase64OutputStream, digest);
             if (!bitmap.compress(format, quality, mDigestOutputStream)) {
                 return null;
             }
             mDigestOutputStream.flush();
             mDigestOutputStream.close();
             long chars = mByteArrayOutputStream.size();
-            if (format != Bitmap.CompressFormat.PNG && quality >= 50 && chars >= Config.AVATAR_CHAR_LIMIT) {
+            if (format != Bitmap.CompressFormat.PNG
+                    && quality >= 50
+                    && chars >= Config.AVATAR_CHAR_LIMIT) {
                 int q = quality - 2;
-                Log.d(Config.LOGTAG, "avatar char length was " + chars + " reducing quality to " + q);
+                Log.d(
+                        Config.LOGTAG,
+                        "avatar char length was " + chars + " reducing quality to " + q);
                 return getPepAvatar(bitmap, format, q);
             }
             Log.d(Config.LOGTAG, "settled on char length " + chars + " with quality=" + quality);
@@ -1123,7 +1268,8 @@ public class FileBackend {
             BitmapFactory.decodeFile(file.getAbsolutePath(), options);
             is = new FileInputStream(file);
             ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream();
-            Base64OutputStream mBase64OutputStream = new Base64OutputStream(mByteArrayOutputStream, Base64.DEFAULT);
+            Base64OutputStream mBase64OutputStream =
+                    new Base64OutputStream(mByteArrayOutputStream, Base64.DEFAULT);
             MessageDigest digest = MessageDigest.getInstance("SHA-1");
             DigestOutputStream os = new DigestOutputStream(mBase64OutputStream, digest);
             byte[] buffer = new byte[4096];
@@ -1157,14 +1303,20 @@ public class FileBackend {
             file = getAvatarFile(avatar.getFilename());
             avatar.size = file.length();
         } else {
-            file = new File(mXmppConnectionService.getCacheDir().getAbsolutePath() + "/" + UUID.randomUUID().toString());
+            file =
+                    new File(
+                            mXmppConnectionService.getCacheDir().getAbsolutePath()
+                                    + "/"
+                                    + UUID.randomUUID().toString());
             if (file.getParentFile().mkdirs()) {
                 Log.d(Config.LOGTAG, "created cache directory");
             }
             OutputStream os = null;
             try {
                 if (!file.createNewFile()) {
-                    Log.d(Config.LOGTAG, "unable to create temporary file " + file.getAbsolutePath());
+                    Log.d(
+                            Config.LOGTAG,
+                            "unable to create temporary file " + file.getAbsolutePath());
                 }
                 os = new FileOutputStream(file);
                 MessageDigest digest = MessageDigest.getInstance("SHA-1");
@@ -1182,7 +1334,9 @@ public class FileBackend {
                     }
                     final File avatarFile = getAvatarFile(avatar.getFilename());
                     if (!file.renameTo(avatarFile)) {
-                        Log.d(Config.LOGTAG, "unable to rename " + file.getAbsolutePath() + " to " + outputFile);
+                        Log.d(
+                                Config.LOGTAG,
+                                "unable to rename " + file.getAbsolutePath() + " to " + outputFile);
                         return false;
                     }
                 } else {
@@ -1294,7 +1448,7 @@ public class FileBackend {
             }
             return dest;
         } catch (SecurityException e) {
-            return null; //android 6.0 with revoked permissions for example
+            return null; // android 6.0 with revoked permissions for example
         } catch (FileNotFoundException e) {
             return null;
         } finally {
@@ -1323,10 +1477,12 @@ public class FileBackend {
         return output;
     }
 
-    private int calcSampleSize(Uri image, int size) throws FileNotFoundException, SecurityException {
+    private int calcSampleSize(Uri image, int size)
+            throws FileNotFoundException, SecurityException {
         final BitmapFactory.Options options = new BitmapFactory.Options();
         options.inJustDecodeBounds = true;
-        final InputStream inputStream = mXmppConnectionService.getContentResolver().openInputStream(image);
+        final InputStream inputStream =
+                mXmppConnectionService.getContentResolver().openInputStream(image);
         BitmapFactory.decodeStream(inputStream, null, options);
         close(inputStream);
         return calcSampleSize(options, size);
@@ -1340,7 +1496,9 @@ public class FileBackend {
         DownloadableFile file = getFile(message);
         final String mime = file.getMimeType();
         final boolean privateMessage = message.isPrivateMessage();
-        final boolean image = message.getType() == Message.TYPE_IMAGE || (mime != null && mime.startsWith("image/"));
+        final boolean image =
+                message.getType() == Message.TYPE_IMAGE
+                        || (mime != null && mime.startsWith("image/"));
         final boolean video = mime != null && mime.startsWith("video/");
         final boolean audio = mime != null && mime.startsWith("audio/");
         final boolean pdf = "application/pdf".equals(mime);
@@ -1363,22 +1521,29 @@ public class FileBackend {
                     body.append('|').append(dimensions.width).append('|').append(dimensions.height);
                 }
             } catch (NotAVideoFile notAVideoFile) {
-                Log.d(Config.LOGTAG, "file with mime type " + file.getMimeType() + " was not a video file");
-                //fall threw
+                Log.d(
+                        Config.LOGTAG,
+                        "file with mime type " + file.getMimeType() + " was not a video file");
+                // fall threw
             }
         } else if (audio) {
             body.append("|0|0|").append(getMediaRuntime(file));
         }
         message.setBody(body.toString());
         message.setDeleted(false);
-        message.setType(privateMessage ? Message.TYPE_PRIVATE_FILE : (image ? Message.TYPE_IMAGE : Message.TYPE_FILE));
+        message.setType(
+                privateMessage
+                        ? Message.TYPE_PRIVATE_FILE
+                        : (image ? Message.TYPE_IMAGE : Message.TYPE_FILE));
     }
 
     private int getMediaRuntime(File file) {
         try {
             MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
             mediaMetadataRetriever.setDataSource(file.toString());
-            return Integer.parseInt(mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
+            return Integer.parseInt(
+                    mediaMetadataRetriever.extractMetadata(
+                            MediaMetadataRetriever.METADATA_KEY_DURATION));
         } catch (RuntimeException e) {
             return 0;
         }
@@ -1431,12 +1596,14 @@ public class FileBackend {
     }
 
     private Dimensions scalePdfDimensions(Dimensions in) {
-        final DisplayMetrics displayMetrics = mXmppConnectionService.getResources().getDisplayMetrics();
+        final DisplayMetrics displayMetrics =
+                mXmppConnectionService.getResources().getDisplayMetrics();
         final int target = (int) (displayMetrics.density * 288);
         return scalePdfDimensions(in, target, true);
     }
 
-    private static Dimensions scalePdfDimensions(final Dimensions in, final int target, final boolean fit) {
+    private static Dimensions scalePdfDimensions(
+            final Dimensions in, final int target, final boolean fit) {
         final int w, h;
         if (fit == (in.width <= in.height)) {
             w = Math.max((int) (in.width / ((double) in.height / target)), 1);
@@ -1491,7 +1658,6 @@ public class FileBackend {
         }
     }
 
-
     public static class FileCopyException extends Exception {
         private final int resId;
 
@@ -1499,8 +1665,7 @@ public class FileBackend {
             this.resId = resId;
         }
 
-        public @StringRes
-        int getResId() {
+        public @StringRes int getResId() {
             return resId;
         }
     }

src/main/java/eu/siacs/conversations/ui/RecordingActivity.java 🔗

@@ -154,9 +154,9 @@ public class RecordingActivity extends Activity implements View.OnClickListener
     }
 
     private static File generateOutputFilename(Context context) {
-        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
-        String filename = "RECORDING_" + dateFormat.format(new Date()) + ".m4a";
-        return new File(FileBackend.getConversationsDirectory(context, STORAGE_DIRECTORY_TYPE_NAME) + "/" + filename);
+        final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
+        final String filename = "RECORDING_" + dateFormat.format(new Date()) + ".m4a";
+        return new File(FileBackend.getLegacyStorageLocation(context, STORAGE_DIRECTORY_TYPE_NAME), filename);
     }
 
     private void setupOutputFile() {