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