1package eu.siacs.conversations.ui.adapter;
2
3import java.net.MalformedURLException;
4import java.net.URL;
5import java.util.HashMap;
6import java.util.List;
7
8import eu.siacs.conversations.Config;
9import eu.siacs.conversations.R;
10import eu.siacs.conversations.entities.Contact;
11import eu.siacs.conversations.entities.Conversation;
12import eu.siacs.conversations.entities.Downloadable;
13import eu.siacs.conversations.entities.Message;
14import eu.siacs.conversations.entities.Message.ImageParams;
15import eu.siacs.conversations.ui.ConversationActivity;
16import eu.siacs.conversations.utils.UIHelper;
17import android.content.Context;
18import android.content.Intent;
19import android.graphics.Bitmap;
20import android.graphics.Typeface;
21import android.text.Spannable;
22import android.text.SpannableString;
23import android.text.style.ForegroundColorSpan;
24import android.text.style.StyleSpan;
25import android.util.DisplayMetrics;
26import android.view.View;
27import android.view.ViewGroup;
28import android.view.View.OnClickListener;
29import android.view.View.OnLongClickListener;
30import android.widget.ArrayAdapter;
31import android.widget.Button;
32import android.widget.ImageView;
33import android.widget.LinearLayout;
34import android.widget.TextView;
35import android.widget.Toast;
36
37public class MessageAdapter extends ArrayAdapter<Message> {
38
39 private static final int SENT = 0;
40 private static final int RECEIVED = 1;
41 private static final int STATUS = 2;
42 private static final int NULL = 3;
43
44 private ConversationActivity activity;
45
46 private Bitmap accountBitmap;
47
48 private BitmapCache mBitmapCache = new BitmapCache();
49 private DisplayMetrics metrics;
50
51 private OnContactPictureClicked mOnContactPictureClickedListener;
52 private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
53
54 public MessageAdapter(ConversationActivity activity, List<Message> messages) {
55 super(activity, 0, messages);
56 this.activity = activity;
57 metrics = getContext().getResources().getDisplayMetrics();
58 }
59
60 private Bitmap getSelfBitmap() {
61 if (this.accountBitmap == null) {
62
63 if (getCount() > 0) {
64 this.accountBitmap = getItem(0).getConversation().getAccount()
65 .getImage(getContext(), 48);
66 }
67 }
68 return this.accountBitmap;
69 }
70
71 public void setOnContactPictureClicked(OnContactPictureClicked listener) {
72 this.mOnContactPictureClickedListener = listener;
73 }
74
75 public void setOnContactPictureLongClicked(
76 OnContactPictureLongClicked listener) {
77 this.mOnContactPictureLongClickedListener = listener;
78 }
79
80 @Override
81 public int getViewTypeCount() {
82 return 4;
83 }
84
85 @Override
86 public int getItemViewType(int position) {
87 if (getItem(position).wasMergedIntoPrevious()) {
88 return NULL;
89 } else if (getItem(position).getType() == Message.TYPE_STATUS) {
90 return STATUS;
91 } else if (getItem(position).getStatus() <= Message.STATUS_RECEIVED) {
92 return RECEIVED;
93 } else {
94 return SENT;
95 }
96 }
97
98 private void displayStatus(ViewHolder viewHolder, Message message) {
99 String filesize = null;
100 String info = null;
101 boolean error = false;
102 if (viewHolder.indicatorReceived != null) {
103 viewHolder.indicatorReceived.setVisibility(View.GONE);
104 }
105 boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
106 && message.getMergedStatus() <= Message.STATUS_RECEIVED;
107 if (message.getType() == Message.TYPE_IMAGE) {
108 ImageParams params = message.getImageParams();
109 if (params.size != 0) {
110 filesize = params.size / 1024 + " KB";
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 info = getContext().getString(R.string.sending);
119 break;
120 case Message.STATUS_OFFERED:
121 info = getContext().getString(R.string.offering);
122 break;
123 case Message.STATUS_SEND_RECEIVED:
124 if (activity.indicateReceived()) {
125 viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
126 }
127 break;
128 case Message.STATUS_SEND_DISPLAYED:
129 if (activity.indicateReceived()) {
130 viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
131 }
132 break;
133 case Message.STATUS_SEND_FAILED:
134 info = getContext().getString(R.string.send_failed);
135 error = true;
136 break;
137 case Message.STATUS_SEND_REJECTED:
138 info = getContext().getString(R.string.send_rejected);
139 error = true;
140 break;
141 default:
142 if (multiReceived) {
143 Contact contact = message.getContact();
144 if (contact != null) {
145 info = contact.getDisplayName();
146 } else {
147 if (message.getPresence() != null) {
148 info = message.getPresence();
149 } else {
150 info = message.getCounterpart();
151 }
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, int r) {
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(getContext().getString(r));
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 String body = Config.PARSE_EMOTICONS ? UIHelper
230 .transformAsciiEmoticons(message.getMergedBody())
231 : message.getMergedBody();
232 viewHolder.messageBody.setText(body);
233 } else {
234 String privateMarker;
235 if (message.getStatus() <= Message.STATUS_RECEIVED) {
236 privateMarker = activity
237 .getString(R.string.private_message);
238 } else {
239 String to;
240 if (message.getPresence() != null) {
241 to = message.getPresence();
242 } else {
243 to = message.getCounterpart();
244 }
245 privateMarker = activity.getString(
246 R.string.private_message_to, to);
247 }
248 SpannableString span = new SpannableString(privateMarker + " "
249 + message.getBody());
250 span.setSpan(
251 new ForegroundColorSpan(activity
252 .getSecondaryTextColor()), 0, privateMarker
253 .length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
254 span.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0,
255 privateMarker.length(),
256 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
257 viewHolder.messageBody.setText(span);
258 }
259 } else {
260 viewHolder.messageBody.setText("");
261 }
262 viewHolder.messageBody.setTextColor(activity.getPrimaryTextColor());
263 viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
264 viewHolder.messageBody.setTextIsSelectable(true);
265 }
266
267 private void displayImageMessage(ViewHolder viewHolder,
268 final Message message) {
269 if (viewHolder.download_button != null) {
270 viewHolder.download_button.setVisibility(View.GONE);
271 }
272 viewHolder.messageBody.setVisibility(View.GONE);
273 viewHolder.image.setVisibility(View.VISIBLE);
274 ImageParams params = message.getImageParams();
275 double target = metrics.density * 288;
276 int scalledW;
277 int scalledH;
278 if (params.width <= params.height) {
279 scalledW = (int) (params.width / ((double) params.height / target));
280 scalledH = (int) target;
281 } else {
282 scalledW = (int) target;
283 scalledH = (int) (params.height / ((double) params.width / target));
284 }
285 viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(
286 scalledW, scalledH));
287 activity.loadBitmap(message, viewHolder.image);
288 viewHolder.image.setOnClickListener(new OnClickListener() {
289
290 @Override
291 public void onClick(View v) {
292 Intent intent = new Intent(Intent.ACTION_VIEW);
293 intent.setDataAndType(activity.xmppConnectionService
294 .getFileBackend().getJingleFileUri(message), "image/*");
295 getContext().startActivity(intent);
296 }
297 });
298 viewHolder.image.setOnLongClickListener(new OnLongClickListener() {
299
300 @Override
301 public boolean onLongClick(View v) {
302 Intent shareIntent = new Intent();
303 shareIntent.setAction(Intent.ACTION_SEND);
304 shareIntent.putExtra(Intent.EXTRA_STREAM,
305 activity.xmppConnectionService.getFileBackend()
306 .getJingleFileUri(message));
307 shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
308 shareIntent.setType("image/webp");
309 getContext().startActivity(
310 Intent.createChooser(shareIntent,
311 getContext().getText(R.string.share_with)));
312 return true;
313 }
314 });
315 }
316
317 @Override
318 public View getView(int position, View view, ViewGroup parent) {
319 final Message item = getItem(position);
320 int type = getItemViewType(position);
321 ViewHolder viewHolder;
322 if (view == null) {
323 viewHolder = new ViewHolder();
324 switch (type) {
325 case NULL:
326 view = (View) activity.getLayoutInflater().inflate(
327 R.layout.message_null, parent, false);
328 break;
329 case SENT:
330 view = (View) activity.getLayoutInflater().inflate(
331 R.layout.message_sent, parent, false);
332 viewHolder.message_box = (LinearLayout) view
333 .findViewById(R.id.message_box);
334 viewHolder.contact_picture = (ImageView) view
335 .findViewById(R.id.message_photo);
336 viewHolder.contact_picture.setImageBitmap(getSelfBitmap());
337 viewHolder.download_button = (Button) view
338 .findViewById(R.id.download_button);
339 viewHolder.indicator = (ImageView) view
340 .findViewById(R.id.security_indicator);
341 viewHolder.image = (ImageView) view
342 .findViewById(R.id.message_image);
343 viewHolder.messageBody = (TextView) view
344 .findViewById(R.id.message_body);
345 viewHolder.time = (TextView) view
346 .findViewById(R.id.message_time);
347 viewHolder.indicatorReceived = (ImageView) view
348 .findViewById(R.id.indicator_received);
349 view.setTag(viewHolder);
350 break;
351 case RECEIVED:
352 view = (View) activity.getLayoutInflater().inflate(
353 R.layout.message_received, parent, false);
354 viewHolder.message_box = (LinearLayout) view
355 .findViewById(R.id.message_box);
356 viewHolder.contact_picture = (ImageView) view
357 .findViewById(R.id.message_photo);
358 viewHolder.download_button = (Button) view
359 .findViewById(R.id.download_button);
360 if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
361 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
362 item.getConversation().getContact(), getContext()));
363 }
364 viewHolder.indicator = (ImageView) view
365 .findViewById(R.id.security_indicator);
366 viewHolder.image = (ImageView) view
367 .findViewById(R.id.message_image);
368 viewHolder.messageBody = (TextView) view
369 .findViewById(R.id.message_body);
370 viewHolder.time = (TextView) view
371 .findViewById(R.id.message_time);
372 view.setTag(viewHolder);
373 break;
374 case STATUS:
375 view = (View) activity.getLayoutInflater().inflate(
376 R.layout.message_status, parent, false);
377 viewHolder.contact_picture = (ImageView) view
378 .findViewById(R.id.message_photo);
379 if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
380
381 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
382 item.getConversation().getContact(), getContext()));
383 viewHolder.contact_picture.setAlpha(0.5f);
384 viewHolder.contact_picture
385 .setOnClickListener(new OnClickListener() {
386
387 @Override
388 public void onClick(View v) {
389 String name = item.getConversation()
390 .getName();
391 String read = getContext()
392 .getString(
393 R.string.contact_has_read_up_to_this_point,
394 name);
395 Toast.makeText(getContext(), read,
396 Toast.LENGTH_SHORT).show();
397 }
398 });
399
400 }
401 break;
402 default:
403 viewHolder = null;
404 break;
405 }
406 } else {
407 viewHolder = (ViewHolder) view.getTag();
408 }
409
410 if (type == STATUS) {
411 return view;
412 }
413 if (type == NULL) {
414 if (position == getCount() - 1) {
415 view.getLayoutParams().height = 1;
416 } else {
417 view.getLayoutParams().height = 0;
418
419 }
420 view.setLayoutParams(view.getLayoutParams());
421 return view;
422 }
423
424 if (viewHolder.contact_picture != null) {
425 viewHolder.contact_picture
426 .setOnClickListener(new OnClickListener() {
427
428 @Override
429 public void onClick(View v) {
430 if (MessageAdapter.this.mOnContactPictureClickedListener != null) {
431 MessageAdapter.this.mOnContactPictureClickedListener
432 .onContactPictureClicked(item);
433 ;
434 }
435
436 }
437 });
438 viewHolder.contact_picture
439 .setOnLongClickListener(new OnLongClickListener() {
440
441 @Override
442 public boolean onLongClick(View v) {
443 if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
444 MessageAdapter.this.mOnContactPictureLongClickedListener
445 .onContactPictureLongClicked(item);
446 return true;
447 } else {
448 return false;
449 }
450 }
451 });
452 }
453
454 if (type == RECEIVED) {
455 if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
456 Contact contact = item.getContact();
457 if (contact != null) {
458 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
459 contact, getContext()));
460 } else {
461 String name = item.getPresence();
462 if (name == null) {
463 name = item.getCounterpart();
464 }
465 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
466 name, getContext()));
467 }
468 }
469 }
470
471 if (item.getType() == Message.TYPE_IMAGE
472 || item.getDownloadable() != null) {
473 Downloadable d = item.getDownloadable();
474 if (d != null && d.getStatus() == Downloadable.STATUS_DOWNLOADING) {
475 displayInfoMessage(viewHolder, R.string.receiving_image);
476 } else if (d != null
477 && d.getStatus() == Downloadable.STATUS_CHECKING) {
478 displayInfoMessage(viewHolder, R.string.checking_image);
479 } else if (d != null && d.getStatus() == Downloadable.STATUS_OFFER) {
480 viewHolder.image.setVisibility(View.GONE);
481 viewHolder.messageBody.setVisibility(View.GONE);
482 viewHolder.download_button.setVisibility(View.VISIBLE);
483 viewHolder.download_button
484 .setOnClickListener(new OnClickListener() {
485
486 @Override
487 public void onClick(View v) {
488 startDonwloadable(item);
489 }
490 });
491 } else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED)
492 || (item.getEncryption() == Message.ENCRYPTION_NONE)
493 || (item.getEncryption() == Message.ENCRYPTION_OTR)) {
494 displayImageMessage(viewHolder, item);
495 } else if (item.getEncryption() == Message.ENCRYPTION_PGP) {
496 displayInfoMessage(viewHolder, R.string.encrypted_message);
497 } else {
498 displayDecryptionFailed(viewHolder);
499 }
500 } else {
501 if (item.getEncryption() == Message.ENCRYPTION_PGP) {
502 if (activity.hasPgp()) {
503 displayInfoMessage(viewHolder, R.string.encrypted_message);
504 } else {
505 displayInfoMessage(viewHolder,
506 R.string.install_openkeychain);
507 viewHolder.message_box
508 .setOnClickListener(new OnClickListener() {
509
510 @Override
511 public void onClick(View v) {
512 activity.showInstallPgpDialog();
513 }
514 });
515 }
516 } else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
517 displayDecryptionFailed(viewHolder);
518 } else {
519 displayTextMessage(viewHolder, item);
520 }
521 }
522
523 displayStatus(viewHolder, item);
524
525 return view;
526 }
527
528 public boolean startDonwloadable(Message message) {
529 Downloadable downloadable = message.getDownloadable();
530 if (downloadable != null) {
531 downloadable.start();
532 return true;
533 } else {
534 ImageParams params = message.getImageParams();
535 if (params.origin != null) {
536 try {
537 URL url = new URL(params.origin);
538 activity.xmppConnectionService.getHttpConnectionManager()
539 .createNewConnection(message, url);
540 return true;
541 } catch (MalformedURLException e) {
542 return false;
543 }
544 } else {
545 return false;
546 }
547 }
548 }
549
550 private static class ViewHolder {
551
552 protected LinearLayout message_box;
553 protected Button download_button;
554 protected ImageView image;
555 protected ImageView indicator;
556 protected ImageView indicatorReceived;
557 protected TextView time;
558 protected TextView messageBody;
559 protected ImageView contact_picture;
560
561 }
562
563 private class BitmapCache {
564 private HashMap<String, Bitmap> contactBitmaps = new HashMap<String, Bitmap>();
565 private HashMap<String, Bitmap> unknownBitmaps = new HashMap<String, Bitmap>();
566
567 public Bitmap get(Contact contact, Context context) {
568 if (!contactBitmaps.containsKey(contact.getJid())) {
569 contactBitmaps.put(contact.getJid(),
570 contact.getImage(48, context));
571 }
572 return contactBitmaps.get(contact.getJid());
573 }
574
575 public Bitmap get(String name, Context context) {
576 if (unknownBitmaps.containsKey(name)) {
577 return unknownBitmaps.get(name);
578 } else {
579 Bitmap bm = UIHelper
580 .getContactPicture(name, 48, context, false);
581 unknownBitmaps.put(name, bm);
582 return bm;
583 }
584 }
585 }
586
587 public interface OnContactPictureClicked {
588 public void onContactPictureClicked(Message message);
589 }
590
591 public interface OnContactPictureLongClicked {
592 public void onContactPictureLongClicked(Message message);
593 }
594}