MessageAdapter.java

  1package eu.siacs.conversations.ui.adapter;
  2
  3import android.content.Intent;
  4import android.graphics.Typeface;
  5import android.net.Uri;
  6import android.text.Spannable;
  7import android.text.SpannableString;
  8import android.text.style.ForegroundColorSpan;
  9import android.text.style.StyleSpan;
 10import android.util.DisplayMetrics;
 11import android.util.Log;
 12import android.view.View;
 13import android.view.View.OnClickListener;
 14import android.view.View.OnLongClickListener;
 15import android.view.ViewGroup;
 16import android.webkit.MimeTypeMap;
 17import android.widget.ArrayAdapter;
 18import android.widget.Button;
 19import android.widget.ImageView;
 20import android.widget.LinearLayout;
 21import android.widget.TextView;
 22import android.widget.Toast;
 23
 24import java.io.FileInputStream;
 25import java.io.FileNotFoundException;
 26import java.io.InputStream;
 27import java.util.List;
 28
 29import eu.siacs.conversations.Config;
 30import eu.siacs.conversations.R;
 31import eu.siacs.conversations.entities.Contact;
 32import eu.siacs.conversations.entities.Conversation;
 33import eu.siacs.conversations.entities.Downloadable;
 34import eu.siacs.conversations.entities.DownloadableFile;
 35import eu.siacs.conversations.entities.Message;
 36import eu.siacs.conversations.entities.Message.ImageParams;
 37import eu.siacs.conversations.ui.ConversationActivity;
 38import eu.siacs.conversations.utils.UIHelper;
 39import eu.siacs.conversations.xmpp.jid.Jid;
 40
 41public class MessageAdapter extends ArrayAdapter<Message> {
 42
 43	private static final int SENT = 0;
 44	private static final int RECEIVED = 1;
 45	private static final int STATUS = 2;
 46	private static final int NULL = 3;
 47
 48	private ConversationActivity activity;
 49
 50	private DisplayMetrics metrics;
 51
 52	private OnContactPictureClicked mOnContactPictureClickedListener;
 53	private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
 54
 55	private OnLongClickListener openContextMenu = new OnLongClickListener() {
 56		
 57		@Override
 58		public boolean onLongClick(View v) {
 59			v.showContextMenu();
 60			return true;
 61		}
 62	};
 63	
 64	public MessageAdapter(ConversationActivity activity, List<Message> messages) {
 65		super(activity, 0, messages);
 66		this.activity = activity;
 67		metrics = getContext().getResources().getDisplayMetrics();
 68	}
 69
 70	public void setOnContactPictureClicked(OnContactPictureClicked listener) {
 71		this.mOnContactPictureClickedListener = listener;
 72	}
 73
 74	public void setOnContactPictureLongClicked(
 75			OnContactPictureLongClicked listener) {
 76		this.mOnContactPictureLongClickedListener = listener;
 77	}
 78
 79	@Override
 80	public int getViewTypeCount() {
 81		return 4;
 82	}
 83
 84	@Override
 85	public int getItemViewType(int position) {
 86		if (getItem(position).wasMergedIntoPrevious()) {
 87			return NULL;
 88		} else if (getItem(position).getType() == Message.TYPE_STATUS) {
 89			return STATUS;
 90		} else if (getItem(position).getStatus() <= Message.STATUS_RECEIVED) {
 91			return RECEIVED;
 92		} else {
 93			return SENT;
 94		}
 95	}
 96
 97	private void displayStatus(ViewHolder viewHolder, Message message) {
 98		String filesize = null;
 99		String info = null;
100		boolean error = false;
101		if (viewHolder.indicatorReceived != null) {
102			viewHolder.indicatorReceived.setVisibility(View.GONE);
103		}
104		boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
105				&& message.getMergedStatus() <= Message.STATUS_RECEIVED;
106		if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getDownloadable() != null) {
107			ImageParams params = message.getImageParams();
108			if (params.size > (1.5 * 1024 * 1024)) {
109				filesize = params.size / (1024 * 1024)+ " MB";
110			} else if (params.size > 0) {
111				filesize = params.size / 1024 + " KB";
112			}
113			if (message.getDownloadable() != null && message.getDownloadable().getStatus() == Downloadable.STATUS_FAILED) {
114				error = true;
115			}
116		}
117		switch (message.getMergedStatus()) {
118		case Message.STATUS_WAITING:
119			info = getContext().getString(R.string.waiting);
120			break;
121		case Message.STATUS_UNSEND:
122			Downloadable d = message.getDownloadable();
123			if (d!=null) {
124				info = getContext().getString(R.string.sending_file,d.getProgress());
125			} else {
126				info = getContext().getString(R.string.sending);
127			}
128			break;
129		case Message.STATUS_OFFERED:
130			info = getContext().getString(R.string.offering);
131			break;
132		case Message.STATUS_SEND_RECEIVED:
133			if (activity.indicateReceived()) {
134				viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
135			}
136			break;
137		case Message.STATUS_SEND_DISPLAYED:
138			if (activity.indicateReceived()) {
139				viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
140			}
141			break;
142		case Message.STATUS_SEND_FAILED:
143			info = getContext().getString(R.string.send_failed);
144			error = true;
145			break;
146		default:
147			if (multiReceived) {
148				Contact contact = message.getContact();
149				if (contact != null) {
150					info = contact.getDisplayName();
151				} else {
152					info = getDisplayedMucCounterpart(message.getCounterpart());
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, String text) {
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(text);
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					final String to;
241					if (message.getCounterpart() != null) {
242						to = message.getCounterpart().getResourcepart();
243					} else {
244						to = "";
245					}
246					privateMarker = activity.getString(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 displayDownloadableMessage(ViewHolder viewHolder,
268			final Message message, String text) {
269		viewHolder.image.setVisibility(View.GONE);
270		viewHolder.messageBody.setVisibility(View.GONE);
271		viewHolder.download_button.setVisibility(View.VISIBLE);
272		viewHolder.download_button.setText(text);
273		viewHolder.download_button.setOnClickListener(new OnClickListener() {
274
275			@Override
276			public void onClick(View v) {
277				startDonwloadable(message);
278			}
279		});
280		viewHolder.download_button.setOnLongClickListener(openContextMenu);
281	}
282
283	private void displayOpenableMessage(ViewHolder viewHolder,final Message message) {
284		viewHolder.image.setVisibility(View.GONE);
285		viewHolder.messageBody.setVisibility(View.GONE);
286		viewHolder.download_button.setVisibility(View.VISIBLE);
287		viewHolder.download_button.setText(activity.getString(R.string.open_file,activity.xmppConnectionService.getFileBackend().getFile(message).getMimeType()));
288		viewHolder.download_button.setOnClickListener(new OnClickListener() {
289
290			@Override
291			public void onClick(View v) {
292				openDonwloadable(message);
293			}
294		});
295		viewHolder.download_button.setOnLongClickListener(openContextMenu);
296	}
297
298	private void displayImageMessage(ViewHolder viewHolder,
299			final Message message) {
300		if (viewHolder.download_button != null) {
301			viewHolder.download_button.setVisibility(View.GONE);
302		}
303		viewHolder.messageBody.setVisibility(View.GONE);
304		viewHolder.image.setVisibility(View.VISIBLE);
305		ImageParams params = message.getImageParams();
306		double target = metrics.density * 288;
307		int scalledW;
308		int scalledH;
309		if (params.width <= params.height) {
310			scalledW = (int) (params.width / ((double) params.height / target));
311			scalledH = (int) target;
312		} else {
313			scalledW = (int) target;
314			scalledH = (int) (params.height / ((double) params.width / target));
315		}
316		viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(
317				scalledW, scalledH));
318		activity.loadBitmap(message, viewHolder.image);
319		viewHolder.image.setOnClickListener(new OnClickListener() {
320
321			@Override
322			public void onClick(View v) {
323				Intent intent = new Intent(Intent.ACTION_VIEW);
324				intent.setDataAndType(activity.xmppConnectionService
325						.getFileBackend().getJingleFileUri(message), "image/*");
326				getContext().startActivity(intent);
327			}
328		});
329		viewHolder.image.setOnLongClickListener(openContextMenu);
330	}
331
332	private String getDisplayedMucCounterpart(final Jid counterpart) {
333		if (counterpart==null) {
334			return "";
335		} else if (!counterpart.isBareJid()) {
336			return counterpart.getResourcepart();
337		} else {
338			return counterpart.toString();
339		}
340	}
341
342	@Override
343	public View getView(int position, View view, ViewGroup parent) {
344		final Message item = getItem(position);
345		int type = getItemViewType(position);
346		ViewHolder viewHolder;
347		if (view == null) {
348			viewHolder = new ViewHolder();
349			switch (type) {
350			case NULL:
351				view = activity.getLayoutInflater().inflate(
352						R.layout.message_null, parent, false);
353				break;
354			case SENT:
355				view = activity.getLayoutInflater().inflate(
356						R.layout.message_sent, parent, false);
357				viewHolder.message_box = (LinearLayout) view
358						.findViewById(R.id.message_box);
359				viewHolder.contact_picture = (ImageView) view
360						.findViewById(R.id.message_photo);
361				viewHolder.download_button = (Button) view
362						.findViewById(R.id.download_button);
363				viewHolder.indicator = (ImageView) view
364						.findViewById(R.id.security_indicator);
365				viewHolder.image = (ImageView) view
366						.findViewById(R.id.message_image);
367				viewHolder.messageBody = (TextView) view
368						.findViewById(R.id.message_body);
369				viewHolder.time = (TextView) view
370						.findViewById(R.id.message_time);
371				viewHolder.indicatorReceived = (ImageView) view
372						.findViewById(R.id.indicator_received);
373				view.setTag(viewHolder);
374				break;
375			case RECEIVED:
376				view = activity.getLayoutInflater().inflate(
377						R.layout.message_received, parent, false);
378				viewHolder.message_box = (LinearLayout) view
379						.findViewById(R.id.message_box);
380				viewHolder.contact_picture = (ImageView) view
381						.findViewById(R.id.message_photo);
382				viewHolder.download_button = (Button) view
383						.findViewById(R.id.download_button);
384				viewHolder.indicator = (ImageView) view
385						.findViewById(R.id.security_indicator);
386				viewHolder.image = (ImageView) view
387						.findViewById(R.id.message_image);
388				viewHolder.messageBody = (TextView) view
389						.findViewById(R.id.message_body);
390				viewHolder.time = (TextView) view
391						.findViewById(R.id.message_time);
392				viewHolder.indicatorReceived = (ImageView) view
393						.findViewById(R.id.indicator_received);
394				view.setTag(viewHolder);
395				break;
396			case STATUS:
397				view = activity.getLayoutInflater().inflate(
398						R.layout.message_status, parent, false);
399				viewHolder.contact_picture = (ImageView) view
400						.findViewById(R.id.message_photo);
401				view.setTag(viewHolder);
402				break;
403			default:
404				viewHolder = null;
405				break;
406			}
407		} else {
408			viewHolder = (ViewHolder) view.getTag();
409		}
410
411		if (type == STATUS) {
412			if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
413				viewHolder.contact_picture.setImageBitmap(activity
414						.avatarService().get(
415								item.getConversation().getContact(),
416								activity.getPixel(32)));
417				viewHolder.contact_picture.setAlpha(0.5f);
418				viewHolder.contact_picture
419						.setOnClickListener(new OnClickListener() {
420
421							@Override
422							public void onClick(View v) {
423								String name = item.getConversation()
424										.getName();
425								String read = getContext()
426										.getString(
427												R.string.contact_has_read_up_to_this_point,
428												name);
429								Toast.makeText(getContext(), read,
430										Toast.LENGTH_SHORT).show();
431							}
432						});
433
434			}
435			return view;
436		} else if (type == NULL) {
437			if (position == getCount() - 1) {
438				view.getLayoutParams().height = 1;
439			} else {
440				view.getLayoutParams().height = 0;
441
442			}
443			view.setLayoutParams(view.getLayoutParams());
444			return view;
445		} else if (type == RECEIVED) {
446			Contact contact = item.getContact();
447			if (contact != null) {
448				viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(contact, activity.getPixel(48)));
449			} else if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
450				viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(getDisplayedMucCounterpart(item.getCounterpart()),
451                        activity.getPixel(48)));
452			}
453		} else if (type == SENT) {
454			viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(item.getConversation().getAccount(), activity.getPixel(48)));
455		}
456
457		if (viewHolder != null && viewHolder.contact_picture != null) {
458			viewHolder.contact_picture
459					.setOnClickListener(new OnClickListener() {
460
461						@Override
462						public void onClick(View v) {
463							if (MessageAdapter.this.mOnContactPictureClickedListener != null) {
464								MessageAdapter.this.mOnContactPictureClickedListener
465										.onContactPictureClicked(item);
466							}
467
468						}
469					});
470			viewHolder.contact_picture
471					.setOnLongClickListener(new OnLongClickListener() {
472
473						@Override
474						public boolean onLongClick(View v) {
475							if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
476								MessageAdapter.this.mOnContactPictureLongClickedListener
477										.onContactPictureLongClicked(item);
478								return true;
479							} else {
480								return false;
481							}
482						}
483					});
484		}
485
486		if (item.getDownloadable() != null && item.getDownloadable().getStatus() != Downloadable.STATUS_UPLOADING) {
487			Downloadable d = item.getDownloadable();
488			if (d.getStatus() == Downloadable.STATUS_DOWNLOADING) {
489				if (item.getType() == Message.TYPE_FILE) {
490					displayInfoMessage(viewHolder,activity.getString(R.string.receiving_file,d.getMimeType(),d.getProgress()));
491				} else {
492					displayInfoMessage(viewHolder,activity.getString(R.string.receiving_image,d.getProgress()));
493				}
494			} else if (d.getStatus() == Downloadable.STATUS_CHECKING) {
495				displayInfoMessage(viewHolder,activity.getString(R.string.checking_image));
496			} else if (d.getStatus() == Downloadable.STATUS_DELETED) {
497				displayInfoMessage(viewHolder,activity.getString(R.string.image_file_deleted));
498			} else if (d.getStatus() == Downloadable.STATUS_OFFER) {
499				if (item.getType() == Message.TYPE_FILE) {
500					displayDownloadableMessage(viewHolder,item,activity.getString(R.string.download_file,d.getMimeType()));
501				} else {
502					displayDownloadableMessage(viewHolder, item,activity.getString(R.string.download_image));
503				}
504			} else if (d.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) {
505				displayDownloadableMessage(viewHolder, item,activity.getString(R.string.check_image_filesize));
506			} else if (d.getStatus() == Downloadable.STATUS_FAILED) {
507				displayInfoMessage(viewHolder, activity.getString(R.string.image_transmission_failed));
508			}
509		} else if (item.getType() == Message.TYPE_IMAGE) {
510			if (item.getEncryption() == Message.ENCRYPTION_PGP) {
511				displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message));
512			} else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
513				displayDecryptionFailed(viewHolder);
514			} else {
515				displayImageMessage(viewHolder, item);
516			}
517		} else if (item.getType() == Message.TYPE_FILE) {
518			if (item.getImageParams().width > 0) {
519				displayImageMessage(viewHolder,item);
520			} else {
521				displayOpenableMessage(viewHolder, item);
522			}
523		} else {
524			if (item.getEncryption() == Message.ENCRYPTION_PGP) {
525				if (activity.hasPgp()) {
526					displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message));
527				} else {
528					displayInfoMessage(viewHolder,
529							activity.getString(R.string.install_openkeychain));
530                    if (viewHolder != null) {
531                        viewHolder.message_box
532                                .setOnClickListener(new OnClickListener() {
533
534                                    @Override
535                                    public void onClick(View v) {
536                                        activity.showInstallPgpDialog();
537                                    }
538                                });
539                    }
540				}
541			} else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
542				displayDecryptionFailed(viewHolder);
543			} else {
544				displayTextMessage(viewHolder, item);
545			}
546		}
547
548		displayStatus(viewHolder, item);
549
550		return view;
551	}
552
553	public void startDonwloadable(Message message) {
554		Downloadable downloadable = message.getDownloadable();
555		if (downloadable != null) {
556			if (!downloadable.start()) {
557				Toast.makeText(activity, R.string.not_connected_try_again,
558						Toast.LENGTH_SHORT).show();
559			}
560		}
561	}
562
563	public void openDonwloadable(Message message) {
564		DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
565		Intent intent = new Intent(Intent.ACTION_VIEW);
566		intent.setDataAndType(Uri.fromFile(file), file.getMimeType());
567		getContext().startActivity(intent);
568	}
569
570	public interface OnContactPictureClicked {
571		public void onContactPictureClicked(Message message);
572	}
573
574	public interface OnContactPictureLongClicked {
575		public void onContactPictureLongClicked(Message message);
576	}
577
578	private static class ViewHolder {
579
580		protected LinearLayout message_box;
581		protected Button download_button;
582		protected ImageView image;
583		protected ImageView indicator;
584		protected ImageView indicatorReceived;
585		protected TextView time;
586		protected TextView messageBody;
587		protected ImageView contact_picture;
588
589	}
590}