1package eu.siacs.conversations.ui.adapter;
2
3import android.app.Activity;
4import android.content.Context;
5import android.content.res.Resources;
6import android.databinding.DataBindingUtil;
7import android.graphics.Bitmap;
8import android.graphics.drawable.BitmapDrawable;
9import android.graphics.drawable.Drawable;
10import android.os.AsyncTask;
11import android.support.annotation.AttrRes;
12import android.support.annotation.NonNull;
13import android.support.v7.widget.RecyclerView;
14import android.view.LayoutInflater;
15import android.view.ViewGroup;
16import android.widget.ImageView;
17
18import java.lang.ref.WeakReference;
19import java.util.ArrayList;
20import java.util.Arrays;
21import java.util.List;
22import java.util.concurrent.RejectedExecutionException;
23
24import eu.siacs.conversations.R;
25import eu.siacs.conversations.databinding.MediaPreviewBinding;
26import eu.siacs.conversations.ui.ConversationFragment;
27import eu.siacs.conversations.ui.XmppActivity;
28import eu.siacs.conversations.ui.util.Attachment;
29import eu.siacs.conversations.ui.util.StyledAttributes;
30
31public class MediaPreviewAdapter extends RecyclerView.Adapter<MediaPreviewAdapter.MediaPreviewViewHolder> {
32
33 private static final List<String> DOCUMENT_MIMES = Arrays.asList(
34 "application/pdf",
35 "application/vnd.oasis.opendocument.text",
36 "application/msword",
37 "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
38 "text/x-tex",
39 "text/plain"
40 );
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 LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
54 MediaPreviewBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.media_preview, parent, false);
55 return new MediaPreviewViewHolder(binding);
56 }
57
58 @Override
59 public void onBindViewHolder(@NonNull MediaPreviewViewHolder holder, int position) {
60 final Context context = conversationFragment.getActivity();
61 final Attachment attachment = mediaPreviews.get(position);
62 if (attachment.renderThumbnail()) {
63 holder.binding.mediaPreview.setImageAlpha(255);
64 loadPreview(attachment, holder.binding.mediaPreview);
65 } else {
66 cancelPotentialWork(attachment, holder.binding.mediaPreview);
67 holder.binding.mediaPreview.setBackgroundColor(StyledAttributes.getColor(context, R.attr.color_background_tertiary));
68 holder.binding.mediaPreview.setImageAlpha(Math.round(StyledAttributes.getFloat(context, R.attr.icon_alpha) * 255));
69 final @AttrRes int attr;
70 if (attachment.getType() == Attachment.Type.LOCATION) {
71 attr = R.attr.media_preview_location;
72 } else if (attachment.getType() == Attachment.Type.RECORDING) {
73 attr = R.attr.media_preview_recording;
74 } else {
75 final String mime = attachment.getMime();
76 if (mime == null) {
77 attr = R.attr.media_preview_unknown;
78 } else if (mime.startsWith("audio/")) {
79 attr = R.attr.media_preview_audio;
80 } else if (mime.equals("text/calendar") || (mime.equals("text/x-vcalendar"))) {
81 attr = R.attr.media_preview_calendar;
82 } else if (mime.equals("text/x-vcard")) {
83 attr = R.attr.media_preview_contact;
84 } else if (mime.equals("application/vnd.android.package-archive")) {
85 attr = R.attr.media_preview_app;
86 } else if (mime.equals("application/zip") || mime.equals("application/rar")) {
87 attr = R.attr.media_preview_archive;
88 } else if (DOCUMENT_MIMES.contains(mime)) {
89 attr = R.attr.media_preview_document;
90 } else {
91 attr = R.attr.media_preview_unknown;
92 }
93 }
94 holder.binding.mediaPreview.setImageDrawable(StyledAttributes.getDrawable(context, attr));
95 }
96 holder.binding.deleteButton.setOnClickListener(v -> {
97 int pos = mediaPreviews.indexOf(attachment);
98 mediaPreviews.remove(pos);
99 notifyItemRemoved(pos);
100 conversationFragment.toggleInputMethod();
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 = activity.xmppConnectionService.getFileBackend().getPreviewForUri(attachment,Math.round(activity.getResources().getDimension(R.dimen.media_preview_size)),true);
113 if (bm != null) {
114 cancelPotentialWork(attachment, imageView);
115 imageView.setImageBitmap(bm);
116 imageView.setBackgroundColor(0x00000000);
117 } else {
118 imageView.setBackgroundColor(0xff333333);
119 imageView.setImageDrawable(null);
120 final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
121 final AsyncDrawable asyncDrawable = new AsyncDrawable(conversationFragment.getActivity().getResources(), null, task);
122 imageView.setImageDrawable(asyncDrawable);
123 try {
124 task.execute(attachment);
125 } catch (final RejectedExecutionException ignored) {
126 }
127 }
128 }
129 }
130
131 private static boolean cancelPotentialWork(Attachment attachment, ImageView imageView) {
132 final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
133
134 if (bitmapWorkerTask != null) {
135 final Attachment oldAttachment = bitmapWorkerTask.attachment;
136 if (oldAttachment == null || !oldAttachment.equals(attachment)) {
137 bitmapWorkerTask.cancel(true);
138 } else {
139 return false;
140 }
141 }
142 return true;
143 }
144
145 private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
146 if (imageView != null) {
147 final Drawable drawable = imageView.getDrawable();
148 if (drawable instanceof AsyncDrawable) {
149 final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
150 return asyncDrawable.getBitmapWorkerTask();
151 }
152 }
153 return null;
154 }
155
156 @Override
157 public int getItemCount() {
158 return mediaPreviews.size();
159 }
160
161 public boolean hasAttachments() {
162 return mediaPreviews.size() > 0;
163 }
164
165 public ArrayList<Attachment> getAttachments() {
166 return mediaPreviews;
167 }
168
169 public void clearPreviews() {
170 this.mediaPreviews.clear();
171 }
172
173 class MediaPreviewViewHolder extends RecyclerView.ViewHolder {
174
175 private final MediaPreviewBinding binding;
176
177 MediaPreviewViewHolder(MediaPreviewBinding binding) {
178 super(binding.getRoot());
179 this.binding = binding;
180 }
181 }
182
183 static class AsyncDrawable extends BitmapDrawable {
184 private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
185
186 AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
187 super(res, bitmap);
188 bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
189 }
190
191 BitmapWorkerTask getBitmapWorkerTask() {
192 return bitmapWorkerTaskReference.get();
193 }
194 }
195
196 class BitmapWorkerTask extends AsyncTask<Attachment, Void, Bitmap> {
197 private final WeakReference<ImageView> imageViewReference;
198 private Attachment attachment = null;
199
200 BitmapWorkerTask(ImageView imageView) {
201 imageViewReference = new WeakReference<>(imageView);
202 }
203
204 @Override
205 protected Bitmap doInBackground(Attachment... params) {
206 Activity activity = conversationFragment.getActivity();
207 if (activity instanceof XmppActivity) {
208 final XmppActivity xmppActivity = (XmppActivity) activity;
209 this.attachment = params[0];
210 return xmppActivity.xmppConnectionService.getFileBackend().getPreviewForUri(this.attachment, Math.round(xmppActivity.getResources().getDimension(R.dimen.media_preview_size)), false);
211 } else {
212 return null;
213 }
214 }
215
216 @Override
217 protected void onPostExecute(Bitmap bitmap) {
218 if (bitmap != null && !isCancelled()) {
219 final ImageView imageView = imageViewReference.get();
220 if (imageView != null) {
221 imageView.setImageBitmap(bitmap);
222 imageView.setBackgroundColor(0x00000000);
223 }
224 }
225 }
226 }
227}