1package eu.siacs.conversations.ui.adapter;
2
3import java.util.HashMap;
4import java.util.List;
5
6import eu.siacs.conversations.Config;
7import eu.siacs.conversations.R;
8import eu.siacs.conversations.entities.Contact;
9import eu.siacs.conversations.entities.Conversation;
10import eu.siacs.conversations.entities.Downloadable;
11import eu.siacs.conversations.entities.Message;
12import eu.siacs.conversations.entities.Message.ImageParams;
13import eu.siacs.conversations.ui.ConversationActivity;
14import eu.siacs.conversations.utils.UIHelper;
15import android.content.Context;
16import android.content.Intent;
17import android.graphics.Bitmap;
18import android.graphics.Typeface;
19import android.text.Spannable;
20import android.text.SpannableString;
21import android.text.style.ForegroundColorSpan;
22import android.text.style.StyleSpan;
23import android.util.DisplayMetrics;
24import android.view.View;
25import android.view.ViewGroup;
26import android.view.View.OnClickListener;
27import android.view.View.OnLongClickListener;
28import android.widget.ArrayAdapter;
29import android.widget.Button;
30import android.widget.ImageView;
31import android.widget.LinearLayout;
32import android.widget.TextView;
33import android.widget.Toast;
34
35public class MessageAdapter extends ArrayAdapter<Message> {
36
37 private static final int SENT = 0;
38 private static final int RECEIVED = 1;
39 private static final int STATUS = 2;
40 private static final int NULL = 3;
41
42 private ConversationActivity activity;
43
44 private Bitmap accountBitmap;
45
46 private BitmapCache mBitmapCache = new BitmapCache();
47 private DisplayMetrics metrics;
48
49 private OnContactPictureClicked mOnContactPictureClickedListener;
50 private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
51
52 public MessageAdapter(ConversationActivity activity, List<Message> messages) {
53 super(activity, 0, messages);
54 this.activity = activity;
55 metrics = getContext().getResources().getDisplayMetrics();
56 }
57
58 private Bitmap getSelfBitmap() {
59 if (this.accountBitmap == null) {
60
61 if (getCount() > 0) {
62 this.accountBitmap = activity.xmppConnectionService
63 .getAvatarService().getAvatar(
64 getItem(0).getConversation().getAccount(),
65 activity.getPixel(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 || message.getDownloadable() != null) {
109 ImageParams params = message.getImageParams();
110 if (params.size != 0) {
111 filesize = params.size / 1024 + " KB";
112 }
113 }
114 switch (message.getMergedStatus()) {
115 case Message.STATUS_WAITING:
116 info = getContext().getString(R.string.waiting);
117 break;
118 case Message.STATUS_UNSEND:
119 info = getContext().getString(R.string.sending);
120 break;
121 case Message.STATUS_OFFERED:
122 info = getContext().getString(R.string.offering);
123 break;
124 case Message.STATUS_SEND_RECEIVED:
125 if (activity.indicateReceived()) {
126 viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
127 }
128 break;
129 case Message.STATUS_SEND_DISPLAYED:
130 if (activity.indicateReceived()) {
131 viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
132 }
133 break;
134 case Message.STATUS_SEND_FAILED:
135 info = getContext().getString(R.string.send_failed);
136 error = true;
137 break;
138 case Message.STATUS_SEND_REJECTED:
139 info = getContext().getString(R.string.send_rejected);
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 String body = Config.PARSE_EMOTICONS ? UIHelper
231 .transformAsciiEmoticons(message.getMergedBody())
232 : message.getMergedBody();
233 viewHolder.messageBody.setText(body);
234 } else {
235 String privateMarker;
236 if (message.getStatus() <= Message.STATUS_RECEIVED) {
237 privateMarker = activity
238 .getString(R.string.private_message);
239 } else {
240 String to;
241 if (message.getPresence() != null) {
242 to = message.getPresence();
243 } else {
244 to = message.getCounterpart();
245 }
246 privateMarker = activity.getString(
247 R.string.private_message_to, to);
248 }
249 SpannableString span = new SpannableString(privateMarker + " "
250 + message.getBody());
251 span.setSpan(
252 new ForegroundColorSpan(activity
253 .getSecondaryTextColor()), 0, privateMarker
254 .length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
255 span.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0,
256 privateMarker.length(),
257 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
258 viewHolder.messageBody.setText(span);
259 }
260 } else {
261 viewHolder.messageBody.setText("");
262 }
263 viewHolder.messageBody.setTextColor(activity.getPrimaryTextColor());
264 viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
265 viewHolder.messageBody.setTextIsSelectable(true);
266 }
267
268 private void displayDownloadableMessage(ViewHolder viewHolder,
269 final Message message, int resid) {
270 viewHolder.image.setVisibility(View.GONE);
271 viewHolder.messageBody.setVisibility(View.GONE);
272 viewHolder.download_button.setVisibility(View.VISIBLE);
273 viewHolder.download_button.setText(resid);
274 viewHolder.download_button.setOnClickListener(new OnClickListener() {
275
276 @Override
277 public void onClick(View v) {
278 startDonwloadable(message);
279 }
280 });
281 }
282
283 private void displayImageMessage(ViewHolder viewHolder,
284 final Message message) {
285 if (viewHolder.download_button != null) {
286 viewHolder.download_button.setVisibility(View.GONE);
287 }
288 viewHolder.messageBody.setVisibility(View.GONE);
289 viewHolder.image.setVisibility(View.VISIBLE);
290 ImageParams params = message.getImageParams();
291 double target = metrics.density * 288;
292 int scalledW;
293 int scalledH;
294 if (params.width <= params.height) {
295 scalledW = (int) (params.width / ((double) params.height / target));
296 scalledH = (int) target;
297 } else {
298 scalledW = (int) target;
299 scalledH = (int) (params.height / ((double) params.width / target));
300 }
301 viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(
302 scalledW, scalledH));
303 activity.loadBitmap(message, viewHolder.image);
304 viewHolder.image.setOnClickListener(new OnClickListener() {
305
306 @Override
307 public void onClick(View v) {
308 Intent intent = new Intent(Intent.ACTION_VIEW);
309 intent.setDataAndType(activity.xmppConnectionService
310 .getFileBackend().getJingleFileUri(message), "image/*");
311 getContext().startActivity(intent);
312 }
313 });
314 viewHolder.image.setOnLongClickListener(new OnLongClickListener() {
315
316 @Override
317 public boolean onLongClick(View v) {
318 Intent shareIntent = new Intent();
319 shareIntent.setAction(Intent.ACTION_SEND);
320 shareIntent.putExtra(Intent.EXTRA_STREAM,
321 activity.xmppConnectionService.getFileBackend()
322 .getJingleFileUri(message));
323 shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
324 shareIntent.setType("image/webp");
325 getContext().startActivity(
326 Intent.createChooser(shareIntent,
327 getContext().getText(R.string.share_with)));
328 return true;
329 }
330 });
331 }
332
333 @Override
334 public View getView(int position, View view, ViewGroup parent) {
335 final Message item = getItem(position);
336 int type = getItemViewType(position);
337 ViewHolder viewHolder;
338 if (view == null) {
339 viewHolder = new ViewHolder();
340 switch (type) {
341 case NULL:
342 view = (View) activity.getLayoutInflater().inflate(
343 R.layout.message_null, parent, false);
344 break;
345 case SENT:
346 view = (View) activity.getLayoutInflater().inflate(
347 R.layout.message_sent, parent, false);
348 viewHolder.message_box = (LinearLayout) view
349 .findViewById(R.id.message_box);
350 viewHolder.contact_picture = (ImageView) view
351 .findViewById(R.id.message_photo);
352 viewHolder.contact_picture.setImageBitmap(getSelfBitmap());
353 viewHolder.download_button = (Button) view
354 .findViewById(R.id.download_button);
355 viewHolder.indicator = (ImageView) view
356 .findViewById(R.id.security_indicator);
357 viewHolder.image = (ImageView) view
358 .findViewById(R.id.message_image);
359 viewHolder.messageBody = (TextView) view
360 .findViewById(R.id.message_body);
361 viewHolder.time = (TextView) view
362 .findViewById(R.id.message_time);
363 viewHolder.indicatorReceived = (ImageView) view
364 .findViewById(R.id.indicator_received);
365 view.setTag(viewHolder);
366 break;
367 case RECEIVED:
368 view = (View) activity.getLayoutInflater().inflate(
369 R.layout.message_received, parent, false);
370 viewHolder.message_box = (LinearLayout) view
371 .findViewById(R.id.message_box);
372 viewHolder.contact_picture = (ImageView) view
373 .findViewById(R.id.message_photo);
374 viewHolder.download_button = (Button) view
375 .findViewById(R.id.download_button);
376 if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
377 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
378 item.getConversation().getContact(), getContext()));
379 }
380 viewHolder.indicator = (ImageView) view
381 .findViewById(R.id.security_indicator);
382 viewHolder.image = (ImageView) view
383 .findViewById(R.id.message_image);
384 viewHolder.messageBody = (TextView) view
385 .findViewById(R.id.message_body);
386 viewHolder.time = (TextView) view
387 .findViewById(R.id.message_time);
388 view.setTag(viewHolder);
389 break;
390 case STATUS:
391 view = (View) activity.getLayoutInflater().inflate(
392 R.layout.message_status, parent, false);
393 viewHolder.contact_picture = (ImageView) view
394 .findViewById(R.id.message_photo);
395 if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
396
397 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
398 item.getConversation().getContact(), getContext()));
399 viewHolder.contact_picture.setAlpha(0.5f);
400 viewHolder.contact_picture
401 .setOnClickListener(new OnClickListener() {
402
403 @Override
404 public void onClick(View v) {
405 String name = item.getConversation()
406 .getName();
407 String read = getContext()
408 .getString(
409 R.string.contact_has_read_up_to_this_point,
410 name);
411 Toast.makeText(getContext(), read,
412 Toast.LENGTH_SHORT).show();
413 }
414 });
415
416 }
417 break;
418 default:
419 viewHolder = null;
420 break;
421 }
422 } else {
423 viewHolder = (ViewHolder) view.getTag();
424 }
425
426 if (type == STATUS) {
427 return view;
428 }
429 if (type == NULL) {
430 if (position == getCount() - 1) {
431 view.getLayoutParams().height = 1;
432 } else {
433 view.getLayoutParams().height = 0;
434
435 }
436 view.setLayoutParams(view.getLayoutParams());
437 return view;
438 }
439
440 if (viewHolder.contact_picture != null) {
441 viewHolder.contact_picture
442 .setOnClickListener(new OnClickListener() {
443
444 @Override
445 public void onClick(View v) {
446 if (MessageAdapter.this.mOnContactPictureClickedListener != null) {
447 MessageAdapter.this.mOnContactPictureClickedListener
448 .onContactPictureClicked(item);
449 ;
450 }
451
452 }
453 });
454 viewHolder.contact_picture
455 .setOnLongClickListener(new OnLongClickListener() {
456
457 @Override
458 public boolean onLongClick(View v) {
459 if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
460 MessageAdapter.this.mOnContactPictureLongClickedListener
461 .onContactPictureLongClicked(item);
462 return true;
463 } else {
464 return false;
465 }
466 }
467 });
468 }
469
470 if (type == RECEIVED) {
471 if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
472 Contact contact = item.getContact();
473 if (contact != null) {
474 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
475 contact, getContext()));
476 } else {
477 String name = item.getPresence();
478 if (name == null) {
479 name = item.getCounterpart();
480 }
481 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
482 name, getContext()));
483 }
484 }
485 }
486
487 if (item.getType() == Message.TYPE_IMAGE
488 || item.getDownloadable() != null) {
489 Downloadable d = item.getDownloadable();
490 if (d != null && d.getStatus() == Downloadable.STATUS_DOWNLOADING) {
491 displayInfoMessage(viewHolder, R.string.receiving_image);
492 } else if (d != null
493 && d.getStatus() == Downloadable.STATUS_CHECKING) {
494 displayInfoMessage(viewHolder, R.string.checking_image);
495 } else if (d != null
496 && d.getStatus() == Downloadable.STATUS_DELETED) {
497 displayInfoMessage(viewHolder, R.string.image_file_deleted);
498 } else if (d != null && d.getStatus() == Downloadable.STATUS_OFFER) {
499 displayDownloadableMessage(viewHolder, item,
500 R.string.download_image);
501 } else if (d != null
502 && d.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) {
503 displayDownloadableMessage(viewHolder, item,
504 R.string.check_image_filesize);
505 } else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED)
506 || (item.getEncryption() == Message.ENCRYPTION_NONE)
507 || (item.getEncryption() == Message.ENCRYPTION_OTR)) {
508 displayImageMessage(viewHolder, item);
509 } else if (item.getEncryption() == Message.ENCRYPTION_PGP) {
510 displayInfoMessage(viewHolder, R.string.encrypted_message);
511 } else {
512 displayDecryptionFailed(viewHolder);
513 }
514 } else {
515 if (item.getEncryption() == Message.ENCRYPTION_PGP) {
516 if (activity.hasPgp()) {
517 displayInfoMessage(viewHolder, R.string.encrypted_message);
518 } else {
519 displayInfoMessage(viewHolder,
520 R.string.install_openkeychain);
521 viewHolder.message_box
522 .setOnClickListener(new OnClickListener() {
523
524 @Override
525 public void onClick(View v) {
526 activity.showInstallPgpDialog();
527 }
528 });
529 }
530 } else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
531 displayDecryptionFailed(viewHolder);
532 } else {
533 displayTextMessage(viewHolder, item);
534 }
535 }
536
537 displayStatus(viewHolder, item);
538
539 return view;
540 }
541
542 public void startDonwloadable(Message message) {
543 Downloadable downloadable = message.getDownloadable();
544 if (downloadable != null) {
545 if (!downloadable.start()) {
546 Toast.makeText(activity, R.string.not_connected_try_again,
547 Toast.LENGTH_SHORT).show();
548 }
549 }
550 }
551
552 private static class ViewHolder {
553
554 protected LinearLayout message_box;
555 protected Button download_button;
556 protected ImageView image;
557 protected ImageView indicator;
558 protected ImageView indicatorReceived;
559 protected TextView time;
560 protected TextView messageBody;
561 protected ImageView contact_picture;
562
563 }
564
565 private class BitmapCache {
566 private HashMap<String, Bitmap> contactBitmaps = new HashMap<String, Bitmap>();
567 private HashMap<String, Bitmap> unknownBitmaps = new HashMap<String, Bitmap>();
568
569 public Bitmap get(Contact contact, Context context) {
570 if (!contactBitmaps.containsKey(contact.getJid())) {
571 contactBitmaps.put(contact.getJid(),
572 activity.xmppConnectionService.getAvatarService()
573 .getAvatar(contact, activity.getPixel(48)));
574 }
575 return contactBitmaps.get(contact.getJid());
576 }
577
578 public Bitmap get(String name, Context context) {
579 if (unknownBitmaps.containsKey(name)) {
580 return unknownBitmaps.get(name);
581 } else {
582 Bitmap bm = activity.xmppConnectionService.getAvatarService()
583 .getAvatar(name, activity.getPixel(48));
584 unknownBitmaps.put(name, bm);
585 return bm;
586 }
587 }
588 }
589
590 public interface OnContactPictureClicked {
591 public void onContactPictureClicked(Message message);
592 }
593
594 public interface OnContactPictureLongClicked {
595 public void onContactPictureLongClicked(Message message);
596 }
597}