diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index 9805af0b95abdac2dd138a3f1776745ea1c88523..5898d7a5a6e7668ec5b0eb44d1b758ca1954bbd9 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -2,11 +2,15 @@ package eu.siacs.conversations.persistance; import android.content.ContentResolver; import android.content.Context; +import android.content.res.Resources; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.ImageDecoder; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.RectF; @@ -914,10 +918,10 @@ public class FileBackend { } } - public Bitmap getThumbnail(Message message, int size, boolean cacheOnly) throws IOException { + public Drawable getThumbnail(Message message, Resources res, int size, boolean cacheOnly) throws IOException { final String uuid = message.getUuid(); - final LruCache cache = mXmppConnectionService.getBitmapCache(); - Bitmap thumbnail = cache.get(uuid); + final LruCache cache = mXmppConnectionService.getDrawableCache(); + Drawable thumbnail = cache.get(uuid); if ((thumbnail == null) && (!cacheOnly)) { synchronized (THUMBNAIL_LOCK) { thumbnail = cache.get(uuid); @@ -927,27 +931,14 @@ public class FileBackend { DownloadableFile file = getFile(message); final String mime = file.getMimeType(); if ("application/pdf".equals(mime) && Compatibility.runsTwentyOne()) { - thumbnail = getPdfDocumentPreview(file, size); + thumbnail = new BitmapDrawable(res, getPdfDocumentPreview(file, size)); } else if (mime.startsWith("video/")) { - thumbnail = getVideoPreview(file, size); + thumbnail = new BitmapDrawable(res, getVideoPreview(file, size)); } else { - final Bitmap fullSize = getFullSizeImagePreview(file, size); - if (fullSize == null) { + thumbnail = getImagePreview(file, res, size, mime); + if (thumbnail == null) { throw new FileNotFoundException(); } - thumbnail = resize(fullSize, size); - 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); - thumbnail.recycle(); - thumbnail = withGifOverlay; - } } cache.put(uuid, thumbnail); } @@ -955,17 +946,74 @@ public class FileBackend { return thumbnail; } - private Bitmap getFullSizeImagePreview(File file, int size) { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inSampleSize = calcSampleSize(file, size); - try { - return BitmapFactory.decodeFile(file.getAbsolutePath(), options); - } catch (OutOfMemoryError e) { - options.inSampleSize *= 2; - return BitmapFactory.decodeFile(file.getAbsolutePath(), options); + public Bitmap getThumbnailBitmap(Message message, Resources res, int size, boolean cacheOnly) throws IOException { + final String uuid = message.getUuid(); + final LruCache cache = mXmppConnectionService.getBitmapCache(); + Bitmap thumbnail = cache.get(uuid); + if ((thumbnail == null) && (!cacheOnly)) { + final Drawable drawable = getThumbnail(message, res, size, cacheOnly); + if (drawable != null) { + thumbnail = drawDrawable(drawable); + cache.put(uuid, thumbnail); + } + } + return thumbnail; + } + + private Drawable getImagePreview(File file, Resources res, int size, final String mime) throws IOException { + if (android.os.Build.VERSION.SDK_INT >= 28) { + ImageDecoder.Source source = ImageDecoder.createSource(file); + return ImageDecoder.decodeDrawable(source, (decoder, info, src) -> { + int w = info.getSize().getWidth(); + int h = info.getSize().getHeight(); + int scalledW; + int scalledH; + if (w <= h) { + scalledW = Math.max((int) (w / ((double) h / size)), 1); + scalledH = size; + } else { + scalledW = size; + scalledH = Math.max((int) (h / ((double) w / size)), 1); + } + decoder.setTargetSize(scalledW, scalledH); + }); + } else { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = calcSampleSize(file, size); + Bitmap bitmap = null; + try { + bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options); + } catch (OutOfMemoryError e) { + options.inSampleSize *= 2; + bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options); + } + bitmap = resize(bitmap, size); + bitmap = rotate(bitmap, getRotation(file)); + if (mime.equals("image/gif")) { + 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); + bitmap.recycle(); + bitmap = withGifOverlay; + } + return new BitmapDrawable(res, bitmap); } } + protected Bitmap drawDrawable(Drawable drawable) { + Bitmap bitmap = null; + + if (drawable instanceof BitmapDrawable) { + bitmap = ((BitmapDrawable) drawable).getBitmap(); + if (bitmap != null) return bitmap; + } + + bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return bitmap; + } + private void drawOverlay(Bitmap bitmap, int resource, float factor) { Bitmap overlay = BitmapFactory.decodeResource(mXmppConnectionService.getResources(), resource); diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index 3fb362d80ec3f2827f1ebbfacd34215c0a913143..bf90c8b5679628be3d9d84939ee0b193bf422b8e 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -1062,7 +1062,7 @@ public class NotificationService { private void modifyForImage(final Builder builder, final Message message, final ArrayList messages) { try { - final Bitmap bitmap = mXmppConnectionService.getFileBackend().getThumbnail(message, getPixel(288), false); + final Bitmap bitmap = mXmppConnectionService.getFileBackend().getThumbnailBitmap(message, mXmppConnectionService.getResources(), getPixel(288), false); final ArrayList tmp = new ArrayList<>(); for (final Message msg : messages) { if (msg.getType() == Message.TYPE_TEXT diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index a1f855158f5a864b375bf1488b6f898fb82b41ed..1b2cc7a26da93de2d892f71a5aa2b9ea01e874f3 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -18,6 +18,8 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.database.ContentObserver; import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.net.ConnectivityManager; import android.net.Network; @@ -486,6 +488,7 @@ public class XmppConnectionService extends Service { private PgpEngine mPgpEngine = null; private WakeLock wakeLock; private LruCache mBitmapCache; + private LruCache mDrawableCache; private final BroadcastReceiver mInternalEventReceiver = new InternalEventReceiver(); private final BroadcastReceiver mInternalScreenEventReceiver = new InternalEventReceiver(); @@ -1151,13 +1154,23 @@ public class XmppConnectionService extends Service { this.mRandom = new SecureRandom(); updateMemorizingTrustmanager(); final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); - final int cacheSize = maxMemory / 8; + final int cacheSize = maxMemory / 9; this.mBitmapCache = new LruCache(cacheSize) { @Override protected int sizeOf(final String key, final Bitmap bitmap) { return bitmap.getByteCount() / 1024; } }; + this.mDrawableCache = new LruCache(cacheSize) { + @Override + protected int sizeOf(final String key, final Drawable drawable) { + if (drawable instanceof BitmapDrawable) { + return ((BitmapDrawable) drawable).getBitmap().getByteCount() / 1024; + } else { + return drawable.getIntrinsicWidth() * drawable.getIntrinsicHeight() * 40 / 1024; + } + } + }; if (mLastActivity == 0) { mLastActivity = getPreferences().getLong(SETTING_LAST_ACTIVITY_TS, System.currentTimeMillis()); } @@ -4327,6 +4340,10 @@ public class XmppConnectionService extends Service { return this.mBitmapCache; } + public LruCache getDrawableCache() { + return this.mDrawableCache; + } + public Collection getKnownHosts() { final Set hosts = new HashSet<>(); for (final Account account : getAccounts()) { diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 4ca49fa500d1f8bb6e84b433d41e14c4504d588c..613ccbbce354c3ce17e388b944f7351a83667279 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -22,6 +22,7 @@ import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Point; +import android.graphics.drawable.AnimatedImageDrawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.ConnectivityManager; @@ -876,16 +877,19 @@ public abstract class XmppActivity extends ActionBarActivity { } public void loadBitmap(Message message, ImageView imageView) { - Bitmap bm; + Drawable bm; try { - bm = xmppConnectionService.getFileBackend().getThumbnail(message, (int) (metrics.density * 288), true); + bm = xmppConnectionService.getFileBackend().getThumbnail(message, getResources(), (int) (metrics.density * 288), true); } catch (IOException e) { bm = null; } if (bm != null) { cancelPotentialWork(message, imageView); - imageView.setImageBitmap(bm); + imageView.setImageDrawable(bm); imageView.setBackgroundColor(0x00000000); + if (Build.VERSION.SDK_INT >= 28 && bm instanceof AnimatedImageDrawable) { + ((AnimatedImageDrawable) bm).start(); + } } else { if (cancelPotentialWork(message, imageView)) { imageView.setBackgroundColor(0xff333333); @@ -939,7 +943,7 @@ public abstract class XmppActivity extends ActionBarActivity { } } - static class BitmapWorkerTask extends AsyncTask { + static class BitmapWorkerTask extends AsyncTask { private final WeakReference imageViewReference; private Message message = null; @@ -948,7 +952,7 @@ public abstract class XmppActivity extends ActionBarActivity { } @Override - protected Bitmap doInBackground(Message... params) { + protected Drawable doInBackground(Message... params) { if (isCancelled()) { return null; } @@ -956,7 +960,7 @@ public abstract class XmppActivity extends ActionBarActivity { try { final XmppActivity activity = find(imageViewReference); if (activity != null && activity.xmppConnectionService != null) { - return activity.xmppConnectionService.getFileBackend().getThumbnail(message, (int) (activity.metrics.density * 288), false); + return activity.xmppConnectionService.getFileBackend().getThumbnail(message, imageViewReference.get().getContext().getResources(), (int) (activity.metrics.density * 288), false); } else { return null; } @@ -966,12 +970,15 @@ public abstract class XmppActivity extends ActionBarActivity { } @Override - protected void onPostExecute(final Bitmap bitmap) { + protected void onPostExecute(final Drawable drawable) { if (!isCancelled()) { final ImageView imageView = imageViewReference.get(); if (imageView != null) { - imageView.setImageBitmap(bitmap); - imageView.setBackgroundColor(bitmap == null ? 0xff333333 : 0x00000000); + imageView.setImageDrawable(drawable); + imageView.setBackgroundColor(drawable == null ? 0xff333333 : 0x00000000); + if (Build.VERSION.SDK_INT >= 28 && drawable instanceof AnimatedImageDrawable) { + ((AnimatedImageDrawable) drawable).start(); + } } } }