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.Spanned;
 11import android.text.style.ForegroundColorSpan;
 12import android.text.style.RelativeSizeSpan;
 13import android.text.style.StyleSpan;
 14import android.util.DisplayMetrics;
 15import android.view.View;
 16import android.view.View.OnClickListener;
 17import android.view.View.OnLongClickListener;
 18import android.view.ViewGroup;
 19import android.widget.ArrayAdapter;
 20import android.widget.Button;
 21import android.widget.ImageView;
 22import android.widget.LinearLayout;
 23import android.widget.TextView;
 24import android.widget.Toast;
 25
 26import java.util.List;
 27
 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.GeoHelper;
 38import eu.siacs.conversations.utils.UIHelper;
 39
 40public class MessageAdapter extends ArrayAdapter<Message> {
 41
 42	private static final int SENT = 0;
 43	private static final int RECEIVED = 1;
 44	private static final int STATUS = 2;
 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 3;
 80	}
 81
 82	@Override
 83	public int getItemViewType(int position) {
 84		if (getItem(position).getType() == Message.TYPE_STATUS) {
 85			return STATUS;
 86		} else if (getItem(position).getStatus() <= Message.STATUS_RECEIVED) {
 87			return RECEIVED;
 88		} else {
 89			return SENT;
 90		}
 91	}
 92
 93	private void displayStatus(ViewHolder viewHolder, Message message) {
 94		String filesize = null;
 95		String info = null;
 96		boolean error = false;
 97		if (viewHolder.indicatorReceived != null) {
 98			viewHolder.indicatorReceived.setVisibility(View.GONE);
 99		}
100		boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
101			&& message.getMergedStatus() <= Message.STATUS_RECEIVED;
102		if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getDownloadable() != null) {
103			ImageParams params = message.getImageParams();
104			if (params.size > (1.5 * 1024 * 1024)) {
105				filesize = params.size / (1024 * 1024)+ " MiB";
106			} else if (params.size > 0) {
107				filesize = params.size / 1024 + " KiB";
108			}
109			if (message.getDownloadable() != null && message.getDownloadable().getStatus() == Downloadable.STATUS_FAILED) {
110				error = true;
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				Downloadable d = message.getDownloadable();
119				if (d!=null) {
120					info = getContext().getString(R.string.sending_file,d.getProgress());
121				} else {
122					info = getContext().getString(R.string.sending);
123				}
124				break;
125			case Message.STATUS_OFFERED:
126				info = getContext().getString(R.string.offering);
127				break;
128			case Message.STATUS_SEND_RECEIVED:
129				if (activity.indicateReceived()) {
130					viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
131				}
132				break;
133			case Message.STATUS_SEND_DISPLAYED:
134				if (activity.indicateReceived()) {
135					viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
136				}
137				break;
138			case Message.STATUS_SEND_FAILED:
139				info = getContext().getString(R.string.send_failed);
140				error = true;
141				break;
142			default:
143				if (multiReceived) {
144					info = UIHelper.getMessageDisplayName(message);
145				}
146				break;
147		}
148		if (error) {
149			viewHolder.time.setTextColor(activity.getWarningTextColor());
150		} else {
151			viewHolder.time.setTextColor(activity.getSecondaryTextColor());
152		}
153		if (message.getEncryption() == Message.ENCRYPTION_NONE) {
154			viewHolder.indicator.setVisibility(View.GONE);
155		} else {
156			viewHolder.indicator.setVisibility(View.VISIBLE);
157		}
158
159		String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(),
160				message.getMergedTimeSent());
161		if (message.getStatus() <= Message.STATUS_RECEIVED) {
162			if ((filesize != null) && (info != null)) {
163				viewHolder.time.setText(filesize + " \u00B7 " + info);
164			} else if ((filesize == null) && (info != null)) {
165				viewHolder.time.setText(formatedTime + " \u00B7 " + info);
166			} else if ((filesize != null) && (info == null)) {
167				viewHolder.time.setText(formatedTime + " \u00B7 " + filesize);
168			} else {
169				viewHolder.time.setText(formatedTime);
170			}
171		} else {
172			if ((filesize != null) && (info != null)) {
173				viewHolder.time.setText(filesize + " \u00B7 " + info);
174			} else if ((filesize == null) && (info != null)) {
175				if (error) {
176					viewHolder.time.setText(info + " \u00B7 " + formatedTime);
177				} else {
178					viewHolder.time.setText(info);
179				}
180			} else if ((filesize != null) && (info == null)) {
181				viewHolder.time.setText(filesize + " \u00B7 " + formatedTime);
182			} else {
183				viewHolder.time.setText(formatedTime);
184			}
185		}
186	}
187
188	private void displayInfoMessage(ViewHolder viewHolder, String text) {
189		if (viewHolder.download_button != null) {
190			viewHolder.download_button.setVisibility(View.GONE);
191		}
192		viewHolder.image.setVisibility(View.GONE);
193		viewHolder.messageBody.setVisibility(View.VISIBLE);
194		viewHolder.messageBody.setText(text);
195		viewHolder.messageBody.setTextColor(activity.getSecondaryTextColor());
196		viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
197		viewHolder.messageBody.setTextIsSelectable(false);
198	}
199
200	private void displayDecryptionFailed(ViewHolder viewHolder) {
201		if (viewHolder.download_button != null) {
202			viewHolder.download_button.setVisibility(View.GONE);
203		}
204		viewHolder.image.setVisibility(View.GONE);
205		viewHolder.messageBody.setVisibility(View.VISIBLE);
206		viewHolder.messageBody.setText(getContext().getString(
207				R.string.decryption_failed));
208		viewHolder.messageBody.setTextColor(activity.getWarningTextColor());
209		viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
210		viewHolder.messageBody.setTextIsSelectable(false);
211	}
212
213	private void displayHeartMessage(final ViewHolder viewHolder, final String body) {
214		if (viewHolder.download_button != null) {
215			viewHolder.download_button.setVisibility(View.GONE);
216		}
217		viewHolder.image.setVisibility(View.GONE);
218		viewHolder.messageBody.setVisibility(View.VISIBLE);
219		viewHolder.messageBody.setIncludeFontPadding(false);
220		Spannable span = new SpannableString(body);
221		span.setSpan(new RelativeSizeSpan(4.0f), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
222		span.setSpan(new ForegroundColorSpan(activity.getWarningTextColor()), 0, body.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
223		viewHolder.messageBody.setText(span);
224	}
225
226	private void displayTextMessage(final ViewHolder viewHolder, final Message message) {
227		if (viewHolder.download_button != null) {
228			viewHolder.download_button.setVisibility(View.GONE);
229		}
230		viewHolder.image.setVisibility(View.GONE);
231		viewHolder.messageBody.setVisibility(View.VISIBLE);
232		viewHolder.messageBody.setIncludeFontPadding(true);
233		if (message.getBody() != null) {
234			final String nick = UIHelper.getMessageDisplayName(message);
235			final String body = message.getMergedBody().replaceAll("^" + Message.ME_COMMAND,nick + " ");
236			final SpannableString formattedBody = new SpannableString(body);
237			int i = body.indexOf(Message.MERGE_SEPARATOR);
238			while(i >= 0) {
239				final int end = i + Message.MERGE_SEPARATOR.length();
240				formattedBody.setSpan(new RelativeSizeSpan(0.3f),i,end,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
241				i = body.indexOf(Message.MERGE_SEPARATOR,end);
242			}
243			if (message.getType() != Message.TYPE_PRIVATE) {
244				if (message.hasMeCommand()) {
245					final Spannable span = new SpannableString(formattedBody);
246					span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(),
247							Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
248					viewHolder.messageBody.setText(span);
249				} else {
250					viewHolder.messageBody.setText(formattedBody);
251				}
252			} else {
253				String privateMarker;
254				if (message.getStatus() <= Message.STATUS_RECEIVED) {
255					privateMarker = activity
256						.getString(R.string.private_message);
257				} else {
258					final String to;
259					if (message.getCounterpart() != null) {
260						to = message.getCounterpart().getResourcepart();
261					} else {
262						to = "";
263					}
264					privateMarker = activity.getString(R.string.private_message_to, to);
265				}
266				final Spannable span = new SpannableString(privateMarker + " "
267						+ formattedBody);
268				span.setSpan(new ForegroundColorSpan(activity
269							.getSecondaryTextColor()), 0, privateMarker
270						.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
271				span.setSpan(new StyleSpan(Typeface.BOLD), 0,
272						privateMarker.length(),
273						Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
274				if (message.hasMeCommand()) {
275					span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), privateMarker.length() + 1,
276							privateMarker.length() + 1 + nick.length(),
277							Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
278				}
279				viewHolder.messageBody.setText(span);
280			}
281		} else {
282			viewHolder.messageBody.setText("");
283		}
284		viewHolder.messageBody.setTextColor(activity.getPrimaryTextColor());
285		viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
286		viewHolder.messageBody.setTextIsSelectable(true);
287	}
288
289	private void displayDownloadableMessage(ViewHolder viewHolder,
290			final Message message, String text) {
291		viewHolder.image.setVisibility(View.GONE);
292		viewHolder.messageBody.setVisibility(View.GONE);
293		viewHolder.download_button.setVisibility(View.VISIBLE);
294		viewHolder.download_button.setText(text);
295		viewHolder.download_button.setOnClickListener(new OnClickListener() {
296
297			@Override
298			public void onClick(View v) {
299				startDownloadable(message);
300			}
301		});
302		viewHolder.download_button.setOnLongClickListener(openContextMenu);
303	}
304
305	private void displayOpenableMessage(ViewHolder viewHolder,final Message message) {
306		viewHolder.image.setVisibility(View.GONE);
307		viewHolder.messageBody.setVisibility(View.GONE);
308		viewHolder.download_button.setVisibility(View.VISIBLE);
309		viewHolder.download_button.setText(activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity, message)));
310		viewHolder.download_button.setOnClickListener(new OnClickListener() {
311
312			@Override
313			public void onClick(View v) {
314				openDownloadable(message);
315			}
316		});
317		viewHolder.download_button.setOnLongClickListener(openContextMenu);
318	}
319
320	private void displayLocationMessage(ViewHolder viewHolder, final Message message) {
321		viewHolder.image.setVisibility(View.GONE);
322		viewHolder.messageBody.setVisibility(View.GONE);
323		viewHolder.download_button.setVisibility(View.VISIBLE);
324		viewHolder.download_button.setText(R.string.show_location);
325		viewHolder.download_button.setOnClickListener(new OnClickListener() {
326
327			@Override
328			public void onClick(View v) {
329				showLocation(message);
330			}
331		});
332		viewHolder.download_button.setOnLongClickListener(openContextMenu);
333	}
334
335	private void displayImageMessage(ViewHolder viewHolder,
336			final Message message) {
337		if (viewHolder.download_button != null) {
338			viewHolder.download_button.setVisibility(View.GONE);
339		}
340		viewHolder.messageBody.setVisibility(View.GONE);
341		viewHolder.image.setVisibility(View.VISIBLE);
342		ImageParams params = message.getImageParams();
343		double target = metrics.density * 288;
344		int scalledW;
345		int scalledH;
346		if (params.width <= params.height) {
347			scalledW = (int) (params.width / ((double) params.height / target));
348			scalledH = (int) target;
349		} else {
350			scalledW = (int) target;
351			scalledH = (int) (params.height / ((double) params.width / target));
352		}
353		viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(
354				scalledW, scalledH));
355		activity.loadBitmap(message, viewHolder.image);
356		viewHolder.image.setOnClickListener(new OnClickListener() {
357
358			@Override
359			public void onClick(View v) {
360				Intent intent = new Intent(Intent.ACTION_VIEW);
361				intent.setDataAndType(activity.xmppConnectionService
362						.getFileBackend().getJingleFileUri(message), "image/*");
363				getContext().startActivity(intent);
364			}
365		});
366		viewHolder.image.setOnLongClickListener(openContextMenu);
367	}
368
369	@Override
370	public View getView(int position, View view, ViewGroup parent) {
371		final Message message = getItem(position);
372		final Conversation conversation = message.getConversation();
373		final Account account = conversation.getAccount();
374		final int type = getItemViewType(position);
375		ViewHolder viewHolder;
376		if (view == null) {
377			viewHolder = new ViewHolder();
378			switch (type) {
379				case SENT:
380					view = activity.getLayoutInflater().inflate(
381							R.layout.message_sent, parent, false);
382					viewHolder.message_box = (LinearLayout) view
383						.findViewById(R.id.message_box);
384					viewHolder.contact_picture = (ImageView) view
385						.findViewById(R.id.message_photo);
386					viewHolder.download_button = (Button) view
387						.findViewById(R.id.download_button);
388					viewHolder.indicator = (ImageView) view
389						.findViewById(R.id.security_indicator);
390					viewHolder.image = (ImageView) view
391						.findViewById(R.id.message_image);
392					viewHolder.messageBody = (TextView) view
393						.findViewById(R.id.message_body);
394					viewHolder.time = (TextView) view
395						.findViewById(R.id.message_time);
396					viewHolder.indicatorReceived = (ImageView) view
397						.findViewById(R.id.indicator_received);
398					break;
399				case RECEIVED:
400					view = activity.getLayoutInflater().inflate(
401							R.layout.message_received, parent, false);
402					viewHolder.message_box = (LinearLayout) view
403						.findViewById(R.id.message_box);
404					viewHolder.contact_picture = (ImageView) view
405						.findViewById(R.id.message_photo);
406					viewHolder.download_button = (Button) view
407						.findViewById(R.id.download_button);
408					viewHolder.indicator = (ImageView) view
409						.findViewById(R.id.security_indicator);
410					viewHolder.image = (ImageView) view
411						.findViewById(R.id.message_image);
412					viewHolder.messageBody = (TextView) view
413						.findViewById(R.id.message_body);
414					viewHolder.time = (TextView) view
415						.findViewById(R.id.message_time);
416					viewHolder.indicatorReceived = (ImageView) view
417						.findViewById(R.id.indicator_received);
418					break;
419				case STATUS:
420					view = activity.getLayoutInflater().inflate(R.layout.message_status, parent, false);
421					viewHolder.contact_picture = (ImageView) view.findViewById(R.id.message_photo);
422					viewHolder.status_message = (TextView) view.findViewById(R.id.status_message);
423					break;
424				default:
425					viewHolder = null;
426					break;
427			}
428			view.setTag(viewHolder);
429		} else {
430			viewHolder = (ViewHolder) view.getTag();
431			if (viewHolder == null) {
432				return view;
433			}
434		}
435
436		if (type == STATUS) {
437			if (conversation.getMode() == Conversation.MODE_SINGLE) {
438				viewHolder.contact_picture.setImageBitmap(activity
439						.avatarService().get(conversation.getContact(),
440							activity.getPixel(32)));
441				viewHolder.contact_picture.setAlpha(0.5f);
442				viewHolder.status_message.setText(message.getBody());
443			}
444			return view;
445		} else if (type == RECEIVED) {
446			Contact contact = message.getContact();
447			if (contact != null) {
448				viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(contact, activity.getPixel(48)));
449			} else if (conversation.getMode() == Conversation.MODE_MULTI) {
450				viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(
451						UIHelper.getMessageDisplayName(message),
452						activity.getPixel(48)));
453			}
454		} else if (type == SENT) {
455			viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(account, activity.getPixel(48)));
456		}
457
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(message);
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(message);
478						return true;
479					} else {
480						return false;
481					}
482				}
483			});
484
485		final Downloadable downloadable = message.getDownloadable();
486		if (downloadable != null && downloadable.getStatus() != Downloadable.STATUS_UPLOADING) {
487			if (downloadable.getStatus() == Downloadable.STATUS_OFFER) {
488				displayDownloadableMessage(viewHolder,message,activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, message)));
489			} else if (downloadable.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) {
490				displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_image_filesize));
491			} else {
492				displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first);
493			}
494		} else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
495			displayImageMessage(viewHolder, message);
496		} else if (message.getType() == Message.TYPE_FILE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
497			if (message.getImageParams().width > 0) {
498				displayImageMessage(viewHolder,message);
499			} else {
500				displayOpenableMessage(viewHolder, message);
501			}
502		} else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
503			if (activity.hasPgp()) {
504				displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message));
505			} else {
506				displayInfoMessage(viewHolder,
507						activity.getString(R.string.install_openkeychain));
508				if (viewHolder != null) {
509					viewHolder.message_box
510						.setOnClickListener(new OnClickListener() {
511
512							@Override
513							public void onClick(View v) {
514								activity.showInstallPgpDialog();
515							}
516						});
517				}
518			}
519		} else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
520			displayDecryptionFailed(viewHolder);
521		} else {
522			if (GeoHelper.isGeoUri(message.getBody())) {
523				displayLocationMessage(viewHolder,message);
524			} else {
525				if (message.bodyIsHeart()) {
526					displayHeartMessage(viewHolder, message.getBody().trim());
527				} else {
528					displayTextMessage(viewHolder, message);
529				}
530			}
531		}
532
533		displayStatus(viewHolder, message);
534
535		return view;
536	}
537
538	public void startDownloadable(Message message) {
539		Downloadable downloadable = message.getDownloadable();
540		if (downloadable != null) {
541			if (!downloadable.start()) {
542				Toast.makeText(activity, R.string.not_connected_try_again,
543						Toast.LENGTH_SHORT).show();
544			}
545		}
546	}
547
548	public void openDownloadable(Message message) {
549		DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
550		if (!file.exists()) {
551			Toast.makeText(activity,R.string.file_deleted,Toast.LENGTH_SHORT).show();
552			return;
553		}
554		Intent openIntent = new Intent(Intent.ACTION_VIEW);
555		openIntent.setDataAndType(Uri.fromFile(file), file.getMimeType());
556		PackageManager manager = activity.getPackageManager();
557		List<ResolveInfo> infos = manager.queryIntentActivities(openIntent, 0);
558		if (infos.size() > 0) {
559			getContext().startActivity(openIntent);
560		} else {
561			Toast.makeText(activity,R.string.no_application_found_to_open_file,Toast.LENGTH_SHORT).show();
562		}
563	}
564
565	public void showLocation(Message message) {
566		for(Intent intent : GeoHelper.createGeoIntentsFromMessage(message)) {
567			if (intent.resolveActivity(getContext().getPackageManager()) != null) {
568				getContext().startActivity(intent);
569				return;
570			}
571		}
572		Toast.makeText(activity,R.string.no_application_found_to_display_location,Toast.LENGTH_SHORT).show();
573	}
574
575	public interface OnContactPictureClicked {
576		public void onContactPictureClicked(Message message);
577	}
578
579	public interface OnContactPictureLongClicked {
580		public void onContactPictureLongClicked(Message message);
581	}
582
583	private static class ViewHolder {
584
585		protected LinearLayout message_box;
586		protected Button download_button;
587		protected ImageView image;
588		protected ImageView indicator;
589		protected ImageView indicatorReceived;
590		protected TextView time;
591		protected TextView messageBody;
592		protected ImageView contact_picture;
593		protected TextView status_message;
594	}
595}