1package eu.siacs.conversations.ui.adapter;
2
3import android.content.Context;
4import android.content.res.Resources;
5import android.databinding.DataBindingUtil;
6import android.graphics.Bitmap;
7import android.graphics.drawable.BitmapDrawable;
8import android.graphics.drawable.Drawable;
9import android.os.AsyncTask;
10import android.support.annotation.AttrRes;
11import android.support.annotation.DimenRes;
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.MediaBinding;
26import eu.siacs.conversations.ui.XmppActivity;
27import eu.siacs.conversations.ui.util.Attachment;
28import eu.siacs.conversations.ui.util.StyledAttributes;
29import eu.siacs.conversations.ui.util.ViewUtil;
30
31public class MediaAdapter extends RecyclerView.Adapter<MediaAdapter.MediaViewHolder> {
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> attachments = new ArrayList<>();
43
44 private final XmppActivity activity;
45
46 private int mediaSize = 0;
47
48 public MediaAdapter(XmppActivity activity, @DimenRes int mediaSize) {
49 this.activity = activity;
50 this.mediaSize = Math.round(activity.getResources().getDimension(mediaSize));
51 }
52
53 public static void setMediaSize(RecyclerView recyclerView, int mediaSize) {
54 RecyclerView.Adapter adapter = recyclerView.getAdapter();
55 if (adapter instanceof MediaAdapter) {
56 ((MediaAdapter) adapter).setMediaSize(mediaSize);
57 }
58 }
59
60 private static @AttrRes int getImageAttr(Attachment attachment) {
61 final @AttrRes int attr;
62 if (attachment.getType() == Attachment.Type.LOCATION) {
63 attr = R.attr.media_preview_location;
64 } else if (attachment.getType() == Attachment.Type.RECORDING) {
65 attr = R.attr.media_preview_recording;
66 } else {
67 final String mime = attachment.getMime();
68 if (mime == null) {
69 attr = R.attr.media_preview_unknown;
70 } else if (mime.startsWith("audio/")) {
71 attr = R.attr.media_preview_audio;
72 } else if (mime.equals("text/calendar") || (mime.equals("text/x-vcalendar"))) {
73 attr = R.attr.media_preview_calendar;
74 } else if (mime.equals("text/x-vcard")) {
75 attr = R.attr.media_preview_contact;
76 } else if (mime.equals("application/vnd.android.package-archive")) {
77 attr = R.attr.media_preview_app;
78 } else if (mime.equals("application/zip") || mime.equals("application/rar")) {
79 attr = R.attr.media_preview_archive;
80 } else if (mime.equals("application/epub+zip") || mime.equals("application/vnd.amazon.mobi8-ebook")) {
81 attr = R.attr.media_preview_ebook;
82 } else if (DOCUMENT_MIMES.contains(mime)) {
83 attr = R.attr.media_preview_document;
84 } else {
85 attr = R.attr.media_preview_unknown;
86 }
87 }
88 return attr;
89 }
90
91 static void renderPreview(Context context, Attachment attachment, ImageView imageView) {
92 imageView.setBackgroundColor(StyledAttributes.getColor(context, R.attr.color_background_tertiary));
93 imageView.setImageAlpha(Math.round(StyledAttributes.getFloat(context, R.attr.icon_alpha) * 255));
94 imageView.setImageDrawable(StyledAttributes.getDrawable(context, getImageAttr(attachment)));
95 }
96
97 private static boolean cancelPotentialWork(Attachment attachment, ImageView imageView) {
98 final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
99
100 if (bitmapWorkerTask != null) {
101 final Attachment oldAttachment = bitmapWorkerTask.attachment;
102 if (oldAttachment == null || !oldAttachment.equals(attachment)) {
103 bitmapWorkerTask.cancel(true);
104 } else {
105 return false;
106 }
107 }
108 return true;
109 }
110
111 private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
112 if (imageView != null) {
113 final Drawable drawable = imageView.getDrawable();
114 if (drawable instanceof AsyncDrawable) {
115 final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
116 return asyncDrawable.getBitmapWorkerTask();
117 }
118 }
119 return null;
120 }
121
122 @NonNull
123 @Override
124 public MediaViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
125 LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
126 MediaBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.media, parent, false);
127 return new MediaViewHolder(binding);
128 }
129
130 @Override
131 public void onBindViewHolder(@NonNull MediaViewHolder holder, int position) {
132 final Attachment attachment = attachments.get(position);
133 if (attachment.renderThumbnail()) {
134 holder.binding.media.setImageAlpha(255);
135 loadPreview(attachment, holder.binding.media);
136 } else {
137 cancelPotentialWork(attachment, holder.binding.media);
138 renderPreview(activity, attachment, holder.binding.media);
139 }
140 holder.binding.media.setOnClickListener(v -> ViewUtil.view(activity, attachment));
141 }
142
143 public void setAttachments(List<Attachment> attachments) {
144 this.attachments.clear();
145 this.attachments.addAll(attachments);
146 notifyDataSetChanged();
147 }
148
149 private void setMediaSize(int mediaSize) {
150 this.mediaSize = mediaSize;
151 }
152
153 private void loadPreview(Attachment attachment, ImageView imageView) {
154 if (cancelPotentialWork(attachment, imageView)) {
155 final Bitmap bm = activity.xmppConnectionService.getFileBackend().getPreviewForUri(attachment,mediaSize,true);
156 if (bm != null) {
157 cancelPotentialWork(attachment, imageView);
158 imageView.setImageBitmap(bm);
159 imageView.setBackgroundColor(0x00000000);
160 } else {
161 imageView.setBackgroundColor(0xff333333);
162 imageView.setImageDrawable(null);
163 final BitmapWorkerTask task = new BitmapWorkerTask(mediaSize, imageView);
164 final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task);
165 imageView.setImageDrawable(asyncDrawable);
166 try {
167 task.execute(attachment);
168 } catch (final RejectedExecutionException ignored) {
169 }
170 }
171 }
172 }
173
174 @Override
175 public int getItemCount() {
176 return attachments.size();
177 }
178
179 static class AsyncDrawable extends BitmapDrawable {
180 private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
181
182 AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
183 super(res, bitmap);
184 bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
185 }
186
187 BitmapWorkerTask getBitmapWorkerTask() {
188 return bitmapWorkerTaskReference.get();
189 }
190 }
191
192 class MediaViewHolder extends RecyclerView.ViewHolder {
193
194 private final MediaBinding binding;
195
196 MediaViewHolder(MediaBinding binding) {
197 super(binding.getRoot());
198 this.binding = binding;
199 }
200 }
201
202 private static class BitmapWorkerTask extends AsyncTask<Attachment, Void, Bitmap> {
203 private final WeakReference<ImageView> imageViewReference;
204 private Attachment attachment = null;
205 private final int mediaSize;
206
207 BitmapWorkerTask(int mediaSize, ImageView imageView) {
208 this.mediaSize = mediaSize;
209 imageViewReference = new WeakReference<>(imageView);
210 }
211
212 @Override
213 protected Bitmap doInBackground(Attachment... params) {
214 this.attachment = params[0];
215 final XmppActivity activity = XmppActivity.find(imageViewReference);
216 if (activity == null) {
217 return null;
218 }
219 return activity.xmppConnectionService.getFileBackend().getPreviewForUri(this.attachment, mediaSize, 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}