From 404c92da9f439aeafff4acec6f2dcd661bdc0dfb Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Sun, 25 Nov 2018 10:01:41 +0100 Subject: [PATCH] Added back async image loading in chat bubbles --- .../linphone/chat/ChatMessageViewHolder.java | 39 ++++- .../java/org/linphone/views/AsyncBitmap.java | 39 +++++ .../org/linphone/views/BitmapWorkerTask.java | 154 ++++++++++++++++++ 3 files changed, 229 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/org/linphone/views/AsyncBitmap.java create mode 100644 app/src/main/java/org/linphone/views/BitmapWorkerTask.java diff --git a/app/src/main/java/org/linphone/chat/ChatMessageViewHolder.java b/app/src/main/java/org/linphone/chat/ChatMessageViewHolder.java index ac3d916a4..cf1629da5 100644 --- a/app/src/main/java/org/linphone/chat/ChatMessageViewHolder.java +++ b/app/src/main/java/org/linphone/chat/ChatMessageViewHolder.java @@ -1,3 +1,5 @@ +package org.linphone.chat; + /* ChatMessageViewHolder.java Copyright (C) 2017 Belledonne Communications, Grenoble, France @@ -17,8 +19,6 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.linphone.chat; - import android.Manifest; import android.content.Context; @@ -27,6 +27,8 @@ import androidx.recyclerview.widget.RecyclerView; import android.content.Intent; import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.net.Uri; import android.text.Spanned; import android.text.method.LinkMovementMethod; @@ -54,6 +56,8 @@ import org.linphone.core.Content; import org.linphone.mediastream.Log; import org.linphone.utils.FileUtils; import org.linphone.utils.LinphoneUtils; +import org.linphone.views.AsyncBitmap; +import org.linphone.views.BitmapWorkerTask; import org.linphone.views.ContactAvatar; import java.io.File; @@ -221,7 +225,7 @@ public class ChatMessageViewHolder extends RecyclerView.ViewHolder implements Vi View v; if (FileUtils.isExtensionImage(filePath)) { v = content.findViewById(R.id.image); - ((ImageView)v).setImageURI(Uri.parse(c.getFilePath())); + loadBitmap(c.getFilePath(), ((ImageView)v)); } else { v = content.findViewById(R.id.file); ((TextView)v).setText(FileUtils.getNameFromFilePath(filePath)); @@ -308,4 +312,33 @@ public class ChatMessageViewHolder extends RecyclerView.ViewHolder implements Vi intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION); mContext.startActivity(intent); } + + private void loadBitmap(String path, ImageView imageView) { + if (cancelPotentialWork(path, imageView)) { + Bitmap defaultBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.chat_file); + BitmapWorkerTask task = new BitmapWorkerTask(mContext, imageView, defaultBitmap); + final AsyncBitmap asyncBitmap = new AsyncBitmap(mContext.getResources(), defaultBitmap, task); + imageView.setImageDrawable(asyncBitmap); + task.execute(path); + } + } + + private boolean cancelPotentialWork(String path, ImageView imageView) { + final BitmapWorkerTask bitmapWorkerTask = BitmapWorkerTask.getBitmapWorkerTask(imageView); + + if (bitmapWorkerTask != null) { + final String bitmapData = bitmapWorkerTask.path; + // If bitmapData is not yet set or it differs from the new data + if (bitmapData == null || bitmapData != path) { + // Cancel previous task + bitmapWorkerTask.cancel(true); + } else { + // The same work is already in progress + return false; + } + } + // No task associated with the ImageView, or an existing task was cancelled + return true; + } + } \ No newline at end of file diff --git a/app/src/main/java/org/linphone/views/AsyncBitmap.java b/app/src/main/java/org/linphone/views/AsyncBitmap.java new file mode 100644 index 000000000..bc66446f9 --- /dev/null +++ b/app/src/main/java/org/linphone/views/AsyncBitmap.java @@ -0,0 +1,39 @@ +package org.linphone.views; + +/* +AsyncBitmap.java +Copyright (C) 2018 Belledonne Communications, Grenoble, France + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; + +import java.lang.ref.WeakReference; + +public class AsyncBitmap extends BitmapDrawable { + private final WeakReference bitmapWorkerTaskReference; + + public AsyncBitmap(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) { + super(res, bitmap); + bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask); + } + + public BitmapWorkerTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } +} \ No newline at end of file diff --git a/app/src/main/java/org/linphone/views/BitmapWorkerTask.java b/app/src/main/java/org/linphone/views/BitmapWorkerTask.java new file mode 100644 index 000000000..bed01989e --- /dev/null +++ b/app/src/main/java/org/linphone/views/BitmapWorkerTask.java @@ -0,0 +1,154 @@ +package org.linphone.views; + +/* +BitmapWorkerTask.java +Copyright (C) 2018 Belledonne Communications, Grenoble, France + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; +import android.graphics.drawable.Drawable; +import android.media.ExifInterface; +import android.net.Uri; +import android.os.AsyncTask; +import android.provider.MediaStore; +import android.widget.ImageView; + +import org.linphone.mediastream.Log; +import org.linphone.utils.FileUtils; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.ref.WeakReference; + +public class BitmapWorkerTask extends AsyncTask { + private Context mContext; + private Bitmap mDefaultBitmap; + + private final WeakReference imageViewReference; + public String path; + + public BitmapWorkerTask(Context context, ImageView imageView, Bitmap defaultBitmap) { + mContext = context; + mDefaultBitmap = defaultBitmap; + path = null; + // Use a WeakReference to ensure the ImageView can be garbage collected + imageViewReference = new WeakReference<>(imageView); + } + + private Bitmap scaleToFitHeight(Bitmap b, int height) { + float factor = height / (float) b.getHeight(); + int dstWidth = (int) (b.getWidth() * factor); + if (dstWidth > 0 && height > 0) { + return Bitmap.createScaledBitmap(b, dstWidth, height, true); + } + return b; + } + + // Decode image in background. + @Override + protected Bitmap doInBackground(String... params) { + path = params[0]; + Bitmap bm = null; + Bitmap thumbnail = null; + if (FileUtils.isExtensionImage(path)) { + if (path.startsWith("content")) { + try { + bm = MediaStore.Images.Media.getBitmap(mContext.getContentResolver(), Uri.parse(path)); + } catch (FileNotFoundException e) { + Log.e(e); + } catch (IOException e) { + Log.e(e); + } + } else { + bm = BitmapFactory.decodeFile(path); + } + + ImageView imageView = imageViewReference.get(); + + try { + // Rotate the bitmap if possible/needed, using EXIF data + Matrix matrix = new Matrix(); + ExifInterface exif = new ExifInterface(path); + int pictureOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0); + if (pictureOrientation == 6 || pictureOrientation == 3 || pictureOrientation == 8) { + if (pictureOrientation == 6) { + matrix.postRotate(90); + } else if (pictureOrientation == 3) { + matrix.postRotate(180); + } else { + matrix.postRotate(270); + } + if (imageView != null) { + if (pictureOrientation == 6 || pictureOrientation == 8) { + matrix.postScale(1, imageView.getMeasuredHeight() / (float) bm.getHeight()); + } else { + matrix.postScale(imageView.getMeasuredHeight() / (float) bm.getHeight(), 1); + } + } + thumbnail = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true); + if (thumbnail != bm) { + bm.recycle(); + bm = null; + } + } + } catch (Exception e) { + Log.e(e); + } + + if (thumbnail == null && bm != null) { + if (imageView == null) return bm; + thumbnail = scaleToFitHeight(bm, imageView.getMeasuredHeight()); + if (thumbnail != bm) { + bm.recycle(); + } + } + return thumbnail; + } else { + return mDefaultBitmap; + } + } + + // Once complete, see if ImageView is still around and set bitmap. + @Override + protected void onPostExecute(Bitmap bitmap) { + if (isCancelled()) { + bitmap.recycle(); + bitmap = null; + } + if (imageViewReference != null && bitmap != null) { + final ImageView imageView = imageViewReference.get(); + final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + if (this == bitmapWorkerTask && imageView != null) { + imageView.setImageBitmap(bitmap); + } + } + } + + public static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { + if (imageView != null) { + final Drawable drawable = imageView.getDrawable(); + if (drawable instanceof AsyncBitmap) { + final AsyncBitmap asyncDrawable = (AsyncBitmap) drawable; + return asyncDrawable.getBitmapWorkerTask(); + } + } + return null; + } +}