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