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 final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
285 viewHolder.image.setVisibility(View.GONE);
286 viewHolder.messageBody.setVisibility(View.GONE);
287 viewHolder.download_button.setVisibility(View.VISIBLE);
288 viewHolder.download_button.setText(activity.getString(R.string.open_file,file.getMimeType()));
289 viewHolder.download_button.setOnClickListener(new OnClickListener() {
290
291 @Override
292 public void onClick(View v) {
293 openDonwloadable(file);
294 }
295 });
296 viewHolder.download_button.setOnLongClickListener(openContextMenu);
297 }
298
299 private void displayImageMessage(ViewHolder viewHolder,
300 final Message message) {
301 if (viewHolder.download_button != null) {
302 viewHolder.download_button.setVisibility(View.GONE);
303 }
304 viewHolder.messageBody.setVisibility(View.GONE);
305 viewHolder.image.setVisibility(View.VISIBLE);
306 ImageParams params = message.getImageParams();
307 double target = metrics.density * 288;
308 int scalledW;
309 int scalledH;
310 if (params.width <= params.height) {
311 scalledW = (int) (params.width / ((double) params.height / target));
312 scalledH = (int) target;
313 } else {
314 scalledW = (int) target;
315 scalledH = (int) (params.height / ((double) params.width / target));
316 }
317 viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(
318 scalledW, scalledH));
319 activity.loadBitmap(message, viewHolder.image);
320 viewHolder.image.setOnClickListener(new OnClickListener() {
321
322 @Override
323 public void onClick(View v) {
324 Intent intent = new Intent(Intent.ACTION_VIEW);
325 intent.setDataAndType(activity.xmppConnectionService
326 .getFileBackend().getJingleFileUri(message), "image/*");
327 getContext().startActivity(intent);
328 }
329 });
330 viewHolder.image.setOnLongClickListener(openContextMenu);
331 }
332
333 private String getDisplayedMucCounterpart(final Jid counterpart) {
334 if (counterpart==null) {
335 return "";
336 } else if (!counterpart.isBareJid()) {
337 return counterpart.getResourcepart();
338 } else {
339 return counterpart.toString();
340 }
341 }
342
343 @Override
344 public View getView(int position, View view, ViewGroup parent) {
345 final Message item = getItem(position);
346 int type = getItemViewType(position);
347 ViewHolder viewHolder;
348 if (view == null) {
349 viewHolder = new ViewHolder();
350 switch (type) {
351 case NULL:
352 view = activity.getLayoutInflater().inflate(
353 R.layout.message_null, parent, false);
354 break;
355 case SENT:
356 view = activity.getLayoutInflater().inflate(
357 R.layout.message_sent, parent, false);
358 viewHolder.message_box = (LinearLayout) view
359 .findViewById(R.id.message_box);
360 viewHolder.contact_picture = (ImageView) view
361 .findViewById(R.id.message_photo);
362 viewHolder.download_button = (Button) view
363 .findViewById(R.id.download_button);
364 viewHolder.indicator = (ImageView) view
365 .findViewById(R.id.security_indicator);
366 viewHolder.image = (ImageView) view
367 .findViewById(R.id.message_image);
368 viewHolder.messageBody = (TextView) view
369 .findViewById(R.id.message_body);
370 viewHolder.time = (TextView) view
371 .findViewById(R.id.message_time);
372 viewHolder.indicatorReceived = (ImageView) view
373 .findViewById(R.id.indicator_received);
374 view.setTag(viewHolder);
375 break;
376 case RECEIVED:
377 view = activity.getLayoutInflater().inflate(
378 R.layout.message_received, parent, false);
379 viewHolder.message_box = (LinearLayout) view
380 .findViewById(R.id.message_box);
381 viewHolder.contact_picture = (ImageView) view
382 .findViewById(R.id.message_photo);
383 viewHolder.download_button = (Button) view
384 .findViewById(R.id.download_button);
385 viewHolder.indicator = (ImageView) view
386 .findViewById(R.id.security_indicator);
387 viewHolder.image = (ImageView) view
388 .findViewById(R.id.message_image);
389 viewHolder.messageBody = (TextView) view
390 .findViewById(R.id.message_body);
391 viewHolder.time = (TextView) view
392 .findViewById(R.id.message_time);
393 viewHolder.indicatorReceived = (ImageView) view
394 .findViewById(R.id.indicator_received);
395 view.setTag(viewHolder);
396 break;
397 case STATUS:
398 view = activity.getLayoutInflater().inflate(
399 R.layout.message_status, parent, false);
400 viewHolder.contact_picture = (ImageView) view
401 .findViewById(R.id.message_photo);
402 view.setTag(viewHolder);
403 break;
404 default:
405 viewHolder = null;
406 break;
407 }
408 } else {
409 viewHolder = (ViewHolder) view.getTag();
410 }
411
412 if (type == STATUS) {
413 if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
414 viewHolder.contact_picture.setImageBitmap(activity
415 .avatarService().get(
416 item.getConversation().getContact(),
417 activity.getPixel(32)));
418 viewHolder.contact_picture.setAlpha(0.5f);
419 viewHolder.contact_picture
420 .setOnClickListener(new OnClickListener() {
421
422 @Override
423 public void onClick(View v) {
424 String name = item.getConversation()
425 .getName();
426 String read = getContext()
427 .getString(
428 R.string.contact_has_read_up_to_this_point,
429 name);
430 Toast.makeText(getContext(), read,
431 Toast.LENGTH_SHORT).show();
432 }
433 });
434
435 }
436 return view;
437 } else if (type == NULL) {
438 if (position == getCount() - 1) {
439 view.getLayoutParams().height = 1;
440 } else {
441 view.getLayoutParams().height = 0;
442
443 }
444 view.setLayoutParams(view.getLayoutParams());
445 return view;
446 } else if (type == RECEIVED) {
447 Contact contact = item.getContact();
448 if (contact != null) {
449 viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(contact, activity.getPixel(48)));
450 } else if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
451 viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(getDisplayedMucCounterpart(item.getCounterpart()),
452 activity.getPixel(48)));
453 }
454 } else if (type == SENT) {
455 viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(item.getConversation().getAccount(), activity.getPixel(48)));
456 }
457
458 if (viewHolder != null && viewHolder.contact_picture != null) {
459 viewHolder.contact_picture
460 .setOnClickListener(new OnClickListener() {
461
462 @Override
463 public void onClick(View v) {
464 if (MessageAdapter.this.mOnContactPictureClickedListener != null) {
465 MessageAdapter.this.mOnContactPictureClickedListener
466 .onContactPictureClicked(item);
467 }
468
469 }
470 });
471 viewHolder.contact_picture
472 .setOnLongClickListener(new OnLongClickListener() {
473
474 @Override
475 public boolean onLongClick(View v) {
476 if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
477 MessageAdapter.this.mOnContactPictureLongClickedListener
478 .onContactPictureLongClicked(item);
479 return true;
480 } else {
481 return false;
482 }
483 }
484 });
485 }
486
487 if (item.getDownloadable() != null && item.getDownloadable().getStatus() != Downloadable.STATUS_UPLOADING) {
488 Downloadable d = item.getDownloadable();
489 if (d.getStatus() == Downloadable.STATUS_DOWNLOADING) {
490 if (item.getType() == Message.TYPE_FILE) {
491 displayInfoMessage(viewHolder,activity.getString(R.string.receiving_file,d.getMimeType(),d.getProgress()));
492 } else {
493 displayInfoMessage(viewHolder,activity.getString(R.string.receiving_image,d.getProgress()));
494 }
495 } else if (d.getStatus() == Downloadable.STATUS_CHECKING) {
496 displayInfoMessage(viewHolder,activity.getString(R.string.checking_image));
497 } else if (d.getStatus() == Downloadable.STATUS_DELETED) {
498 displayInfoMessage(viewHolder,activity.getString(R.string.image_file_deleted));
499 } else if (d.getStatus() == Downloadable.STATUS_OFFER) {
500 if (item.getType() == Message.TYPE_FILE) {
501 displayDownloadableMessage(viewHolder,item,activity.getString(R.string.download_file,d.getMimeType()));
502 } else {
503 displayDownloadableMessage(viewHolder, item,activity.getString(R.string.download_image));
504 }
505 } else if (d.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) {
506 displayDownloadableMessage(viewHolder, item,activity.getString(R.string.check_image_filesize));
507 } else if (d.getStatus() == Downloadable.STATUS_FAILED) {
508 displayInfoMessage(viewHolder, activity.getString(R.string.image_transmission_failed));
509 }
510 } else if (item.getType() == Message.TYPE_IMAGE && item.getEncryption() != Message.ENCRYPTION_PGP && item.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
511 displayImageMessage(viewHolder, item);
512 } else if (item.getType() == Message.TYPE_FILE && item.getEncryption() != Message.ENCRYPTION_PGP && item.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
513 if (item.getImageParams().width > 0) {
514 displayImageMessage(viewHolder,item);
515 } else {
516 displayOpenableMessage(viewHolder, item);
517 }
518 } else if (item.getEncryption() == Message.ENCRYPTION_PGP) {
519 if (activity.hasPgp()) {
520 displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message));
521 } else {
522 displayInfoMessage(viewHolder,
523 activity.getString(R.string.install_openkeychain));
524 if (viewHolder != null) {
525 viewHolder.message_box
526 .setOnClickListener(new OnClickListener() {
527
528 @Override
529 public void onClick(View v) {
530 activity.showInstallPgpDialog();
531 }
532 });
533 }
534 }
535 } else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
536 displayDecryptionFailed(viewHolder);
537 } else {
538 displayTextMessage(viewHolder, item);
539 }
540
541 displayStatus(viewHolder, item);
542
543 return view;
544 }
545
546 public void startDonwloadable(Message message) {
547 Downloadable downloadable = message.getDownloadable();
548 if (downloadable != null) {
549 if (!downloadable.start()) {
550 Toast.makeText(activity, R.string.not_connected_try_again,
551 Toast.LENGTH_SHORT).show();
552 }
553 }
554 }
555
556 public void openDonwloadable(DownloadableFile file) {
557 Log.d(Config.LOGTAG,"file "+file.getAbsolutePath());
558 Intent intent = new Intent(Intent.ACTION_VIEW);
559 intent.setDataAndType(Uri.fromFile(file), file.getMimeType());
560 getContext().startActivity(intent);
561 }
562
563 public interface OnContactPictureClicked {
564 public void onContactPictureClicked(Message message);
565 }
566
567 public interface OnContactPictureLongClicked {
568 public void onContactPictureLongClicked(Message message);
569 }
570
571 private static class ViewHolder {
572
573 protected LinearLayout message_box;
574 protected Button download_button;
575 protected ImageView image;
576 protected ImageView indicator;
577 protected ImageView indicatorReceived;
578 protected TextView time;
579 protected TextView messageBody;
580 protected ImageView contact_picture;
581
582 }
583}