display PDF previews

This commit is contained in:
Daniel Gultsch 2020-02-11 17:41:54 +01:00
parent 6acb15dd15
commit 2aee26c49a
3 changed files with 88 additions and 19 deletions

View file

@ -11,6 +11,7 @@ import android.graphics.Color;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.RectF; import android.graphics.RectF;
import android.graphics.pdf.PdfRenderer;
import android.media.MediaMetadataRetriever; import android.media.MediaMetadataRetriever;
import android.media.MediaScannerConnection; import android.media.MediaScannerConnection;
import android.net.Uri; import android.net.Uri;
@ -25,6 +26,7 @@ import android.system.Os;
import android.system.StructStat; import android.system.StructStat;
import android.util.Base64; import android.util.Base64;
import android.util.Base64OutputStream; import android.util.Base64OutputStream;
import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import android.util.LruCache; import android.util.LruCache;
@ -59,6 +61,7 @@ import eu.siacs.conversations.services.AttachFileToConversationRunnable;
import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.ui.RecordingActivity; import eu.siacs.conversations.ui.RecordingActivity;
import eu.siacs.conversations.ui.util.Attachment; import eu.siacs.conversations.ui.util.Attachment;
import eu.siacs.conversations.utils.Compatibility;
import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.ExifHelper; import eu.siacs.conversations.utils.ExifHelper;
import eu.siacs.conversations.utils.FileUtils; import eu.siacs.conversations.utils.FileUtils;
@ -509,7 +512,6 @@ public class FileBackend {
} }
public DownloadableFile getFileForPath(String path) { public DownloadableFile getFileForPath(String path) {
return getFileForPath(path, MimeUtils.guessMimeTypeFromExtension(MimeUtils.extractRelevantExtension(path))); return getFileForPath(path, MimeUtils.guessMimeTypeFromExtension(MimeUtils.extractRelevantExtension(path)));
} }
@ -818,7 +820,9 @@ public class FileBackend {
} }
DownloadableFile file = getFile(message); DownloadableFile file = getFile(message);
final String mime = file.getMimeType(); 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); thumbnail = getVideoPreview(file, size);
} else { } else {
Bitmap fullsize = getFullsizeImagePreview(file, size); Bitmap fullsize = getFullsizeImagePreview(file, size);
@ -897,8 +901,8 @@ public class FileBackend {
} }
} }
private Bitmap getVideoPreview(File file, int size) { private Bitmap getVideoPreview(final File file, final int size) {
MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever(); final MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever();
Bitmap frame; Bitmap frame;
try { try {
metadataRetriever.setDataSource(file.getAbsolutePath()); metadataRetriever.setDataSource(file.getAbsolutePath());
@ -913,6 +917,24 @@ public class FileBackend {
return frame; 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() { public Uri getTakePhotoUri() {
File file; File file;
if (Config.ONLY_INTERNAL_STORAGE) { 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 image = message.getType() == Message.TYPE_IMAGE || (mime != null && mime.startsWith("image/"));
final boolean video = mime != null && mime.startsWith("video/"); final boolean video = mime != null && mime.startsWith("video/");
final boolean audio = mime != null && mime.startsWith("audio/"); final boolean audio = mime != null && mime.startsWith("audio/");
final boolean pdf = "application/pdf".equals(mime);
final StringBuilder body = new StringBuilder(); final StringBuilder body = new StringBuilder();
if (url != null) { if (url != null) {
body.append(url.toString()); body.append(url.toString());
} }
body.append('|').append(file.getSize()); body.append('|').append(file.getSize());
if (image || video) { if (image || video || (pdf && Compatibility.runsTwentyOne())) {
try { 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()) { if (dimensions.valid()) {
body.append('|').append(dimensions.width).append('|').append(dimensions.height); body.append('|').append(dimensions.width).append('|').append(dimensions.height);
} }
@ -1264,6 +1294,45 @@ public class FileBackend {
return getVideoDimensions(metadataRetriever); 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) { public Bitmap getAvatar(String avatar, int size) {
if (avatar == null) { if (avatar == null) {
return null; return null;
@ -1275,10 +1344,6 @@ public class FileBackend {
return bm; return bm;
} }
public boolean isFileAvailable(Message message) {
return getFile(message).exists();
}
private static class Dimensions { private static class Dimensions {
public final int width; public final int width;
public final int height; public final int height;

View file

@ -540,15 +540,15 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
this.audioPlayer.init(audioPlayer, message); 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); toggleWhisperInfo(viewHolder, message, darkBackground);
viewHolder.download_button.setVisibility(View.GONE); viewHolder.download_button.setVisibility(View.GONE);
viewHolder.audioPlayer.setVisibility(View.GONE); viewHolder.audioPlayer.setVisibility(View.GONE);
viewHolder.image.setVisibility(View.VISIBLE); viewHolder.image.setVisibility(View.VISIBLE);
FileParams params = message.getFileParams(); final FileParams params = message.getFileParams();
double target = metrics.density * 288; final double target = metrics.density * 288;
int scaledW; final int scaledW;
int scaledH; final int scaledH;
if (Math.max(params.height, params.width) * metrics.density <= target) { if (Math.max(params.height, params.width) * metrics.density <= target) {
scaledW = (int) (params.width * metrics.density); scaledW = (int) (params.width * metrics.density);
scaledH = (int) (params.height * 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) { } else if (message.isFileOrImage() && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
if (message.getFileParams().width > 0 && message.getFileParams().height > 0) { if (message.getFileParams().width > 0 && message.getFileParams().height > 0) {
displayImageMessage(viewHolder, message, darkBackground); displayMediaPreviewMessage(viewHolder, message, darkBackground);
} else if (message.getFileParams().runtime > 0) { } else if (message.getFileParams().runtime > 0) {
displayAudioMessage(viewHolder, message, darkBackground); displayAudioMessage(viewHolder, message, darkBackground);
} else { } else {

View file

@ -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; 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() { public static boolean runsTwentyOne() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; 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; 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() { public static boolean twentyEight() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P; return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
} }