display PDF previews

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/persistance/FileBackend.java   | 85 
src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java | 12 
src/main/java/eu/siacs/conversations/utils/Compatibility.java       | 10 
3 files changed, 88 insertions(+), 19 deletions(-)

Detailed changes

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;

src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java 🔗

@@ -540,15 +540,15 @@ public class MessageAdapter extends ArrayAdapter<Message> 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<Message> 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 {

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;
     }