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 (final ActivityNotFoundException e) {
79 Toast.makeText(context, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT).show();
80 } catch (final SecurityException e) {
81 Toast.makeText(context, R.string.sharing_application_not_grant_permission, Toast.LENGTH_SHORT).show();
82 }
83 }
84
85 public void addMediaPreviews(List<Attachment> attachments) {
86 this.mediaPreviews.addAll(attachments);
87 notifyDataSetChanged();
88 }
89
90 private void loadPreview(Attachment attachment, ImageView imageView) {
91 if (cancelPotentialWork(attachment, imageView)) {
92 XmppActivity activity = (XmppActivity) conversationFragment.getActivity();
93 final Bitmap bm = activity.xmppConnectionService.getFileBackend().getPreviewForUri(attachment,Math.round(activity.getResources().getDimension(R.dimen.media_preview_size)),true);
94 if (bm != null) {
95 cancelPotentialWork(attachment, imageView);
96 imageView.setImageBitmap(bm);
97 imageView.setBackgroundColor(0x00000000);
98 } else {
99 imageView.setBackgroundColor(0xff333333);
100 imageView.setImageDrawable(null);
101 final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
102 final AsyncDrawable asyncDrawable = new AsyncDrawable(conversationFragment.getActivity().getResources(), null, task);
103 imageView.setImageDrawable(asyncDrawable);
104 try {
105 task.execute(attachment);
106 } catch (final RejectedExecutionException ignored) {
107 }
108 }
109 }
110 }
111
112 private static boolean cancelPotentialWork(Attachment attachment, ImageView imageView) {
113 final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
114
115 if (bitmapWorkerTask != null) {
116 final Attachment oldAttachment = bitmapWorkerTask.attachment;
117 if (oldAttachment == null || !oldAttachment.equals(attachment)) {
118 bitmapWorkerTask.cancel(true);
119 } else {
120 return false;
121 }
122 }
123 return true;
124 }
125
126 private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
127 if (imageView != null) {
128 final Drawable drawable = imageView.getDrawable();
129 if (drawable instanceof AsyncDrawable) {
130 final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
131 return asyncDrawable.getBitmapWorkerTask();
132 }
133 }
134 return null;
135 }
136
137 @Override
138 public int getItemCount() {
139 return mediaPreviews.size();
140 }
141
142 public boolean hasAttachments() {
143 return mediaPreviews.size() > 0;
144 }
145
146 public ArrayList<Attachment> getAttachments() {
147 return mediaPreviews;
148 }
149
150 public void clearPreviews() {
151 this.mediaPreviews.clear();
152 }
153
154 class MediaPreviewViewHolder extends RecyclerView.ViewHolder {
155
156 private final MediaPreviewBinding binding;
157
158 MediaPreviewViewHolder(MediaPreviewBinding binding) {
159 super(binding.getRoot());
160 this.binding = binding;
161 }
162 }
163
164 static class AsyncDrawable extends BitmapDrawable {
165 private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
166
167 AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
168 super(res, bitmap);
169 bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
170 }
171
172 BitmapWorkerTask getBitmapWorkerTask() {
173 return bitmapWorkerTaskReference.get();
174 }
175 }
176
177 private static class BitmapWorkerTask extends AsyncTask<Attachment, Void, Bitmap> {
178 private final WeakReference<ImageView> imageViewReference;
179 private Attachment attachment = null;
180
181 BitmapWorkerTask(ImageView imageView) {
182 imageViewReference = new WeakReference<>(imageView);
183 }
184
185 @Override
186 protected Bitmap doInBackground(Attachment... params) {
187 this.attachment = params[0];
188 final XmppActivity activity = XmppActivity.find(imageViewReference);
189 if (activity == null) {
190 return null;
191 }
192 return activity.xmppConnectionService.getFileBackend().getPreviewForUri(this.attachment, Math.round(activity.getResources().getDimension(R.dimen.media_preview_size)), false);
193 }
194
195 @Override
196 protected void onPostExecute(Bitmap bitmap) {
197 if (bitmap != null && !isCancelled()) {
198 final ImageView imageView = imageViewReference.get();
199 if (imageView != null) {
200 imageView.setImageBitmap(bitmap);
201 imageView.setBackgroundColor(0x00000000);
202 }
203 }
204 }
205 }
206}