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 (type == RECEIVED) {
425 if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
426 Contact contact = item.getContact();
427 if (contact != null) {
428 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
429 contact, getContext()));
430 } else {
431 String name = item.getPresence();
432 if (name == null) {
433 name = item.getCounterpart();
434 }
435 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
436 name, getContext()));
437 }
438 viewHolder.contact_picture
439 .setOnClickListener(new OnClickListener() {
440
441 @Override
442 public void onClick(View v) {
443 if (MessageAdapter.this.mOnContactPictureClickedListener != null) {
444 MessageAdapter.this.mOnContactPictureClickedListener
445 .onContactPictureClicked(item);
446 ;
447 }
448
449 }
450 });
451 viewHolder.contact_picture
452 .setOnLongClickListener(new OnLongClickListener() {
453
454 @Override
455 public boolean onLongClick(View v) {
456 if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
457 MessageAdapter.this.mOnContactPictureLongClickedListener
458 .onContactPictureLongClicked(item);
459 return true;
460 } else {
461 return false;
462 }
463 }
464 });
465 }
466 }
467
468 if (item.getType() == Message.TYPE_IMAGE) {
469 if (item.getStatus() == Message.STATUS_RECEIVING) {
470 displayInfoMessage(viewHolder, R.string.receiving_image);
471 } else if (item.getStatus() == Message.STATUS_RECEIVED_OFFER) {
472 viewHolder.image.setVisibility(View.GONE);
473 viewHolder.messageBody.setVisibility(View.GONE);
474 viewHolder.download_button.setVisibility(View.VISIBLE);
475 viewHolder.download_button
476 .setOnClickListener(new OnClickListener() {
477
478 @Override
479 public void onClick(View v) {
480 Downloadable downloadable = item
481 .getDownloadable();
482 if (downloadable != null) {
483 downloadable.start();
484 }
485 }
486 });
487 } else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED)
488 || (item.getEncryption() == Message.ENCRYPTION_NONE)
489 || (item.getEncryption() == Message.ENCRYPTION_OTR)) {
490 displayImageMessage(viewHolder, item);
491 } else if (item.getEncryption() == Message.ENCRYPTION_PGP) {
492 displayInfoMessage(viewHolder, R.string.encrypted_message);
493 } else {
494 displayDecryptionFailed(viewHolder);
495 }
496 } else {
497 if (item.getEncryption() == Message.ENCRYPTION_PGP) {
498 if (activity.hasPgp()) {
499 displayInfoMessage(viewHolder, R.string.encrypted_message);
500 } else {
501 displayInfoMessage(viewHolder,
502 R.string.install_openkeychain);
503 viewHolder.message_box
504 .setOnClickListener(new OnClickListener() {
505
506 @Override
507 public void onClick(View v) {
508 activity.showInstallPgpDialog();
509 }
510 });
511 }
512 } else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
513 displayDecryptionFailed(viewHolder);
514 } else {
515 displayTextMessage(viewHolder, item);
516 }
517 }
518
519 displayStatus(viewHolder, item);
520
521 return view;
522 }
523
524 private static class ViewHolder {
525
526 protected LinearLayout message_box;
527 protected Button download_button;
528 protected ImageView image;
529 protected ImageView indicator;
530 protected ImageView indicatorReceived;
531 protected TextView time;
532 protected TextView messageBody;
533 protected ImageView contact_picture;
534
535 }
536
537 private class BitmapCache {
538 private HashMap<String, Bitmap> contactBitmaps = new HashMap<String, Bitmap>();
539 private HashMap<String, Bitmap> unknownBitmaps = new HashMap<String, Bitmap>();
540
541 public Bitmap get(Contact contact, Context context) {
542 if (!contactBitmaps.containsKey(contact.getJid())) {
543 contactBitmaps.put(contact.getJid(),
544 contact.getImage(48, context));
545 }
546 return contactBitmaps.get(contact.getJid());
547 }
548
549 public Bitmap get(String name, Context context) {
550 if (unknownBitmaps.containsKey(name)) {
551 return unknownBitmaps.get(name);
552 } else {
553 Bitmap bm = UIHelper
554 .getContactPicture(name, 48, context, false);
555 unknownBitmaps.put(name, bm);
556 return bm;
557 }
558 }
559 }
560
561 public interface OnContactPictureClicked {
562 public void onContactPictureClicked(Message message);
563 }
564
565 public interface OnContactPictureLongClicked {
566 public void onContactPictureLongClicked(Message message);
567 }
568}