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.util.Log;
14import android.view.View;
15import android.view.View.OnClickListener;
16import android.view.View.OnLongClickListener;
17import android.view.ViewGroup;
18import android.widget.ArrayAdapter;
19import android.widget.Button;
20import android.widget.ImageView;
21import android.widget.LinearLayout;
22import android.widget.TextView;
23import android.widget.Toast;
24
25import java.util.List;
26
27import eu.siacs.conversations.Config;
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.UIHelper;
38import eu.siacs.conversations.xmpp.jid.Jid;
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 private static final int NULL = 3;
46
47 private ConversationActivity activity;
48
49 private DisplayMetrics metrics;
50
51 private OnContactPictureClicked mOnContactPictureClickedListener;
52 private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
53
54 private OnLongClickListener openContextMenu = new OnLongClickListener() {
55
56 @Override
57 public boolean onLongClick(View v) {
58 v.showContextMenu();
59 return true;
60 }
61 };
62
63 public MessageAdapter(ConversationActivity activity, List<Message> messages) {
64 super(activity, 0, messages);
65 this.activity = activity;
66 metrics = getContext().getResources().getDisplayMetrics();
67 }
68
69 public void setOnContactPictureClicked(OnContactPictureClicked listener) {
70 this.mOnContactPictureClickedListener = listener;
71 }
72
73 public void setOnContactPictureLongClicked(
74 OnContactPictureLongClicked listener) {
75 this.mOnContactPictureLongClickedListener = listener;
76 }
77
78 @Override
79 public int getViewTypeCount() {
80 return 4;
81 }
82
83 @Override
84 public int getItemViewType(int position) {
85 if (getItem(position).wasMergedIntoPrevious()) {
86 return NULL;
87 } else if (getItem(position).getType() == Message.TYPE_STATUS) {
88 return STATUS;
89 } else if (getItem(position).getStatus() <= Message.STATUS_RECEIVED) {
90 return RECEIVED;
91 } else {
92 return SENT;
93 }
94 }
95
96 private void displayStatus(ViewHolder viewHolder, Message message) {
97 String filesize = null;
98 String info = null;
99 boolean error = false;
100 if (viewHolder.indicatorReceived != null) {
101 viewHolder.indicatorReceived.setVisibility(View.GONE);
102 }
103 boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
104 && message.getMergedStatus() <= Message.STATUS_RECEIVED;
105 if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getDownloadable() != null) {
106 ImageParams params = message.getImageParams();
107 if (params.size > (1.5 * 1024 * 1024)) {
108 filesize = params.size / (1024 * 1024)+ " MiB";
109 } else if (params.size > 0) {
110 filesize = params.size / 1024 + " KiB";
111 }
112 if (message.getDownloadable() != null && message.getDownloadable().getStatus() == Downloadable.STATUS_FAILED) {
113 error = true;
114 }
115 }
116 switch (message.getMergedStatus()) {
117 case Message.STATUS_WAITING:
118 info = getContext().getString(R.string.waiting);
119 break;
120 case Message.STATUS_UNSEND:
121 Downloadable d = message.getDownloadable();
122 if (d!=null) {
123 info = getContext().getString(R.string.sending_file,d.getProgress());
124 } else {
125 info = getContext().getString(R.string.sending);
126 }
127 break;
128 case Message.STATUS_OFFERED:
129 info = getContext().getString(R.string.offering);
130 break;
131 case Message.STATUS_SEND_RECEIVED:
132 if (activity.indicateReceived()) {
133 viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
134 }
135 break;
136 case Message.STATUS_SEND_DISPLAYED:
137 if (activity.indicateReceived()) {
138 viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
139 }
140 break;
141 case Message.STATUS_SEND_FAILED:
142 info = getContext().getString(R.string.send_failed);
143 error = true;
144 break;
145 default:
146 if (multiReceived) {
147 Contact contact = message.getContact();
148 if (contact != null) {
149 info = contact.getDisplayName();
150 } else {
151 info = getDisplayedMucCounterpart(message.getCounterpart());
152 }
153 }
154 break;
155 }
156 if (error) {
157 viewHolder.time.setTextColor(activity.getWarningTextColor());
158 } else {
159 viewHolder.time.setTextColor(activity.getSecondaryTextColor());
160 }
161 if (message.getEncryption() == Message.ENCRYPTION_NONE) {
162 viewHolder.indicator.setVisibility(View.GONE);
163 } else {
164 viewHolder.indicator.setVisibility(View.VISIBLE);
165 }
166
167 String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(),
168 message.getMergedTimeSent());
169 if (message.getStatus() <= Message.STATUS_RECEIVED) {
170 if ((filesize != null) && (info != null)) {
171 viewHolder.time.setText(filesize + " \u00B7 " + info);
172 } else if ((filesize == null) && (info != null)) {
173 viewHolder.time.setText(formatedTime + " \u00B7 " + info);
174 } else if ((filesize != null) && (info == null)) {
175 viewHolder.time.setText(formatedTime + " \u00B7 " + filesize);
176 } else {
177 viewHolder.time.setText(formatedTime);
178 }
179 } else {
180 if ((filesize != null) && (info != null)) {
181 viewHolder.time.setText(filesize + " \u00B7 " + info);
182 } else if ((filesize == null) && (info != null)) {
183 if (error) {
184 viewHolder.time.setText(info + " \u00B7 " + formatedTime);
185 } else {
186 viewHolder.time.setText(info);
187 }
188 } else if ((filesize != null) && (info == null)) {
189 viewHolder.time.setText(filesize + " \u00B7 " + formatedTime);
190 } else {
191 viewHolder.time.setText(formatedTime);
192 }
193 }
194 }
195
196 private void displayInfoMessage(ViewHolder viewHolder, String text) {
197 if (viewHolder.download_button != null) {
198 viewHolder.download_button.setVisibility(View.GONE);
199 }
200 viewHolder.image.setVisibility(View.GONE);
201 viewHolder.messageBody.setVisibility(View.VISIBLE);
202 viewHolder.messageBody.setText(text);
203 viewHolder.messageBody.setTextColor(activity.getSecondaryTextColor());
204 viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
205 viewHolder.messageBody.setTextIsSelectable(false);
206 }
207
208 private void displayDecryptionFailed(ViewHolder viewHolder) {
209 if (viewHolder.download_button != null) {
210 viewHolder.download_button.setVisibility(View.GONE);
211 }
212 viewHolder.image.setVisibility(View.GONE);
213 viewHolder.messageBody.setVisibility(View.VISIBLE);
214 viewHolder.messageBody.setText(getContext().getString(
215 R.string.decryption_failed));
216 viewHolder.messageBody.setTextColor(activity.getWarningTextColor());
217 viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
218 viewHolder.messageBody.setTextIsSelectable(false);
219 }
220
221 private void displayTextMessage(ViewHolder viewHolder, Message message) {
222 if (viewHolder.download_button != null) {
223 viewHolder.download_button.setVisibility(View.GONE);
224 }
225 viewHolder.image.setVisibility(View.GONE);
226 viewHolder.messageBody.setVisibility(View.VISIBLE);
227 if (message.getBody() != null) {
228 if (message.getType() != Message.TYPE_PRIVATE) {
229 viewHolder.messageBody.setText(message.getMergedBody());
230 } else {
231 String privateMarker;
232 if (message.getStatus() <= Message.STATUS_RECEIVED) {
233 privateMarker = activity
234 .getString(R.string.private_message);
235 } else {
236 final String to;
237 if (message.getCounterpart() != null) {
238 to = message.getCounterpart().getResourcepart();
239 } else {
240 to = "";
241 }
242 privateMarker = activity.getString(R.string.private_message_to, to);
243 }
244 SpannableString span = new SpannableString(privateMarker + " "
245 + message.getBody());
246 span.setSpan(
247 new ForegroundColorSpan(activity
248 .getSecondaryTextColor()), 0, privateMarker
249 .length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
250 span.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0,
251 privateMarker.length(),
252 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
253 viewHolder.messageBody.setText(span);
254 }
255 } else {
256 viewHolder.messageBody.setText("");
257 }
258 viewHolder.messageBody.setTextColor(activity.getPrimaryTextColor());
259 viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
260 viewHolder.messageBody.setTextIsSelectable(true);
261 }
262
263 private void displayDownloadableMessage(ViewHolder viewHolder,
264 final Message message, String text) {
265 viewHolder.image.setVisibility(View.GONE);
266 viewHolder.messageBody.setVisibility(View.GONE);
267 viewHolder.download_button.setVisibility(View.VISIBLE);
268 viewHolder.download_button.setText(text);
269 viewHolder.download_button.setOnClickListener(new OnClickListener() {
270
271 @Override
272 public void onClick(View v) {
273 startDownloadable(message);
274 }
275 });
276 viewHolder.download_button.setOnLongClickListener(openContextMenu);
277 }
278
279 private void displayOpenableMessage(ViewHolder viewHolder,final Message message) {
280 final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
281 viewHolder.image.setVisibility(View.GONE);
282 viewHolder.messageBody.setVisibility(View.GONE);
283 viewHolder.download_button.setVisibility(View.VISIBLE);
284 viewHolder.download_button.setText(activity.getString(R.string.open_file,file.getMimeType()));
285 viewHolder.download_button.setOnClickListener(new OnClickListener() {
286
287 @Override
288 public void onClick(View v) {
289 openDownloadable(file);
290 }
291 });
292 viewHolder.download_button.setOnLongClickListener(openContextMenu);
293 }
294
295 private void displayImageMessage(ViewHolder viewHolder,
296 final Message message) {
297 if (viewHolder.download_button != null) {
298 viewHolder.download_button.setVisibility(View.GONE);
299 }
300 viewHolder.messageBody.setVisibility(View.GONE);
301 viewHolder.image.setVisibility(View.VISIBLE);
302 ImageParams params = message.getImageParams();
303 double target = metrics.density * 288;
304 int scalledW;
305 int scalledH;
306 if (params.width <= params.height) {
307 scalledW = (int) (params.width / ((double) params.height / target));
308 scalledH = (int) target;
309 } else {
310 scalledW = (int) target;
311 scalledH = (int) (params.height / ((double) params.width / target));
312 }
313 viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(
314 scalledW, scalledH));
315 activity.loadBitmap(message, viewHolder.image);
316 viewHolder.image.setOnClickListener(new OnClickListener() {
317
318 @Override
319 public void onClick(View v) {
320 Intent intent = new Intent(Intent.ACTION_VIEW);
321 intent.setDataAndType(activity.xmppConnectionService
322 .getFileBackend().getJingleFileUri(message), "image/*");
323 getContext().startActivity(intent);
324 }
325 });
326 viewHolder.image.setOnLongClickListener(openContextMenu);
327 }
328
329 private String getDisplayedMucCounterpart(final Jid counterpart) {
330 if (counterpart==null) {
331 return "";
332 } else if (!counterpart.isBareJid()) {
333 return counterpart.getResourcepart();
334 } else {
335 return counterpart.toString();
336 }
337 }
338
339 @Override
340 public View getView(int position, View view, ViewGroup parent) {
341 final Message message = getItem(position);
342 final Conversation conversation = message.getConversation();
343 final Account account = conversation.getAccount();
344 final int type = getItemViewType(position);
345 ViewHolder viewHolder;
346 if (view == null) {
347 viewHolder = new ViewHolder();
348 switch (type) {
349 case NULL:
350 view = activity.getLayoutInflater().inflate(
351 R.layout.message_null, parent, false);
352 break;
353 case SENT:
354 view = activity.getLayoutInflater().inflate(
355 R.layout.message_sent, parent, false);
356 viewHolder.message_box = (LinearLayout) view
357 .findViewById(R.id.message_box);
358 viewHolder.contact_picture = (ImageView) view
359 .findViewById(R.id.message_photo);
360 viewHolder.download_button = (Button) view
361 .findViewById(R.id.download_button);
362 viewHolder.indicator = (ImageView) view
363 .findViewById(R.id.security_indicator);
364 viewHolder.image = (ImageView) view
365 .findViewById(R.id.message_image);
366 viewHolder.messageBody = (TextView) view
367 .findViewById(R.id.message_body);
368 viewHolder.time = (TextView) view
369 .findViewById(R.id.message_time);
370 viewHolder.indicatorReceived = (ImageView) view
371 .findViewById(R.id.indicator_received);
372 break;
373 case RECEIVED:
374 view = activity.getLayoutInflater().inflate(
375 R.layout.message_received, parent, false);
376 viewHolder.message_box = (LinearLayout) view
377 .findViewById(R.id.message_box);
378 viewHolder.contact_picture = (ImageView) view
379 .findViewById(R.id.message_photo);
380 viewHolder.download_button = (Button) view
381 .findViewById(R.id.download_button);
382 viewHolder.indicator = (ImageView) view
383 .findViewById(R.id.security_indicator);
384 viewHolder.image = (ImageView) view
385 .findViewById(R.id.message_image);
386 viewHolder.messageBody = (TextView) view
387 .findViewById(R.id.message_body);
388 viewHolder.time = (TextView) view
389 .findViewById(R.id.message_time);
390 viewHolder.indicatorReceived = (ImageView) view
391 .findViewById(R.id.indicator_received);
392 break;
393 case STATUS:
394 view = activity.getLayoutInflater().inflate(
395 R.layout.message_status, parent, false);
396 viewHolder.contact_picture = (ImageView) view
397 .findViewById(R.id.message_photo);
398 break;
399 default:
400 viewHolder = null;
401 break;
402 }
403 view.setTag(viewHolder);
404 } else {
405 viewHolder = (ViewHolder) view.getTag();
406 if (viewHolder == null) {
407 return view;
408 }
409 }
410
411 if (type == STATUS) {
412 if (conversation.getMode() == Conversation.MODE_SINGLE) {
413 viewHolder.contact_picture.setImageBitmap(activity
414 .avatarService().get(conversation.getContact(),
415 activity.getPixel(32)));
416 viewHolder.contact_picture.setAlpha(0.5f);
417 viewHolder.contact_picture
418 .setOnClickListener(new OnClickListener() {
419
420 @Override
421 public void onClick(View v) {
422 String name = conversation.getName();
423 String read = getContext()
424 .getString(
425 R.string.contact_has_read_up_to_this_point,
426 name);
427 Toast.makeText(getContext(), read,
428 Toast.LENGTH_SHORT).show();
429 }
430 });
431
432 }
433 return view;
434 } else if (type == NULL) {
435 if (viewHolder.message_box != null) {
436 Log.e(Config.LOGTAG, "detected type=NULL but with wrong cached view");
437 view = activity.getLayoutInflater().inflate(R.layout.message_null, parent, false);
438 view.setTag(new ViewHolder());
439 }
440 if (position == getCount() - 1) {
441 view.getLayoutParams().height = 1;
442 } else {
443 view.getLayoutParams().height = 0;
444
445 }
446 view.setLayoutParams(view.getLayoutParams());
447 return view;
448 } else if (message.wasMergedIntoPrevious()) {
449 Log.e(Config.LOGTAG,"detected wasMergedIntoPrevious with wrong type");
450 return view;
451 } else if (viewHolder.messageBody == null || viewHolder.image == null) {
452 return view; //avoiding weird platform bugs
453 } else if (type == RECEIVED) {
454 Contact contact = message.getContact();
455 if (contact != null) {
456 viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(contact, activity.getPixel(48)));
457 } else if (conversation.getMode() == Conversation.MODE_MULTI) {
458 viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(getDisplayedMucCounterpart(message.getCounterpart()),
459 activity.getPixel(48)));
460 }
461 } else if (type == SENT) {
462 viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(account, activity.getPixel(48)));
463 }
464
465 viewHolder.contact_picture
466 .setOnClickListener(new OnClickListener() {
467
468 @Override
469 public void onClick(View v) {
470 if (MessageAdapter.this.mOnContactPictureClickedListener != null) {
471 MessageAdapter.this.mOnContactPictureClickedListener
472 .onContactPictureClicked(message);
473 }
474
475 }
476 });
477 viewHolder.contact_picture
478 .setOnLongClickListener(new OnLongClickListener() {
479
480 @Override
481 public boolean onLongClick(View v) {
482 if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
483 MessageAdapter.this.mOnContactPictureLongClickedListener
484 .onContactPictureLongClicked(message);
485 return true;
486 } else {
487 return false;
488 }
489 }
490 });
491
492 if (message.getDownloadable() != null && message.getDownloadable().getStatus() != Downloadable.STATUS_UPLOADING) {
493 Downloadable d = message.getDownloadable();
494 if (d.getStatus() == Downloadable.STATUS_DOWNLOADING) {
495 if (message.getType() == Message.TYPE_FILE) {
496 displayInfoMessage(viewHolder,activity.getString(R.string.receiving_file,d.getMimeType(),d.getProgress()));
497 } else {
498 displayInfoMessage(viewHolder,activity.getString(R.string.receiving_image,d.getProgress()));
499 }
500 } else if (d.getStatus() == Downloadable.STATUS_CHECKING) {
501 displayInfoMessage(viewHolder,activity.getString(R.string.checking_image));
502 } else if (d.getStatus() == Downloadable.STATUS_DELETED) {
503 if (message.getType() == Message.TYPE_FILE) {
504 displayInfoMessage(viewHolder, activity.getString(R.string.file_deleted));
505 } else {
506 displayInfoMessage(viewHolder, activity.getString(R.string.image_file_deleted));
507 }
508 } else if (d.getStatus() == Downloadable.STATUS_OFFER) {
509 if (message.getType() == Message.TYPE_FILE) {
510 displayDownloadableMessage(viewHolder,message,activity.getString(R.string.download_file,d.getMimeType()));
511 } else {
512 displayDownloadableMessage(viewHolder, message,activity.getString(R.string.download_image));
513 }
514 } else if (d.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) {
515 displayDownloadableMessage(viewHolder, message,activity.getString(R.string.check_image_filesize));
516 } else if (d.getStatus() == Downloadable.STATUS_FAILED) {
517 if (message.getType() == Message.TYPE_FILE) {
518 displayInfoMessage(viewHolder, activity.getString(R.string.file_transmission_failed));
519 } else {
520 displayInfoMessage(viewHolder, activity.getString(R.string.image_transmission_failed));
521 }
522 }
523 } else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
524 displayImageMessage(viewHolder, message);
525 } else if (message.getType() == Message.TYPE_FILE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
526 if (message.getImageParams().width > 0) {
527 displayImageMessage(viewHolder,message);
528 } else {
529 displayOpenableMessage(viewHolder, message);
530 }
531 } else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
532 if (activity.hasPgp()) {
533 displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message));
534 } else {
535 displayInfoMessage(viewHolder,
536 activity.getString(R.string.install_openkeychain));
537 if (viewHolder != null) {
538 viewHolder.message_box
539 .setOnClickListener(new OnClickListener() {
540
541 @Override
542 public void onClick(View v) {
543 activity.showInstallPgpDialog();
544 }
545 });
546 }
547 }
548 } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
549 displayDecryptionFailed(viewHolder);
550 } else {
551 displayTextMessage(viewHolder, message);
552 }
553
554 displayStatus(viewHolder, message);
555
556 return view;
557 }
558
559 public void startDownloadable(Message message) {
560 Downloadable downloadable = message.getDownloadable();
561 if (downloadable != null) {
562 if (!downloadable.start()) {
563 Toast.makeText(activity, R.string.not_connected_try_again,
564 Toast.LENGTH_SHORT).show();
565 }
566 }
567 }
568
569 public void openDownloadable(DownloadableFile file) {
570 if (!file.exists()) {
571 Toast.makeText(activity,R.string.file_deleted,Toast.LENGTH_SHORT).show();
572 return;
573 }
574 Intent openIntent = new Intent(Intent.ACTION_VIEW);
575 openIntent.setDataAndType(Uri.fromFile(file), file.getMimeType());
576 PackageManager manager = activity.getPackageManager();
577 List<ResolveInfo> infos = manager.queryIntentActivities(openIntent, 0);
578 if (infos.size() > 0) {
579 getContext().startActivity(openIntent);
580 } else {
581 Toast.makeText(activity,R.string.no_application_found_to_open_file,Toast.LENGTH_SHORT).show();
582 }
583 }
584
585 public interface OnContactPictureClicked {
586 public void onContactPictureClicked(Message message);
587 }
588
589 public interface OnContactPictureLongClicked {
590 public void onContactPictureLongClicked(Message message);
591 }
592
593 private static class ViewHolder {
594
595 protected LinearLayout message_box;
596 protected Button download_button;
597 protected ImageView image;
598 protected ImageView indicator;
599 protected ImageView indicatorReceived;
600 protected TextView time;
601 protected TextView messageBody;
602 protected ImageView contact_picture;
603
604 }
605}