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