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.view.View;
 14import android.view.View.OnClickListener;
 15import android.view.View.OnLongClickListener;
 16import android.view.ViewGroup;
 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.util.List;
 25
 26import eu.siacs.conversations.Config;
 27import eu.siacs.conversations.R;
 28import eu.siacs.conversations.entities.Account;
 29import eu.siacs.conversations.entities.Contact;
 30import eu.siacs.conversations.entities.Conversation;
 31import eu.siacs.conversations.entities.Downloadable;
 32import eu.siacs.conversations.entities.DownloadableFile;
 33import eu.siacs.conversations.entities.Message;
 34import eu.siacs.conversations.entities.Message.ImageParams;
 35import eu.siacs.conversations.ui.ConversationActivity;
 36import eu.siacs.conversations.utils.UIHelper;
 37import eu.siacs.conversations.xmpp.jid.Jid;
 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)+ " MB";
108			} else if (params.size > 0) {
109				filesize = params.size / 1024 + " KB";
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				Contact contact = message.getContact();
147				if (contact != null) {
148					info = contact.getDisplayName();
149				} else {
150					info = getDisplayedMucCounterpart(message.getCounterpart());
151				}
152			}
153			break;
154		}
155		if (error) {
156			viewHolder.time.setTextColor(activity.getWarningTextColor());
157		} else {
158			viewHolder.time.setTextColor(activity.getSecondaryTextColor());
159		}
160		if (message.getEncryption() == Message.ENCRYPTION_NONE) {
161			viewHolder.indicator.setVisibility(View.GONE);
162		} else {
163			viewHolder.indicator.setVisibility(View.VISIBLE);
164		}
165
166		String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(),
167				message.getMergedTimeSent());
168		if (message.getStatus() <= Message.STATUS_RECEIVED) {
169			if ((filesize != null) && (info != null)) {
170				viewHolder.time.setText(filesize + " \u00B7 " + info);
171			} else if ((filesize == null) && (info != null)) {
172				viewHolder.time.setText(formatedTime + " \u00B7 " + info);
173			} else if ((filesize != null) && (info == null)) {
174				viewHolder.time.setText(formatedTime + " \u00B7 " + filesize);
175			} else {
176				viewHolder.time.setText(formatedTime);
177			}
178		} else {
179			if ((filesize != null) && (info != null)) {
180				viewHolder.time.setText(filesize + " \u00B7 " + info);
181			} else if ((filesize == null) && (info != null)) {
182				if (error) {
183					viewHolder.time.setText(info + " \u00B7 " + formatedTime);
184				} else {
185					viewHolder.time.setText(info);
186				}
187			} else if ((filesize != null) && (info == null)) {
188				viewHolder.time.setText(filesize + " \u00B7 " + formatedTime);
189			} else {
190				viewHolder.time.setText(formatedTime);
191			}
192		}
193	}
194
195	private void displayInfoMessage(ViewHolder viewHolder, String text) {
196		if (viewHolder.download_button != null) {
197			viewHolder.download_button.setVisibility(View.GONE);
198		}
199		viewHolder.image.setVisibility(View.GONE);
200		viewHolder.messageBody.setVisibility(View.VISIBLE);
201		viewHolder.messageBody.setText(text);
202		viewHolder.messageBody.setTextColor(activity.getSecondaryTextColor());
203		viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
204		viewHolder.messageBody.setTextIsSelectable(false);
205	}
206
207	private void displayDecryptionFailed(ViewHolder viewHolder) {
208		if (viewHolder.download_button != null) {
209			viewHolder.download_button.setVisibility(View.GONE);
210		}
211		viewHolder.image.setVisibility(View.GONE);
212		viewHolder.messageBody.setVisibility(View.VISIBLE);
213		viewHolder.messageBody.setText(getContext().getString(
214				R.string.decryption_failed));
215		viewHolder.messageBody.setTextColor(activity.getWarningTextColor());
216		viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
217		viewHolder.messageBody.setTextIsSelectable(false);
218	}
219
220	private void displayTextMessage(ViewHolder viewHolder, Message message) {
221		if (viewHolder.download_button != null) {
222			viewHolder.download_button.setVisibility(View.GONE);
223		}
224		viewHolder.image.setVisibility(View.GONE);
225		viewHolder.messageBody.setVisibility(View.VISIBLE);
226		if (message.getBody() != null) {
227			if (message.getType() != Message.TYPE_PRIVATE) {
228				String body = Config.PARSE_EMOTICONS ? UIHelper
229						.transformAsciiEmoticons(message.getMergedBody())
230						: message.getMergedBody();
231				viewHolder.messageBody.setText(body);
232			} else {
233				String privateMarker;
234				if (message.getStatus() <= Message.STATUS_RECEIVED) {
235					privateMarker = activity
236							.getString(R.string.private_message);
237				} else {
238					final String to;
239					if (message.getCounterpart() != null) {
240						to = message.getCounterpart().getResourcepart();
241					} else {
242						to = "";
243					}
244					privateMarker = activity.getString(R.string.private_message_to, to);
245				}
246				SpannableString span = new SpannableString(privateMarker + " "
247						+ message.getBody());
248				span.setSpan(
249						new ForegroundColorSpan(activity
250								.getSecondaryTextColor()), 0, privateMarker
251								.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
252				span.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0,
253						privateMarker.length(),
254						Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
255				viewHolder.messageBody.setText(span);
256			}
257		} else {
258			viewHolder.messageBody.setText("");
259		}
260		viewHolder.messageBody.setTextColor(activity.getPrimaryTextColor());
261		viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
262		viewHolder.messageBody.setTextIsSelectable(true);
263	}
264
265	private void displayDownloadableMessage(ViewHolder viewHolder,
266			final Message message, String text) {
267		viewHolder.image.setVisibility(View.GONE);
268		viewHolder.messageBody.setVisibility(View.GONE);
269		viewHolder.download_button.setVisibility(View.VISIBLE);
270		viewHolder.download_button.setText(text);
271		viewHolder.download_button.setOnClickListener(new OnClickListener() {
272
273			@Override
274			public void onClick(View v) {
275				startDonwloadable(message);
276			}
277		});
278		viewHolder.download_button.setOnLongClickListener(openContextMenu);
279	}
280
281	private void displayOpenableMessage(ViewHolder viewHolder,final Message message) {
282		final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
283		viewHolder.image.setVisibility(View.GONE);
284		viewHolder.messageBody.setVisibility(View.GONE);
285		viewHolder.download_button.setVisibility(View.VISIBLE);
286		viewHolder.download_button.setText(activity.getString(R.string.open_file,file.getMimeType()));
287		viewHolder.download_button.setOnClickListener(new OnClickListener() {
288
289			@Override
290			public void onClick(View v) {
291				openDonwloadable(file);
292			}
293		});
294		viewHolder.download_button.setOnLongClickListener(openContextMenu);
295	}
296
297	private void displayImageMessage(ViewHolder viewHolder,
298			final Message message) {
299		if (viewHolder.download_button != null) {
300			viewHolder.download_button.setVisibility(View.GONE);
301		}
302		viewHolder.messageBody.setVisibility(View.GONE);
303		viewHolder.image.setVisibility(View.VISIBLE);
304		ImageParams params = message.getImageParams();
305		double target = metrics.density * 288;
306		int scalledW;
307		int scalledH;
308		if (params.width <= params.height) {
309			scalledW = (int) (params.width / ((double) params.height / target));
310			scalledH = (int) target;
311		} else {
312			scalledW = (int) target;
313			scalledH = (int) (params.height / ((double) params.width / target));
314		}
315		viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(
316				scalledW, scalledH));
317		activity.loadBitmap(message, viewHolder.image);
318		viewHolder.image.setOnClickListener(new OnClickListener() {
319
320			@Override
321			public void onClick(View v) {
322				Intent intent = new Intent(Intent.ACTION_VIEW);
323				intent.setDataAndType(activity.xmppConnectionService
324						.getFileBackend().getJingleFileUri(message), "image/*");
325				getContext().startActivity(intent);
326			}
327		});
328		viewHolder.image.setOnLongClickListener(openContextMenu);
329	}
330
331	private String getDisplayedMucCounterpart(final Jid counterpart) {
332		if (counterpart==null) {
333			return "";
334		} else if (!counterpart.isBareJid()) {
335			return counterpart.getResourcepart();
336		} else {
337			return counterpart.toString();
338		}
339	}
340
341	@Override
342	public View getView(int position, View view, ViewGroup parent) {
343		final Message message = getItem(position);
344		final Conversation conversation = message.getConversation();
345		final Account account = conversation.getAccount();
346		final int type = getItemViewType(position);
347		ViewHolder viewHolder;
348		if (view == null) {
349			viewHolder = new ViewHolder();
350			switch (type) {
351			case NULL:
352				view = activity.getLayoutInflater().inflate(
353						R.layout.message_null, parent, false);
354				break;
355			case SENT:
356				view = activity.getLayoutInflater().inflate(
357						R.layout.message_sent, parent, false);
358				viewHolder.message_box = (LinearLayout) view
359						.findViewById(R.id.message_box);
360				viewHolder.contact_picture = (ImageView) view
361						.findViewById(R.id.message_photo);
362				viewHolder.download_button = (Button) view
363						.findViewById(R.id.download_button);
364				viewHolder.indicator = (ImageView) view
365						.findViewById(R.id.security_indicator);
366				viewHolder.image = (ImageView) view
367						.findViewById(R.id.message_image);
368				viewHolder.messageBody = (TextView) view
369						.findViewById(R.id.message_body);
370				viewHolder.time = (TextView) view
371						.findViewById(R.id.message_time);
372				viewHolder.indicatorReceived = (ImageView) view
373						.findViewById(R.id.indicator_received);
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				break;
395			case STATUS:
396				view = activity.getLayoutInflater().inflate(
397						R.layout.message_status, parent, false);
398				viewHolder.contact_picture = (ImageView) view
399						.findViewById(R.id.message_photo);
400				break;
401			default:
402				viewHolder = null;
403				break;
404			}
405			view.setTag(viewHolder);
406		} else {
407			viewHolder = (ViewHolder) view.getTag();
408			if (viewHolder == null) {
409				return view;
410			}
411		}
412
413		if (type == STATUS) {
414			if (conversation.getMode() == Conversation.MODE_SINGLE) {
415				viewHolder.contact_picture.setImageBitmap(activity
416						.avatarService().get(conversation.getContact(),
417								activity.getPixel(32)));
418				viewHolder.contact_picture.setAlpha(0.5f);
419				viewHolder.contact_picture
420						.setOnClickListener(new OnClickListener() {
421
422							@Override
423							public void onClick(View v) {
424								String name = conversation.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 = 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(getDisplayedMucCounterpart(message.getCounterpart()),
451                        activity.getPixel(48)));
452			}
453		} else if (type == SENT && viewHolder.contact_picture != null) {
454			viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(account, 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(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
486		if (message.getDownloadable() != null && message.getDownloadable().getStatus() != Downloadable.STATUS_UPLOADING) {
487			Downloadable d = message.getDownloadable();
488			if (d.getStatus() == Downloadable.STATUS_DOWNLOADING) {
489				if (message.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				if (message.getType() == Message.TYPE_FILE) {
498					displayInfoMessage(viewHolder, activity.getString(R.string.file_deleted));
499				} else {
500					displayInfoMessage(viewHolder, activity.getString(R.string.image_file_deleted));
501				}
502			} else if (d.getStatus() == Downloadable.STATUS_OFFER) {
503				if (message.getType() == Message.TYPE_FILE) {
504					displayDownloadableMessage(viewHolder,message,activity.getString(R.string.download_file,d.getMimeType()));
505				} else {
506					displayDownloadableMessage(viewHolder, message,activity.getString(R.string.download_image));
507				}
508			} else if (d.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) {
509				displayDownloadableMessage(viewHolder, message,activity.getString(R.string.check_image_filesize));
510			} else if (d.getStatus() == Downloadable.STATUS_FAILED) {
511				if (message.getType() == Message.TYPE_FILE) {
512					displayInfoMessage(viewHolder, activity.getString(R.string.file_transmission_failed));
513				} else {
514					displayInfoMessage(viewHolder, activity.getString(R.string.image_transmission_failed));
515				}
516			}
517		} else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
518			displayImageMessage(viewHolder, message);
519		} else if (message.getType() == Message.TYPE_FILE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
520			if (message.getImageParams().width > 0) {
521				displayImageMessage(viewHolder,message);
522			} else {
523				displayOpenableMessage(viewHolder, message);
524			}
525		} else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
526			if (activity.hasPgp()) {
527				displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message));
528			} else {
529				displayInfoMessage(viewHolder,
530						activity.getString(R.string.install_openkeychain));
531				if (viewHolder != null) {
532					viewHolder.message_box
533							.setOnClickListener(new OnClickListener() {
534
535								@Override
536								public void onClick(View v) {
537									activity.showInstallPgpDialog();
538								}
539							});
540				}
541			}
542		} else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
543			displayDecryptionFailed(viewHolder);
544		} else {
545			displayTextMessage(viewHolder, message);
546		}
547
548		displayStatus(viewHolder, message);
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(DownloadableFile file) {
564		if (!file.exists()) {
565			Toast.makeText(activity,R.string.file_deleted,Toast.LENGTH_SHORT).show();
566			return;
567		}
568		Intent openIntent = new Intent(Intent.ACTION_VIEW);
569		openIntent.setDataAndType(Uri.fromFile(file), file.getMimeType());
570		PackageManager manager = activity.getPackageManager();
571		List<ResolveInfo> infos = manager.queryIntentActivities(openIntent, 0);
572		if (infos.size() > 0) {
573			getContext().startActivity(openIntent);
574		} else {
575			Toast.makeText(activity,R.string.no_application_found_to_open_file,Toast.LENGTH_SHORT).show();
576		}
577	}
578
579	public interface OnContactPictureClicked {
580		public void onContactPictureClicked(Message message);
581	}
582
583	public interface OnContactPictureLongClicked {
584		public void onContactPictureLongClicked(Message message);
585	}
586
587	private static class ViewHolder {
588
589		protected LinearLayout message_box;
590		protected Button download_button;
591		protected ImageView image;
592		protected ImageView indicator;
593		protected ImageView indicatorReceived;
594		protected TextView time;
595		protected TextView messageBody;
596		protected ImageView contact_picture;
597
598	}
599}