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