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