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