MessageAdapter.java

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