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