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 (DOCUMENT_MIMES.contains(mime)) {
81 attr = R.attr.media_preview_document;
82 } else {
83 attr = R.attr.media_preview_unknown;
84 }
85 }
86 return attr;
87 }
88
89 public static void renderPreview(Context context, Attachment attachment, ImageView imageView) {
90 imageView.setBackgroundColor(StyledAttributes.getColor(context, R.attr.color_background_tertiary));
91 imageView.setImageAlpha(Math.round(StyledAttributes.getFloat(context, R.attr.icon_alpha) * 255));
92 imageView.setImageDrawable(StyledAttributes.getDrawable(context, getImageAttr(attachment)));
93 }
94
95 private static boolean cancelPotentialWork(Attachment attachment, ImageView imageView) {
96 final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
97
98 if (bitmapWorkerTask != null) {
99 final Attachment oldAttachment = bitmapWorkerTask.attachment;
100 if (oldAttachment == null || !oldAttachment.equals(attachment)) {
101 bitmapWorkerTask.cancel(true);
102 } else {
103 return false;
104 }
105 }
106 return true;
107 }
108
109 private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
110 if (imageView != null) {
111 final Drawable drawable = imageView.getDrawable();
112 if (drawable instanceof AsyncDrawable) {
113 final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
114 return asyncDrawable.getBitmapWorkerTask();
115 }
116 }
117 return null;
118 }
119
120 @NonNull
121 @Override
122 public MediaViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
123 LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
124 MediaBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.media, parent, false);
125 return new MediaViewHolder(binding);
126 }
127
128 @Override
129 public void onBindViewHolder(@NonNull MediaViewHolder holder, int position) {
130 final Attachment attachment = attachments.get(position);
131 if (attachment.renderThumbnail()) {
132 holder.binding.media.setImageAlpha(255);
133 loadPreview(attachment, holder.binding.media);
134 } else {
135 cancelPotentialWork(attachment, holder.binding.media);
136 renderPreview(activity, attachment, holder.binding.media);
137 }
138 holder.binding.media.setOnClickListener(v -> ViewUtil.view(activity, attachment));
139 }
140
141 public void setAttachments(List<Attachment> attachments) {
142 this.attachments.clear();
143 this.attachments.addAll(attachments);
144 notifyDataSetChanged();
145 }
146
147 private void setMediaSize(int mediaSize) {
148 this.mediaSize = mediaSize;
149 }
150
151 private void loadPreview(Attachment attachment, ImageView imageView) {
152 if (cancelPotentialWork(attachment, imageView)) {
153 final Bitmap bm = activity.xmppConnectionService.getFileBackend().getPreviewForUri(attachment,mediaSize,true);
154 if (bm != null) {
155 cancelPotentialWork(attachment, imageView);
156 imageView.setImageBitmap(bm);
157 imageView.setBackgroundColor(0x00000000);
158 } else {
159 imageView.setBackgroundColor(0xff333333);
160 imageView.setImageDrawable(null);
161 final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
162 final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task);
163 imageView.setImageDrawable(asyncDrawable);
164 try {
165 task.execute(attachment);
166 } catch (final RejectedExecutionException ignored) {
167 }
168 }
169 }
170 }
171
172 @Override
173 public int getItemCount() {
174 return attachments.size();
175 }
176
177 static class AsyncDrawable extends BitmapDrawable {
178 private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
179
180 AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
181 super(res, bitmap);
182 bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
183 }
184
185 BitmapWorkerTask getBitmapWorkerTask() {
186 return bitmapWorkerTaskReference.get();
187 }
188 }
189
190 class MediaViewHolder extends RecyclerView.ViewHolder {
191
192 private final MediaBinding binding;
193
194 MediaViewHolder(MediaBinding binding) {
195 super(binding.getRoot());
196 this.binding = binding;
197 }
198 }
199
200 class BitmapWorkerTask extends AsyncTask<Attachment, Void, Bitmap> {
201 private final WeakReference<ImageView> imageViewReference;
202 private Attachment attachment = null;
203
204 BitmapWorkerTask(ImageView imageView) {
205 imageViewReference = new WeakReference<>(imageView);
206 }
207
208 @Override
209 protected Bitmap doInBackground(Attachment... params) {
210 this.attachment = params[0];
211 return activity.xmppConnectionService.getFileBackend().getPreviewForUri(this.attachment, mediaSize, false);
212 }
213
214 @Override
215 protected void onPostExecute(Bitmap bitmap) {
216 if (bitmap != null && !isCancelled()) {
217 final ImageView imageView = imageViewReference.get();
218 if (imageView != null) {
219 imageView.setImageBitmap(bitmap);
220 imageView.setBackgroundColor(0x00000000);
221 }
222 }
223 }
224 }
225}