MessageAdapter.java

  1package eu.siacs.conversations.ui.adapter;
  2
  3import android.content.Intent;
  4import android.content.pm.PackageManager;
  5import android.content.pm.ResolveInfo;
  6import android.graphics.Typeface;
  7import android.net.Uri;
  8import android.text.Spannable;
  9import android.text.SpannableString;
 10import android.text.style.ForegroundColorSpan;
 11import android.text.style.StyleSpan;
 12import android.util.DisplayMetrics;
 13import android.util.Log;
 14import android.view.View;
 15import android.view.View.OnClickListener;
 16import android.view.View.OnLongClickListener;
 17import android.view.ViewGroup;
 18import android.widget.ArrayAdapter;
 19import android.widget.Button;
 20import android.widget.ImageView;
 21import android.widget.LinearLayout;
 22import android.widget.TextView;
 23import android.widget.Toast;
 24
 25import java.util.List;
 26
 27import eu.siacs.conversations.Config;
 28import eu.siacs.conversations.R;
 29import eu.siacs.conversations.entities.Account;
 30import eu.siacs.conversations.entities.Contact;
 31import eu.siacs.conversations.entities.Conversation;
 32import eu.siacs.conversations.entities.Downloadable;
 33import eu.siacs.conversations.entities.DownloadableFile;
 34import eu.siacs.conversations.entities.Message;
 35import eu.siacs.conversations.entities.Message.ImageParams;
 36import eu.siacs.conversations.ui.ConversationActivity;
 37import eu.siacs.conversations.utils.UIHelper;
 38
 39public class MessageAdapter extends ArrayAdapter<Message> {
 40
 41	private static final int SENT = 0;
 42	private static final int RECEIVED = 1;
 43	private static final int STATUS = 2;
 44	private static final int NULL = 3;
 45
 46	private ConversationActivity activity;
 47
 48	private DisplayMetrics metrics;
 49
 50	private OnContactPictureClicked mOnContactPictureClickedListener;
 51	private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
 52
 53	private OnLongClickListener openContextMenu = new OnLongClickListener() {
 54
 55		@Override
 56		public boolean onLongClick(View v) {
 57			v.showContextMenu();
 58			return true;
 59		}
 60	};
 61
 62	public MessageAdapter(ConversationActivity activity, List<Message> messages) {
 63		super(activity, 0, messages);
 64		this.activity = activity;
 65		metrics = getContext().getResources().getDisplayMetrics();
 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		if (viewHolder.indicatorReceived != null) {
100			viewHolder.indicatorReceived.setVisibility(View.GONE);
101		}
102		boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
103			&& message.getMergedStatus() <= Message.STATUS_RECEIVED;
104		if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getDownloadable() != null) {
105			ImageParams params = message.getImageParams();
106			if (params.size > (1.5 * 1024 * 1024)) {
107				filesize = params.size / (1024 * 1024)+ " MiB";
108			} else if (params.size > 0) {
109				filesize = params.size / 1024 + " KiB";
110			}
111			if (message.getDownloadable() != null && message.getDownloadable().getStatus() == Downloadable.STATUS_FAILED) {
112				error = true;
113			}
114		}
115		switch (message.getMergedStatus()) {
116			case Message.STATUS_WAITING:
117				info = getContext().getString(R.string.waiting);
118				break;
119			case Message.STATUS_UNSEND:
120				Downloadable d = message.getDownloadable();
121				if (d!=null) {
122					info = getContext().getString(R.string.sending_file,d.getProgress());
123				} else {
124					info = getContext().getString(R.string.sending);
125				}
126				break;
127			case Message.STATUS_OFFERED:
128				info = getContext().getString(R.string.offering);
129				break;
130			case Message.STATUS_SEND_RECEIVED:
131				if (activity.indicateReceived()) {
132					viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
133				}
134				break;
135			case Message.STATUS_SEND_DISPLAYED:
136				if (activity.indicateReceived()) {
137					viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
138				}
139				break;
140			case Message.STATUS_SEND_FAILED:
141				info = getContext().getString(R.string.send_failed);
142				error = true;
143				break;
144			default:
145				if (multiReceived) {
146					info = UIHelper.getMessageDisplayName(message);
147				}
148				break;
149		}
150		if (error) {
151			viewHolder.time.setTextColor(activity.getWarningTextColor());
152		} else {
153			viewHolder.time.setTextColor(activity.getSecondaryTextColor());
154		}
155		if (message.getEncryption() == Message.ENCRYPTION_NONE) {
156			viewHolder.indicator.setVisibility(View.GONE);
157		} else {
158			viewHolder.indicator.setVisibility(View.VISIBLE);
159		}
160
161		String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(),
162				message.getMergedTimeSent());
163		if (message.getStatus() <= Message.STATUS_RECEIVED) {
164			if ((filesize != null) && (info != null)) {
165				viewHolder.time.setText(filesize + " \u00B7 " + info);
166			} else if ((filesize == null) && (info != null)) {
167				viewHolder.time.setText(formatedTime + " \u00B7 " + info);
168			} else if ((filesize != null) && (info == null)) {
169				viewHolder.time.setText(formatedTime + " \u00B7 " + filesize);
170			} else {
171				viewHolder.time.setText(formatedTime);
172			}
173		} else {
174			if ((filesize != null) && (info != null)) {
175				viewHolder.time.setText(filesize + " \u00B7 " + info);
176			} else if ((filesize == null) && (info != null)) {
177				if (error) {
178					viewHolder.time.setText(info + " \u00B7 " + formatedTime);
179				} else {
180					viewHolder.time.setText(info);
181				}
182			} else if ((filesize != null) && (info == null)) {
183				viewHolder.time.setText(filesize + " \u00B7 " + formatedTime);
184			} else {
185				viewHolder.time.setText(formatedTime);
186			}
187		}
188	}
189
190	private void displayInfoMessage(ViewHolder viewHolder, String text) {
191		if (viewHolder.download_button != null) {
192			viewHolder.download_button.setVisibility(View.GONE);
193		}
194		viewHolder.image.setVisibility(View.GONE);
195		viewHolder.messageBody.setVisibility(View.VISIBLE);
196		viewHolder.messageBody.setText(text);
197		viewHolder.messageBody.setTextColor(activity.getSecondaryTextColor());
198		viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
199		viewHolder.messageBody.setTextIsSelectable(false);
200	}
201
202	private void displayDecryptionFailed(ViewHolder viewHolder) {
203		if (viewHolder.download_button != null) {
204			viewHolder.download_button.setVisibility(View.GONE);
205		}
206		viewHolder.image.setVisibility(View.GONE);
207		viewHolder.messageBody.setVisibility(View.VISIBLE);
208		viewHolder.messageBody.setText(getContext().getString(
209					R.string.decryption_failed));
210		viewHolder.messageBody.setTextColor(activity.getWarningTextColor());
211		viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
212		viewHolder.messageBody.setTextIsSelectable(false);
213	}
214
215	private void displayTextMessage(final ViewHolder viewHolder, final Message message) {
216		if (viewHolder.download_button != null) {
217			viewHolder.download_button.setVisibility(View.GONE);
218		}
219		viewHolder.image.setVisibility(View.GONE);
220		viewHolder.messageBody.setVisibility(View.VISIBLE);
221		if (message.getBody() != null) {
222			final String nick = UIHelper.getMessageDisplayName(message);
223			final String formattedBody = message.getMergedBody().replaceAll("^" + Message.ME_COMMAND,
224					nick + " ");
225			if (message.getType() != Message.TYPE_PRIVATE) {
226				if (message.hasMeCommand()) {
227					final Spannable span = new SpannableString(formattedBody);
228					span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(),
229							Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
230					viewHolder.messageBody.setText(span);
231				} else {
232					viewHolder.messageBody.setText(message.getMergedBody());
233				}
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				final Spannable span = new SpannableString(privateMarker + " "
249						+ formattedBody);
250				span.setSpan(new ForegroundColorSpan(activity
251							.getSecondaryTextColor()), 0, privateMarker
252						.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
253				span.setSpan(new StyleSpan(Typeface.BOLD), 0,
254						privateMarker.length(),
255						Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
256				if (message.hasMeCommand()) {
257					span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), privateMarker.length() + 1,
258							privateMarker.length() + 1 + nick.length(),
259							Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
260				}
261				viewHolder.messageBody.setText(span);
262			}
263		} else {
264			viewHolder.messageBody.setText("");
265		}
266		viewHolder.messageBody.setTextColor(activity.getPrimaryTextColor());
267		viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
268		viewHolder.messageBody.setTextIsSelectable(true);
269	}
270
271	private void displayDownloadableMessage(ViewHolder viewHolder,
272			final Message message, String text) {
273		viewHolder.image.setVisibility(View.GONE);
274		viewHolder.messageBody.setVisibility(View.GONE);
275		viewHolder.download_button.setVisibility(View.VISIBLE);
276		viewHolder.download_button.setText(text);
277		viewHolder.download_button.setOnClickListener(new OnClickListener() {
278
279			@Override
280			public void onClick(View v) {
281				startDownloadable(message);
282			}
283		});
284		viewHolder.download_button.setOnLongClickListener(openContextMenu);
285	}
286
287	private void displayOpenableMessage(ViewHolder viewHolder,final Message message) {
288		viewHolder.image.setVisibility(View.GONE);
289		viewHolder.messageBody.setVisibility(View.GONE);
290		viewHolder.download_button.setVisibility(View.VISIBLE);
291		viewHolder.download_button.setText(activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity,message)));
292		viewHolder.download_button.setOnClickListener(new OnClickListener() {
293
294			@Override
295			public void onClick(View v) {
296				openDownloadable(message);
297			}
298		});
299		viewHolder.download_button.setOnLongClickListener(openContextMenu);
300	}
301
302	private void displayImageMessage(ViewHolder viewHolder,
303			final Message message) {
304		if (viewHolder.download_button != null) {
305			viewHolder.download_button.setVisibility(View.GONE);
306		}
307		viewHolder.messageBody.setVisibility(View.GONE);
308		viewHolder.image.setVisibility(View.VISIBLE);
309		ImageParams params = message.getImageParams();
310		double target = metrics.density * 288;
311		int scalledW;
312		int scalledH;
313		if (params.width <= params.height) {
314			scalledW = (int) (params.width / ((double) params.height / target));
315			scalledH = (int) target;
316		} else {
317			scalledW = (int) target;
318			scalledH = (int) (params.height / ((double) params.width / target));
319		}
320		viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(
321					scalledW, scalledH));
322		activity.loadBitmap(message, viewHolder.image);
323		viewHolder.image.setOnClickListener(new OnClickListener() {
324
325			@Override
326			public void onClick(View v) {
327				Intent intent = new Intent(Intent.ACTION_VIEW);
328				intent.setDataAndType(activity.xmppConnectionService
329						.getFileBackend().getJingleFileUri(message), "image/*");
330				getContext().startActivity(intent);
331			}
332		});
333		viewHolder.image.setOnLongClickListener(openContextMenu);
334	}
335
336	@Override
337	public View getView(int position, View view, ViewGroup parent) {
338		final Message message = getItem(position);
339		final Conversation conversation = message.getConversation();
340		final Account account = conversation.getAccount();
341		final int type = getItemViewType(position);
342		ViewHolder viewHolder;
343		if (view == null) {
344			viewHolder = new ViewHolder();
345			switch (type) {
346				case NULL:
347					view = activity.getLayoutInflater().inflate(
348							R.layout.message_null, parent, false);
349					break;
350				case SENT:
351					view = activity.getLayoutInflater().inflate(
352							R.layout.message_sent, parent, false);
353					viewHolder.message_box = (LinearLayout) view
354						.findViewById(R.id.message_box);
355					viewHolder.contact_picture = (ImageView) view
356						.findViewById(R.id.message_photo);
357					viewHolder.download_button = (Button) view
358						.findViewById(R.id.download_button);
359					viewHolder.indicator = (ImageView) view
360						.findViewById(R.id.security_indicator);
361					viewHolder.image = (ImageView) view
362						.findViewById(R.id.message_image);
363					viewHolder.messageBody = (TextView) view
364						.findViewById(R.id.message_body);
365					viewHolder.time = (TextView) view
366						.findViewById(R.id.message_time);
367					viewHolder.indicatorReceived = (ImageView) view
368						.findViewById(R.id.indicator_received);
369					break;
370				case RECEIVED:
371					view = activity.getLayoutInflater().inflate(
372							R.layout.message_received, parent, false);
373					viewHolder.message_box = (LinearLayout) view
374						.findViewById(R.id.message_box);
375					viewHolder.contact_picture = (ImageView) view
376						.findViewById(R.id.message_photo);
377					viewHolder.download_button = (Button) view
378						.findViewById(R.id.download_button);
379					viewHolder.indicator = (ImageView) view
380						.findViewById(R.id.security_indicator);
381					viewHolder.image = (ImageView) view
382						.findViewById(R.id.message_image);
383					viewHolder.messageBody = (TextView) view
384						.findViewById(R.id.message_body);
385					viewHolder.time = (TextView) view
386						.findViewById(R.id.message_time);
387					viewHolder.indicatorReceived = (ImageView) view
388						.findViewById(R.id.indicator_received);
389					break;
390				case STATUS:
391					view = activity.getLayoutInflater().inflate(R.layout.message_status, parent, false);
392					viewHolder.contact_picture = (ImageView) view.findViewById(R.id.message_photo);
393					viewHolder.status_message = (TextView) view.findViewById(R.id.status_message);
394					break;
395				default:
396					viewHolder = null;
397					break;
398			}
399			view.setTag(viewHolder);
400		} else {
401			viewHolder = (ViewHolder) view.getTag();
402			if (viewHolder == null) {
403				return view;
404			}
405		}
406
407		if (type == STATUS) {
408			if (conversation.getMode() == Conversation.MODE_SINGLE) {
409				viewHolder.contact_picture.setImageBitmap(activity
410						.avatarService().get(conversation.getContact(),
411							activity.getPixel(32)));
412				viewHolder.contact_picture.setAlpha(0.5f);
413				viewHolder.status_message.setText(message.getBody());
414			}
415			return view;
416		} else if (type == NULL) {
417			if (viewHolder.message_box != null) {
418				Log.e(Config.LOGTAG, "detected type=NULL but with wrong cached view");
419				view = activity.getLayoutInflater().inflate(R.layout.message_null, parent, false);
420				view.setTag(new ViewHolder());
421			}
422			if (position == getCount() - 1) {
423				view.getLayoutParams().height = 1;
424			} else {
425				view.getLayoutParams().height = 0;
426
427			}
428			view.setLayoutParams(view.getLayoutParams());
429			return view;
430		} else if (message.wasMergedIntoPrevious()) {
431			Log.e(Config.LOGTAG,"detected wasMergedIntoPrevious with wrong type");
432			return view;
433		} else if (viewHolder.messageBody == null || viewHolder.image == null) {
434			return view; //avoiding weird platform bugs
435		} else if (type == RECEIVED) {
436			Contact contact = message.getContact();
437			if (contact != null) {
438				viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(contact, activity.getPixel(48)));
439			} else if (conversation.getMode() == Conversation.MODE_MULTI) {
440				viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(
441						UIHelper.getMessageDisplayName(message),
442						activity.getPixel(48)));
443			}
444		} else if (type == SENT) {
445			viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(account, activity.getPixel(48)));
446		}
447
448		viewHolder.contact_picture
449			.setOnClickListener(new OnClickListener() {
450
451				@Override
452				public void onClick(View v) {
453					if (MessageAdapter.this.mOnContactPictureClickedListener != null) {
454						MessageAdapter.this.mOnContactPictureClickedListener
455							.onContactPictureClicked(message);
456					}
457
458				}
459			});
460		viewHolder.contact_picture
461			.setOnLongClickListener(new OnLongClickListener() {
462
463				@Override
464				public boolean onLongClick(View v) {
465					if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
466						MessageAdapter.this.mOnContactPictureLongClickedListener
467							.onContactPictureLongClicked(message);
468						return true;
469					} else {
470						return false;
471					}
472				}
473			});
474
475		final Downloadable downloadable = message.getDownloadable();
476		if (downloadable != null && downloadable.getStatus() != Downloadable.STATUS_UPLOADING) {
477			if (downloadable.getStatus() == Downloadable.STATUS_OFFER) {
478				displayDownloadableMessage(viewHolder,message,activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, message)));
479			} else if (downloadable.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) {
480				displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_image_filesize));
481			} else {
482				displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first);
483			}
484		} else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
485			displayImageMessage(viewHolder, message);
486		} else if (message.getType() == Message.TYPE_FILE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
487			if (message.getImageParams().width > 0) {
488				displayImageMessage(viewHolder,message);
489			} else {
490				displayOpenableMessage(viewHolder, message);
491			}
492		} else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
493			if (activity.hasPgp()) {
494				displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message));
495			} else {
496				displayInfoMessage(viewHolder,
497						activity.getString(R.string.install_openkeychain));
498				if (viewHolder != null) {
499					viewHolder.message_box
500						.setOnClickListener(new OnClickListener() {
501
502							@Override
503							public void onClick(View v) {
504								activity.showInstallPgpDialog();
505							}
506						});
507				}
508			}
509		} else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
510			displayDecryptionFailed(viewHolder);
511		} else {
512			displayTextMessage(viewHolder, message);
513		}
514
515		displayStatus(viewHolder, message);
516
517		return view;
518	}
519
520	public void startDownloadable(Message message) {
521		Downloadable downloadable = message.getDownloadable();
522		if (downloadable != null) {
523			if (!downloadable.start()) {
524				Toast.makeText(activity, R.string.not_connected_try_again,
525						Toast.LENGTH_SHORT).show();
526			}
527		}
528	}
529
530	public void openDownloadable(Message message) {
531		DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
532		if (!file.exists()) {
533			Toast.makeText(activity,R.string.file_deleted,Toast.LENGTH_SHORT).show();
534			return;
535		}
536		Intent openIntent = new Intent(Intent.ACTION_VIEW);
537		openIntent.setDataAndType(Uri.fromFile(file), file.getMimeType());
538		PackageManager manager = activity.getPackageManager();
539		List<ResolveInfo> infos = manager.queryIntentActivities(openIntent, 0);
540		if (infos.size() > 0) {
541			getContext().startActivity(openIntent);
542		} else {
543			Toast.makeText(activity,R.string.no_application_found_to_open_file,Toast.LENGTH_SHORT).show();
544		}
545	}
546
547	public interface OnContactPictureClicked {
548		public void onContactPictureClicked(Message message);
549	}
550
551	public interface OnContactPictureLongClicked {
552		public void onContactPictureLongClicked(Message message);
553	}
554
555	private static class ViewHolder {
556
557		protected LinearLayout message_box;
558		protected Button download_button;
559		protected ImageView image;
560		protected ImageView indicator;
561		protected ImageView indicatorReceived;
562		protected TextView time;
563		protected TextView messageBody;
564		protected ImageView contact_picture;
565		protected TextView status_message;
566	}
567}