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.view.View;
14import android.view.View.OnClickListener;
15import android.view.View.OnLongClickListener;
16import android.view.ViewGroup;
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.util.List;
25
26import eu.siacs.conversations.Config;
27import eu.siacs.conversations.R;
28import eu.siacs.conversations.entities.Account;
29import eu.siacs.conversations.entities.Contact;
30import eu.siacs.conversations.entities.Conversation;
31import eu.siacs.conversations.entities.Downloadable;
32import eu.siacs.conversations.entities.DownloadableFile;
33import eu.siacs.conversations.entities.Message;
34import eu.siacs.conversations.entities.Message.ImageParams;
35import eu.siacs.conversations.ui.ConversationActivity;
36import eu.siacs.conversations.utils.UIHelper;
37import eu.siacs.conversations.xmpp.jid.Jid;
38
39public class MessageAdapter extends ArrayAdapter<Message> {
40
41 private static final int SENT = 0;
42 private static final int RECEIVED = 1;
43 private static final int STATUS = 2;
44 private static final int NULL = 3;
45
46 private ConversationActivity activity;
47
48 private DisplayMetrics metrics;
49
50 private OnContactPictureClicked mOnContactPictureClickedListener;
51 private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
52
53 private OnLongClickListener openContextMenu = new OnLongClickListener() {
54
55 @Override
56 public boolean onLongClick(View v) {
57 v.showContextMenu();
58 return true;
59 }
60 };
61
62 public MessageAdapter(ConversationActivity activity, List<Message> messages) {
63 super(activity, 0, messages);
64 this.activity = activity;
65 metrics = getContext().getResources().getDisplayMetrics();
66 }
67
68 public void setOnContactPictureClicked(OnContactPictureClicked listener) {
69 this.mOnContactPictureClickedListener = listener;
70 }
71
72 public void setOnContactPictureLongClicked(
73 OnContactPictureLongClicked listener) {
74 this.mOnContactPictureLongClickedListener = listener;
75 }
76
77 @Override
78 public int getViewTypeCount() {
79 return 4;
80 }
81
82 @Override
83 public int getItemViewType(int position) {
84 if (getItem(position).wasMergedIntoPrevious()) {
85 return NULL;
86 } else if (getItem(position).getType() == Message.TYPE_STATUS) {
87 return STATUS;
88 } else if (getItem(position).getStatus() <= Message.STATUS_RECEIVED) {
89 return RECEIVED;
90 } else {
91 return SENT;
92 }
93 }
94
95 private void displayStatus(ViewHolder viewHolder, Message message) {
96 String filesize = null;
97 String info = null;
98 boolean error = false;
99 if (viewHolder.indicatorReceived != null) {
100 viewHolder.indicatorReceived.setVisibility(View.GONE);
101 }
102 boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
103 && message.getMergedStatus() <= Message.STATUS_RECEIVED;
104 if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getDownloadable() != null) {
105 ImageParams params = message.getImageParams();
106 if (params.size > (1.5 * 1024 * 1024)) {
107 filesize = params.size / (1024 * 1024)+ " MB";
108 } else if (params.size > 0) {
109 filesize = params.size / 1024 + " KB";
110 }
111 if (message.getDownloadable() != null && message.getDownloadable().getStatus() == Downloadable.STATUS_FAILED) {
112 error = true;
113 }
114 }
115 switch (message.getMergedStatus()) {
116 case Message.STATUS_WAITING:
117 info = getContext().getString(R.string.waiting);
118 break;
119 case Message.STATUS_UNSEND:
120 Downloadable d = message.getDownloadable();
121 if (d!=null) {
122 info = getContext().getString(R.string.sending_file,d.getProgress());
123 } else {
124 info = getContext().getString(R.string.sending);
125 }
126 break;
127 case Message.STATUS_OFFERED:
128 info = getContext().getString(R.string.offering);
129 break;
130 case Message.STATUS_SEND_RECEIVED:
131 if (activity.indicateReceived()) {
132 viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
133 }
134 break;
135 case Message.STATUS_SEND_DISPLAYED:
136 if (activity.indicateReceived()) {
137 viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
138 }
139 break;
140 case Message.STATUS_SEND_FAILED:
141 info = getContext().getString(R.string.send_failed);
142 error = true;
143 break;
144 default:
145 if (multiReceived) {
146 Contact contact = message.getContact();
147 if (contact != null) {
148 info = contact.getDisplayName();
149 } else {
150 info = getDisplayedMucCounterpart(message.getCounterpart());
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, String text) {
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(text);
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 final String to;
239 if (message.getCounterpart() != null) {
240 to = message.getCounterpart().getResourcepart();
241 } else {
242 to = "";
243 }
244 privateMarker = activity.getString(R.string.private_message_to, to);
245 }
246 SpannableString span = new SpannableString(privateMarker + " "
247 + message.getBody());
248 span.setSpan(
249 new ForegroundColorSpan(activity
250 .getSecondaryTextColor()), 0, privateMarker
251 .length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
252 span.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0,
253 privateMarker.length(),
254 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
255 viewHolder.messageBody.setText(span);
256 }
257 } else {
258 viewHolder.messageBody.setText("");
259 }
260 viewHolder.messageBody.setTextColor(activity.getPrimaryTextColor());
261 viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
262 viewHolder.messageBody.setTextIsSelectable(true);
263 }
264
265 private void displayDownloadableMessage(ViewHolder viewHolder,
266 final Message message, String text) {
267 viewHolder.image.setVisibility(View.GONE);
268 viewHolder.messageBody.setVisibility(View.GONE);
269 viewHolder.download_button.setVisibility(View.VISIBLE);
270 viewHolder.download_button.setText(text);
271 viewHolder.download_button.setOnClickListener(new OnClickListener() {
272
273 @Override
274 public void onClick(View v) {
275 startDonwloadable(message);
276 }
277 });
278 viewHolder.download_button.setOnLongClickListener(openContextMenu);
279 }
280
281 private void displayOpenableMessage(ViewHolder viewHolder,final Message message) {
282 final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
283 viewHolder.image.setVisibility(View.GONE);
284 viewHolder.messageBody.setVisibility(View.GONE);
285 viewHolder.download_button.setVisibility(View.VISIBLE);
286 viewHolder.download_button.setText(activity.getString(R.string.open_file,file.getMimeType()));
287 viewHolder.download_button.setOnClickListener(new OnClickListener() {
288
289 @Override
290 public void onClick(View v) {
291 openDonwloadable(file);
292 }
293 });
294 viewHolder.download_button.setOnLongClickListener(openContextMenu);
295 }
296
297 private void displayImageMessage(ViewHolder viewHolder,
298 final Message message) {
299 if (viewHolder.download_button != null) {
300 viewHolder.download_button.setVisibility(View.GONE);
301 }
302 viewHolder.messageBody.setVisibility(View.GONE);
303 viewHolder.image.setVisibility(View.VISIBLE);
304 ImageParams params = message.getImageParams();
305 double target = metrics.density * 288;
306 int scalledW;
307 int scalledH;
308 if (params.width <= params.height) {
309 scalledW = (int) (params.width / ((double) params.height / target));
310 scalledH = (int) target;
311 } else {
312 scalledW = (int) target;
313 scalledH = (int) (params.height / ((double) params.width / target));
314 }
315 viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(
316 scalledW, scalledH));
317 activity.loadBitmap(message, viewHolder.image);
318 viewHolder.image.setOnClickListener(new OnClickListener() {
319
320 @Override
321 public void onClick(View v) {
322 Intent intent = new Intent(Intent.ACTION_VIEW);
323 intent.setDataAndType(activity.xmppConnectionService
324 .getFileBackend().getJingleFileUri(message), "image/*");
325 getContext().startActivity(intent);
326 }
327 });
328 viewHolder.image.setOnLongClickListener(openContextMenu);
329 }
330
331 private String getDisplayedMucCounterpart(final Jid counterpart) {
332 if (counterpart==null) {
333 return "";
334 } else if (!counterpart.isBareJid()) {
335 return counterpart.getResourcepart();
336 } else {
337 return counterpart.toString();
338 }
339 }
340
341 @Override
342 public View getView(int position, View view, ViewGroup parent) {
343 final Message message = getItem(position);
344 final Conversation conversation = message.getConversation();
345 final Account account = conversation.getAccount();
346 final 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 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 break;
395 case STATUS:
396 view = activity.getLayoutInflater().inflate(
397 R.layout.message_status, parent, false);
398 viewHolder.contact_picture = (ImageView) view
399 .findViewById(R.id.message_photo);
400 break;
401 default:
402 viewHolder = null;
403 break;
404 }
405 view.setTag(viewHolder);
406 } else {
407 viewHolder = (ViewHolder) view.getTag();
408 if (viewHolder == null) {
409 return view;
410 }
411 }
412
413 if (type == STATUS) {
414 if (conversation.getMode() == Conversation.MODE_SINGLE) {
415 viewHolder.contact_picture.setImageBitmap(activity
416 .avatarService().get(conversation.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 = conversation.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 = message.getContact();
447 if (contact != null) {
448 viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(contact, activity.getPixel(48)));
449 } else if (conversation.getMode() == Conversation.MODE_MULTI) {
450 viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(getDisplayedMucCounterpart(message.getCounterpart()),
451 activity.getPixel(48)));
452 }
453 } else if (type == SENT && viewHolder.contact_picture != null) {
454 viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(account, 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(message);
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(message);
478 return true;
479 } else {
480 return false;
481 }
482 }
483 });
484 }
485
486 if (message.getDownloadable() != null && message.getDownloadable().getStatus() != Downloadable.STATUS_UPLOADING) {
487 Downloadable d = message.getDownloadable();
488 if (d.getStatus() == Downloadable.STATUS_DOWNLOADING) {
489 if (message.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 if (message.getType() == Message.TYPE_FILE) {
498 displayInfoMessage(viewHolder, activity.getString(R.string.file_deleted));
499 } else {
500 displayInfoMessage(viewHolder, activity.getString(R.string.image_file_deleted));
501 }
502 } else if (d.getStatus() == Downloadable.STATUS_OFFER) {
503 if (message.getType() == Message.TYPE_FILE) {
504 displayDownloadableMessage(viewHolder,message,activity.getString(R.string.download_file,d.getMimeType()));
505 } else {
506 displayDownloadableMessage(viewHolder, message,activity.getString(R.string.download_image));
507 }
508 } else if (d.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) {
509 displayDownloadableMessage(viewHolder, message,activity.getString(R.string.check_image_filesize));
510 } else if (d.getStatus() == Downloadable.STATUS_FAILED) {
511 if (message.getType() == Message.TYPE_FILE) {
512 displayInfoMessage(viewHolder, activity.getString(R.string.file_transmission_failed));
513 } else {
514 displayInfoMessage(viewHolder, activity.getString(R.string.image_transmission_failed));
515 }
516 }
517 } else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
518 displayImageMessage(viewHolder, message);
519 } else if (message.getType() == Message.TYPE_FILE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
520 if (message.getImageParams().width > 0) {
521 displayImageMessage(viewHolder,message);
522 } else {
523 displayOpenableMessage(viewHolder, message);
524 }
525 } else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
526 if (activity.hasPgp()) {
527 displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message));
528 } else {
529 displayInfoMessage(viewHolder,
530 activity.getString(R.string.install_openkeychain));
531 if (viewHolder != null) {
532 viewHolder.message_box
533 .setOnClickListener(new OnClickListener() {
534
535 @Override
536 public void onClick(View v) {
537 activity.showInstallPgpDialog();
538 }
539 });
540 }
541 }
542 } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
543 displayDecryptionFailed(viewHolder);
544 } else {
545 displayTextMessage(viewHolder, message);
546 }
547
548 displayStatus(viewHolder, message);
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(DownloadableFile file) {
564 if (!file.exists()) {
565 Toast.makeText(activity,R.string.file_deleted,Toast.LENGTH_SHORT).show();
566 return;
567 }
568 Intent openIntent = new Intent(Intent.ACTION_VIEW);
569 openIntent.setDataAndType(Uri.fromFile(file), file.getMimeType());
570 PackageManager manager = activity.getPackageManager();
571 List<ResolveInfo> infos = manager.queryIntentActivities(openIntent, 0);
572 if (infos.size() > 0) {
573 getContext().startActivity(openIntent);
574 } else {
575 Toast.makeText(activity,R.string.no_application_found_to_open_file,Toast.LENGTH_SHORT).show();
576 }
577 }
578
579 public interface OnContactPictureClicked {
580 public void onContactPictureClicked(Message message);
581 }
582
583 public interface OnContactPictureLongClicked {
584 public void onContactPictureLongClicked(Message message);
585 }
586
587 private static class ViewHolder {
588
589 protected LinearLayout message_box;
590 protected Button download_button;
591 protected ImageView image;
592 protected ImageView indicator;
593 protected ImageView indicatorReceived;
594 protected TextView time;
595 protected TextView messageBody;
596 protected ImageView contact_picture;
597
598 }
599}