MessageAdapter.java

  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}