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.Html;
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(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.getStatus() <= 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.getStatus()) {
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.getTimeSent());
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.getString(R.string.private_message);
223 } else {
224 String to;
225 if (message.getPresence() != null) {
226 to = message.getPresence();
227 } else {
228 to = message.getCounterpart();
229 }
230 privateMarker = activity.getString(R.string.private_message_to, to);
231 }
232 SpannableString span = new SpannableString(privateMarker+" "+message.getBody());
233 span.setSpan(new ForegroundColorSpan(activity.getSecondaryTextColor()), 0, privateMarker.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
234 span.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, privateMarker.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
235 viewHolder.messageBody.setText(span);
236 }
237 } else {
238 viewHolder.messageBody.setText("");
239 }
240 viewHolder.messageBody.setTextColor(activity.getPrimaryTextColor());
241 viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
242 viewHolder.messageBody.setTextIsSelectable(true);
243 }
244
245 private void displayImageMessage(ViewHolder viewHolder,
246 final Message message) {
247 if (viewHolder.download_button != null) {
248 viewHolder.download_button.setVisibility(View.GONE);
249 }
250 viewHolder.messageBody.setVisibility(View.GONE);
251 viewHolder.image.setVisibility(View.VISIBLE);
252 String[] fileParams = message.getBody().split(",");
253 if (fileParams.length == 3) {
254 double target = metrics.density * 288;
255 int w = Integer.parseInt(fileParams[1]);
256 int h = Integer.parseInt(fileParams[2]);
257 int scalledW;
258 int scalledH;
259 if (w <= h) {
260 scalledW = (int) (w / ((double) h / target));
261 scalledH = (int) target;
262 } else {
263 scalledW = (int) target;
264 scalledH = (int) (h / ((double) w / target));
265 }
266 viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(
267 scalledW, scalledH));
268 }
269 activity.loadBitmap(message, viewHolder.image);
270 viewHolder.image.setOnClickListener(new OnClickListener() {
271
272 @Override
273 public void onClick(View v) {
274 Intent intent = new Intent(Intent.ACTION_VIEW);
275 intent.setDataAndType(activity.xmppConnectionService
276 .getFileBackend().getJingleFileUri(message), "image/*");
277 getContext().startActivity(intent);
278 }
279 });
280 viewHolder.image.setOnLongClickListener(new OnLongClickListener() {
281
282 @Override
283 public boolean onLongClick(View v) {
284 Intent shareIntent = new Intent();
285 shareIntent.setAction(Intent.ACTION_SEND);
286 shareIntent.putExtra(Intent.EXTRA_STREAM,
287 activity.xmppConnectionService.getFileBackend()
288 .getJingleFileUri(message));
289 shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
290 shareIntent.setType("image/webp");
291 getContext().startActivity(
292 Intent.createChooser(shareIntent,
293 getContext().getText(R.string.share_with)));
294 return true;
295 }
296 });
297 }
298
299 @Override
300 public View getView(int position, View view, ViewGroup parent) {
301 final Message item = getItem(position);
302 int type = getItemViewType(position);
303 ViewHolder viewHolder;
304 if (view == null) {
305 viewHolder = new ViewHolder();
306 switch (type) {
307 case NULL:
308 view = (View) activity.getLayoutInflater().inflate(
309 R.layout.message_null, parent, false);
310 break;
311 case SENT:
312 view = (View) activity.getLayoutInflater().inflate(
313 R.layout.message_sent, parent, false);
314 viewHolder.message_box = (LinearLayout) view
315 .findViewById(R.id.message_box);
316 viewHolder.contact_picture = (ImageView) view
317 .findViewById(R.id.message_photo);
318 viewHolder.contact_picture.setImageBitmap(getSelfBitmap());
319 viewHolder.indicator = (ImageView) view
320 .findViewById(R.id.security_indicator);
321 viewHolder.image = (ImageView) view
322 .findViewById(R.id.message_image);
323 viewHolder.messageBody = (TextView) view
324 .findViewById(R.id.message_body);
325 viewHolder.time = (TextView) view
326 .findViewById(R.id.message_time);
327 view.setTag(viewHolder);
328 break;
329 case RECEIVED:
330 view = (View) activity.getLayoutInflater().inflate(
331 R.layout.message_received, parent, false);
332 viewHolder.message_box = (LinearLayout) view
333 .findViewById(R.id.message_box);
334 viewHolder.contact_picture = (ImageView) view
335 .findViewById(R.id.message_photo);
336
337 viewHolder.download_button = (Button) view
338 .findViewById(R.id.download_button);
339
340 if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
341
342 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
343 item.getConversation().getContact(), getContext()));
344
345 }
346 viewHolder.indicator = (ImageView) view
347 .findViewById(R.id.security_indicator);
348 viewHolder.image = (ImageView) view
349 .findViewById(R.id.message_image);
350 viewHolder.messageBody = (TextView) view
351 .findViewById(R.id.message_body);
352 viewHolder.time = (TextView) view
353 .findViewById(R.id.message_time);
354 view.setTag(viewHolder);
355 break;
356 case STATUS:
357 view = (View) activity.getLayoutInflater().inflate(
358 R.layout.message_status, parent, false);
359 viewHolder.contact_picture = (ImageView) view
360 .findViewById(R.id.message_photo);
361 if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
362
363 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
364 item.getConversation().getContact(), getContext()));
365 viewHolder.contact_picture.setAlpha(128);
366 viewHolder.contact_picture
367 .setOnClickListener(new OnClickListener() {
368
369 @Override
370 public void onClick(View v) {
371 String name = item.getConversation()
372 .getName(true);
373 String read = getContext()
374 .getString(
375 R.string.contact_has_read_up_to_this_point,
376 name);
377 Toast.makeText(getContext(), read,
378 Toast.LENGTH_SHORT).show();
379 }
380 });
381
382 }
383 break;
384 default:
385 viewHolder = null;
386 break;
387 }
388 } else {
389 viewHolder = (ViewHolder) view.getTag();
390 }
391
392 if (type == STATUS || type == NULL) {
393 return view;
394 }
395
396 if (type == RECEIVED) {
397 if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
398 Contact contact = item.getContact();
399 if (contact != null) {
400 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
401 contact, getContext()));
402 } else {
403 String name = item.getPresence();
404 if (name==null) {
405 name = item.getCounterpart();
406 }
407 viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(name, getContext()));
408 }
409 viewHolder.contact_picture
410 .setOnClickListener(new OnClickListener() {
411
412 @Override
413 public void onClick(View v) {
414 if (MessageAdapter.this.mOnContactPictureClickedListener != null) {
415 MessageAdapter.this.mOnContactPictureClickedListener
416 .onContactPictureClicked(item);
417 ;
418 }
419
420 }
421 });
422 viewHolder.contact_picture.setOnLongClickListener(new OnLongClickListener() {
423
424 @Override
425 public boolean onLongClick(View v) {
426 if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
427 MessageAdapter.this.mOnContactPictureLongClickedListener.onContactPictureLongClicked(item);
428 return true;
429 } else {
430 return false;
431 }
432 }
433 });
434 }
435 }
436
437 if (item.getType() == Message.TYPE_IMAGE) {
438 if (item.getStatus() == Message.STATUS_RECEIVING) {
439 displayInfoMessage(viewHolder, R.string.receiving_image);
440 } else if (item.getStatus() == Message.STATUS_RECEIVED_OFFER) {
441 viewHolder.image.setVisibility(View.GONE);
442 viewHolder.messageBody.setVisibility(View.GONE);
443 viewHolder.download_button.setVisibility(View.VISIBLE);
444 viewHolder.download_button
445 .setOnClickListener(new OnClickListener() {
446
447 @Override
448 public void onClick(View v) {
449 JingleConnection connection = item
450 .getJingleConnection();
451 if (connection != null) {
452 connection.accept();
453 }
454 }
455 });
456 } else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED)
457 || (item.getEncryption() == Message.ENCRYPTION_NONE)
458 || (item.getEncryption() == Message.ENCRYPTION_OTR)) {
459 displayImageMessage(viewHolder, item);
460 } else if (item.getEncryption() == Message.ENCRYPTION_PGP) {
461 displayInfoMessage(viewHolder, R.string.encrypted_message);
462 } else {
463 displayDecryptionFailed(viewHolder);
464 }
465 } else {
466 if (item.getEncryption() == Message.ENCRYPTION_PGP) {
467 if (activity.hasPgp()) {
468 displayInfoMessage(viewHolder, R.string.encrypted_message);
469 } else {
470 displayInfoMessage(viewHolder,
471 R.string.install_openkeychain);
472 viewHolder.message_box
473 .setOnClickListener(new OnClickListener() {
474
475 @Override
476 public void onClick(View v) {
477 activity.showInstallPgpDialog();
478 }
479 });
480 }
481 } else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
482 displayDecryptionFailed(viewHolder);
483 } else {
484 displayTextMessage(viewHolder, item);
485 }
486 }
487
488 displayStatus(viewHolder, item);
489
490 return view;
491 }
492
493 private static class ViewHolder {
494
495 protected LinearLayout message_box;
496 protected Button download_button;
497 protected ImageView image;
498 protected ImageView indicator;
499 protected TextView time;
500 protected TextView messageBody;
501 protected ImageView contact_picture;
502
503 }
504
505 private class BitmapCache {
506 private HashMap<String, Bitmap> contactBitmaps = new HashMap<String, Bitmap>();
507 private HashMap<String, Bitmap> unknownBitmaps = new HashMap<String, Bitmap>();
508
509 public Bitmap get(Contact contact, Context context) {
510 if (!contactBitmaps.containsKey(contact.getJid())) {
511 contactBitmaps.put(contact.getJid(),
512 contact.getImage(48, context));
513 }
514 return contactBitmaps.get(contact.getJid());
515 }
516
517 public Bitmap get(String name, Context context) {
518 if (unknownBitmaps.containsKey(name)) {
519 return unknownBitmaps.get(name);
520 } else {
521 Bitmap bm = UIHelper
522 .getContactPicture(name, 48, context, false);
523 unknownBitmaps.put(name, bm);
524 return bm;
525 }
526 }
527 }
528
529 public interface OnContactPictureClicked {
530 public void onContactPictureClicked(Message message);
531 }
532
533 public interface OnContactPictureLongClicked {
534 public void onContactPictureLongClicked(Message message);
535 }
536}