MessageAdapter.java

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