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