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