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.Downloadable;
33import eu.siacs.conversations.entities.DownloadableFile;
34import eu.siacs.conversations.entities.Message;
35import eu.siacs.conversations.entities.Message.ImageParams;
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.getDownloadable() != null) {
103 ImageParams params = message.getImageParams();
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.getDownloadable() != null && message.getDownloadable().getStatus() == Downloadable.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 Downloadable d = message.getDownloadable();
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(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("\n\n");
238 while(i >= 0) {
239 formattedBody.setSpan(new RelativeSizeSpan(0.2f),i,i+2,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
240 i = body.indexOf("\n\n",i+2);
241 }
242 if (message.getType() != Message.TYPE_PRIVATE) {
243 if (message.hasMeCommand()) {
244 final Spannable span = new SpannableString(formattedBody);
245 span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(),
246 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
247 viewHolder.messageBody.setText(span);
248 } else {
249 viewHolder.messageBody.setText(formattedBody);
250 }
251 } else {
252 String privateMarker;
253 if (message.getStatus() <= Message.STATUS_RECEIVED) {
254 privateMarker = activity
255 .getString(R.string.private_message);
256 } else {
257 final String to;
258 if (message.getCounterpart() != null) {
259 to = message.getCounterpart().getResourcepart();
260 } else {
261 to = "";
262 }
263 privateMarker = activity.getString(R.string.private_message_to, to);
264 }
265 final Spannable span = new SpannableString(privateMarker + " "
266 + formattedBody);
267 span.setSpan(new ForegroundColorSpan(activity
268 .getSecondaryTextColor()), 0, privateMarker
269 .length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
270 span.setSpan(new StyleSpan(Typeface.BOLD), 0,
271 privateMarker.length(),
272 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
273 if (message.hasMeCommand()) {
274 span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), privateMarker.length() + 1,
275 privateMarker.length() + 1 + nick.length(),
276 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
277 }
278 viewHolder.messageBody.setText(span);
279 }
280 } else {
281 viewHolder.messageBody.setText("");
282 }
283 viewHolder.messageBody.setTextColor(activity.getPrimaryTextColor());
284 viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
285 viewHolder.messageBody.setTextIsSelectable(true);
286 }
287
288 private void displayDownloadableMessage(ViewHolder viewHolder,
289 final Message message, String text) {
290 viewHolder.image.setVisibility(View.GONE);
291 viewHolder.messageBody.setVisibility(View.GONE);
292 viewHolder.download_button.setVisibility(View.VISIBLE);
293 viewHolder.download_button.setText(text);
294 viewHolder.download_button.setOnClickListener(new OnClickListener() {
295
296 @Override
297 public void onClick(View v) {
298 startDownloadable(message);
299 }
300 });
301 viewHolder.download_button.setOnLongClickListener(openContextMenu);
302 }
303
304 private void displayOpenableMessage(ViewHolder viewHolder,final Message message) {
305 viewHolder.image.setVisibility(View.GONE);
306 viewHolder.messageBody.setVisibility(View.GONE);
307 viewHolder.download_button.setVisibility(View.VISIBLE);
308 viewHolder.download_button.setText(activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity, message)));
309 viewHolder.download_button.setOnClickListener(new OnClickListener() {
310
311 @Override
312 public void onClick(View v) {
313 openDownloadable(message);
314 }
315 });
316 viewHolder.download_button.setOnLongClickListener(openContextMenu);
317 }
318
319 private void displayLocationMessage(ViewHolder viewHolder, final Message message) {
320 viewHolder.image.setVisibility(View.GONE);
321 viewHolder.messageBody.setVisibility(View.GONE);
322 viewHolder.download_button.setVisibility(View.VISIBLE);
323 viewHolder.download_button.setText(R.string.show_location);
324 viewHolder.download_button.setOnClickListener(new OnClickListener() {
325
326 @Override
327 public void onClick(View v) {
328 showLocation(message);
329 }
330 });
331 viewHolder.download_button.setOnLongClickListener(openContextMenu);
332 }
333
334 private void displayImageMessage(ViewHolder viewHolder,
335 final Message message) {
336 if (viewHolder.download_button != null) {
337 viewHolder.download_button.setVisibility(View.GONE);
338 }
339 viewHolder.messageBody.setVisibility(View.GONE);
340 viewHolder.image.setVisibility(View.VISIBLE);
341 ImageParams params = message.getImageParams();
342 double target = metrics.density * 288;
343 int scalledW;
344 int scalledH;
345 if (params.width <= params.height) {
346 scalledW = (int) (params.width / ((double) params.height / target));
347 scalledH = (int) target;
348 } else {
349 scalledW = (int) target;
350 scalledH = (int) (params.height / ((double) params.width / target));
351 }
352 viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(
353 scalledW, scalledH));
354 activity.loadBitmap(message, viewHolder.image);
355 viewHolder.image.setOnClickListener(new OnClickListener() {
356
357 @Override
358 public void onClick(View v) {
359 Intent intent = new Intent(Intent.ACTION_VIEW);
360 intent.setDataAndType(activity.xmppConnectionService
361 .getFileBackend().getJingleFileUri(message), "image/*");
362 getContext().startActivity(intent);
363 }
364 });
365 viewHolder.image.setOnLongClickListener(openContextMenu);
366 }
367
368 @Override
369 public View getView(int position, View view, ViewGroup parent) {
370 final Message message = getItem(position);
371 final Conversation conversation = message.getConversation();
372 final Account account = conversation.getAccount();
373 final int type = getItemViewType(position);
374 ViewHolder viewHolder;
375 if (view == null) {
376 viewHolder = new ViewHolder();
377 switch (type) {
378 case SENT:
379 view = activity.getLayoutInflater().inflate(
380 R.layout.message_sent, 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 break;
398 case RECEIVED:
399 view = activity.getLayoutInflater().inflate(
400 R.layout.message_received, parent, false);
401 viewHolder.message_box = (LinearLayout) view
402 .findViewById(R.id.message_box);
403 viewHolder.contact_picture = (ImageView) view
404 .findViewById(R.id.message_photo);
405 viewHolder.download_button = (Button) view
406 .findViewById(R.id.download_button);
407 viewHolder.indicator = (ImageView) view
408 .findViewById(R.id.security_indicator);
409 viewHolder.image = (ImageView) view
410 .findViewById(R.id.message_image);
411 viewHolder.messageBody = (TextView) view
412 .findViewById(R.id.message_body);
413 viewHolder.time = (TextView) view
414 .findViewById(R.id.message_time);
415 viewHolder.indicatorReceived = (ImageView) view
416 .findViewById(R.id.indicator_received);
417 break;
418 case STATUS:
419 view = activity.getLayoutInflater().inflate(R.layout.message_status, parent, false);
420 viewHolder.contact_picture = (ImageView) view.findViewById(R.id.message_photo);
421 viewHolder.status_message = (TextView) view.findViewById(R.id.status_message);
422 break;
423 default:
424 viewHolder = null;
425 break;
426 }
427 view.setTag(viewHolder);
428 } else {
429 viewHolder = (ViewHolder) view.getTag();
430 if (viewHolder == null) {
431 return view;
432 }
433 }
434
435 if (type == STATUS) {
436 if (conversation.getMode() == Conversation.MODE_SINGLE) {
437 viewHolder.contact_picture.setImageBitmap(activity
438 .avatarService().get(conversation.getContact(),
439 activity.getPixel(32)));
440 viewHolder.contact_picture.setAlpha(0.5f);
441 viewHolder.status_message.setText(message.getBody());
442 }
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(
450 UIHelper.getMessageDisplayName(message),
451 activity.getPixel(48)));
452 }
453 } else if (type == SENT) {
454 viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(account, activity.getPixel(48)));
455 }
456
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 final Downloadable downloadable = message.getDownloadable();
485 if (downloadable != null && downloadable.getStatus() != Downloadable.STATUS_UPLOADING) {
486 if (downloadable.getStatus() == Downloadable.STATUS_OFFER) {
487 displayDownloadableMessage(viewHolder,message,activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, message)));
488 } else if (downloadable.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) {
489 displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_image_filesize));
490 } else {
491 displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first);
492 }
493 } else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
494 displayImageMessage(viewHolder, message);
495 } else if (message.getType() == Message.TYPE_FILE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
496 if (message.getImageParams().width > 0) {
497 displayImageMessage(viewHolder,message);
498 } else {
499 displayOpenableMessage(viewHolder, message);
500 }
501 } else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
502 if (activity.hasPgp()) {
503 displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message));
504 } else {
505 displayInfoMessage(viewHolder,
506 activity.getString(R.string.install_openkeychain));
507 if (viewHolder != null) {
508 viewHolder.message_box
509 .setOnClickListener(new OnClickListener() {
510
511 @Override
512 public void onClick(View v) {
513 activity.showInstallPgpDialog();
514 }
515 });
516 }
517 }
518 } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
519 displayDecryptionFailed(viewHolder);
520 } else {
521 if (GeoHelper.isGeoUri(message.getBody())) {
522 displayLocationMessage(viewHolder,message);
523 } else {
524 if (message.bodyIsHeart()) {
525 displayHeartMessage(viewHolder, message.getBody().trim());
526 } else {
527 displayTextMessage(viewHolder, message);
528 }
529 }
530 }
531
532 displayStatus(viewHolder, message);
533
534 return view;
535 }
536
537 public void startDownloadable(Message message) {
538 Downloadable downloadable = message.getDownloadable();
539 if (downloadable != null) {
540 if (!downloadable.start()) {
541 Toast.makeText(activity, R.string.not_connected_try_again,
542 Toast.LENGTH_SHORT).show();
543 }
544 }
545 }
546
547 public void openDownloadable(Message message) {
548 DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
549 if (!file.exists()) {
550 Toast.makeText(activity,R.string.file_deleted,Toast.LENGTH_SHORT).show();
551 return;
552 }
553 Intent openIntent = new Intent(Intent.ACTION_VIEW);
554 openIntent.setDataAndType(Uri.fromFile(file), file.getMimeType());
555 PackageManager manager = activity.getPackageManager();
556 List<ResolveInfo> infos = manager.queryIntentActivities(openIntent, 0);
557 if (infos.size() > 0) {
558 getContext().startActivity(openIntent);
559 } else {
560 Toast.makeText(activity,R.string.no_application_found_to_open_file,Toast.LENGTH_SHORT).show();
561 }
562 }
563
564 public void showLocation(Message message) {
565 for(Intent intent : GeoHelper.createGeoIntentsFromMessage(message)) {
566 if (intent.resolveActivity(getContext().getPackageManager()) != null) {
567 getContext().startActivity(intent);
568 return;
569 }
570 }
571 Toast.makeText(activity,R.string.no_application_found_to_display_location,Toast.LENGTH_SHORT).show();
572 }
573
574 public interface OnContactPictureClicked {
575 public void onContactPictureClicked(Message message);
576 }
577
578 public interface OnContactPictureLongClicked {
579 public void onContactPictureLongClicked(Message message);
580 }
581
582 private static class ViewHolder {
583
584 protected LinearLayout message_box;
585 protected Button download_button;
586 protected ImageView image;
587 protected ImageView indicator;
588 protected ImageView indicatorReceived;
589 protected TextView time;
590 protected TextView messageBody;
591 protected ImageView contact_picture;
592 protected TextView status_message;
593 }
594}