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}