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