1package eu.siacs.conversations.ui.adapter;
2
3import android.content.ActivityNotFoundException;
4import android.content.Intent;
5import android.content.pm.PackageManager;
6import android.content.pm.ResolveInfo;
7import android.content.res.Resources;
8import android.graphics.Bitmap;
9import android.graphics.Typeface;
10import android.graphics.drawable.BitmapDrawable;
11import android.graphics.drawable.Drawable;
12import android.net.Uri;
13import android.os.AsyncTask;
14import android.text.Spannable;
15import android.text.SpannableString;
16import android.text.Spanned;
17import android.text.style.ForegroundColorSpan;
18import android.text.style.RelativeSizeSpan;
19import android.text.style.StyleSpan;
20import android.text.util.Linkify;
21import android.util.DisplayMetrics;
22import android.util.Patterns;
23import android.view.View;
24import android.view.View.OnClickListener;
25import android.view.View.OnLongClickListener;
26import android.view.ViewGroup;
27import android.widget.ArrayAdapter;
28import android.widget.Button;
29import android.widget.ImageView;
30import android.widget.LinearLayout;
31import android.widget.TextView;
32import android.widget.Toast;
33
34import java.lang.ref.WeakReference;
35import java.net.URL;
36import java.util.List;
37import java.util.concurrent.RejectedExecutionException;
38import java.util.regex.MatchResult;
39import java.util.regex.Matcher;
40import java.util.regex.Pattern;
41
42import eu.siacs.conversations.R;
43import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
44import eu.siacs.conversations.entities.Account;
45import eu.siacs.conversations.entities.Conversation;
46import eu.siacs.conversations.entities.DownloadableFile;
47import eu.siacs.conversations.entities.Message;
48import eu.siacs.conversations.entities.Message.FileParams;
49import eu.siacs.conversations.entities.Transferable;
50import eu.siacs.conversations.ui.ConversationActivity;
51import eu.siacs.conversations.utils.CryptoHelper;
52import eu.siacs.conversations.utils.GeoHelper;
53import eu.siacs.conversations.utils.UIHelper;
54
55public class MessageAdapter extends ArrayAdapter<Message> {
56
57 private static final int SENT = 0;
58 private static final int RECEIVED = 1;
59 private static final int STATUS = 2;
60 private static final Pattern XMPP_PATTERN = Pattern
61 .compile("xmpp\\:(?:(?:["
62 + Patterns.GOOD_IRI_CHAR
63 + "\\;\\/\\?\\@\\&\\=\\#\\~\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])"
64 + "|(?:\\%[a-fA-F0-9]{2}))+");
65
66 private ConversationActivity activity;
67
68 private DisplayMetrics metrics;
69
70 private OnContactPictureClicked mOnContactPictureClickedListener;
71 private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
72
73 private OnLongClickListener openContextMenu = new OnLongClickListener() {
74
75 @Override
76 public boolean onLongClick(View v) {
77 v.showContextMenu();
78 return true;
79 }
80 };
81 private boolean mIndicateReceived = false;
82 private boolean mUseWhiteBackground = false;
83
84 public MessageAdapter(ConversationActivity activity, List<Message> messages) {
85 super(activity, 0, messages);
86 this.activity = activity;
87 metrics = getContext().getResources().getDisplayMetrics();
88 updatePreferences();
89 }
90
91 public void setOnContactPictureClicked(OnContactPictureClicked listener) {
92 this.mOnContactPictureClickedListener = listener;
93 }
94
95 public void setOnContactPictureLongClicked(
96 OnContactPictureLongClicked listener) {
97 this.mOnContactPictureLongClickedListener = listener;
98 }
99
100 @Override
101 public int getViewTypeCount() {
102 return 3;
103 }
104
105 public int getItemViewType(Message message) {
106 if (message.getType() == Message.TYPE_STATUS) {
107 return STATUS;
108 } else if (message.getStatus() <= Message.STATUS_RECEIVED) {
109 return RECEIVED;
110 }
111
112 return SENT;
113 }
114
115 @Override
116 public int getItemViewType(int position) {
117 return this.getItemViewType(getItem(position));
118 }
119
120 private int getMessageTextColor(boolean onDark, boolean primary) {
121 if (onDark) {
122 return activity.getResources().getColor(primary ? R.color.white : R.color.white70);
123 } else {
124 return activity.getResources().getColor(primary ? R.color.black87 : R.color.black54);
125 }
126 }
127
128 private void displayStatus(ViewHolder viewHolder, Message message, int type, boolean darkBackground) {
129 String filesize = null;
130 String info = null;
131 boolean error = false;
132 if (viewHolder.indicatorReceived != null) {
133 viewHolder.indicatorReceived.setVisibility(View.GONE);
134 }
135
136 if (viewHolder.edit_indicator != null) {
137 if (message.edited()) {
138 viewHolder.edit_indicator.setVisibility(View.VISIBLE);
139 viewHolder.edit_indicator.setImageResource(darkBackground ? R.drawable.ic_mode_edit_white_18dp : R.drawable.ic_mode_edit_black_18dp);
140 viewHolder.edit_indicator.setAlpha(darkBackground ? 0.7f : 0.57f);
141 } else {
142 viewHolder.edit_indicator.setVisibility(View.GONE);
143 }
144 }
145 boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
146 && message.getMergedStatus() <= Message.STATUS_RECEIVED;
147 if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getTransferable() != null) {
148 FileParams params = message.getFileParams();
149 if (params.size > (1.5 * 1024 * 1024)) {
150 filesize = params.size / (1024 * 1024)+ " MiB";
151 } else if (params.size > 0) {
152 filesize = params.size / 1024 + " KiB";
153 }
154 if (message.getTransferable() != null && message.getTransferable().getStatus() == Transferable.STATUS_FAILED) {
155 error = true;
156 }
157 }
158 switch (message.getMergedStatus()) {
159 case Message.STATUS_WAITING:
160 info = getContext().getString(R.string.waiting);
161 break;
162 case Message.STATUS_UNSEND:
163 Transferable d = message.getTransferable();
164 if (d!=null) {
165 info = getContext().getString(R.string.sending_file,d.getProgress());
166 } else {
167 info = getContext().getString(R.string.sending);
168 }
169 break;
170 case Message.STATUS_OFFERED:
171 info = getContext().getString(R.string.offering);
172 break;
173 case Message.STATUS_SEND_RECEIVED:
174 if (mIndicateReceived) {
175 viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
176 }
177 break;
178 case Message.STATUS_SEND_DISPLAYED:
179 if (mIndicateReceived) {
180 viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
181 }
182 break;
183 case Message.STATUS_SEND_FAILED:
184 info = getContext().getString(R.string.send_failed);
185 error = true;
186 break;
187 default:
188 if (multiReceived) {
189 info = UIHelper.getMessageDisplayName(message);
190 }
191 break;
192 }
193 if (error && type == SENT) {
194 viewHolder.time.setTextColor(activity.getWarningTextColor());
195 } else {
196 viewHolder.time.setTextColor(this.getMessageTextColor(darkBackground,false));
197 }
198 if (message.getEncryption() == Message.ENCRYPTION_NONE) {
199 viewHolder.indicator.setVisibility(View.GONE);
200 } else {
201 viewHolder.indicator.setImageResource(darkBackground ? R.drawable.ic_lock_white_18dp : R.drawable.ic_lock_black_18dp);
202 viewHolder.indicator.setVisibility(View.VISIBLE);
203 if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
204 XmppAxolotlSession.Trust trust = message.getConversation()
205 .getAccount().getAxolotlService().getFingerprintTrust(
206 message.getFingerprint());
207
208 if(trust == null || (!trust.trusted() && !trust.trustedInactive())) {
209 viewHolder.indicator.setColorFilter(activity.getWarningTextColor());
210 viewHolder.indicator.setAlpha(1.0f);
211 } else {
212 viewHolder.indicator.clearColorFilter();
213 if (darkBackground) {
214 viewHolder.indicator.setAlpha(0.7f);
215 } else {
216 viewHolder.indicator.setAlpha(0.57f);
217 }
218 }
219 } else {
220 viewHolder.indicator.clearColorFilter();
221 if (darkBackground) {
222 viewHolder.indicator.setAlpha(0.7f);
223 } else {
224 viewHolder.indicator.setAlpha(0.57f);
225 }
226 }
227 }
228
229 String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(),
230 message.getMergedTimeSent());
231 if (message.getStatus() <= Message.STATUS_RECEIVED) {
232 if ((filesize != null) && (info != null)) {
233 viewHolder.time.setText(formatedTime + " \u00B7 " + filesize +" \u00B7 " + info);
234 } else if ((filesize == null) && (info != null)) {
235 viewHolder.time.setText(formatedTime + " \u00B7 " + info);
236 } else if ((filesize != null) && (info == null)) {
237 viewHolder.time.setText(formatedTime + " \u00B7 " + filesize);
238 } else {
239 viewHolder.time.setText(formatedTime);
240 }
241 } else {
242 if ((filesize != null) && (info != null)) {
243 viewHolder.time.setText(filesize + " \u00B7 " + info);
244 } else if ((filesize == null) && (info != null)) {
245 if (error) {
246 viewHolder.time.setText(info + " \u00B7 " + formatedTime);
247 } else {
248 viewHolder.time.setText(info);
249 }
250 } else if ((filesize != null) && (info == null)) {
251 viewHolder.time.setText(filesize + " \u00B7 " + formatedTime);
252 } else {
253 viewHolder.time.setText(formatedTime);
254 }
255 }
256 }
257
258 private void displayInfoMessage(ViewHolder viewHolder, String text, boolean darkBackground) {
259 if (viewHolder.download_button != null) {
260 viewHolder.download_button.setVisibility(View.GONE);
261 }
262 viewHolder.image.setVisibility(View.GONE);
263 viewHolder.messageBody.setVisibility(View.VISIBLE);
264 viewHolder.messageBody.setText(text);
265 viewHolder.messageBody.setTextColor(getMessageTextColor(darkBackground, false));
266 viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
267 viewHolder.messageBody.setTextIsSelectable(false);
268 }
269
270 private void displayDecryptionFailed(ViewHolder viewHolder, boolean darkBackground) {
271 if (viewHolder.download_button != null) {
272 viewHolder.download_button.setVisibility(View.GONE);
273 }
274 viewHolder.image.setVisibility(View.GONE);
275 viewHolder.messageBody.setVisibility(View.VISIBLE);
276 viewHolder.messageBody.setText(getContext().getString(
277 R.string.decryption_failed));
278 viewHolder.messageBody.setTextColor(getMessageTextColor(darkBackground, false));
279 viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
280 viewHolder.messageBody.setTextIsSelectable(false);
281 }
282
283 private void displayHeartMessage(final ViewHolder viewHolder, final String body) {
284 if (viewHolder.download_button != null) {
285 viewHolder.download_button.setVisibility(View.GONE);
286 }
287 viewHolder.image.setVisibility(View.GONE);
288 viewHolder.messageBody.setVisibility(View.VISIBLE);
289 viewHolder.messageBody.setIncludeFontPadding(false);
290 Spannable span = new SpannableString(body);
291 span.setSpan(new RelativeSizeSpan(4.0f), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
292 span.setSpan(new ForegroundColorSpan(activity.getWarningTextColor()), 0, body.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
293 viewHolder.messageBody.setText(span);
294 }
295
296 private void displayTextMessage(final ViewHolder viewHolder, final Message message, boolean darkBackground) {
297 if (viewHolder.download_button != null) {
298 viewHolder.download_button.setVisibility(View.GONE);
299 }
300 viewHolder.image.setVisibility(View.GONE);
301 viewHolder.messageBody.setVisibility(View.VISIBLE);
302 viewHolder.messageBody.setIncludeFontPadding(true);
303 if (message.getBody() != null) {
304 final String nick = UIHelper.getMessageDisplayName(message);
305 String body;
306 try {
307 body = message.getMergedBody().replaceAll("^" + Message.ME_COMMAND, nick + " ");
308 } catch (ArrayIndexOutOfBoundsException e) {
309 body = message.getMergedBody();
310 }
311 final SpannableString formattedBody = new SpannableString(body);
312 int i = body.indexOf(Message.MERGE_SEPARATOR);
313 while(i >= 0) {
314 final int end = i + Message.MERGE_SEPARATOR.length();
315 formattedBody.setSpan(new RelativeSizeSpan(0.3f),i,end,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
316 i = body.indexOf(Message.MERGE_SEPARATOR,end);
317 }
318 if (message.getType() != Message.TYPE_PRIVATE) {
319 if (message.hasMeCommand()) {
320 final Spannable span = new SpannableString(formattedBody);
321 span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(),
322 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
323 viewHolder.messageBody.setText(span);
324 } else {
325 viewHolder.messageBody.setText(formattedBody);
326 }
327 } else {
328 String privateMarker;
329 if (message.getStatus() <= Message.STATUS_RECEIVED) {
330 privateMarker = activity
331 .getString(R.string.private_message);
332 } else {
333 final String to;
334 if (message.getCounterpart() != null) {
335 to = message.getCounterpart().getResourcepart();
336 } else {
337 to = "";
338 }
339 privateMarker = activity.getString(R.string.private_message_to, to);
340 }
341 final Spannable span = new SpannableString(privateMarker + " "
342 + formattedBody);
343 span.setSpan(new ForegroundColorSpan(getMessageTextColor(darkBackground,false)), 0, privateMarker
344 .length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
345 span.setSpan(new StyleSpan(Typeface.BOLD), 0,
346 privateMarker.length(),
347 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
348 if (message.hasMeCommand()) {
349 span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), privateMarker.length() + 1,
350 privateMarker.length() + 1 + nick.length(),
351 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
352 }
353 viewHolder.messageBody.setText(span);
354 }
355 int urlCount = 0;
356 final Matcher matcher = Patterns.WEB_URL.matcher(body);
357 int beginWebURL = Integer.MAX_VALUE;
358 int endWebURL = 0;
359 while (matcher.find()) {
360 MatchResult result = matcher.toMatchResult();
361 beginWebURL = result.start();
362 endWebURL = result.end();
363 urlCount++;
364 }
365 final Matcher geoMatcher = GeoHelper.GEO_URI.matcher(body);
366 while (geoMatcher.find()) {
367 urlCount++;
368 }
369 final Matcher xmppMatcher = XMPP_PATTERN.matcher(body);
370 while (xmppMatcher.find()) {
371 MatchResult result = xmppMatcher.toMatchResult();
372 if (beginWebURL < result.start() || endWebURL > result.end()) {
373 urlCount++;
374 }
375 }
376 viewHolder.messageBody.setTextIsSelectable(urlCount <= 1);
377 viewHolder.messageBody.setAutoLinkMask(0);
378 Linkify.addLinks(viewHolder.messageBody, Linkify.WEB_URLS);
379 Linkify.addLinks(viewHolder.messageBody, XMPP_PATTERN, "xmpp");
380 Linkify.addLinks(viewHolder.messageBody, GeoHelper.GEO_URI, "geo");
381 } else {
382 viewHolder.messageBody.setText("");
383 viewHolder.messageBody.setTextIsSelectable(false);
384 }
385 viewHolder.messageBody.setTextColor(this.getMessageTextColor(darkBackground, true));
386 viewHolder.messageBody.setLinkTextColor(this.getMessageTextColor(darkBackground, true));
387 viewHolder.messageBody.setHighlightColor(activity.getResources().getColor(darkBackground ? R.color.grey800 : R.color.grey500));
388 viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
389 viewHolder.messageBody.setOnLongClickListener(openContextMenu);
390 }
391
392 private void displayDownloadableMessage(ViewHolder viewHolder,
393 final Message message, String text) {
394 viewHolder.image.setVisibility(View.GONE);
395 viewHolder.messageBody.setVisibility(View.GONE);
396 viewHolder.download_button.setVisibility(View.VISIBLE);
397 viewHolder.download_button.setText(text);
398 viewHolder.download_button.setOnClickListener(new OnClickListener() {
399
400 @Override
401 public void onClick(View v) {
402 activity.startDownloadable(message);
403 }
404 });
405 viewHolder.download_button.setOnLongClickListener(openContextMenu);
406 }
407
408 private void displayOpenableMessage(ViewHolder viewHolder,final Message message) {
409 viewHolder.image.setVisibility(View.GONE);
410 viewHolder.messageBody.setVisibility(View.GONE);
411 viewHolder.download_button.setVisibility(View.VISIBLE);
412 viewHolder.download_button.setText(activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity, message)));
413 viewHolder.download_button.setOnClickListener(new OnClickListener() {
414
415 @Override
416 public void onClick(View v) {
417 openDownloadable(message);
418 }
419 });
420 viewHolder.download_button.setOnLongClickListener(openContextMenu);
421 }
422
423 private void displayLocationMessage(ViewHolder viewHolder, final Message message) {
424 viewHolder.image.setVisibility(View.GONE);
425 viewHolder.messageBody.setVisibility(View.GONE);
426 viewHolder.download_button.setVisibility(View.VISIBLE);
427 viewHolder.download_button.setText(R.string.show_location);
428 viewHolder.download_button.setOnClickListener(new OnClickListener() {
429
430 @Override
431 public void onClick(View v) {
432 showLocation(message);
433 }
434 });
435 viewHolder.download_button.setOnLongClickListener(openContextMenu);
436 }
437
438 private void displayImageMessage(ViewHolder viewHolder,
439 final Message message) {
440 if (viewHolder.download_button != null) {
441 viewHolder.download_button.setVisibility(View.GONE);
442 }
443 viewHolder.messageBody.setVisibility(View.GONE);
444 viewHolder.image.setVisibility(View.VISIBLE);
445 FileParams params = message.getFileParams();
446 double target = metrics.density * 288;
447 int scalledW;
448 int scalledH;
449 if (params.width <= params.height) {
450 scalledW = (int) (params.width / ((double) params.height / target));
451 scalledH = (int) target;
452 } else {
453 scalledW = (int) target;
454 scalledH = (int) (params.height / ((double) params.width / target));
455 }
456 LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(scalledW, scalledH);
457 layoutParams.setMargins(0, (int) (metrics.density * 4), 0, (int) (metrics.density * 4));
458 viewHolder.image.setLayoutParams(layoutParams);
459 activity.loadBitmap(message, viewHolder.image);
460 viewHolder.image.setOnClickListener(new OnClickListener() {
461
462 @Override
463 public void onClick(View v) {
464 openDownloadable(message);
465 }
466 });
467 viewHolder.image.setOnLongClickListener(openContextMenu);
468 }
469
470 private void loadMoreMessages(Conversation conversation) {
471 conversation.setLastClearHistory(0);
472 conversation.setHasMessagesLeftOnServer(true);
473 conversation.setFirstMamReference(null);
474 long timestamp = conversation.getLastMessageTransmitted();
475 if (timestamp == 0) {
476 timestamp = System.currentTimeMillis();
477 }
478 activity.setMessagesLoaded();
479 activity.xmppConnectionService.getMessageArchiveService().query(conversation, 0, timestamp);
480 Toast.makeText(activity, R.string.fetching_history_from_server,Toast.LENGTH_LONG).show();
481 }
482
483 @Override
484 public View getView(int position, View view, ViewGroup parent) {
485 final Message message = getItem(position);
486 final boolean isInValidSession = message.isValidInSession();
487 final Conversation conversation = message.getConversation();
488 final Account account = conversation.getAccount();
489 final int type = getItemViewType(position);
490 ViewHolder viewHolder;
491 if (view == null) {
492 viewHolder = new ViewHolder();
493 switch (type) {
494 case SENT:
495 view = activity.getLayoutInflater().inflate(
496 R.layout.message_sent, parent, false);
497 viewHolder.message_box = (LinearLayout) view
498 .findViewById(R.id.message_box);
499 viewHolder.contact_picture = (ImageView) view
500 .findViewById(R.id.message_photo);
501 viewHolder.download_button = (Button) view
502 .findViewById(R.id.download_button);
503 viewHolder.indicator = (ImageView) view
504 .findViewById(R.id.security_indicator);
505 viewHolder.edit_indicator = (ImageView) view.findViewById(R.id.edit_indicator);
506 viewHolder.image = (ImageView) view
507 .findViewById(R.id.message_image);
508 viewHolder.messageBody = (TextView) view
509 .findViewById(R.id.message_body);
510 viewHolder.time = (TextView) view
511 .findViewById(R.id.message_time);
512 viewHolder.indicatorReceived = (ImageView) view
513 .findViewById(R.id.indicator_received);
514 break;
515 case RECEIVED:
516 view = activity.getLayoutInflater().inflate(
517 R.layout.message_received, parent, false);
518 viewHolder.message_box = (LinearLayout) view
519 .findViewById(R.id.message_box);
520 viewHolder.contact_picture = (ImageView) view
521 .findViewById(R.id.message_photo);
522 viewHolder.download_button = (Button) view
523 .findViewById(R.id.download_button);
524 viewHolder.indicator = (ImageView) view
525 .findViewById(R.id.security_indicator);
526 viewHolder.edit_indicator = (ImageView) view.findViewById(R.id.edit_indicator);
527 viewHolder.image = (ImageView) view
528 .findViewById(R.id.message_image);
529 viewHolder.messageBody = (TextView) view
530 .findViewById(R.id.message_body);
531 viewHolder.time = (TextView) view
532 .findViewById(R.id.message_time);
533 viewHolder.indicatorReceived = (ImageView) view
534 .findViewById(R.id.indicator_received);
535 viewHolder.encryption = (TextView) view.findViewById(R.id.message_encryption);
536 break;
537 case STATUS:
538 view = activity.getLayoutInflater().inflate(R.layout.message_status, parent, false);
539 viewHolder.contact_picture = (ImageView) view.findViewById(R.id.message_photo);
540 viewHolder.status_message = (TextView) view.findViewById(R.id.status_message);
541 viewHolder.load_more_messages = (Button) view.findViewById(R.id.load_more_messages);
542 break;
543 default:
544 viewHolder = null;
545 break;
546 }
547 view.setTag(viewHolder);
548 } else {
549 viewHolder = (ViewHolder) view.getTag();
550 if (viewHolder == null) {
551 return view;
552 }
553 }
554
555 boolean darkBackground = (type == RECEIVED && (!isInValidSession || !mUseWhiteBackground));
556
557 if (type == STATUS) {
558 if ("LOAD_MORE".equals(message.getBody())) {
559 viewHolder.status_message.setVisibility(View.GONE);
560 viewHolder.contact_picture.setVisibility(View.GONE);
561 viewHolder.load_more_messages.setVisibility(View.VISIBLE);
562 viewHolder.load_more_messages.setOnClickListener(new OnClickListener() {
563 @Override
564 public void onClick(View v) {
565 loadMoreMessages(message.getConversation());
566 }
567 });
568 } else {
569 viewHolder.status_message.setVisibility(View.VISIBLE);
570 viewHolder.contact_picture.setVisibility(View.VISIBLE);
571 viewHolder.load_more_messages.setVisibility(View.GONE);
572 if (conversation.getMode() == Conversation.MODE_SINGLE) {
573 viewHolder.contact_picture.setImageBitmap(activity
574 .avatarService().get(conversation.getContact(),
575 activity.getPixel(32)));
576 viewHolder.contact_picture.setAlpha(0.5f);
577 }
578 viewHolder.status_message.setText(message.getBody());
579 }
580 return view;
581 } else {
582 loadAvatar(message, viewHolder.contact_picture);
583 }
584
585 viewHolder.contact_picture
586 .setOnClickListener(new OnClickListener() {
587
588 @Override
589 public void onClick(View v) {
590 if (MessageAdapter.this.mOnContactPictureClickedListener != null) {
591 MessageAdapter.this.mOnContactPictureClickedListener
592 .onContactPictureClicked(message);
593 }
594
595 }
596 });
597 viewHolder.contact_picture
598 .setOnLongClickListener(new OnLongClickListener() {
599
600 @Override
601 public boolean onLongClick(View v) {
602 if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
603 MessageAdapter.this.mOnContactPictureLongClickedListener
604 .onContactPictureLongClicked(message);
605 return true;
606 } else {
607 return false;
608 }
609 }
610 });
611
612 final Transferable transferable = message.getTransferable();
613 if (transferable != null && transferable.getStatus() != Transferable.STATUS_UPLOADING) {
614 if (transferable.getStatus() == Transferable.STATUS_OFFER) {
615 displayDownloadableMessage(viewHolder,message,activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, message)));
616 } else if (transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) {
617 displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message)));
618 } else {
619 displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first,darkBackground);
620 }
621 } else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
622 displayImageMessage(viewHolder, message);
623 } else if (message.getType() == Message.TYPE_FILE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
624 if (message.getFileParams().width > 0) {
625 displayImageMessage(viewHolder,message);
626 } else {
627 displayOpenableMessage(viewHolder, message);
628 }
629 } else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
630 if (activity.hasPgp()) {
631 if (account.getPgpDecryptionService().isRunning()) {
632 displayInfoMessage(viewHolder, activity.getString(R.string.message_decrypting), darkBackground);
633 } else {
634 displayInfoMessage(viewHolder, activity.getString(R.string.pgp_message), darkBackground);
635 }
636 } else {
637 displayInfoMessage(viewHolder,activity.getString(R.string.install_openkeychain),darkBackground);
638 if (viewHolder != null) {
639 viewHolder.message_box
640 .setOnClickListener(new OnClickListener() {
641
642 @Override
643 public void onClick(View v) {
644 activity.showInstallPgpDialog();
645 }
646 });
647 }
648 }
649 } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
650 displayDecryptionFailed(viewHolder,darkBackground);
651 } else {
652 if (GeoHelper.isGeoUri(message.getBody())) {
653 displayLocationMessage(viewHolder,message);
654 } else if (message.bodyIsHeart()) {
655 displayHeartMessage(viewHolder, message.getBody().trim());
656 } else if (message.treatAsDownloadable() == Message.Decision.MUST) {
657 try {
658 URL url = new URL(message.getBody());
659 displayDownloadableMessage(viewHolder,
660 message,
661 activity.getString(R.string.check_x_filesize_on_host,
662 UIHelper.getFileDescriptionString(activity, message),
663 url.getHost()));
664 } catch (Exception e) {
665 displayDownloadableMessage(viewHolder,
666 message,
667 activity.getString(R.string.check_x_filesize,
668 UIHelper.getFileDescriptionString(activity, message)));
669 }
670 } else {
671 displayTextMessage(viewHolder, message, darkBackground);
672 }
673 }
674
675 if (type == RECEIVED) {
676 if(isInValidSession) {
677 if (mUseWhiteBackground) {
678 viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_white);
679 } else {
680 viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received);
681 }
682 viewHolder.encryption.setVisibility(View.GONE);
683 } else {
684 viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning);
685 viewHolder.encryption.setVisibility(View.VISIBLE);
686 viewHolder.encryption.setText(CryptoHelper.encryptionTypeToText(message.getEncryption()));
687 }
688 }
689
690 displayStatus(viewHolder, message, type, darkBackground);
691
692 return view;
693 }
694
695 public void openDownloadable(Message message) {
696 DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
697 if (!file.exists()) {
698 Toast.makeText(activity,R.string.file_deleted,Toast.LENGTH_SHORT).show();
699 return;
700 }
701 Intent openIntent = new Intent(Intent.ACTION_VIEW);
702 String mime = file.getMimeType();
703 if (mime == null) {
704 mime = "*/*";
705 }
706 openIntent.setDataAndType(Uri.fromFile(file), mime);
707 PackageManager manager = activity.getPackageManager();
708 List<ResolveInfo> infos = manager.queryIntentActivities(openIntent, 0);
709 if (infos.size() == 0) {
710 openIntent.setDataAndType(Uri.fromFile(file),"*/*");
711 }
712 try {
713 getContext().startActivity(openIntent);
714 return;
715 } catch (ActivityNotFoundException e) {
716 //ignored
717 }
718 Toast.makeText(activity,R.string.no_application_found_to_open_file,Toast.LENGTH_SHORT).show();
719 }
720
721 public void showLocation(Message message) {
722 for(Intent intent : GeoHelper.createGeoIntentsFromMessage(message)) {
723 if (intent.resolveActivity(getContext().getPackageManager()) != null) {
724 getContext().startActivity(intent);
725 return;
726 }
727 }
728 Toast.makeText(activity,R.string.no_application_found_to_display_location,Toast.LENGTH_SHORT).show();
729 }
730
731 public void updatePreferences() {
732 this.mIndicateReceived = activity.indicateReceived();
733 this.mUseWhiteBackground = activity.useWhiteBackground();
734 }
735
736 public interface OnContactPictureClicked {
737 void onContactPictureClicked(Message message);
738 }
739
740 public interface OnContactPictureLongClicked {
741 void onContactPictureLongClicked(Message message);
742 }
743
744 private static class ViewHolder {
745
746 protected LinearLayout message_box;
747 protected Button download_button;
748 protected ImageView image;
749 protected ImageView indicator;
750 protected ImageView indicatorReceived;
751 protected TextView time;
752 protected TextView messageBody;
753 protected ImageView contact_picture;
754 protected TextView status_message;
755 protected TextView encryption;
756 public Button load_more_messages;
757 public ImageView edit_indicator;
758 }
759
760 class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
761 private final WeakReference<ImageView> imageViewReference;
762 private Message message = null;
763
764 public BitmapWorkerTask(ImageView imageView) {
765 imageViewReference = new WeakReference<>(imageView);
766 }
767
768 @Override
769 protected Bitmap doInBackground(Message... params) {
770 return activity.avatarService().get(params[0], activity.getPixel(48), isCancelled());
771 }
772
773 @Override
774 protected void onPostExecute(Bitmap bitmap) {
775 if (bitmap != null && !isCancelled()) {
776 final ImageView imageView = imageViewReference.get();
777 if (imageView != null) {
778 imageView.setImageBitmap(bitmap);
779 imageView.setBackgroundColor(0x00000000);
780 }
781 }
782 }
783 }
784
785 public void loadAvatar(Message message, ImageView imageView) {
786 if (cancelPotentialWork(message, imageView)) {
787 final Bitmap bm = activity.avatarService().get(message, activity.getPixel(48), true);
788 if (bm != null) {
789 cancelPotentialWork(message, imageView);
790 imageView.setImageBitmap(bm);
791 imageView.setBackgroundColor(0x00000000);
792 } else {
793 imageView.setBackgroundColor(UIHelper.getColorForName(UIHelper.getMessageDisplayName(message)));
794 imageView.setImageDrawable(null);
795 final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
796 final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task);
797 imageView.setImageDrawable(asyncDrawable);
798 try {
799 task.execute(message);
800 } catch (final RejectedExecutionException ignored) {
801 }
802 }
803 }
804 }
805
806 public static boolean cancelPotentialWork(Message message, ImageView imageView) {
807 final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
808
809 if (bitmapWorkerTask != null) {
810 final Message oldMessage = bitmapWorkerTask.message;
811 if (oldMessage == null || message != oldMessage) {
812 bitmapWorkerTask.cancel(true);
813 } else {
814 return false;
815 }
816 }
817 return true;
818 }
819
820 private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
821 if (imageView != null) {
822 final Drawable drawable = imageView.getDrawable();
823 if (drawable instanceof AsyncDrawable) {
824 final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
825 return asyncDrawable.getBitmapWorkerTask();
826 }
827 }
828 return null;
829 }
830
831 static class AsyncDrawable extends BitmapDrawable {
832 private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
833
834 public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
835 super(res, bitmap);
836 bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
837 }
838
839 public BitmapWorkerTask getBitmapWorkerTask() {
840 return bitmapWorkerTaskReference.get();
841 }
842 }
843}