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 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 (conversation.getMode() == Conversation.MODE_SINGLE) {
414 viewHolder.contact_picture.setImageBitmap(activity
415 .avatarService().get(conversation.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 = conversation.getName();
424 String read = getContext()
425 .getString(
426 R.string.contact_has_read_up_to_this_point,
427 name);
428 Toast.makeText(getContext(), read,
429 Toast.LENGTH_SHORT).show();
430 }
431 });
432
433 }
434 return view;
435 } else if (type == NULL) {
436 if (position == getCount() - 1) {
437 view.getLayoutParams().height = 1;
438 } else {
439 view.getLayoutParams().height = 0;
440
441 }
442 view.setLayoutParams(view.getLayoutParams());
443 return view;
444 } else if (type == RECEIVED) {
445 Contact contact = message.getContact();
446 if (contact != null) {
447 viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(contact, activity.getPixel(48)));
448 } else if (conversation.getMode() == Conversation.MODE_MULTI) {
449 viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(getDisplayedMucCounterpart(message.getCounterpart()),
450 activity.getPixel(48)));
451 }
452 } else if (type == SENT && viewHolder.contact_picture != null) {
453 viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(account, activity.getPixel(48)));
454 }
455
456 if (viewHolder != null && viewHolder.contact_picture != null) {
457 viewHolder.contact_picture
458 .setOnClickListener(new OnClickListener() {
459
460 @Override
461 public void onClick(View v) {
462 if (MessageAdapter.this.mOnContactPictureClickedListener != null) {
463 MessageAdapter.this.mOnContactPictureClickedListener
464 .onContactPictureClicked(message);
465 }
466
467 }
468 });
469 viewHolder.contact_picture
470 .setOnLongClickListener(new OnLongClickListener() {
471
472 @Override
473 public boolean onLongClick(View v) {
474 if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
475 MessageAdapter.this.mOnContactPictureLongClickedListener
476 .onContactPictureLongClicked(message);
477 return true;
478 } else {
479 return false;
480 }
481 }
482 });
483 }
484
485 if (message.getDownloadable() != null && message.getDownloadable().getStatus() != Downloadable.STATUS_UPLOADING) {
486 Downloadable d = message.getDownloadable();
487 if (d.getStatus() == Downloadable.STATUS_DOWNLOADING) {
488 if (message.getType() == Message.TYPE_FILE) {
489 displayInfoMessage(viewHolder,activity.getString(R.string.receiving_file,d.getMimeType(),d.getProgress()));
490 } else {
491 displayInfoMessage(viewHolder,activity.getString(R.string.receiving_image,d.getProgress()));
492 }
493 } else if (d.getStatus() == Downloadable.STATUS_CHECKING) {
494 displayInfoMessage(viewHolder,activity.getString(R.string.checking_image));
495 } else if (d.getStatus() == Downloadable.STATUS_DELETED) {
496 if (message.getType() == Message.TYPE_FILE) {
497 displayInfoMessage(viewHolder, activity.getString(R.string.file_deleted));
498 } else {
499 displayInfoMessage(viewHolder, activity.getString(R.string.image_file_deleted));
500 }
501 } else if (d.getStatus() == Downloadable.STATUS_OFFER) {
502 if (message.getType() == Message.TYPE_FILE) {
503 displayDownloadableMessage(viewHolder,message,activity.getString(R.string.download_file,d.getMimeType()));
504 } else {
505 displayDownloadableMessage(viewHolder, message,activity.getString(R.string.download_image));
506 }
507 } else if (d.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) {
508 displayDownloadableMessage(viewHolder, message,activity.getString(R.string.check_image_filesize));
509 } else if (d.getStatus() == Downloadable.STATUS_FAILED) {
510 if (message.getType() == Message.TYPE_FILE) {
511 displayInfoMessage(viewHolder, activity.getString(R.string.file_transmission_failed));
512 } else {
513 displayInfoMessage(viewHolder, activity.getString(R.string.image_transmission_failed));
514 }
515 }
516 } else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
517 displayImageMessage(viewHolder, message);
518 } else if (message.getType() == Message.TYPE_FILE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
519 if (message.getImageParams().width > 0) {
520 displayImageMessage(viewHolder,message);
521 } else {
522 displayOpenableMessage(viewHolder, message);
523 }
524 } else if (message.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 (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
542 displayDecryptionFailed(viewHolder);
543 } else {
544 displayTextMessage(viewHolder, message);
545 }
546
547 displayStatus(viewHolder, message);
548
549 return view;
550 }
551
552 public void startDonwloadable(Message message) {
553 Downloadable downloadable = message.getDownloadable();
554 if (downloadable != null) {
555 if (!downloadable.start()) {
556 Toast.makeText(activity, R.string.not_connected_try_again,
557 Toast.LENGTH_SHORT).show();
558 }
559 }
560 }
561
562 public void openDonwloadable(DownloadableFile file) {
563 if (!file.exists()) {
564 Toast.makeText(activity,R.string.file_deleted,Toast.LENGTH_SHORT).show();
565 return;
566 }
567 Intent openIntent = new Intent(Intent.ACTION_VIEW);
568 openIntent.setDataAndType(Uri.fromFile(file), file.getMimeType());
569 PackageManager manager = activity.getPackageManager();
570 List<ResolveInfo> infos = manager.queryIntentActivities(openIntent, 0);
571 if (infos.size() > 0) {
572 getContext().startActivity(openIntent);
573 } else {
574 Toast.makeText(activity,R.string.no_application_found_to_open_file,Toast.LENGTH_SHORT).show();
575 }
576 }
577
578 public interface OnContactPictureClicked {
579 public void onContactPictureClicked(Message message);
580 }
581
582 public interface OnContactPictureLongClicked {
583 public void onContactPictureLongClicked(Message message);
584 }
585
586 private static class ViewHolder {
587
588 protected LinearLayout message_box;
589 protected Button download_button;
590 protected ImageView image;
591 protected ImageView indicator;
592 protected ImageView indicatorReceived;
593 protected TextView time;
594 protected TextView messageBody;
595 protected ImageView contact_picture;
596
597 }
598}