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