1package eu.siacs.conversations.ui.adapter;
2
3import java.util.HashMap;
4import java.util.List;
5
6import eu.siacs.conversations.Config;
7import eu.siacs.conversations.R;
8import eu.siacs.conversations.entities.Contact;
9import eu.siacs.conversations.entities.Conversation;
10import eu.siacs.conversations.entities.Downloadable;
11import eu.siacs.conversations.entities.Message;
12import eu.siacs.conversations.ui.ConversationActivity;
13import eu.siacs.conversations.utils.UIHelper;
14import android.content.Context;
15import android.content.Intent;
16import android.graphics.Bitmap;
17import android.graphics.Typeface;
18import android.text.Spannable;
19import android.text.SpannableString;
20import android.text.style.ForegroundColorSpan;
21import android.text.style.StyleSpan;
22import android.util.DisplayMetrics;
23import android.view.View;
24import android.view.ViewGroup;
25import android.view.View.OnClickListener;
26import android.view.View.OnLongClickListener;
27import android.widget.ArrayAdapter;
28import android.widget.Button;
29import android.widget.ImageView;
30import android.widget.LinearLayout;
31import android.widget.TextView;
32import android.widget.Toast;
33
34public class MessageAdapter extends ArrayAdapter<Message> {
35
36 private static final int SENT = 0;
37 private static final int RECEIVED = 1;
38 private static final int STATUS = 2;
39 private static final int NULL = 3;
40
41 private ConversationActivity activity;
42
43 private Bitmap accountBitmap;
44
45 private BitmapCache mBitmapCache = new BitmapCache();
46 private DisplayMetrics metrics;
47
48 private OnContactPictureClicked mOnContactPictureClickedListener;
49 private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
50
51 public MessageAdapter(ConversationActivity activity, List<Message> messages) {
52 super(activity, 0, messages);
53 this.activity = activity;
54 metrics = getContext().getResources().getDisplayMetrics();
55 }
56
57 private Bitmap getSelfBitmap() {
58 if (this.accountBitmap == null) {
59
60 if (getCount() > 0) {
61 this.accountBitmap = getItem(0).getConversation().getAccount()
62 .getImage(getContext(), 48);
63 }
64 }
65 return this.accountBitmap;
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 4;
80 }
81
82 @Override
83 public int getItemViewType(int position) {
84 if (getItem(position).wasMergedIntoPrevious()) {
85 return NULL;
86 } else if (getItem(position).getType() == Message.TYPE_STATUS) {
87 return STATUS;
88 } else if (getItem(position).getStatus() <= Message.STATUS_RECEIVED) {
89 return RECEIVED;
90 } else {
91 return SENT;
92 }
93 }
94
95 private void displayStatus(ViewHolder viewHolder, Message message) {
96 String filesize = null;
97 String info = null;
98 boolean error = false;
99 if (viewHolder.indicatorReceived != null) {
100 viewHolder.indicatorReceived.setVisibility(View.GONE);
101 }
102 boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
103 && message.getMergedStatus() <= Message.STATUS_RECEIVED;
104 if (message.getType() == Message.TYPE_IMAGE) {
105 String[] fileParams = message.getBody().split(",");
106 try {
107 long size = Long.parseLong(fileParams[0]);
108 filesize = size / 1024 + " KB";
109 } catch (NumberFormatException e) {
110 filesize = "0 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 case Message.STATUS_RECEPTION_FAILED:
142 info = getContext().getString(R.string.reception_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 if (message.getPresence() != null) {
152 info = message.getPresence();
153 } else {
154 info = message.getCounterpart();
155 }
156 }
157 }
158 break;
159 }
160 if (error) {
161 viewHolder.time.setTextColor(activity.getWarningTextColor());
162 } else {
163 viewHolder.time.setTextColor(activity.getSecondaryTextColor());
164 }
165 if (message.getEncryption() == Message.ENCRYPTION_NONE) {
166 viewHolder.indicator.setVisibility(View.GONE);
167 } else {
168 viewHolder.indicator.setVisibility(View.VISIBLE);
169 }
170
171 String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(),
172 message.getMergedTimeSent());
173 if (message.getStatus() <= Message.STATUS_RECEIVED) {
174 if ((filesize != null) && (info != null)) {
175 viewHolder.time.setText(filesize + " \u00B7 " + info);
176 } else if ((filesize == null) && (info != null)) {
177 viewHolder.time.setText(formatedTime + " \u00B7 " + info);
178 } else if ((filesize != null) && (info == null)) {
179 viewHolder.time.setText(formatedTime + " \u00B7 " + filesize);
180 } else {
181 viewHolder.time.setText(formatedTime);
182 }
183 } else {
184 if ((filesize != null) && (info != null)) {
185 viewHolder.time.setText(filesize + " \u00B7 " + info);
186 } else if ((filesize == null) && (info != null)) {
187 if (error) {
188 viewHolder.time.setText(info + " \u00B7 " + formatedTime);
189 } else {
190 viewHolder.time.setText(info);
191 }
192 } else if ((filesize != null) && (info == null)) {
193 viewHolder.time.setText(filesize + " \u00B7 " + formatedTime);
194 } else {
195 viewHolder.time.setText(formatedTime);
196 }
197 }
198 }
199
200 private void displayInfoMessage(ViewHolder viewHolder, int r) {
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(r));
207 viewHolder.messageBody.setTextColor(activity.getSecondaryTextColor());
208 viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
209 viewHolder.messageBody.setTextIsSelectable(false);
210 }
211
212 private void displayDecryptionFailed(ViewHolder viewHolder) {
213 if (viewHolder.download_button != null) {
214 viewHolder.download_button.setVisibility(View.GONE);
215 }
216 viewHolder.image.setVisibility(View.GONE);
217 viewHolder.messageBody.setVisibility(View.VISIBLE);
218 viewHolder.messageBody.setText(getContext().getString(
219 R.string.decryption_failed));
220 viewHolder.messageBody.setTextColor(activity.getWarningTextColor());
221 viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
222 viewHolder.messageBody.setTextIsSelectable(false);
223 }
224
225 private void displayTextMessage(ViewHolder viewHolder, 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 if (message.getType() != Message.TYPE_PRIVATE) {
233 String body = Config.PARSE_EMOTICONS ? UIHelper
234 .transformAsciiEmoticons(message.getMergedBody())
235 : message.getMergedBody();
236 viewHolder.messageBody.setText(body);
237 } else {
238 String privateMarker;
239 if (message.getStatus() <= Message.STATUS_RECEIVED) {
240 privateMarker = activity
241 .getString(R.string.private_message);
242 } else {
243 String to;
244 if (message.getPresence() != null) {
245 to = message.getPresence();
246 } else {
247 to = message.getCounterpart();
248 }
249 privateMarker = activity.getString(
250 R.string.private_message_to, to);
251 }
252 SpannableString span = new SpannableString(privateMarker + " "
253 + message.getBody());
254 span.setSpan(
255 new ForegroundColorSpan(activity
256 .getSecondaryTextColor()), 0, privateMarker
257 .length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
258 span.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0,
259 privateMarker.length(),
260 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
261 viewHolder.messageBody.setText(span);
262 }
263 } else {
264 viewHolder.messageBody.setText("");
265 }
266 viewHolder.messageBody.setTextColor(activity.getPrimaryTextColor());
267 viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
268 viewHolder.messageBody.setTextIsSelectable(true);
269 }
270
271 private void displayImageMessage(ViewHolder viewHolder,
272 final Message message) {
273 if (viewHolder.download_button != null) {
274 viewHolder.download_button.setVisibility(View.GONE);
275 }
276 viewHolder.messageBody.setVisibility(View.GONE);
277 viewHolder.image.setVisibility(View.VISIBLE);
278 String[] fileParams = message.getBody().split(",");
279 if (fileParams.length == 3) {
280 double target = metrics.density * 288;
281 int w = Integer.parseInt(fileParams[1]);
282 int h = Integer.parseInt(fileParams[2]);
283 int scalledW;
284 int scalledH;
285 if (w <= h) {
286 scalledW = (int) (w / ((double) h / target));
287 scalledH = (int) target;
288 } else {
289 scalledW = (int) target;
290 scalledH = (int) (h / ((double) w / target));
291 }
292 viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(
293 scalledW, scalledH));
294 }
295 activity.loadBitmap(message, viewHolder.image);
296 viewHolder.image.setOnClickListener(new OnClickListener() {
297
298 @Override
299 public void onClick(View v) {
300 Intent intent = new Intent(Intent.ACTION_VIEW);
301 intent.setDataAndType(activity.xmppConnectionService
302 .getFileBackend().getJingleFileUri(message), "image/*");
303 getContext().startActivity(intent);
304 }
305 });
306 viewHolder.image.setOnLongClickListener(new OnLongClickListener() {
307
308 @Override
309 public boolean onLongClick(View v) {
310 Intent shareIntent = new Intent();
311 shareIntent.setAction(Intent.ACTION_SEND);
312 shareIntent.putExtra(Intent.EXTRA_STREAM,
313 activity.xmppConnectionService.getFileBackend()
314 .getJingleFileUri(message));
315 shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
316 shareIntent.setType("image/webp");
317 getContext().startActivity(
318 Intent.createChooser(shareIntent,
319 getContext().getText(R.string.share_with)));
320 return true;
321 }
322 });
323 }
324
325 @Override
326 public View getView(int position, View view, ViewGroup parent) {
327 final Message item = getItem(position);
328 int type = getItemViewType(position);
329 ViewHolder viewHolder;
330 if (view == null) {
331 viewHolder = new ViewHolder();
332 switch (type) {
333 case NULL:
334 view = (View) activity.getLayoutInflater().inflate(
335 R.layout.message_null, parent, false);
336 break;
337 case SENT:
338 view = (View) activity.getLayoutInflater().inflate(
339 R.layout.message_sent, parent, false);
340 viewHolder.message_box = (LinearLayout) view
341 .findViewById(R.id.message_box);
342 viewHolder.contact_picture = (ImageView) view
343 .findViewById(R.id.message_photo);
344 viewHolder.contact_picture.setImageBitmap(getSelfBitmap());
345 viewHolder.indicator = (ImageView) view
346 .findViewById(R.id.security_indicator);
347 viewHolder.image = (ImageView) view
348 .findViewById(R.id.message_image);
349 viewHolder.messageBody = (TextView) view
350 .findViewById(R.id.message_body);
351 viewHolder.time = (TextView) view
352 .findViewById(R.id.message_time);
353 viewHolder.indicatorReceived = (ImageView) view
354 .findViewById(R.id.indicator_received);
355 view.setTag(viewHolder);
356 break;
357 case RECEIVED:
358 view = (View) activity.getLayoutInflater().inflate(
359 R.layout.message_received, parent, false);
360 viewHolder.message_box = (LinearLayout) view
361 .findViewById(R.id.message_box);
362 viewHolder.contact_picture = (ImageView) view
363 .findViewById(R.id.message_photo);
364
365 viewHolder.download_button = (Button) view
366 .findViewById(R.id.download_button);
367
368 if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
369
370 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
371 item.getConversation().getContact(), getContext()));
372
373 }
374 viewHolder.indicator = (ImageView) view
375 .findViewById(R.id.security_indicator);
376 viewHolder.image = (ImageView) view
377 .findViewById(R.id.message_image);
378 viewHolder.messageBody = (TextView) view
379 .findViewById(R.id.message_body);
380 viewHolder.time = (TextView) view
381 .findViewById(R.id.message_time);
382 view.setTag(viewHolder);
383 break;
384 case STATUS:
385 view = (View) activity.getLayoutInflater().inflate(
386 R.layout.message_status, parent, false);
387 viewHolder.contact_picture = (ImageView) view
388 .findViewById(R.id.message_photo);
389 if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
390
391 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
392 item.getConversation().getContact(), getContext()));
393 viewHolder.contact_picture.setAlpha(0.5f);
394 viewHolder.contact_picture
395 .setOnClickListener(new OnClickListener() {
396
397 @Override
398 public void onClick(View v) {
399 String name = item.getConversation()
400 .getName();
401 String read = getContext()
402 .getString(
403 R.string.contact_has_read_up_to_this_point,
404 name);
405 Toast.makeText(getContext(), read,
406 Toast.LENGTH_SHORT).show();
407 }
408 });
409
410 }
411 break;
412 default:
413 viewHolder = null;
414 break;
415 }
416 } else {
417 viewHolder = (ViewHolder) view.getTag();
418 }
419
420 if (type == STATUS) {
421 return view;
422 }
423 if (type == NULL) {
424 if (position == getCount() - 1) {
425 view.getLayoutParams().height = 1;
426 } else {
427 view.getLayoutParams().height = 0;
428
429 }
430 view.setLayoutParams(view.getLayoutParams());
431 return view;
432 }
433
434 if (viewHolder.contact_picture != null) {
435 viewHolder.contact_picture
436 .setOnClickListener(new OnClickListener() {
437
438 @Override
439 public void onClick(View v) {
440 if (MessageAdapter.this.mOnContactPictureClickedListener != null) {
441 MessageAdapter.this.mOnContactPictureClickedListener
442 .onContactPictureClicked(item);
443 ;
444 }
445
446 }
447 });
448 viewHolder.contact_picture
449 .setOnLongClickListener(new OnLongClickListener() {
450
451 @Override
452 public boolean onLongClick(View v) {
453 if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
454 MessageAdapter.this.mOnContactPictureLongClickedListener
455 .onContactPictureLongClicked(item);
456 return true;
457 } else {
458 return false;
459 }
460 }
461 });
462 }
463
464 if (type == RECEIVED) {
465 if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
466 Contact contact = item.getContact();
467 if (contact != null) {
468 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
469 contact, getContext()));
470 } else {
471 String name = item.getPresence();
472 if (name == null) {
473 name = item.getCounterpart();
474 }
475 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
476 name, getContext()));
477 }
478 }
479 }
480
481 if (item.getType() == Message.TYPE_IMAGE) {
482 if (item.getStatus() == Message.STATUS_RECEIVING) {
483 displayInfoMessage(viewHolder, R.string.receiving_image);
484 } else if (item.getStatus() == Message.STATUS_RECEIVED_OFFER) {
485 viewHolder.image.setVisibility(View.GONE);
486 viewHolder.messageBody.setVisibility(View.GONE);
487 viewHolder.download_button.setVisibility(View.VISIBLE);
488 viewHolder.download_button
489 .setOnClickListener(new OnClickListener() {
490
491 @Override
492 public void onClick(View v) {
493 Downloadable downloadable = item
494 .getDownloadable();
495 if (downloadable != null) {
496 downloadable.start();
497 }
498 }
499 });
500 } else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED)
501 || (item.getEncryption() == Message.ENCRYPTION_NONE)
502 || (item.getEncryption() == Message.ENCRYPTION_OTR)) {
503 displayImageMessage(viewHolder, item);
504 } else if (item.getEncryption() == Message.ENCRYPTION_PGP) {
505 displayInfoMessage(viewHolder, R.string.encrypted_message);
506 } else {
507 displayDecryptionFailed(viewHolder);
508 }
509 } else {
510 if (item.getEncryption() == Message.ENCRYPTION_PGP) {
511 if (activity.hasPgp()) {
512 displayInfoMessage(viewHolder, R.string.encrypted_message);
513 } else {
514 displayInfoMessage(viewHolder,
515 R.string.install_openkeychain);
516 viewHolder.message_box
517 .setOnClickListener(new OnClickListener() {
518
519 @Override
520 public void onClick(View v) {
521 activity.showInstallPgpDialog();
522 }
523 });
524 }
525 } else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
526 displayDecryptionFailed(viewHolder);
527 } else {
528 displayTextMessage(viewHolder, item);
529 }
530 }
531
532 displayStatus(viewHolder, item);
533
534 return view;
535 }
536
537 private static class ViewHolder {
538
539 protected LinearLayout message_box;
540 protected Button download_button;
541 protected ImageView image;
542 protected ImageView indicator;
543 protected ImageView indicatorReceived;
544 protected TextView time;
545 protected TextView messageBody;
546 protected ImageView contact_picture;
547
548 }
549
550 private class BitmapCache {
551 private HashMap<String, Bitmap> contactBitmaps = new HashMap<String, Bitmap>();
552 private HashMap<String, Bitmap> unknownBitmaps = new HashMap<String, Bitmap>();
553
554 public Bitmap get(Contact contact, Context context) {
555 if (!contactBitmaps.containsKey(contact.getJid())) {
556 contactBitmaps.put(contact.getJid(),
557 contact.getImage(48, context));
558 }
559 return contactBitmaps.get(contact.getJid());
560 }
561
562 public Bitmap get(String name, Context context) {
563 if (unknownBitmaps.containsKey(name)) {
564 return unknownBitmaps.get(name);
565 } else {
566 Bitmap bm = UIHelper
567 .getContactPicture(name, 48, context, false);
568 unknownBitmaps.put(name, bm);
569 return bm;
570 }
571 }
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}