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