1package eu.siacs.conversations.ui.adapter;
2
3import java.net.MalformedURLException;
4import java.net.URL;
5import java.util.HashMap;
6import java.util.List;
7
8import eu.siacs.conversations.Config;
9import eu.siacs.conversations.R;
10import eu.siacs.conversations.entities.Contact;
11import eu.siacs.conversations.entities.Conversation;
12import eu.siacs.conversations.entities.Downloadable;
13import eu.siacs.conversations.entities.Message;
14import eu.siacs.conversations.entities.Message.ImageParams;
15import eu.siacs.conversations.ui.ConversationActivity;
16import eu.siacs.conversations.utils.UIHelper;
17import android.content.Context;
18import android.content.Intent;
19import android.graphics.Bitmap;
20import android.graphics.Typeface;
21import android.text.Spannable;
22import android.text.SpannableString;
23import android.text.style.ForegroundColorSpan;
24import android.text.style.StyleSpan;
25import android.util.DisplayMetrics;
26import android.view.View;
27import android.view.ViewGroup;
28import android.view.View.OnClickListener;
29import android.view.View.OnLongClickListener;
30import android.widget.ArrayAdapter;
31import android.widget.Button;
32import android.widget.ImageView;
33import android.widget.LinearLayout;
34import android.widget.TextView;
35import android.widget.Toast;
36
37public class MessageAdapter extends ArrayAdapter<Message> {
38
39 private static final int SENT = 0;
40 private static final int RECEIVED = 1;
41 private static final int STATUS = 2;
42 private static final int NULL = 3;
43
44 private ConversationActivity activity;
45
46 private Bitmap accountBitmap;
47
48 private BitmapCache mBitmapCache = new BitmapCache();
49 private DisplayMetrics metrics;
50
51 private OnContactPictureClicked mOnContactPictureClickedListener;
52 private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
53
54 public MessageAdapter(ConversationActivity activity, List<Message> messages) {
55 super(activity, 0, messages);
56 this.activity = activity;
57 metrics = getContext().getResources().getDisplayMetrics();
58 }
59
60 private Bitmap getSelfBitmap() {
61 if (this.accountBitmap == null) {
62
63 if (getCount() > 0) {
64 this.accountBitmap = getItem(0).getConversation().getAccount()
65 .getImage(getContext(), 48);
66 }
67 }
68 return this.accountBitmap;
69 }
70
71 public void setOnContactPictureClicked(OnContactPictureClicked listener) {
72 this.mOnContactPictureClickedListener = listener;
73 }
74
75 public void setOnContactPictureLongClicked(
76 OnContactPictureLongClicked listener) {
77 this.mOnContactPictureLongClickedListener = listener;
78 }
79
80 @Override
81 public int getViewTypeCount() {
82 return 4;
83 }
84
85 @Override
86 public int getItemViewType(int position) {
87 if (getItem(position).wasMergedIntoPrevious()) {
88 return NULL;
89 } else if (getItem(position).getType() == Message.TYPE_STATUS) {
90 return STATUS;
91 } else if (getItem(position).getStatus() <= Message.STATUS_RECEIVED) {
92 return RECEIVED;
93 } else {
94 return SENT;
95 }
96 }
97
98 private void displayStatus(ViewHolder viewHolder, Message message) {
99 String filesize = null;
100 String info = null;
101 boolean error = false;
102 if (viewHolder.indicatorReceived != null) {
103 viewHolder.indicatorReceived.setVisibility(View.GONE);
104 }
105 boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
106 && message.getMergedStatus() <= Message.STATUS_RECEIVED;
107 if (message.getType() == Message.TYPE_IMAGE) {
108 filesize = message.getImageParams().size / 1024 + " KB";
109 }
110 switch (message.getMergedStatus()) {
111 case Message.STATUS_WAITING:
112 info = getContext().getString(R.string.waiting);
113 break;
114 case Message.STATUS_UNSEND:
115 info = getContext().getString(R.string.sending);
116 break;
117 case Message.STATUS_OFFERED:
118 info = getContext().getString(R.string.offering);
119 break;
120 case Message.STATUS_SEND_RECEIVED:
121 if (activity.indicateReceived()) {
122 viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
123 }
124 break;
125 case Message.STATUS_SEND_DISPLAYED:
126 if (activity.indicateReceived()) {
127 viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
128 }
129 break;
130 case Message.STATUS_SEND_FAILED:
131 info = getContext().getString(R.string.send_failed);
132 error = true;
133 break;
134 case Message.STATUS_SEND_REJECTED:
135 info = getContext().getString(R.string.send_rejected);
136 error = true;
137 break;
138 case Message.STATUS_RECEPTION_FAILED:
139 info = getContext().getString(R.string.reception_failed);
140 error = true;
141 break;
142 default:
143 if (multiReceived) {
144 Contact contact = message.getContact();
145 if (contact != null) {
146 info = contact.getDisplayName();
147 } else {
148 if (message.getPresence() != null) {
149 info = message.getPresence();
150 } else {
151 info = message.getCounterpart();
152 }
153 }
154 }
155 break;
156 }
157 if (error) {
158 viewHolder.time.setTextColor(activity.getWarningTextColor());
159 } else {
160 viewHolder.time.setTextColor(activity.getSecondaryTextColor());
161 }
162 if (message.getEncryption() == Message.ENCRYPTION_NONE) {
163 viewHolder.indicator.setVisibility(View.GONE);
164 } else {
165 viewHolder.indicator.setVisibility(View.VISIBLE);
166 }
167
168 String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(),
169 message.getMergedTimeSent());
170 if (message.getStatus() <= Message.STATUS_RECEIVED) {
171 if ((filesize != null) && (info != null)) {
172 viewHolder.time.setText(filesize + " \u00B7 " + info);
173 } else if ((filesize == null) && (info != null)) {
174 viewHolder.time.setText(formatedTime + " \u00B7 " + info);
175 } else if ((filesize != null) && (info == null)) {
176 viewHolder.time.setText(formatedTime + " \u00B7 " + filesize);
177 } else {
178 viewHolder.time.setText(formatedTime);
179 }
180 } else {
181 if ((filesize != null) && (info != null)) {
182 viewHolder.time.setText(filesize + " \u00B7 " + info);
183 } else if ((filesize == null) && (info != null)) {
184 if (error) {
185 viewHolder.time.setText(info + " \u00B7 " + formatedTime);
186 } else {
187 viewHolder.time.setText(info);
188 }
189 } else if ((filesize != null) && (info == null)) {
190 viewHolder.time.setText(filesize + " \u00B7 " + formatedTime);
191 } else {
192 viewHolder.time.setText(formatedTime);
193 }
194 }
195 }
196
197 private void displayInfoMessage(ViewHolder viewHolder, int r) {
198 if (viewHolder.download_button != null) {
199 viewHolder.download_button.setVisibility(View.GONE);
200 }
201 viewHolder.image.setVisibility(View.GONE);
202 viewHolder.messageBody.setVisibility(View.VISIBLE);
203 viewHolder.messageBody.setText(getContext().getString(r));
204 viewHolder.messageBody.setTextColor(activity.getSecondaryTextColor());
205 viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
206 viewHolder.messageBody.setTextIsSelectable(false);
207 }
208
209 private void displayDecryptionFailed(ViewHolder viewHolder) {
210 if (viewHolder.download_button != null) {
211 viewHolder.download_button.setVisibility(View.GONE);
212 }
213 viewHolder.image.setVisibility(View.GONE);
214 viewHolder.messageBody.setVisibility(View.VISIBLE);
215 viewHolder.messageBody.setText(getContext().getString(
216 R.string.decryption_failed));
217 viewHolder.messageBody.setTextColor(activity.getWarningTextColor());
218 viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
219 viewHolder.messageBody.setTextIsSelectable(false);
220 }
221
222 private void displayTextMessage(ViewHolder viewHolder, Message message) {
223 if (viewHolder.download_button != null) {
224 viewHolder.download_button.setVisibility(View.GONE);
225 }
226 viewHolder.image.setVisibility(View.GONE);
227 viewHolder.messageBody.setVisibility(View.VISIBLE);
228 if (message.getBody() != null) {
229 if (message.getType() != Message.TYPE_PRIVATE) {
230 if (message.getType() == Message.TYPE_IMAGE) {
231 String orign = message.getImageParams().origin;
232 if (orign!=null) {
233 viewHolder.messageBody.setText(orign);
234 } else {
235 viewHolder.messageBody.setText(message.getBody());
236 }
237 } else {
238 String body = Config.PARSE_EMOTICONS ? UIHelper
239 .transformAsciiEmoticons(message.getMergedBody())
240 : message.getMergedBody();
241 viewHolder.messageBody.setText(body);
242 }
243 } else {
244 String privateMarker;
245 if (message.getStatus() <= Message.STATUS_RECEIVED) {
246 privateMarker = activity
247 .getString(R.string.private_message);
248 } else {
249 String to;
250 if (message.getPresence() != null) {
251 to = message.getPresence();
252 } else {
253 to = message.getCounterpart();
254 }
255 privateMarker = activity.getString(
256 R.string.private_message_to, to);
257 }
258 SpannableString span = new SpannableString(privateMarker + " "
259 + message.getBody());
260 span.setSpan(
261 new ForegroundColorSpan(activity
262 .getSecondaryTextColor()), 0, privateMarker
263 .length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
264 span.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0,
265 privateMarker.length(),
266 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
267 viewHolder.messageBody.setText(span);
268 }
269 } else {
270 viewHolder.messageBody.setText("");
271 }
272 viewHolder.messageBody.setTextColor(activity.getPrimaryTextColor());
273 viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
274 viewHolder.messageBody.setTextIsSelectable(true);
275 }
276
277 private void displayImageMessage(ViewHolder viewHolder,
278 final Message message) {
279 if (viewHolder.download_button != null) {
280 viewHolder.download_button.setVisibility(View.GONE);
281 }
282 viewHolder.messageBody.setVisibility(View.GONE);
283 viewHolder.image.setVisibility(View.VISIBLE);
284 ImageParams params = message.getImageParams();
285 double target = metrics.density * 288;
286 int scalledW;
287 int scalledH;
288 if (params.width <= params.height) {
289 scalledW = (int) (params.width / ((double) params.height / target));
290 scalledH = (int) target;
291 } else {
292 scalledW = (int) target;
293 scalledH = (int) (params.height / ((double) params.width / target));
294 }
295 viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(
296 scalledW, scalledH));
297 activity.loadBitmap(message, viewHolder.image);
298 viewHolder.image.setOnClickListener(new OnClickListener() {
299
300 @Override
301 public void onClick(View v) {
302 Intent intent = new Intent(Intent.ACTION_VIEW);
303 intent.setDataAndType(activity.xmppConnectionService
304 .getFileBackend().getJingleFileUri(message), "image/*");
305 getContext().startActivity(intent);
306 }
307 });
308 viewHolder.image.setOnLongClickListener(new OnLongClickListener() {
309
310 @Override
311 public boolean onLongClick(View v) {
312 Intent shareIntent = new Intent();
313 shareIntent.setAction(Intent.ACTION_SEND);
314 shareIntent.putExtra(Intent.EXTRA_STREAM,
315 activity.xmppConnectionService.getFileBackend()
316 .getJingleFileUri(message));
317 shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
318 shareIntent.setType("image/webp");
319 getContext().startActivity(
320 Intent.createChooser(shareIntent,
321 getContext().getText(R.string.share_with)));
322 return true;
323 }
324 });
325 }
326
327 @Override
328 public View getView(int position, View view, ViewGroup parent) {
329 final Message item = getItem(position);
330 int type = getItemViewType(position);
331 ViewHolder viewHolder;
332 if (view == null) {
333 viewHolder = new ViewHolder();
334 switch (type) {
335 case NULL:
336 view = (View) activity.getLayoutInflater().inflate(
337 R.layout.message_null, parent, false);
338 break;
339 case SENT:
340 view = (View) activity.getLayoutInflater().inflate(
341 R.layout.message_sent, parent, false);
342 viewHolder.message_box = (LinearLayout) view
343 .findViewById(R.id.message_box);
344 viewHolder.contact_picture = (ImageView) view
345 .findViewById(R.id.message_photo);
346 viewHolder.contact_picture.setImageBitmap(getSelfBitmap());
347 viewHolder.indicator = (ImageView) view
348 .findViewById(R.id.security_indicator);
349 viewHolder.image = (ImageView) view
350 .findViewById(R.id.message_image);
351 viewHolder.messageBody = (TextView) view
352 .findViewById(R.id.message_body);
353 viewHolder.time = (TextView) view
354 .findViewById(R.id.message_time);
355 viewHolder.indicatorReceived = (ImageView) view
356 .findViewById(R.id.indicator_received);
357 view.setTag(viewHolder);
358 break;
359 case RECEIVED:
360 view = (View) activity.getLayoutInflater().inflate(
361 R.layout.message_received, parent, false);
362 viewHolder.message_box = (LinearLayout) view
363 .findViewById(R.id.message_box);
364 viewHolder.contact_picture = (ImageView) view
365 .findViewById(R.id.message_photo);
366
367 viewHolder.download_button = (Button) view
368 .findViewById(R.id.download_button);
369
370 if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
371
372 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
373 item.getConversation().getContact(), getContext()));
374
375 }
376 viewHolder.indicator = (ImageView) view
377 .findViewById(R.id.security_indicator);
378 viewHolder.image = (ImageView) view
379 .findViewById(R.id.message_image);
380 viewHolder.messageBody = (TextView) view
381 .findViewById(R.id.message_body);
382 viewHolder.time = (TextView) view
383 .findViewById(R.id.message_time);
384 view.setTag(viewHolder);
385 break;
386 case STATUS:
387 view = (View) activity.getLayoutInflater().inflate(
388 R.layout.message_status, parent, false);
389 viewHolder.contact_picture = (ImageView) view
390 .findViewById(R.id.message_photo);
391 if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
392
393 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
394 item.getConversation().getContact(), getContext()));
395 viewHolder.contact_picture.setAlpha(0.5f);
396 viewHolder.contact_picture
397 .setOnClickListener(new OnClickListener() {
398
399 @Override
400 public void onClick(View v) {
401 String name = item.getConversation()
402 .getName();
403 String read = getContext()
404 .getString(
405 R.string.contact_has_read_up_to_this_point,
406 name);
407 Toast.makeText(getContext(), read,
408 Toast.LENGTH_SHORT).show();
409 }
410 });
411
412 }
413 break;
414 default:
415 viewHolder = null;
416 break;
417 }
418 } else {
419 viewHolder = (ViewHolder) view.getTag();
420 }
421
422 if (type == STATUS) {
423 return view;
424 }
425 if (type == NULL) {
426 if (position == getCount() - 1) {
427 view.getLayoutParams().height = 1;
428 } else {
429 view.getLayoutParams().height = 0;
430
431 }
432 view.setLayoutParams(view.getLayoutParams());
433 return view;
434 }
435
436 if (viewHolder.contact_picture != null) {
437 viewHolder.contact_picture
438 .setOnClickListener(new OnClickListener() {
439
440 @Override
441 public void onClick(View v) {
442 if (MessageAdapter.this.mOnContactPictureClickedListener != null) {
443 MessageAdapter.this.mOnContactPictureClickedListener
444 .onContactPictureClicked(item);
445 ;
446 }
447
448 }
449 });
450 viewHolder.contact_picture
451 .setOnLongClickListener(new OnLongClickListener() {
452
453 @Override
454 public boolean onLongClick(View v) {
455 if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
456 MessageAdapter.this.mOnContactPictureLongClickedListener
457 .onContactPictureLongClicked(item);
458 return true;
459 } else {
460 return false;
461 }
462 }
463 });
464 }
465
466 if (type == RECEIVED) {
467 if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
468 Contact contact = item.getContact();
469 if (contact != null) {
470 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
471 contact, getContext()));
472 } else {
473 String name = item.getPresence();
474 if (name == null) {
475 name = item.getCounterpart();
476 }
477 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
478 name, getContext()));
479 }
480 }
481 }
482
483 if (item.getType() == Message.TYPE_IMAGE) {
484 if (item.getStatus() == Message.STATUS_RECEIVING) {
485 displayInfoMessage(viewHolder, R.string.receiving_image);
486 } else if (item.getStatus() == Message.STATUS_RECEIVED_CHECKING) {
487 displayInfoMessage(viewHolder, R.string.checking_image);
488 } else if (item.getStatus() == Message.STATUS_RECEPTION_FAILED) {
489 displayTextMessage(viewHolder, item);
490 } else if (item.getStatus() == Message.STATUS_RECEIVED_OFFER) {
491 viewHolder.image.setVisibility(View.GONE);
492 viewHolder.messageBody.setVisibility(View.GONE);
493 viewHolder.download_button.setVisibility(View.VISIBLE);
494 viewHolder.download_button
495 .setOnClickListener(new OnClickListener() {
496
497 @Override
498 public void onClick(View v) {
499 if (!startDonwloadable(item)) {
500 activity.xmppConnectionService.markMessage(
501 item,
502 Message.STATUS_RECEPTION_FAILED);
503 }
504 }
505 });
506 } else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED)
507 || (item.getEncryption() == Message.ENCRYPTION_NONE)
508 || (item.getEncryption() == Message.ENCRYPTION_OTR)) {
509 displayImageMessage(viewHolder, item);
510 } else if (item.getEncryption() == Message.ENCRYPTION_PGP) {
511 displayInfoMessage(viewHolder, R.string.encrypted_message);
512 } else {
513 displayDecryptionFailed(viewHolder);
514 }
515 } else {
516 if (item.getEncryption() == Message.ENCRYPTION_PGP) {
517 if (activity.hasPgp()) {
518 displayInfoMessage(viewHolder, R.string.encrypted_message);
519 } else {
520 displayInfoMessage(viewHolder,
521 R.string.install_openkeychain);
522 viewHolder.message_box
523 .setOnClickListener(new OnClickListener() {
524
525 @Override
526 public void onClick(View v) {
527 activity.showInstallPgpDialog();
528 }
529 });
530 }
531 } else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
532 displayDecryptionFailed(viewHolder);
533 } else {
534 displayTextMessage(viewHolder, item);
535 }
536 }
537
538 displayStatus(viewHolder, item);
539
540 return view;
541 }
542
543 public boolean startDonwloadable(Message message) {
544 Downloadable downloadable = message.getDownloadable();
545 if (downloadable != null) {
546 downloadable.start();
547 return true;
548 } else {
549 ImageParams params = message.getImageParams();
550 if (params.origin != null) {
551 try {
552 URL url = new URL(params.origin);
553 activity.xmppConnectionService.getHttpConnectionManager()
554 .createNewConnection(message, url);
555 return true;
556 } catch (MalformedURLException e) {
557 return false;
558 }
559 } else {
560 return false;
561 }
562 }
563 }
564
565 private static class ViewHolder {
566
567 protected LinearLayout message_box;
568 protected Button download_button;
569 protected ImageView image;
570 protected ImageView indicator;
571 protected ImageView indicatorReceived;
572 protected TextView time;
573 protected TextView messageBody;
574 protected ImageView contact_picture;
575
576 }
577
578 private class BitmapCache {
579 private HashMap<String, Bitmap> contactBitmaps = new HashMap<String, Bitmap>();
580 private HashMap<String, Bitmap> unknownBitmaps = new HashMap<String, Bitmap>();
581
582 public Bitmap get(Contact contact, Context context) {
583 if (!contactBitmaps.containsKey(contact.getJid())) {
584 contactBitmaps.put(contact.getJid(),
585 contact.getImage(48, context));
586 }
587 return contactBitmaps.get(contact.getJid());
588 }
589
590 public Bitmap get(String name, Context context) {
591 if (unknownBitmaps.containsKey(name)) {
592 return unknownBitmaps.get(name);
593 } else {
594 Bitmap bm = UIHelper
595 .getContactPicture(name, 48, context, false);
596 unknownBitmaps.put(name, bm);
597 return bm;
598 }
599 }
600 }
601
602 public interface OnContactPictureClicked {
603 public void onContactPictureClicked(Message message);
604 }
605
606 public interface OnContactPictureLongClicked {
607 public void onContactPictureLongClicked(Message message);
608 }
609}