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		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				view.setTag(viewHolder);
375				break;
376			case RECEIVED:
377				view = activity.getLayoutInflater().inflate(
378						R.layout.message_received, parent, false);
379				viewHolder.message_box = (LinearLayout) view
380						.findViewById(R.id.message_box);
381				viewHolder.contact_picture = (ImageView) view
382						.findViewById(R.id.message_photo);
383				viewHolder.download_button = (Button) view
384						.findViewById(R.id.download_button);
385				viewHolder.indicator = (ImageView) view
386						.findViewById(R.id.security_indicator);
387				viewHolder.image = (ImageView) view
388						.findViewById(R.id.message_image);
389				viewHolder.messageBody = (TextView) view
390						.findViewById(R.id.message_body);
391				viewHolder.time = (TextView) view
392						.findViewById(R.id.message_time);
393				viewHolder.indicatorReceived = (ImageView) view
394						.findViewById(R.id.indicator_received);
395				view.setTag(viewHolder);
396				break;
397			case STATUS:
398				view = activity.getLayoutInflater().inflate(
399						R.layout.message_status, parent, false);
400				viewHolder.contact_picture = (ImageView) view
401						.findViewById(R.id.message_photo);
402				view.setTag(viewHolder);
403				break;
404			default:
405				viewHolder = null;
406				break;
407			}
408		} else {
409			viewHolder = (ViewHolder) view.getTag();
410		}
411
412		if (type == STATUS) {
413			if (conversation.getMode() == Conversation.MODE_SINGLE) {
414				viewHolder.contact_picture.setImageBitmap(activity
415						.avatarService().get(conversation.getContact(),
416								activity.getPixel(32)));
417				viewHolder.contact_picture.setAlpha(0.5f);
418				viewHolder.contact_picture
419						.setOnClickListener(new OnClickListener() {
420
421							@Override
422							public void onClick(View v) {
423								String name = conversation.getName();
424								String read = getContext()
425										.getString(
426												R.string.contact_has_read_up_to_this_point,
427												name);
428								Toast.makeText(getContext(), read,
429										Toast.LENGTH_SHORT).show();
430							}
431						});
432
433			}
434			return view;
435		} else if (type == NULL) {
436			if (position == getCount() - 1) {
437				view.getLayoutParams().height = 1;
438			} else {
439				view.getLayoutParams().height = 0;
440
441			}
442			view.setLayoutParams(view.getLayoutParams());
443			return view;
444		} else if (type == RECEIVED) {
445			Contact contact = message.getContact();
446			if (contact != null) {
447				viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(contact, activity.getPixel(48)));
448			} else if (conversation.getMode() == Conversation.MODE_MULTI) {
449				viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(getDisplayedMucCounterpart(message.getCounterpart()),
450                        activity.getPixel(48)));
451			}
452		} else if (type == SENT && viewHolder.contact_picture != null) {
453			viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(account, activity.getPixel(48)));
454		}
455
456		if (viewHolder != null && viewHolder.contact_picture != null) {
457			viewHolder.contact_picture
458					.setOnClickListener(new OnClickListener() {
459
460						@Override
461						public void onClick(View v) {
462							if (MessageAdapter.this.mOnContactPictureClickedListener != null) {
463								MessageAdapter.this.mOnContactPictureClickedListener
464										.onContactPictureClicked(message);
465							}
466
467						}
468					});
469			viewHolder.contact_picture
470					.setOnLongClickListener(new OnLongClickListener() {
471
472						@Override
473						public boolean onLongClick(View v) {
474							if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
475								MessageAdapter.this.mOnContactPictureLongClickedListener
476										.onContactPictureLongClicked(message);
477								return true;
478							} else {
479								return false;
480							}
481						}
482					});
483		}
484
485		if (message.getDownloadable() != null && message.getDownloadable().getStatus() != Downloadable.STATUS_UPLOADING) {
486			Downloadable d = message.getDownloadable();
487			if (d.getStatus() == Downloadable.STATUS_DOWNLOADING) {
488				if (message.getType() == Message.TYPE_FILE) {
489					displayInfoMessage(viewHolder,activity.getString(R.string.receiving_file,d.getMimeType(),d.getProgress()));
490				} else {
491					displayInfoMessage(viewHolder,activity.getString(R.string.receiving_image,d.getProgress()));
492				}
493			} else if (d.getStatus() == Downloadable.STATUS_CHECKING) {
494				displayInfoMessage(viewHolder,activity.getString(R.string.checking_image));
495			} else if (d.getStatus() == Downloadable.STATUS_DELETED) {
496				if (message.getType() == Message.TYPE_FILE) {
497					displayInfoMessage(viewHolder, activity.getString(R.string.file_deleted));
498				} else {
499					displayInfoMessage(viewHolder, activity.getString(R.string.image_file_deleted));
500				}
501			} else if (d.getStatus() == Downloadable.STATUS_OFFER) {
502				if (message.getType() == Message.TYPE_FILE) {
503					displayDownloadableMessage(viewHolder,message,activity.getString(R.string.download_file,d.getMimeType()));
504				} else {
505					displayDownloadableMessage(viewHolder, message,activity.getString(R.string.download_image));
506				}
507			} else if (d.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) {
508				displayDownloadableMessage(viewHolder, message,activity.getString(R.string.check_image_filesize));
509			} else if (d.getStatus() == Downloadable.STATUS_FAILED) {
510				if (message.getType() == Message.TYPE_FILE) {
511					displayInfoMessage(viewHolder, activity.getString(R.string.file_transmission_failed));
512				} else {
513					displayInfoMessage(viewHolder, activity.getString(R.string.image_transmission_failed));
514				}
515			}
516		} else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
517			displayImageMessage(viewHolder, message);
518		} else if (message.getType() == Message.TYPE_FILE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
519			if (message.getImageParams().width > 0) {
520				displayImageMessage(viewHolder,message);
521			} else {
522				displayOpenableMessage(viewHolder, message);
523			}
524		} else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
525			if (activity.hasPgp()) {
526				displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message));
527			} else {
528				displayInfoMessage(viewHolder,
529						activity.getString(R.string.install_openkeychain));
530				if (viewHolder != null) {
531					viewHolder.message_box
532							.setOnClickListener(new OnClickListener() {
533
534								@Override
535								public void onClick(View v) {
536									activity.showInstallPgpDialog();
537								}
538							});
539				}
540			}
541		} else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
542			displayDecryptionFailed(viewHolder);
543		} else {
544			displayTextMessage(viewHolder, message);
545		}
546
547		displayStatus(viewHolder, message);
548
549		return view;
550	}
551
552	public void startDonwloadable(Message message) {
553		Downloadable downloadable = message.getDownloadable();
554		if (downloadable != null) {
555			if (!downloadable.start()) {
556				Toast.makeText(activity, R.string.not_connected_try_again,
557						Toast.LENGTH_SHORT).show();
558			}
559		}
560	}
561
562	public void openDonwloadable(DownloadableFile file) {
563		if (!file.exists()) {
564			Toast.makeText(activity,R.string.file_deleted,Toast.LENGTH_SHORT).show();
565			return;
566		}
567		Intent openIntent = new Intent(Intent.ACTION_VIEW);
568		openIntent.setDataAndType(Uri.fromFile(file), file.getMimeType());
569		PackageManager manager = activity.getPackageManager();
570		List<ResolveInfo> infos = manager.queryIntentActivities(openIntent, 0);
571		if (infos.size() > 0) {
572			getContext().startActivity(openIntent);
573		} else {
574			Toast.makeText(activity,R.string.no_application_found_to_open_file,Toast.LENGTH_SHORT).show();
575		}
576	}
577
578	public interface OnContactPictureClicked {
579		public void onContactPictureClicked(Message message);
580	}
581
582	public interface OnContactPictureLongClicked {
583		public void onContactPictureLongClicked(Message message);
584	}
585
586	private static class ViewHolder {
587
588		protected LinearLayout message_box;
589		protected Button download_button;
590		protected ImageView image;
591		protected ImageView indicator;
592		protected ImageView indicatorReceived;
593		protected TextView time;
594		protected TextView messageBody;
595		protected ImageView contact_picture;
596
597	}
598}