MediaPreviewAdapter.java

  1package eu.siacs.conversations.ui.adapter;
  2
  3import android.content.ActivityNotFoundException;
  4import android.content.Context;
  5import android.content.Intent;
  6import android.content.res.Resources;
  7import android.graphics.Bitmap;
  8import android.graphics.drawable.BitmapDrawable;
  9import android.graphics.drawable.Drawable;
 10import android.net.Uri;
 11import android.os.AsyncTask;
 12import android.util.Log;
 13import android.view.LayoutInflater;
 14import android.view.ViewGroup;
 15import android.widget.ImageView;
 16import android.widget.Toast;
 17
 18import androidx.annotation.NonNull;
 19import androidx.core.content.ContextCompat;
 20import androidx.core.widget.ImageViewCompat;
 21import androidx.databinding.DataBindingUtil;
 22import androidx.recyclerview.widget.RecyclerView;
 23
 24import eu.siacs.conversations.Config;
 25import eu.siacs.conversations.R;
 26import eu.siacs.conversations.databinding.ItemMediaPreviewBinding;
 27import eu.siacs.conversations.persistance.FileBackend;
 28import eu.siacs.conversations.ui.ConversationFragment;
 29import eu.siacs.conversations.ui.ShowLocationActivity;
 30import eu.siacs.conversations.ui.XmppActivity;
 31import eu.siacs.conversations.ui.util.Attachment;
 32
 33import java.lang.ref.WeakReference;
 34import java.util.ArrayList;
 35import java.util.List;
 36import java.util.Locale;
 37import java.util.concurrent.RejectedExecutionException;
 38
 39public class MediaPreviewAdapter
 40        extends RecyclerView.Adapter<MediaPreviewAdapter.MediaPreviewViewHolder> {
 41
 42    private final ArrayList<Attachment> mediaPreviews = new ArrayList<>();
 43
 44    private final ConversationFragment conversationFragment;
 45
 46    public MediaPreviewAdapter(ConversationFragment fragment) {
 47        this.conversationFragment = fragment;
 48    }
 49
 50    @NonNull
 51    @Override
 52    public MediaPreviewViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
 53        final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
 54        ItemMediaPreviewBinding binding =
 55                DataBindingUtil.inflate(layoutInflater, R.layout.item_media_preview, parent, false);
 56        return new MediaPreviewViewHolder(binding);
 57    }
 58
 59    @Override
 60    public void onBindViewHolder(@NonNull MediaPreviewViewHolder holder, int position) {
 61        final Context context = conversationFragment.getActivity();
 62        final Attachment attachment = mediaPreviews.get(position);
 63        if (attachment.renderThumbnail()) {
 64            ImageViewCompat.setImageTintList(holder.binding.mediaPreview, null);
 65            loadPreview(attachment, holder.binding.mediaPreview);
 66        } else {
 67            cancelPotentialWork(attachment, holder.binding.mediaPreview);
 68            MediaAdapter.renderPreview(attachment, holder.binding.mediaPreview);
 69        }
 70        holder.binding.deleteButton.setOnClickListener(
 71                v -> {
 72                    final int pos = mediaPreviews.indexOf(attachment);
 73                    mediaPreviews.remove(pos);
 74                    notifyItemRemoved(pos);
 75                    conversationFragment.toggleInputMethod();
 76                });
 77        holder.binding.mediaPreview.setOnClickListener(v -> view(context, attachment));
 78    }
 79
 80    private static void view(final Context context, final Attachment attachment) {
 81        final Intent view = new Intent(Intent.ACTION_VIEW);
 82        if (attachment.getType() == Attachment.Type.LOCATION) {
 83            view.setClass(context, ShowLocationActivity.class);
 84            view.setData(attachment.getUri());
 85        } else {
 86            final Uri uri = FileBackend.getUriForUri(context, attachment.getUri());
 87            view.setDataAndType(uri, attachment.getMime());
 88            view.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
 89        }
 90        try {
 91            context.startActivity(view);
 92        } catch (final ActivityNotFoundException e) {
 93            Toast.makeText(context, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT)
 94                    .show();
 95        } catch (final SecurityException e) {
 96            Toast.makeText(
 97                            context,
 98                            R.string.sharing_application_not_grant_permission,
 99                            Toast.LENGTH_SHORT)
100                    .show();
101        }
102    }
103
104    public void addMediaPreviews(List<Attachment> attachments) {
105        this.mediaPreviews.addAll(attachments);
106        notifyDataSetChanged();
107    }
108
109    private void loadPreview(Attachment attachment, ImageView imageView) {
110        if (cancelPotentialWork(attachment, imageView)) {
111            XmppActivity activity = (XmppActivity) conversationFragment.getActivity();
112            final Bitmap bm =
113                    activity.xmppConnectionService
114                            .getFileBackend()
115                            .getPreviewForUri(
116                                    attachment,
117                                    Math.round(
118                                            activity.getResources()
119                                                    .getDimension(R.dimen.media_preview_size)),
120                                    true);
121            if (bm != null) {
122                cancelPotentialWork(attachment, imageView);
123                imageView.setImageBitmap(bm);
124                imageView.setBackgroundColor(0x00000000);
125            } else {
126                imageView.setBackgroundColor(
127                        ContextCompat.getColor(imageView.getContext(), R.color.gray_800));
128                imageView.setImageDrawable(null);
129                final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
130                final AsyncDrawable asyncDrawable =
131                        new AsyncDrawable(
132                                conversationFragment.getActivity().getResources(), null, task);
133                imageView.setImageDrawable(asyncDrawable);
134                try {
135                    task.execute(attachment);
136                } catch (final RejectedExecutionException ignored) {
137                }
138            }
139        }
140    }
141
142    private static boolean cancelPotentialWork(Attachment attachment, ImageView imageView) {
143        final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
144
145        if (bitmapWorkerTask != null) {
146            final Attachment oldAttachment = bitmapWorkerTask.attachment;
147            if (oldAttachment == null || !oldAttachment.equals(attachment)) {
148                bitmapWorkerTask.cancel(true);
149            } else {
150                return false;
151            }
152        }
153        return true;
154    }
155
156    private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
157        if (imageView != null) {
158            final Drawable drawable = imageView.getDrawable();
159            if (drawable instanceof AsyncDrawable asyncDrawable) {
160                return asyncDrawable.getBitmapWorkerTask();
161            }
162        }
163        return null;
164    }
165
166    @Override
167    public int getItemCount() {
168        return mediaPreviews.size();
169    }
170
171    public boolean hasAttachments() {
172        return !mediaPreviews.isEmpty();
173    }
174
175    public ArrayList<Attachment> getAttachments() {
176        return mediaPreviews;
177    }
178
179    public void clearPreviews() {
180        this.mediaPreviews.clear();
181    }
182
183    static class MediaPreviewViewHolder extends RecyclerView.ViewHolder {
184
185        private final ItemMediaPreviewBinding binding;
186
187        MediaPreviewViewHolder(ItemMediaPreviewBinding binding) {
188            super(binding.getRoot());
189            this.binding = binding;
190        }
191    }
192
193    static class AsyncDrawable extends BitmapDrawable {
194        private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
195
196        AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
197            super(res, bitmap);
198            bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
199        }
200
201        BitmapWorkerTask getBitmapWorkerTask() {
202            return bitmapWorkerTaskReference.get();
203        }
204    }
205
206    private static class BitmapWorkerTask extends AsyncTask<Attachment, Void, Bitmap> {
207        private final WeakReference<ImageView> imageViewReference;
208        private Attachment attachment = null;
209
210        BitmapWorkerTask(ImageView imageView) {
211            imageViewReference = new WeakReference<>(imageView);
212        }
213
214        @Override
215        protected Bitmap doInBackground(Attachment... params) {
216            this.attachment = params[0];
217            final XmppActivity activity = XmppActivity.find(imageViewReference);
218            if (activity == null) {
219                return null;
220            }
221            return activity.xmppConnectionService
222                    .getFileBackend()
223                    .getPreviewForUri(
224                            this.attachment,
225                            Math.round(
226                                    activity.getResources()
227                                            .getDimension(R.dimen.media_preview_size)),
228                            false);
229        }
230
231        @Override
232        protected void onPostExecute(Bitmap bitmap) {
233            if (bitmap != null && !isCancelled()) {
234                final ImageView imageView = imageViewReference.get();
235                if (imageView != null) {
236                    imageView.setImageBitmap(bitmap);
237                    imageView.setBackgroundColor(0x00000000);
238                }
239            }
240        }
241    }
242}