diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index 57a129f70..a7a5b470b 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -11,6 +11,7 @@ import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.RectF; +import android.graphics.pdf.PdfRenderer; import android.media.MediaMetadataRetriever; import android.media.MediaScannerConnection; import android.net.Uri; @@ -25,6 +26,7 @@ import android.system.Os; import android.system.StructStat; import android.util.Base64; import android.util.Base64OutputStream; +import android.util.DisplayMetrics; import android.util.Log; import android.util.LruCache; @@ -59,6 +61,7 @@ import eu.siacs.conversations.services.AttachFileToConversationRunnable; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.RecordingActivity; import eu.siacs.conversations.ui.util.Attachment; +import eu.siacs.conversations.utils.Compatibility; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.ExifHelper; import eu.siacs.conversations.utils.FileUtils; @@ -509,7 +512,6 @@ public class FileBackend { } - public DownloadableFile getFileForPath(String path) { return getFileForPath(path, MimeUtils.guessMimeTypeFromExtension(MimeUtils.extractRelevantExtension(path))); } @@ -818,7 +820,9 @@ public class FileBackend { } DownloadableFile file = getFile(message); final String mime = file.getMimeType(); - if (mime.startsWith("video/")) { + if ("application/pdf".equals(mime) && Compatibility.runsTwentyOne()) { + thumbnail = getPdfDocumentPreview(file, size); + } else if (mime.startsWith("video/")) { thumbnail = getVideoPreview(file, size); } else { Bitmap fullsize = getFullsizeImagePreview(file, size); @@ -897,8 +901,8 @@ public class FileBackend { } } - private Bitmap getVideoPreview(File file, int size) { - MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever(); + private Bitmap getVideoPreview(final File file, final int size) { + final MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever(); Bitmap frame; try { metadataRetriever.setDataSource(file.getAbsolutePath()); @@ -913,6 +917,24 @@ public class FileBackend { 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 PdfRenderer pdfRenderer = new PdfRenderer(fileDescriptor); + final PdfRenderer.Page page = pdfRenderer.openPage(0); + Dimensions dimensions = scalePdfDimensions(new Dimensions(page.getHeight(), page.getWidth())); + final Bitmap rendered = Bitmap.createBitmap(dimensions.width, dimensions.height, Bitmap.Config.ARGB_8888); + page.render(rendered, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY); + return rendered; + } catch (IOException e) { + Log.d(Config.LOGTAG, "unable to render PDF document preview", e); + final Bitmap placeholder = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + placeholder.eraseColor(0xff000000); + return placeholder; + } + } + public Uri getTakePhotoUri() { File file; if (Config.ONLY_INTERNAL_STORAGE) { @@ -1210,14 +1232,22 @@ public class FileBackend { 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); final StringBuilder body = new StringBuilder(); if (url != null) { body.append(url.toString()); } body.append('|').append(file.getSize()); - if (image || video) { + if (image || video || (pdf && Compatibility.runsTwentyOne())) { try { - Dimensions dimensions = image ? getImageDimensions(file) : getVideoDimensions(file); + final Dimensions dimensions; + if (video) { + dimensions = getVideoDimensions(file); + } else if (pdf && Compatibility.runsTwentyOne()) { + dimensions = getPdfDocumentDimensions(file); + } else { + dimensions = getImageDimensions(file); + } if (dimensions.valid()) { body.append('|').append(dimensions.width).append('|').append(dimensions.height); } @@ -1264,6 +1294,45 @@ public class FileBackend { return getVideoDimensions(metadataRetriever); } + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + private Dimensions getPdfDocumentDimensions(final File file) { + final ParcelFileDescriptor fileDescriptor; + try { + fileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); + if (fileDescriptor == null) { + return new Dimensions(0, 0); + } + } catch (FileNotFoundException e) { + return new Dimensions(0, 0); + } + try { + final PdfRenderer pdfRenderer = new PdfRenderer(fileDescriptor); + final PdfRenderer.Page page = pdfRenderer.openPage(0); + final int height = page.getHeight(); + final int width = page.getWidth(); + page.close(); + pdfRenderer.close(); + return scalePdfDimensions(new Dimensions(height, width)); + } catch (IOException e) { + Log.d(Config.LOGTAG, "unable to get dimensions for pdf document", e); + return new Dimensions(0, 0); + } + } + + private Dimensions scalePdfDimensions(Dimensions in) { + final DisplayMetrics displayMetrics = mXmppConnectionService.getResources().getDisplayMetrics(); + final int target = (int) (displayMetrics.density * 288); + final int w, h; + if (in.width <= in.height) { + w = Math.max((int) (in.width / ((double) in.height / target)), 1); + h = target; + } else { + w = target; + h = Math.max((int) (in.height / ((double) in.width / target)), 1); + } + return new Dimensions(h, w); + } + public Bitmap getAvatar(String avatar, int size) { if (avatar == null) { return null; @@ -1275,10 +1344,6 @@ public class FileBackend { return bm; } - public boolean isFileAvailable(Message message) { - return getFile(message).exists(); - } - private static class Dimensions { public final int width; public final int height; diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 6867bbeb5..3ee927e00 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -540,15 +540,15 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie this.audioPlayer.init(audioPlayer, message); } - private void displayImageMessage(ViewHolder viewHolder, final Message message, final boolean darkBackground) { + private void displayMediaPreviewMessage(ViewHolder viewHolder, final Message message, final boolean darkBackground) { toggleWhisperInfo(viewHolder, message, darkBackground); viewHolder.download_button.setVisibility(View.GONE); viewHolder.audioPlayer.setVisibility(View.GONE); viewHolder.image.setVisibility(View.VISIBLE); - FileParams params = message.getFileParams(); - double target = metrics.density * 288; - int scaledW; - int scaledH; + final FileParams params = message.getFileParams(); + final double target = metrics.density * 288; + final int scaledW; + final int scaledH; if (Math.max(params.height, params.width) * metrics.density <= target) { scaledW = (int) (params.width * metrics.density); scaledH = (int) (params.height * metrics.density); @@ -747,7 +747,7 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie } } else if (message.isFileOrImage() && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) { if (message.getFileParams().width > 0 && message.getFileParams().height > 0) { - displayImageMessage(viewHolder, message, darkBackground); + displayMediaPreviewMessage(viewHolder, message, darkBackground); } else if (message.getFileParams().runtime > 0) { displayAudioMessage(viewHolder, message, darkBackground); } else { diff --git a/src/main/java/eu/siacs/conversations/utils/Compatibility.java b/src/main/java/eu/siacs/conversations/utils/Compatibility.java index 4a2a14111..9f7b9c997 100644 --- a/src/main/java/eu/siacs/conversations/utils/Compatibility.java +++ b/src/main/java/eu/siacs/conversations/utils/Compatibility.java @@ -38,14 +38,18 @@ public class Compatibility { return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; } - public static boolean runsTwentySix() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; + public static boolean runsTwentyOne() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; } - public static boolean runsTwentyFour() { + private static boolean runsTwentyFour() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N; } + public static boolean runsTwentySix() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; + } + public static boolean twentyEight() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P; }