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.readableTimeDifference(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(128);
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 || type == NULL) {
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 if (item.getStatus() == Message.STATUS_RECEIVING) {
473 displayInfoMessage(viewHolder, R.string.receiving_image);
474 } else if (item.getStatus() == Message.STATUS_RECEIVED_OFFER) {
475 viewHolder.image.setVisibility(View.GONE);
476 viewHolder.messageBody.setVisibility(View.GONE);
477 viewHolder.download_button.setVisibility(View.VISIBLE);
478 viewHolder.download_button
479 .setOnClickListener(new OnClickListener() {
480
481 @Override
482 public void onClick(View v) {
483 Downloadable downloadable = item
484 .getDownloadable();
485 if (downloadable != null) {
486 downloadable.start();
487 }
488 }
489 });
490 } else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED)
491 || (item.getEncryption() == Message.ENCRYPTION_NONE)
492 || (item.getEncryption() == Message.ENCRYPTION_OTR)) {
493 displayImageMessage(viewHolder, item);
494 } else if (item.getEncryption() == Message.ENCRYPTION_PGP) {
495 displayInfoMessage(viewHolder, R.string.encrypted_message);
496 } else {
497 displayDecryptionFailed(viewHolder);
498 }
499 } else {
500 if (item.getEncryption() == Message.ENCRYPTION_PGP) {
501 if (activity.hasPgp()) {
502 displayInfoMessage(viewHolder, R.string.encrypted_message);
503 } else {
504 displayInfoMessage(viewHolder,
505 R.string.install_openkeychain);
506 viewHolder.message_box
507 .setOnClickListener(new OnClickListener() {
508
509 @Override
510 public void onClick(View v) {
511 activity.showInstallPgpDialog();
512 }
513 });
514 }
515 } else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
516 displayDecryptionFailed(viewHolder);
517 } else {
518 displayTextMessage(viewHolder, item);
519 }
520 }
521
522 displayStatus(viewHolder, item);
523
524 return view;
525 }
526
527 private static class ViewHolder {
528
529 protected LinearLayout message_box;
530 protected Button download_button;
531 protected ImageView image;
532 protected ImageView indicator;
533 protected ImageView indicatorReceived;
534 protected TextView time;
535 protected TextView messageBody;
536 protected ImageView contact_picture;
537
538 }
539
540 private class BitmapCache {
541 private HashMap<String, Bitmap> contactBitmaps = new HashMap<String, Bitmap>();
542 private HashMap<String, Bitmap> unknownBitmaps = new HashMap<String, Bitmap>();
543
544 public Bitmap get(Contact contact, Context context) {
545 if (!contactBitmaps.containsKey(contact.getJid())) {
546 contactBitmaps.put(contact.getJid(),
547 contact.getImage(48, context));
548 }
549 return contactBitmaps.get(contact.getJid());
550 }
551
552 public Bitmap get(String name, Context context) {
553 if (unknownBitmaps.containsKey(name)) {
554 return unknownBitmaps.get(name);
555 } else {
556 Bitmap bm = UIHelper
557 .getContactPicture(name, 48, context, false);
558 unknownBitmaps.put(name, bm);
559 return bm;
560 }
561 }
562 }
563
564 public interface OnContactPictureClicked {
565 public void onContactPictureClicked(Message message);
566 }
567
568 public interface OnContactPictureLongClicked {
569 public void onContactPictureLongClicked(Message message);
570 }
571}