diff --git a/app/src/main/java/eu/faircode/email/AdapterMessage.java b/app/src/main/java/eu/faircode/email/AdapterMessage.java index 35e0f14933..b8ecd1e2b0 100644 --- a/app/src/main/java/eu/faircode/email/AdapterMessage.java +++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java @@ -30,11 +30,8 @@ import android.content.pm.PackageManager; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.Typeface; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; @@ -51,7 +48,6 @@ import android.text.format.DateUtils; import android.text.method.LinkMovementMethod; import android.text.style.ImageSpan; import android.text.style.URLSpan; -import android.util.Base64; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -71,12 +67,8 @@ import com.google.android.material.snackbar.Snackbar; import org.xml.sax.XMLReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.net.URL; import java.text.Collator; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -782,124 +774,8 @@ public class AdapterMessage extends RecyclerView.Adapter"; - EntityAttachment attachment = DB.getInstance(context).attachment().getAttachment(message.id, cid); - if (attachment == null || !attachment.available) { - Drawable d = context.getResources().getDrawable(R.drawable.baseline_photo_library_24, context.getTheme()); - d.setBounds(0, 0, px, px); - return d; - } else { - File file = EntityAttachment.getFile(context, attachment.id); - Drawable d = Drawable.createFromPath(file.getAbsolutePath()); - if (d == null) { - d = context.getResources().getDrawable(R.drawable.baseline_broken_image_24, context.getTheme()); - d.setBounds(0, 0, px / 2, px / 2); - } else - d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); - return d; - } - } - - // Data URI - if (data) { - // "\"Red"; - - String base64 = source.substring(source.indexOf(',') + 1); - byte[] bytes = Base64.decode(base64.getBytes(), 0); - - Bitmap bm = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); - if (bm == null) - throw new IllegalArgumentException("decode byte array failed"); - - Drawable d = new BitmapDrawable(context.getResources(), bm); - d.setBounds(0, 0, bm.getWidth(), bm.getHeight()); - return d; - } - - // Get cache folder - File dir = new File(context.getCacheDir(), "images"); - if (!dir.exists()) - dir.mkdir(); - - InputStream is = null; - FileOutputStream os = null; - try { - // Create unique file name - File file = new File(dir, message.id + "_" + source.hashCode()); - - // Get input stream - if (file.exists()) { - Log.i(Helper.TAG, "Using cached " + file); - is = new FileInputStream(file); - } else { - Log.i(Helper.TAG, "Downloading " + source); - is = new URL(source).openStream(); - } - - // Decode image from stream - Bitmap bm = BitmapFactory.decodeStream(is); - if (bm == null) - throw new IllegalArgumentException("decode stream failed"); - - // Cache bitmap - if (!file.exists()) { - os = new FileOutputStream(file); - bm.compress(Bitmap.CompressFormat.PNG, 100, os); - } - - // Create drawable from bitmap - Drawable d = new BitmapDrawable(context.getResources(), bm); - d.setBounds(0, 0, bm.getWidth(), bm.getHeight()); - return d; - } catch (Throwable ex) { - // Show warning icon - Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex)); - Drawable d = context.getResources().getDrawable(R.drawable.baseline_broken_image_24, context.getTheme()); - d.setBounds(0, 0, px / 2, px / 2); - return d; - } finally { - // Close streams - if (is != null) { - try { - is.close(); - } catch (IOException e) { - Log.w(Helper.TAG, e + "\n" + Log.getStackTraceString(e)); - } - } - if (os != null) { - try { - os.close(); - } catch (IOException e) { - Log.w(Helper.TAG, e + "\n" + Log.getStackTraceString(e)); - } - } - } - } else { - // Show placeholder icon - int resid = (embedded || data ? R.drawable.baseline_photo_library_24 : R.drawable.baseline_image_24); - Drawable d = context.getResources().getDrawable(resid, context.getTheme()); - d.setBounds(0, 0, px, px); - return d; - } + boolean show = properties.showImages(message.id); + return HtmlHelper.decodeImage(source, context, message.id, show); } }, new Html.TagHandler() { @Override diff --git a/app/src/main/java/eu/faircode/email/HtmlHelper.java b/app/src/main/java/eu/faircode/email/HtmlHelper.java index bcbe35246b..c031687552 100644 --- a/app/src/main/java/eu/faircode/email/HtmlHelper.java +++ b/app/src/main/java/eu/faircode/email/HtmlHelper.java @@ -19,6 +19,15 @@ package eu.faircode.email; Copyright 2018 by Marcel Bokhorst (M66B) */ +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.util.Base64; +import android.util.Log; + import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; @@ -28,6 +37,12 @@ import org.jsoup.safety.Whitelist; import org.jsoup.select.NodeTraversor; import org.jsoup.select.NodeVisitor; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -82,4 +97,125 @@ public class HtmlHelper { }, document.body()); return document.body().html(); } + + static Drawable decodeImage(String source, Context context, long id, boolean show) { + float scale = context.getResources().getDisplayMetrics().density; + int px = Math.round(48 * scale); + + if (TextUtils.isEmpty(source)) { + Drawable d = context.getResources().getDrawable(R.drawable.baseline_broken_image_24, context.getTheme()); + d.setBounds(0, 0, px / 2, px / 2); + return d; + } + + boolean embedded = source.startsWith("cid:"); + boolean data = source.startsWith("data:"); + Log.i(Helper.TAG, "Image embedded=" + embedded + " data=" + data + " source=" + source); + + if (show) { + // Embedded images + if (embedded) { + String cid = "<" + source.split(":")[1] + ">"; + EntityAttachment attachment = DB.getInstance(context).attachment().getAttachment(id, cid); + if (attachment == null || !attachment.available) { + Drawable d = context.getResources().getDrawable(R.drawable.baseline_photo_library_24, context.getTheme()); + d.setBounds(0, 0, px, px); + return d; + } else { + File file = EntityAttachment.getFile(context, attachment.id); + Drawable d = Drawable.createFromPath(file.getAbsolutePath()); + if (d == null) { + d = context.getResources().getDrawable(R.drawable.baseline_broken_image_24, context.getTheme()); + d.setBounds(0, 0, px / 2, px / 2); + } else + d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); + return d; + } + } + + // Data URI + if (data) { + // "\"Red"; + + String base64 = source.substring(source.indexOf(',') + 1); + byte[] bytes = Base64.decode(base64.getBytes(), 0); + + Bitmap bm = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); + if (bm == null) + throw new IllegalArgumentException("decode byte array failed"); + + Drawable d = new BitmapDrawable(context.getResources(), bm); + d.setBounds(0, 0, bm.getWidth(), bm.getHeight()); + return d; + } + + // Get cache folder + File dir = new File(context.getCacheDir(), "images"); + if (!dir.exists()) + dir.mkdir(); + + InputStream is = null; + FileOutputStream os = null; + try { + // Create unique file name + File file = new File(dir, id + "_" + source.hashCode()); + + // Get input stream + if (file.exists()) { + Log.i(Helper.TAG, "Using cached " + file); + is = new FileInputStream(file); + } else { + Log.i(Helper.TAG, "Downloading " + source); + is = new URL(source).openStream(); + } + + // Decode image from stream + Bitmap bm = BitmapFactory.decodeStream(is); + if (bm == null) + throw new IllegalArgumentException("decode stream failed"); + + // Cache bitmap + if (!file.exists()) { + os = new FileOutputStream(file); + bm.compress(Bitmap.CompressFormat.PNG, 100, os); + } + + // Create drawable from bitmap + Drawable d = new BitmapDrawable(context.getResources(), bm); + d.setBounds(0, 0, bm.getWidth(), bm.getHeight()); + return d; + } catch (Throwable ex) { + // Show warning icon + Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex)); + Drawable d = context.getResources().getDrawable(R.drawable.baseline_broken_image_24, context.getTheme()); + d.setBounds(0, 0, px / 2, px / 2); + return d; + } finally { + // Close streams + if (is != null) { + try { + is.close(); + } catch (IOException e) { + Log.w(Helper.TAG, e + "\n" + Log.getStackTraceString(e)); + } + } + if (os != null) { + try { + os.close(); + } catch (IOException e) { + Log.w(Helper.TAG, e + "\n" + Log.getStackTraceString(e)); + } + } + } + } else { + // Show placeholder icon + int resid = (embedded || data ? R.drawable.baseline_photo_library_24 : R.drawable.baseline_image_24); + Drawable d = context.getResources().getDrawable(resid, context.getTheme()); + d.setBounds(0, 0, px, px); + return d; + } + } }