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.Spanned;
11import android.text.style.ForegroundColorSpan;
12import android.text.style.RelativeSizeSpan;
13import android.text.style.StyleSpan;
14import android.util.DisplayMetrics;
15import android.view.View;
16import android.view.View.OnClickListener;
17import android.view.View.OnLongClickListener;
18import android.view.ViewGroup;
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.util.List;
27
28import eu.siacs.conversations.R;
29import eu.siacs.conversations.entities.Account;
30import eu.siacs.conversations.entities.Contact;
31import eu.siacs.conversations.entities.Conversation;
32import eu.siacs.conversations.entities.Transferable;
33import eu.siacs.conversations.entities.DownloadableFile;
34import eu.siacs.conversations.entities.Message;
35import eu.siacs.conversations.entities.Message.FileParams;
36import eu.siacs.conversations.ui.ConversationActivity;
37import eu.siacs.conversations.utils.GeoHelper;
38import eu.siacs.conversations.utils.UIHelper;
39
40public class MessageAdapter extends ArrayAdapter<Message> {
41
42 private static final int SENT = 0;
43 private static final int RECEIVED = 1;
44 private static final int STATUS = 2;
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 3;
80 }
81
82 @Override
83 public int getItemViewType(int position) {
84 if (getItem(position).getType() == Message.TYPE_STATUS) {
85 return STATUS;
86 } else if (getItem(position).getStatus() <= Message.STATUS_RECEIVED) {
87 return RECEIVED;
88 } else {
89 return SENT;
90 }
91 }
92
93 private void displayStatus(ViewHolder viewHolder, Message message) {
94 String filesize = null;
95 String info = null;
96 boolean error = false;
97 if (viewHolder.indicatorReceived != null) {
98 viewHolder.indicatorReceived.setVisibility(View.GONE);
99 }
100 boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
101 && message.getMergedStatus() <= Message.STATUS_RECEIVED;
102 if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getTransferable() != null) {
103 FileParams params = message.getFileParams();
104 if (params.size > (1.5 * 1024 * 1024)) {
105 filesize = params.size / (1024 * 1024)+ " MiB";
106 } else if (params.size > 0) {
107 filesize = params.size / 1024 + " KiB";
108 }
109 if (message.getTransferable() != null && message.getTransferable().getStatus() == Transferable.STATUS_FAILED) {
110 error = true;
111 }
112 }
113 switch (message.getMergedStatus()) {
114 case Message.STATUS_WAITING:
115 info = getContext().getString(R.string.waiting);
116 break;
117 case Message.STATUS_UNSEND:
118 Transferable d = message.getTransferable();
119 if (d!=null) {
120 info = getContext().getString(R.string.sending_file,d.getProgress());
121 } else {
122 info = getContext().getString(R.string.sending);
123 }
124 break;
125 case Message.STATUS_OFFERED:
126 info = getContext().getString(R.string.offering);
127 break;
128 case Message.STATUS_SEND_RECEIVED:
129 if (activity.indicateReceived()) {
130 viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
131 }
132 break;
133 case Message.STATUS_SEND_DISPLAYED:
134 if (activity.indicateReceived()) {
135 viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
136 }
137 break;
138 case Message.STATUS_SEND_FAILED:
139 info = getContext().getString(R.string.send_failed);
140 error = true;
141 break;
142 default:
143 if (multiReceived) {
144 info = UIHelper.getMessageDisplayName(message);
145 }
146 break;
147 }
148 if (error) {
149 viewHolder.time.setTextColor(activity.getWarningTextColor());
150 } else {
151 viewHolder.time.setTextColor(activity.getSecondaryTextColor());
152 }
153 if (message.getEncryption() == Message.ENCRYPTION_NONE) {
154 viewHolder.indicator.setVisibility(View.GONE);
155 } else {
156 viewHolder.indicator.setVisibility(View.VISIBLE);
157 }
158
159 String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(),
160 message.getMergedTimeSent());
161 if (message.getStatus() <= Message.STATUS_RECEIVED) {
162 if ((filesize != null) && (info != null)) {
163 viewHolder.time.setText(formatedTime + " \u00B7 " + filesize +" \u00B7 " + info);
164 } else if ((filesize == null) && (info != null)) {
165 viewHolder.time.setText(formatedTime + " \u00B7 " + info);
166 } else if ((filesize != null) && (info == null)) {
167 viewHolder.time.setText(formatedTime + " \u00B7 " + filesize);
168 } else {
169 viewHolder.time.setText(formatedTime);
170 }
171 } else {
172 if ((filesize != null) && (info != null)) {
173 viewHolder.time.setText(filesize + " \u00B7 " + info);
174 } else if ((filesize == null) && (info != null)) {
175 if (error) {
176 viewHolder.time.setText(info + " \u00B7 " + formatedTime);
177 } else {
178 viewHolder.time.setText(info);
179 }
180 } else if ((filesize != null) && (info == null)) {
181 viewHolder.time.setText(filesize + " \u00B7 " + formatedTime);
182 } else {
183 viewHolder.time.setText(formatedTime);
184 }
185 }
186 }
187
188 private void displayInfoMessage(ViewHolder viewHolder, String text) {
189 if (viewHolder.download_button != null) {
190 viewHolder.download_button.setVisibility(View.GONE);
191 }
192 viewHolder.image.setVisibility(View.GONE);
193 viewHolder.messageBody.setVisibility(View.VISIBLE);
194 viewHolder.messageBody.setText(text);
195 viewHolder.messageBody.setTextColor(activity.getSecondaryTextColor());
196 viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
197 viewHolder.messageBody.setTextIsSelectable(false);
198 }
199
200 private void displayDecryptionFailed(ViewHolder viewHolder) {
201 if (viewHolder.download_button != null) {
202 viewHolder.download_button.setVisibility(View.GONE);
203 }
204 viewHolder.image.setVisibility(View.GONE);
205 viewHolder.messageBody.setVisibility(View.VISIBLE);
206 viewHolder.messageBody.setText(getContext().getString(
207 R.string.decryption_failed));
208 viewHolder.messageBody.setTextColor(activity.getWarningTextColor());
209 viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
210 viewHolder.messageBody.setTextIsSelectable(false);
211 }
212
213 private void displayHeartMessage(final ViewHolder viewHolder, final String body) {
214 if (viewHolder.download_button != null) {
215 viewHolder.download_button.setVisibility(View.GONE);
216 }
217 viewHolder.image.setVisibility(View.GONE);
218 viewHolder.messageBody.setVisibility(View.VISIBLE);
219 viewHolder.messageBody.setIncludeFontPadding(false);
220 Spannable span = new SpannableString(body);
221 span.setSpan(new RelativeSizeSpan(4.0f), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
222 span.setSpan(new ForegroundColorSpan(activity.getWarningTextColor()), 0, body.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
223 viewHolder.messageBody.setText(span);
224 }
225
226 private void displayTextMessage(final ViewHolder viewHolder, final Message message) {
227 if (viewHolder.download_button != null) {
228 viewHolder.download_button.setVisibility(View.GONE);
229 }
230 viewHolder.image.setVisibility(View.GONE);
231 viewHolder.messageBody.setVisibility(View.VISIBLE);
232 viewHolder.messageBody.setIncludeFontPadding(true);
233 if (message.getBody() != null) {
234 final String nick = UIHelper.getMessageDisplayName(message);
235 final String body = message.getMergedBody().replaceAll("^" + Message.ME_COMMAND,nick + " ");
236 final SpannableString formattedBody = new SpannableString(body);
237 int i = body.indexOf(Message.MERGE_SEPARATOR);
238 while(i >= 0) {
239 final int end = i + Message.MERGE_SEPARATOR.length();
240 formattedBody.setSpan(new RelativeSizeSpan(0.3f),i,end,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
241 i = body.indexOf(Message.MERGE_SEPARATOR,end);
242 }
243 if (message.getType() != Message.TYPE_PRIVATE) {
244 if (message.hasMeCommand()) {
245 final Spannable span = new SpannableString(formattedBody);
246 span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(),
247 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
248 viewHolder.messageBody.setText(span);
249 } else {
250 viewHolder.messageBody.setText(formattedBody);
251 }
252 } else {
253 String privateMarker;
254 if (message.getStatus() <= Message.STATUS_RECEIVED) {
255 privateMarker = activity
256 .getString(R.string.private_message);
257 } else {
258 final String to;
259 if (message.getCounterpart() != null) {
260 to = message.getCounterpart().getResourcepart();
261 } else {
262 to = "";
263 }
264 privateMarker = activity.getString(R.string.private_message_to, to);
265 }
266 final Spannable span = new SpannableString(privateMarker + " "
267 + formattedBody);
268 span.setSpan(new ForegroundColorSpan(activity
269 .getSecondaryTextColor()), 0, privateMarker
270 .length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
271 span.setSpan(new StyleSpan(Typeface.BOLD), 0,
272 privateMarker.length(),
273 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
274 if (message.hasMeCommand()) {
275 span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), privateMarker.length() + 1,
276 privateMarker.length() + 1 + nick.length(),
277 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
278 }
279 viewHolder.messageBody.setText(span);
280 }
281 } else {
282 viewHolder.messageBody.setText("");
283 }
284 viewHolder.messageBody.setTextColor(activity.getPrimaryTextColor());
285 viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
286 viewHolder.messageBody.setTextIsSelectable(true);
287 }
288
289 private void displayDownloadableMessage(ViewHolder viewHolder,
290 final Message message, String text) {
291 viewHolder.image.setVisibility(View.GONE);
292 viewHolder.messageBody.setVisibility(View.GONE);
293 viewHolder.download_button.setVisibility(View.VISIBLE);
294 viewHolder.download_button.setText(text);
295 viewHolder.download_button.setOnClickListener(new OnClickListener() {
296
297 @Override
298 public void onClick(View v) {
299 startDownloadable(message);
300 }
301 });
302 viewHolder.download_button.setOnLongClickListener(openContextMenu);
303 }
304
305 private void displayOpenableMessage(ViewHolder viewHolder,final Message message) {
306 viewHolder.image.setVisibility(View.GONE);
307 viewHolder.messageBody.setVisibility(View.GONE);
308 viewHolder.download_button.setVisibility(View.VISIBLE);
309 viewHolder.download_button.setText(activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity, message)));
310 viewHolder.download_button.setOnClickListener(new OnClickListener() {
311
312 @Override
313 public void onClick(View v) {
314 openDownloadable(message);
315 }
316 });
317 viewHolder.download_button.setOnLongClickListener(openContextMenu);
318 }
319
320 private void displayLocationMessage(ViewHolder viewHolder, final Message message) {
321 viewHolder.image.setVisibility(View.GONE);
322 viewHolder.messageBody.setVisibility(View.GONE);
323 viewHolder.download_button.setVisibility(View.VISIBLE);
324 viewHolder.download_button.setText(R.string.show_location);
325 viewHolder.download_button.setOnClickListener(new OnClickListener() {
326
327 @Override
328 public void onClick(View v) {
329 showLocation(message);
330 }
331 });
332 viewHolder.download_button.setOnLongClickListener(openContextMenu);
333 }
334
335 private void displayImageMessage(ViewHolder viewHolder,
336 final Message message) {
337 if (viewHolder.download_button != null) {
338 viewHolder.download_button.setVisibility(View.GONE);
339 }
340 viewHolder.messageBody.setVisibility(View.GONE);
341 viewHolder.image.setVisibility(View.VISIBLE);
342 FileParams params = message.getFileParams();
343 double target = metrics.density * 288;
344 int scalledW;
345 int scalledH;
346 if (params.width <= params.height) {
347 scalledW = (int) (params.width / ((double) params.height / target));
348 scalledH = (int) target;
349 } else {
350 scalledW = (int) target;
351 scalledH = (int) (params.height / ((double) params.width / target));
352 }
353 viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(
354 scalledW, scalledH));
355 activity.loadBitmap(message, viewHolder.image);
356 viewHolder.image.setOnClickListener(new OnClickListener() {
357
358 @Override
359 public void onClick(View v) {
360 Intent intent = new Intent(Intent.ACTION_VIEW);
361 intent.setDataAndType(activity.xmppConnectionService
362 .getFileBackend().getJingleFileUri(message), "image/*");
363 getContext().startActivity(intent);
364 }
365 });
366 viewHolder.image.setOnLongClickListener(openContextMenu);
367 }
368
369 @Override
370 public View getView(int position, View view, ViewGroup parent) {
371 final Message message = getItem(position);
372 final Conversation conversation = message.getConversation();
373 final Account account = conversation.getAccount();
374 final int type = getItemViewType(position);
375 ViewHolder viewHolder;
376 if (view == null) {
377 viewHolder = new ViewHolder();
378 switch (type) {
379 case SENT:
380 view = activity.getLayoutInflater().inflate(
381 R.layout.message_sent, parent, false);
382 viewHolder.message_box = (LinearLayout) view
383 .findViewById(R.id.message_box);
384 viewHolder.contact_picture = (ImageView) view
385 .findViewById(R.id.message_photo);
386 viewHolder.download_button = (Button) view
387 .findViewById(R.id.download_button);
388 viewHolder.indicator = (ImageView) view
389 .findViewById(R.id.security_indicator);
390 viewHolder.image = (ImageView) view
391 .findViewById(R.id.message_image);
392 viewHolder.messageBody = (TextView) view
393 .findViewById(R.id.message_body);
394 viewHolder.time = (TextView) view
395 .findViewById(R.id.message_time);
396 viewHolder.indicatorReceived = (ImageView) view
397 .findViewById(R.id.indicator_received);
398 break;
399 case RECEIVED:
400 view = activity.getLayoutInflater().inflate(
401 R.layout.message_received, parent, false);
402 viewHolder.message_box = (LinearLayout) view
403 .findViewById(R.id.message_box);
404 viewHolder.contact_picture = (ImageView) view
405 .findViewById(R.id.message_photo);
406 viewHolder.download_button = (Button) view
407 .findViewById(R.id.download_button);
408 viewHolder.indicator = (ImageView) view
409 .findViewById(R.id.security_indicator);
410 viewHolder.image = (ImageView) view
411 .findViewById(R.id.message_image);
412 viewHolder.messageBody = (TextView) view
413 .findViewById(R.id.message_body);
414 viewHolder.time = (TextView) view
415 .findViewById(R.id.message_time);
416 viewHolder.indicatorReceived = (ImageView) view
417 .findViewById(R.id.indicator_received);
418 break;
419 case STATUS:
420 view = activity.getLayoutInflater().inflate(R.layout.message_status, parent, false);
421 viewHolder.contact_picture = (ImageView) view.findViewById(R.id.message_photo);
422 viewHolder.status_message = (TextView) view.findViewById(R.id.status_message);
423 break;
424 default:
425 viewHolder = null;
426 break;
427 }
428 view.setTag(viewHolder);
429 } else {
430 viewHolder = (ViewHolder) view.getTag();
431 if (viewHolder == null) {
432 return view;
433 }
434 }
435
436 if (type == STATUS) {
437 if (conversation.getMode() == Conversation.MODE_SINGLE) {
438 viewHolder.contact_picture.setImageBitmap(activity
439 .avatarService().get(conversation.getContact(),
440 activity.getPixel(32)));
441 viewHolder.contact_picture.setAlpha(0.5f);
442 viewHolder.status_message.setText(message.getBody());
443 }
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(
451 UIHelper.getMessageDisplayName(message),
452 activity.getPixel(48)));
453 }
454 } else if (type == SENT) {
455 viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(account, activity.getPixel(48)));
456 }
457
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 final Transferable transferable = message.getTransferable();
486 if (transferable != null && transferable.getStatus() != Transferable.STATUS_UPLOADING) {
487 if (transferable.getStatus() == Transferable.STATUS_OFFER) {
488 displayDownloadableMessage(viewHolder,message,activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, message)));
489 } else if (transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) {
490 displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message)));
491 } else {
492 displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first);
493 }
494 } else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
495 displayImageMessage(viewHolder, message);
496 } else if (message.getType() == Message.TYPE_FILE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
497 if (message.getFileParams().width > 0) {
498 displayImageMessage(viewHolder,message);
499 } else {
500 displayOpenableMessage(viewHolder, message);
501 }
502 } else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
503 if (activity.hasPgp()) {
504 displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message));
505 } else {
506 displayInfoMessage(viewHolder,
507 activity.getString(R.string.install_openkeychain));
508 if (viewHolder != null) {
509 viewHolder.message_box
510 .setOnClickListener(new OnClickListener() {
511
512 @Override
513 public void onClick(View v) {
514 activity.showInstallPgpDialog();
515 }
516 });
517 }
518 }
519 } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
520 displayDecryptionFailed(viewHolder);
521 } else {
522 if (GeoHelper.isGeoUri(message.getBody())) {
523 displayLocationMessage(viewHolder,message);
524 } else if (message.bodyIsHeart()) {
525 displayHeartMessage(viewHolder, message.getBody().trim());
526 } else if (message.treatAsDownloadable() == Message.Decision.MUST) {
527 displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message)));
528 } else {
529 displayTextMessage(viewHolder, message);
530 }
531 }
532
533 displayStatus(viewHolder, message);
534
535 return view;
536 }
537
538 public void startDownloadable(Message message) {
539 Transferable transferable = message.getTransferable();
540 if (transferable != null) {
541 if (!transferable.start()) {
542 Toast.makeText(activity, R.string.not_connected_try_again,
543 Toast.LENGTH_SHORT).show();
544 }
545 } else if (message.treatAsDownloadable() != Message.Decision.NEVER) {
546 activity.xmppConnectionService.getHttpConnectionManager().createNewConnection(message);
547 }
548 }
549
550 public void openDownloadable(Message message) {
551 DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
552 if (!file.exists()) {
553 Toast.makeText(activity,R.string.file_deleted,Toast.LENGTH_SHORT).show();
554 return;
555 }
556 Intent openIntent = new Intent(Intent.ACTION_VIEW);
557 openIntent.setDataAndType(Uri.fromFile(file), file.getMimeType());
558 PackageManager manager = activity.getPackageManager();
559 List<ResolveInfo> infos = manager.queryIntentActivities(openIntent, 0);
560 if (infos.size() > 0) {
561 getContext().startActivity(openIntent);
562 } else {
563 Toast.makeText(activity,R.string.no_application_found_to_open_file,Toast.LENGTH_SHORT).show();
564 }
565 }
566
567 public void showLocation(Message message) {
568 for(Intent intent : GeoHelper.createGeoIntentsFromMessage(message)) {
569 if (intent.resolveActivity(getContext().getPackageManager()) != null) {
570 getContext().startActivity(intent);
571 return;
572 }
573 }
574 Toast.makeText(activity,R.string.no_application_found_to_display_location,Toast.LENGTH_SHORT).show();
575 }
576
577 public interface OnContactPictureClicked {
578 public void onContactPictureClicked(Message message);
579 }
580
581 public interface OnContactPictureLongClicked {
582 public void onContactPictureLongClicked(Message message);
583 }
584
585 private static class ViewHolder {
586
587 protected LinearLayout message_box;
588 protected Button download_button;
589 protected ImageView image;
590 protected ImageView indicator;
591 protected ImageView indicatorReceived;
592 protected TextView time;
593 protected TextView messageBody;
594 protected ImageView contact_picture;
595 protected TextView status_message;
596 }
597}