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