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