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.Message;
11import eu.siacs.conversations.ui.ConversationActivity;
12import eu.siacs.conversations.utils.UIHelper;
13import eu.siacs.conversations.xmpp.jingle.JingleConnection;
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 boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
100 && message.getMergedStatus() <= Message.STATUS_RECEIVED;
101 if (message.getType() == Message.TYPE_IMAGE) {
102 String[] fileParams = message.getBody().split(",");
103 try {
104 long size = Long.parseLong(fileParams[0]);
105 filesize = size / 1024 + " KB";
106 } catch (NumberFormatException e) {
107 filesize = "0 KB";
108 }
109 }
110 switch (message.getMergedStatus()) {
111 case Message.STATUS_WAITING:
112 info = getContext().getString(R.string.waiting);
113 break;
114 case Message.STATUS_UNSEND:
115 info = getContext().getString(R.string.sending);
116 break;
117 case Message.STATUS_OFFERED:
118 info = getContext().getString(R.string.offering);
119 break;
120 case Message.STATUS_SEND_FAILED:
121 info = getContext().getString(R.string.send_failed);
122 error = true;
123 break;
124 case Message.STATUS_SEND_REJECTED:
125 info = getContext().getString(R.string.send_rejected);
126 error = true;
127 break;
128 case Message.STATUS_RECEPTION_FAILED:
129 info = getContext().getString(R.string.reception_failed);
130 error = true;
131 default:
132 if (multiReceived) {
133 Contact contact = message.getContact();
134 if (contact != null) {
135 info = contact.getDisplayName();
136 } else {
137 if (message.getPresence() != null) {
138 info = message.getPresence();
139 } else {
140 info = message.getCounterpart();
141 }
142 }
143 }
144 break;
145 }
146 if (error) {
147 viewHolder.time.setTextColor(0xFFe92727);
148 } else {
149 viewHolder.time.setTextColor(activity.getSecondaryTextColor());
150 }
151 if (message.getEncryption() == Message.ENCRYPTION_NONE) {
152 viewHolder.indicator.setVisibility(View.GONE);
153 } else {
154 viewHolder.indicator.setVisibility(View.VISIBLE);
155 }
156
157 String formatedTime = UIHelper.readableTimeDifference(getContext(),
158 message.getMergedTimeSent());
159 if (message.getStatus() <= Message.STATUS_RECEIVED) {
160 if ((filesize != null) && (info != null)) {
161 viewHolder.time.setText(filesize + " \u00B7 " + info);
162 } else if ((filesize == null) && (info != null)) {
163 viewHolder.time.setText(formatedTime + " \u00B7 " + info);
164 } else if ((filesize != null) && (info == null)) {
165 viewHolder.time.setText(formatedTime + " \u00B7 " + filesize);
166 } else {
167 viewHolder.time.setText(formatedTime);
168 }
169 } else {
170 if ((filesize != null) && (info != null)) {
171 viewHolder.time.setText(filesize + " \u00B7 " + info);
172 } else if ((filesize == null) && (info != null)) {
173 if (error) {
174 viewHolder.time.setText(info + " \u00B7 " + formatedTime);
175 } else {
176 viewHolder.time.setText(info);
177 }
178 } else if ((filesize != null) && (info == null)) {
179 viewHolder.time.setText(filesize + " \u00B7 " + formatedTime);
180 } else {
181 viewHolder.time.setText(formatedTime);
182 }
183 }
184 }
185
186 private void displayInfoMessage(ViewHolder viewHolder, int r) {
187 if (viewHolder.download_button != null) {
188 viewHolder.download_button.setVisibility(View.GONE);
189 }
190 viewHolder.image.setVisibility(View.GONE);
191 viewHolder.messageBody.setVisibility(View.VISIBLE);
192 viewHolder.messageBody.setText(getContext().getString(r));
193 viewHolder.messageBody.setTextColor(0xff33B5E5);
194 viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
195 viewHolder.messageBody.setTextIsSelectable(false);
196 }
197
198 private void displayDecryptionFailed(ViewHolder viewHolder) {
199 if (viewHolder.download_button != null) {
200 viewHolder.download_button.setVisibility(View.GONE);
201 }
202 viewHolder.image.setVisibility(View.GONE);
203 viewHolder.messageBody.setVisibility(View.VISIBLE);
204 viewHolder.messageBody.setText(getContext().getString(
205 R.string.decryption_failed));
206 viewHolder.messageBody.setTextColor(0xFFe92727);
207 viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
208 viewHolder.messageBody.setTextIsSelectable(false);
209 }
210
211 private void displayTextMessage(ViewHolder viewHolder, Message message) {
212 if (viewHolder.download_button != null) {
213 viewHolder.download_button.setVisibility(View.GONE);
214 }
215 viewHolder.image.setVisibility(View.GONE);
216 viewHolder.messageBody.setVisibility(View.VISIBLE);
217 if (message.getBody() != null) {
218 if (message.getType() != Message.TYPE_PRIVATE) {
219 String body = Config.PARSE_EMOTICONS ? UIHelper
220 .transformAsciiEmoticons(message.getMergedBody())
221 : message.getMergedBody();
222 viewHolder.messageBody.setText(body);
223 } else {
224 String privateMarker;
225 if (message.getStatus() <= Message.STATUS_RECEIVED) {
226 privateMarker = activity
227 .getString(R.string.private_message);
228 } else {
229 String to;
230 if (message.getPresence() != null) {
231 to = message.getPresence();
232 } else {
233 to = message.getCounterpart();
234 }
235 privateMarker = activity.getString(
236 R.string.private_message_to, to);
237 }
238 SpannableString span = new SpannableString(privateMarker + " "
239 + message.getBody());
240 span.setSpan(
241 new ForegroundColorSpan(activity
242 .getSecondaryTextColor()), 0, privateMarker
243 .length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
244 span.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0,
245 privateMarker.length(),
246 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
247 viewHolder.messageBody.setText(span);
248 }
249 } else {
250 viewHolder.messageBody.setText("");
251 }
252 viewHolder.messageBody.setTextColor(activity.getPrimaryTextColor());
253 viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
254 viewHolder.messageBody.setTextIsSelectable(true);
255 }
256
257 private void displayImageMessage(ViewHolder viewHolder,
258 final Message message) {
259 if (viewHolder.download_button != null) {
260 viewHolder.download_button.setVisibility(View.GONE);
261 }
262 viewHolder.messageBody.setVisibility(View.GONE);
263 viewHolder.image.setVisibility(View.VISIBLE);
264 String[] fileParams = message.getBody().split(",");
265 if (fileParams.length == 3) {
266 double target = metrics.density * 288;
267 int w = Integer.parseInt(fileParams[1]);
268 int h = Integer.parseInt(fileParams[2]);
269 int scalledW;
270 int scalledH;
271 if (w <= h) {
272 scalledW = (int) (w / ((double) h / target));
273 scalledH = (int) target;
274 } else {
275 scalledW = (int) target;
276 scalledH = (int) (h / ((double) w / target));
277 }
278 viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(
279 scalledW, scalledH));
280 }
281 activity.loadBitmap(message, viewHolder.image);
282 viewHolder.image.setOnClickListener(new OnClickListener() {
283
284 @Override
285 public void onClick(View v) {
286 Intent intent = new Intent(Intent.ACTION_VIEW);
287 intent.setDataAndType(activity.xmppConnectionService
288 .getFileBackend().getJingleFileUri(message), "image/*");
289 getContext().startActivity(intent);
290 }
291 });
292 viewHolder.image.setOnLongClickListener(new OnLongClickListener() {
293
294 @Override
295 public boolean onLongClick(View v) {
296 Intent shareIntent = new Intent();
297 shareIntent.setAction(Intent.ACTION_SEND);
298 shareIntent.putExtra(Intent.EXTRA_STREAM,
299 activity.xmppConnectionService.getFileBackend()
300 .getJingleFileUri(message));
301 shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
302 shareIntent.setType("image/webp");
303 getContext().startActivity(
304 Intent.createChooser(shareIntent,
305 getContext().getText(R.string.share_with)));
306 return true;
307 }
308 });
309 }
310
311 @Override
312 public View getView(int position, View view, ViewGroup parent) {
313 final Message item = getItem(position);
314 int type = getItemViewType(position);
315 ViewHolder viewHolder;
316 if (view == null) {
317 viewHolder = new ViewHolder();
318 switch (type) {
319 case NULL:
320 view = (View) activity.getLayoutInflater().inflate(
321 R.layout.message_null, parent, false);
322 break;
323 case SENT:
324 view = (View) activity.getLayoutInflater().inflate(
325 R.layout.message_sent, parent, false);
326 viewHolder.message_box = (LinearLayout) view
327 .findViewById(R.id.message_box);
328 viewHolder.contact_picture = (ImageView) view
329 .findViewById(R.id.message_photo);
330 viewHolder.contact_picture.setImageBitmap(getSelfBitmap());
331 viewHolder.indicator = (ImageView) view
332 .findViewById(R.id.security_indicator);
333 viewHolder.image = (ImageView) view
334 .findViewById(R.id.message_image);
335 viewHolder.messageBody = (TextView) view
336 .findViewById(R.id.message_body);
337 viewHolder.time = (TextView) view
338 .findViewById(R.id.message_time);
339 view.setTag(viewHolder);
340 break;
341 case RECEIVED:
342 view = (View) activity.getLayoutInflater().inflate(
343 R.layout.message_received, parent, false);
344 viewHolder.message_box = (LinearLayout) view
345 .findViewById(R.id.message_box);
346 viewHolder.contact_picture = (ImageView) view
347 .findViewById(R.id.message_photo);
348
349 viewHolder.download_button = (Button) view
350 .findViewById(R.id.download_button);
351
352 if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
353
354 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
355 item.getConversation().getContact(), getContext()));
356
357 }
358 viewHolder.indicator = (ImageView) view
359 .findViewById(R.id.security_indicator);
360 viewHolder.image = (ImageView) view
361 .findViewById(R.id.message_image);
362 viewHolder.messageBody = (TextView) view
363 .findViewById(R.id.message_body);
364 viewHolder.time = (TextView) view
365 .findViewById(R.id.message_time);
366 view.setTag(viewHolder);
367 break;
368 case STATUS:
369 view = (View) activity.getLayoutInflater().inflate(
370 R.layout.message_status, parent, false);
371 viewHolder.contact_picture = (ImageView) view
372 .findViewById(R.id.message_photo);
373 if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
374
375 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
376 item.getConversation().getContact(), getContext()));
377 viewHolder.contact_picture.setAlpha(128);
378 viewHolder.contact_picture
379 .setOnClickListener(new OnClickListener() {
380
381 @Override
382 public void onClick(View v) {
383 String name = item.getConversation()
384 .getName();
385 String read = getContext()
386 .getString(
387 R.string.contact_has_read_up_to_this_point,
388 name);
389 Toast.makeText(getContext(), read,
390 Toast.LENGTH_SHORT).show();
391 }
392 });
393
394 }
395 break;
396 default:
397 viewHolder = null;
398 break;
399 }
400 } else {
401 viewHolder = (ViewHolder) view.getTag();
402 }
403
404 if (type == STATUS || type == NULL) {
405 return view;
406 }
407
408 if (type == RECEIVED) {
409 if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
410 Contact contact = item.getContact();
411 if (contact != null) {
412 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
413 contact, getContext()));
414 } else {
415 String name = item.getPresence();
416 if (name == null) {
417 name = item.getCounterpart();
418 }
419 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
420 name, getContext()));
421 }
422 viewHolder.contact_picture
423 .setOnClickListener(new OnClickListener() {
424
425 @Override
426 public void onClick(View v) {
427 if (MessageAdapter.this.mOnContactPictureClickedListener != null) {
428 MessageAdapter.this.mOnContactPictureClickedListener
429 .onContactPictureClicked(item);
430 ;
431 }
432
433 }
434 });
435 viewHolder.contact_picture
436 .setOnLongClickListener(new OnLongClickListener() {
437
438 @Override
439 public boolean onLongClick(View v) {
440 if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
441 MessageAdapter.this.mOnContactPictureLongClickedListener
442 .onContactPictureLongClicked(item);
443 return true;
444 } else {
445 return false;
446 }
447 }
448 });
449 }
450 }
451
452 if (item.getType() == Message.TYPE_IMAGE) {
453 if (item.getStatus() == Message.STATUS_RECEIVING) {
454 displayInfoMessage(viewHolder, R.string.receiving_image);
455 } else if (item.getStatus() == Message.STATUS_RECEIVED_OFFER) {
456 viewHolder.image.setVisibility(View.GONE);
457 viewHolder.messageBody.setVisibility(View.GONE);
458 viewHolder.download_button.setVisibility(View.VISIBLE);
459 viewHolder.download_button
460 .setOnClickListener(new OnClickListener() {
461
462 @Override
463 public void onClick(View v) {
464 JingleConnection connection = item
465 .getJingleConnection();
466 if (connection != null) {
467 connection.accept();
468 }
469 }
470 });
471 } else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED)
472 || (item.getEncryption() == Message.ENCRYPTION_NONE)
473 || (item.getEncryption() == Message.ENCRYPTION_OTR)) {
474 displayImageMessage(viewHolder, item);
475 } else if (item.getEncryption() == Message.ENCRYPTION_PGP) {
476 displayInfoMessage(viewHolder, R.string.encrypted_message);
477 } else {
478 displayDecryptionFailed(viewHolder);
479 }
480 } else {
481 if (item.getEncryption() == Message.ENCRYPTION_PGP) {
482 if (activity.hasPgp()) {
483 displayInfoMessage(viewHolder, R.string.encrypted_message);
484 } else {
485 displayInfoMessage(viewHolder,
486 R.string.install_openkeychain);
487 viewHolder.message_box
488 .setOnClickListener(new OnClickListener() {
489
490 @Override
491 public void onClick(View v) {
492 activity.showInstallPgpDialog();
493 }
494 });
495 }
496 } else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
497 displayDecryptionFailed(viewHolder);
498 } else if (item.getEncryption() == Message.ENCRYPTION_OTR
499 && item.getStatus() == Message.STATUS_RECEPTION_FAILED
500 && item.getType() == Message.TYPE_TEXT) {
501 displayInfoMessage(viewHolder,
502 R.string.unable_to_decrypt_otr_message);
503 } else {
504 displayTextMessage(viewHolder, item);
505 }
506 }
507
508 displayStatus(viewHolder, item);
509
510 return view;
511 }
512
513 private static class ViewHolder {
514
515 protected LinearLayout message_box;
516 protected Button download_button;
517 protected ImageView image;
518 protected ImageView indicator;
519 protected TextView time;
520 protected TextView messageBody;
521 protected ImageView contact_picture;
522
523 }
524
525 private class BitmapCache {
526 private HashMap<String, Bitmap> contactBitmaps = new HashMap<String, Bitmap>();
527 private HashMap<String, Bitmap> unknownBitmaps = new HashMap<String, Bitmap>();
528
529 public Bitmap get(Contact contact, Context context) {
530 if (!contactBitmaps.containsKey(contact.getJid())) {
531 contactBitmaps.put(contact.getJid(),
532 contact.getImage(48, context));
533 }
534 return contactBitmaps.get(contact.getJid());
535 }
536
537 public Bitmap get(String name, Context context) {
538 if (unknownBitmaps.containsKey(name)) {
539 return unknownBitmaps.get(name);
540 } else {
541 Bitmap bm = UIHelper
542 .getContactPicture(name, 48, context, false);
543 unknownBitmaps.put(name, bm);
544 return bm;
545 }
546 }
547 }
548
549 public interface OnContactPictureClicked {
550 public void onContactPictureClicked(Message message);
551 }
552
553 public interface OnContactPictureLongClicked {
554 public void onContactPictureLongClicked(Message message);
555 }
556}