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