ConversationFragment.java

  1package eu.siacs.conversations.ui;
  2
  3import java.util.ArrayList;
  4import java.util.HashMap;
  5import java.util.List;
  6import java.util.Set;
  7
  8import net.java.otr4j.session.SessionStatus;
  9import eu.siacs.conversations.R;
 10import eu.siacs.conversations.crypto.PgpEngine;
 11import eu.siacs.conversations.entities.Account;
 12import eu.siacs.conversations.entities.Contact;
 13import eu.siacs.conversations.entities.Conversation;
 14import eu.siacs.conversations.entities.Message;
 15import eu.siacs.conversations.entities.MucOptions;
 16import eu.siacs.conversations.services.ImageProvider;
 17import eu.siacs.conversations.services.XmppConnectionService;
 18import eu.siacs.conversations.ui.XmppActivity.OnPresenceSelected;
 19import eu.siacs.conversations.utils.UIHelper;
 20import eu.siacs.conversations.xmpp.jingle.JingleConnection;
 21import android.app.AlertDialog;
 22import android.app.Fragment;
 23import android.app.PendingIntent;
 24import android.content.Context;
 25import android.content.DialogInterface;
 26import android.content.Intent;
 27import android.content.IntentSender;
 28import android.content.SharedPreferences;
 29import android.content.IntentSender.SendIntentException;
 30import android.graphics.Bitmap;
 31import android.graphics.Typeface;
 32import android.os.Bundle;
 33import android.preference.PreferenceManager;
 34import android.text.Editable;
 35import android.text.Selection;
 36import android.util.DisplayMetrics;
 37import android.view.Gravity;
 38import android.view.LayoutInflater;
 39import android.view.View;
 40import android.view.View.OnClickListener;
 41import android.view.View.OnLongClickListener;
 42import android.view.ViewGroup;
 43import android.widget.AbsListView.OnScrollListener;
 44import android.widget.AbsListView;
 45import android.widget.ArrayAdapter;
 46import android.widget.Button;
 47import android.widget.EditText;
 48import android.widget.LinearLayout;
 49import android.widget.ListView;
 50import android.widget.ImageButton;
 51import android.widget.ImageView;
 52import android.widget.RelativeLayout;
 53import android.widget.TextView;
 54import android.widget.Toast;
 55
 56public class ConversationFragment extends Fragment {
 57
 58	protected Conversation conversation;
 59	protected ListView messagesView;
 60	protected LayoutInflater inflater;
 61	protected List<Message> messageList = new ArrayList<Message>();
 62	protected ArrayAdapter<Message> messageListAdapter;
 63	protected Contact contact;
 64	protected BitmapCache mBitmapCache = new BitmapCache();
 65
 66	protected int mPrimaryTextColor;
 67	protected int mSecondaryTextColor;
 68
 69	protected String queuedPqpMessage = null;
 70
 71	private EditText chatMsg;
 72	private String pastedText = null;
 73	private RelativeLayout snackbar;
 74	private TextView snackbarMessage;
 75	private TextView snackbarAction;
 76
 77	protected Bitmap selfBitmap;
 78
 79	private boolean useSubject = true;
 80	private boolean messagesLoaded = false;
 81
 82	private IntentSender askForPassphraseIntent = null;
 83
 84	private OnClickListener sendMsgListener = new OnClickListener() {
 85
 86		@Override
 87		public void onClick(View v) {
 88			if (chatMsg.getText().length() < 1)
 89				return;
 90			Message message = new Message(conversation, chatMsg.getText()
 91					.toString(), conversation.getNextEncryption());
 92			if (conversation.getNextEncryption() == Message.ENCRYPTION_OTR) {
 93				sendOtrMessage(message);
 94			} else if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
 95				sendPgpMessage(message);
 96			} else {
 97				sendPlainTextMessage(message);
 98			}
 99		}
100	};
101	protected OnClickListener clickToDecryptListener = new OnClickListener() {
102
103		@Override
104		public void onClick(View v) {
105			if (activity.hasPgp() && askForPassphraseIntent != null) {
106				try {
107					getActivity().startIntentSenderForResult(
108							askForPassphraseIntent,
109							ConversationActivity.REQUEST_DECRYPT_PGP, null, 0,
110							0, 0);
111				} catch (SendIntentException e) {
112					//
113				}
114			}
115		}
116	};
117
118	private OnClickListener clickToMuc = new OnClickListener() {
119
120		@Override
121		public void onClick(View v) {
122			Intent intent = new Intent(getActivity(),
123					ConferenceDetailsActivity.class);
124			intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC);
125			intent.putExtra("uuid", conversation.getUuid());
126			startActivity(intent);
127		}
128	};
129
130	private OnClickListener leaveMuc = new OnClickListener() {
131
132		@Override
133		public void onClick(View v) {
134			activity.endConversation(conversation);
135		}
136	};
137
138	private OnScrollListener mOnScrollListener = new OnScrollListener() {
139
140		@Override
141		public void onScrollStateChanged(AbsListView view, int scrollState) {
142			// TODO Auto-generated method stub
143
144		}
145
146		@Override
147		public void onScroll(AbsListView view, int firstVisibleItem,
148				int visibleItemCount, int totalItemCount) {
149			if (firstVisibleItem == 0 && messagesLoaded) {
150				long timestamp = messageList.get(0).getTimeSent();
151				messagesLoaded = false;
152				List<Message> messages = activity.xmppConnectionService
153						.getMoreMessages(conversation, timestamp);
154				messageList.addAll(0, messages);
155				messageListAdapter.notifyDataSetChanged();
156				if (messages.size() != 0) {
157					messagesLoaded = true;
158				}
159				messagesView.setSelectionFromTop(messages.size() + 1, 0);
160			}
161		}
162	};
163
164	private ConversationActivity activity;
165
166	public void updateChatMsgHint() {
167		switch (conversation.getNextEncryption()) {
168		case Message.ENCRYPTION_NONE:
169			chatMsg.setHint(getString(R.string.send_plain_text_message));
170			break;
171		case Message.ENCRYPTION_OTR:
172			chatMsg.setHint(getString(R.string.send_otr_message));
173			break;
174		case Message.ENCRYPTION_PGP:
175			chatMsg.setHint(getString(R.string.send_pgp_message));
176			break;
177		default:
178			break;
179		}
180	}
181
182	@Override
183	public View onCreateView(final LayoutInflater inflater,
184			ViewGroup container, Bundle savedInstanceState) {
185
186		final DisplayMetrics metrics = getResources().getDisplayMetrics();
187
188		this.inflater = inflater;
189
190		mPrimaryTextColor = getResources().getColor(R.color.primarytext);
191		mSecondaryTextColor = getResources().getColor(R.color.secondarytext);
192
193		final View view = inflater.inflate(R.layout.fragment_conversation,
194				container, false);
195		chatMsg = (EditText) view.findViewById(R.id.textinput);
196		chatMsg.setOnClickListener(new OnClickListener() {
197
198			@Override
199			public void onClick(View v) {
200				if (activity.getSlidingPaneLayout().isSlideable()) {
201					activity.getSlidingPaneLayout().closePane();
202				}
203			}
204		});
205
206		ImageButton sendButton = (ImageButton) view
207				.findViewById(R.id.textSendButton);
208		sendButton.setOnClickListener(this.sendMsgListener);
209
210		snackbar = (RelativeLayout) view.findViewById(R.id.snackbar);
211		snackbarMessage = (TextView) view.findViewById(R.id.snackbar_message);
212		snackbarAction = (TextView) view.findViewById(R.id.snackbar_action);
213
214		messagesView = (ListView) view.findViewById(R.id.messages_view);
215		messagesView.setOnScrollListener(mOnScrollListener);
216		messagesView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL);
217
218		messageListAdapter = new ArrayAdapter<Message>(this.getActivity()
219				.getApplicationContext(), R.layout.message_sent,
220				this.messageList) {
221
222			private static final int SENT = 0;
223			private static final int RECIEVED = 1;
224			private static final int STATUS = 2;
225
226			@Override
227			public int getViewTypeCount() {
228				return 3;
229			}
230
231			@Override
232			public int getItemViewType(int position) {
233				if (getItem(position).getType() == Message.TYPE_STATUS) {
234					return STATUS;
235				} else if (getItem(position).getStatus() <= Message.STATUS_RECIEVED) {
236					return RECIEVED;
237				} else {
238					return SENT;
239				}
240			}
241
242			private void displayStatus(ViewHolder viewHolder, Message message) {
243				String filesize = null;
244				String info = null;
245				boolean error = false;
246				boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
247						&& message.getStatus() <= Message.STATUS_RECIEVED;
248				if (message.getType() == Message.TYPE_IMAGE) {
249					String[] fileParams = message.getBody().split(",");
250					try {
251						long size = Long.parseLong(fileParams[0]);
252						filesize = size / 1024 + " KB";
253					} catch (NumberFormatException e) {
254						filesize = "0 KB";
255					}
256				}
257				switch (message.getStatus()) {
258				case Message.STATUS_WAITING:
259					info = getString(R.string.waiting);
260					break;
261				case Message.STATUS_UNSEND:
262					info = getString(R.string.sending);
263					break;
264				case Message.STATUS_OFFERED:
265					info = getString(R.string.offering);
266					break;
267				case Message.STATUS_SEND_FAILED:
268					info = getString(R.string.send_failed);
269					error = true;
270					break;
271				case Message.STATUS_SEND_REJECTED:
272					info = getString(R.string.send_rejected);
273					error = true;
274					break;
275				case Message.STATUS_RECEPTION_FAILED:
276					info = getString(R.string.reception_failed);
277					error = true;
278				default:
279					if (multiReceived) {
280						info = message.getCounterpart();
281					}
282					break;
283				}
284				if (error) {
285					viewHolder.time.setTextColor(0xFFe92727);
286				} else {
287					viewHolder.time.setTextColor(mSecondaryTextColor);
288				}
289				if (message.getEncryption() == Message.ENCRYPTION_NONE) {
290					viewHolder.indicator.setVisibility(View.GONE);
291				} else {
292					viewHolder.indicator.setVisibility(View.VISIBLE);
293				}
294
295				String formatedTime = UIHelper.readableTimeDifference(
296						getContext(), message.getTimeSent());
297				if (message.getStatus() <= Message.STATUS_RECIEVED) {
298					if ((filesize != null) && (info != null)) {
299						viewHolder.time.setText(filesize + " \u00B7 " + info);
300					} else if ((filesize == null) && (info != null)) {
301						viewHolder.time.setText(formatedTime + " \u00B7 "
302								+ info);
303					} else if ((filesize != null) && (info == null)) {
304						viewHolder.time.setText(formatedTime + " \u00B7 "
305								+ filesize);
306					} else {
307						viewHolder.time.setText(formatedTime);
308					}
309				} else {
310					if ((filesize != null) && (info != null)) {
311						viewHolder.time.setText(filesize + " \u00B7 " + info);
312					} else if ((filesize == null) && (info != null)) {
313						if (error) {
314							viewHolder.time.setText(info + " \u00B7 "
315									+ formatedTime);
316						} else {
317							viewHolder.time.setText(info);
318						}
319					} else if ((filesize != null) && (info == null)) {
320						viewHolder.time.setText(filesize + " \u00B7 "
321								+ formatedTime);
322					} else {
323						viewHolder.time.setText(formatedTime);
324					}
325				}
326			}
327
328			private void displayInfoMessage(ViewHolder viewHolder, int r) {
329				if (viewHolder.download_button != null) {
330					viewHolder.download_button.setVisibility(View.GONE);
331				}
332				viewHolder.image.setVisibility(View.GONE);
333				viewHolder.messageBody.setVisibility(View.VISIBLE);
334				viewHolder.messageBody.setText(getString(r));
335				viewHolder.messageBody.setTextColor(0xff33B5E5);
336				viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
337				viewHolder.messageBody.setTextIsSelectable(false);
338			}
339
340			private void displayDecryptionFailed(ViewHolder viewHolder) {
341				if (viewHolder.download_button != null) {
342					viewHolder.download_button.setVisibility(View.GONE);
343				}
344				viewHolder.image.setVisibility(View.GONE);
345				viewHolder.messageBody.setVisibility(View.VISIBLE);
346				viewHolder.messageBody
347						.setText(getString(R.string.decryption_failed));
348				viewHolder.messageBody.setTextColor(0xFFe92727);
349				viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
350				viewHolder.messageBody.setTextIsSelectable(false);
351			}
352
353			private void displayTextMessage(ViewHolder viewHolder, String text) {
354				if (viewHolder.download_button != null) {
355					viewHolder.download_button.setVisibility(View.GONE);
356				}
357				viewHolder.image.setVisibility(View.GONE);
358				viewHolder.messageBody.setVisibility(View.VISIBLE);
359				if (text != null) {
360					viewHolder.messageBody.setText(text.trim());
361				} else {
362					viewHolder.messageBody.setText("");
363				}
364				viewHolder.messageBody.setTextColor(mPrimaryTextColor);
365				viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
366				viewHolder.messageBody.setTextIsSelectable(true);
367			}
368
369			private void displayImageMessage(ViewHolder viewHolder,
370					final Message message) {
371				if (viewHolder.download_button != null) {
372					viewHolder.download_button.setVisibility(View.GONE);
373				}
374				viewHolder.messageBody.setVisibility(View.GONE);
375				viewHolder.image.setVisibility(View.VISIBLE);
376				String[] fileParams = message.getBody().split(",");
377				if (fileParams.length == 3) {
378					double target = metrics.density * 288;
379					int w = Integer.parseInt(fileParams[1]);
380					int h = Integer.parseInt(fileParams[2]);
381					int scalledW;
382					int scalledH;
383					if (w <= h) {
384						scalledW = (int) (w / ((double) h / target));
385						scalledH = (int) target;
386					} else {
387						scalledW = (int) target;
388						scalledH = (int) (h / ((double) w / target));
389					}
390					viewHolder.image
391							.setLayoutParams(new LinearLayout.LayoutParams(
392									scalledW, scalledH));
393				}
394				activity.loadBitmap(message, viewHolder.image);
395				viewHolder.image.setOnClickListener(new OnClickListener() {
396
397					@Override
398					public void onClick(View v) {
399						Intent intent = new Intent(Intent.ACTION_VIEW);
400						intent.setDataAndType(
401								ImageProvider.getContentUri(message), "image/*");
402						startActivity(intent);
403					}
404				});
405				viewHolder.image
406						.setOnLongClickListener(new OnLongClickListener() {
407
408							@Override
409							public boolean onLongClick(View v) {
410								Intent shareIntent = new Intent();
411								shareIntent.setAction(Intent.ACTION_SEND);
412								shareIntent.putExtra(Intent.EXTRA_STREAM,
413										ImageProvider.getContentUri(message));
414								shareIntent.setType("image/webp");
415								startActivity(Intent.createChooser(shareIntent,
416										getText(R.string.share_with)));
417								return true;
418							}
419						});
420			}
421
422			@Override
423			public View getView(int position, View view, ViewGroup parent) {
424				final Message item = getItem(position);
425				int type = getItemViewType(position);
426				ViewHolder viewHolder;
427				if (view == null) {
428					viewHolder = new ViewHolder();
429					switch (type) {
430					case SENT:
431						view = (View) inflater.inflate(R.layout.message_sent,
432								null);
433						viewHolder.message_box = (LinearLayout) view
434								.findViewById(R.id.message_box);
435						viewHolder.contact_picture = (ImageView) view
436								.findViewById(R.id.message_photo);
437						viewHolder.contact_picture.setImageBitmap(selfBitmap);
438						viewHolder.indicator = (ImageView) view
439								.findViewById(R.id.security_indicator);
440						viewHolder.image = (ImageView) view
441								.findViewById(R.id.message_image);
442						viewHolder.messageBody = (TextView) view
443								.findViewById(R.id.message_body);
444						viewHolder.time = (TextView) view
445								.findViewById(R.id.message_time);
446						view.setTag(viewHolder);
447						break;
448					case RECIEVED:
449						view = (View) inflater.inflate(
450								R.layout.message_recieved, null);
451						viewHolder.message_box = (LinearLayout) view
452								.findViewById(R.id.message_box);
453						viewHolder.contact_picture = (ImageView) view
454								.findViewById(R.id.message_photo);
455
456						viewHolder.download_button = (Button) view
457								.findViewById(R.id.download_button);
458
459						if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
460
461							viewHolder.contact_picture
462									.setImageBitmap(mBitmapCache.get(
463											item.getConversation().getName(
464													useSubject), item
465													.getConversation()
466													.getContact(),
467											getActivity()
468													.getApplicationContext()));
469
470						}
471						viewHolder.indicator = (ImageView) view
472								.findViewById(R.id.security_indicator);
473						viewHolder.image = (ImageView) view
474								.findViewById(R.id.message_image);
475						viewHolder.messageBody = (TextView) view
476								.findViewById(R.id.message_body);
477						viewHolder.time = (TextView) view
478								.findViewById(R.id.message_time);
479						view.setTag(viewHolder);
480						break;
481					case STATUS:
482						view = (View) inflater.inflate(R.layout.message_status,
483								null);
484						viewHolder.contact_picture = (ImageView) view
485								.findViewById(R.id.message_photo);
486						if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
487
488							viewHolder.contact_picture
489									.setImageBitmap(mBitmapCache.get(
490											item.getConversation().getName(
491													useSubject), item
492													.getConversation()
493													.getContact(),
494											getActivity()
495													.getApplicationContext()));
496							viewHolder.contact_picture.setAlpha(128);
497
498						}
499						break;
500					default:
501						viewHolder = null;
502						break;
503					}
504				} else {
505					viewHolder = (ViewHolder) view.getTag();
506				}
507
508				if (type == STATUS) {
509					return view;
510				}
511
512				if (type == RECIEVED) {
513					if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
514						viewHolder.contact_picture.setImageBitmap(mBitmapCache
515								.get(item.getCounterpart(), null, getActivity()
516										.getApplicationContext()));
517						viewHolder.contact_picture
518								.setOnClickListener(new OnClickListener() {
519
520									@Override
521									public void onClick(View v) {
522										highlightInConference(item
523												.getCounterpart());
524									}
525								});
526					}
527				}
528
529				if (item.getType() == Message.TYPE_IMAGE) {
530					if (item.getStatus() == Message.STATUS_RECIEVING) {
531						displayInfoMessage(viewHolder, R.string.receiving_image);
532					} else if (item.getStatus() == Message.STATUS_RECEIVED_OFFER) {
533						viewHolder.image.setVisibility(View.GONE);
534						viewHolder.messageBody.setVisibility(View.GONE);
535						viewHolder.download_button.setVisibility(View.VISIBLE);
536						viewHolder.download_button
537								.setOnClickListener(new OnClickListener() {
538
539									@Override
540									public void onClick(View v) {
541										JingleConnection connection = item
542												.getJingleConnection();
543										if (connection != null) {
544											connection.accept();
545										}
546									}
547								});
548					} else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED)
549							|| (item.getEncryption() == Message.ENCRYPTION_NONE)
550							|| (item.getEncryption() == Message.ENCRYPTION_OTR)) {
551						displayImageMessage(viewHolder, item);
552					} else if (item.getEncryption() == Message.ENCRYPTION_PGP) {
553						displayInfoMessage(viewHolder,
554								R.string.encrypted_message);
555					} else {
556						displayDecryptionFailed(viewHolder);
557					}
558				} else {
559					if (item.getEncryption() == Message.ENCRYPTION_PGP) {
560						if (activity.hasPgp()) {
561							displayInfoMessage(viewHolder,
562									R.string.encrypted_message);
563						} else {
564							displayInfoMessage(viewHolder,
565									R.string.install_openkeychain);
566							viewHolder.message_box
567									.setOnClickListener(new OnClickListener() {
568
569										@Override
570										public void onClick(View v) {
571											activity.showInstallPgpDialog();
572										}
573									});
574						}
575					} else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
576						displayDecryptionFailed(viewHolder);
577					} else {
578						displayTextMessage(viewHolder, item.getBody());
579					}
580				}
581
582				displayStatus(viewHolder, item);
583
584				return view;
585			}
586		};
587		messagesView.setAdapter(messageListAdapter);
588
589		return view;
590	}
591
592	protected void highlightInConference(String nick) {
593		String oldString = chatMsg.getText().toString().trim();
594		if (oldString.isEmpty()) {
595			chatMsg.setText(nick + ": ");
596		} else {
597			chatMsg.setText(oldString + " " + nick + " ");
598		}
599		int position = chatMsg.length();
600		Editable etext = chatMsg.getText();
601		Selection.setSelection(etext, position);
602	}
603
604	protected Bitmap findSelfPicture() {
605		SharedPreferences sharedPref = PreferenceManager
606				.getDefaultSharedPreferences(getActivity()
607						.getApplicationContext());
608		boolean showPhoneSelfContactPicture = sharedPref.getBoolean(
609				"show_phone_selfcontact_picture", true);
610
611		return UIHelper.getSelfContactPicture(conversation.getAccount(), 48,
612				showPhoneSelfContactPicture, getActivity());
613	}
614
615	@Override
616	public void onStart() {
617		super.onStart();
618		this.activity = (ConversationActivity) getActivity();
619		SharedPreferences preferences = PreferenceManager
620				.getDefaultSharedPreferences(activity);
621		this.useSubject = preferences.getBoolean("use_subject_in_muc", true);
622		if (activity.xmppConnectionServiceBound) {
623			this.onBackendConnected();
624		}
625	}
626
627	@Override
628	public void onStop() {
629		super.onStop();
630		if (this.conversation != null) {
631			this.conversation.setNextMessage(chatMsg.getText().toString());
632		}
633	}
634
635	public void onBackendConnected() {
636		this.conversation = activity.getSelectedConversation();
637		if (this.conversation == null) {
638			return;
639		}
640		String oldString = conversation.getNextMessage().trim();
641		if (this.pastedText == null) {
642			this.chatMsg.setText(oldString);
643		} else {
644
645			if (oldString.isEmpty()) {
646				chatMsg.setText(pastedText);
647			} else {
648				chatMsg.setText(oldString + " " + pastedText);
649			}
650			pastedText = null;
651		}
652		int position = chatMsg.length();
653		Editable etext = chatMsg.getText();
654		Selection.setSelection(etext, position);
655		this.selfBitmap = findSelfPicture();
656		updateMessages();
657		if (activity.getSlidingPaneLayout().isSlideable()) {
658			if (!activity.shouldPaneBeOpen()) {
659				activity.getSlidingPaneLayout().closePane();
660				activity.getActionBar().setDisplayHomeAsUpEnabled(true);
661				activity.getActionBar().setHomeButtonEnabled(true);
662				activity.getActionBar().setTitle(
663						conversation.getName(useSubject));
664				activity.invalidateOptionsMenu();
665			}
666		}
667	}
668
669	private void decryptMessage(Message message) {
670		PgpEngine engine = activity.xmppConnectionService.getPgpEngine();
671		if (engine != null) {
672			engine.decrypt(message, new UiCallback<Message>() {
673
674				@Override
675				public void userInputRequried(PendingIntent pi, Message message) {
676					askForPassphraseIntent = pi.getIntentSender();
677					showSnackbar(R.string.openpgp_messages_found,
678							R.string.decrypt, clickToDecryptListener);
679				}
680
681				@Override
682				public void success(Message message) {
683					activity.xmppConnectionService.databaseBackend
684							.updateMessage(message);
685					updateMessages();
686				}
687
688				@Override
689				public void error(int error, Message message) {
690					message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
691					// updateMessages();
692				}
693			});
694		}
695	}
696
697	public void updateMessages() {
698		if (getView() == null) {
699			return;
700		}
701		hideSnackbar();
702		final ConversationActivity activity = (ConversationActivity) getActivity();
703		if (this.conversation != null) {
704			final Contact contact = this.conversation.getContact();
705			if (!contact.showInRoster() && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
706				showSnackbar(R.string.contact_added_you, R.string.add_back, new OnClickListener() {
707					
708					@Override
709					public void onClick(View v) {
710						activity.xmppConnectionService.createContact(contact);
711						activity.switchToContactDetails(contact);
712					}
713				});
714			}
715			for (Message message : this.conversation.getMessages()) {
716				if ((message.getEncryption() == Message.ENCRYPTION_PGP)
717						&& ((message.getStatus() == Message.STATUS_RECIEVED) || (message
718								.getStatus() == Message.STATUS_SEND))) {
719					decryptMessage(message);
720					break;
721				}
722			}
723			if (this.conversation.getMessages().size() == 0) {
724				this.messageList.clear();
725				messagesLoaded = false;
726			} else {
727				for (Message message : this.conversation.getMessages()) {
728					if (!this.messageList.contains(message)) {
729						this.messageList.add(message);
730					}
731				}
732				messagesLoaded = true;
733				updateStatusMessages();
734			}
735			this.messageListAdapter.notifyDataSetChanged();
736			if (conversation.getMode() == Conversation.MODE_SINGLE) {
737				if (messageList.size() >= 1) {
738					makeFingerprintWarning(conversation.getLatestEncryption());
739				}
740			} else {
741				if (!conversation.getMucOptions().online()
742						&& conversation.getAccount().getStatus() == Account.STATUS_ONLINE) {
743					if (conversation.getMucOptions().getError() == MucOptions.ERROR_NICK_IN_USE) {
744						showSnackbar(R.string.nick_in_use, R.string.edit,
745								clickToMuc);
746					} else if (conversation.getMucOptions().getError() == MucOptions.ERROR_ROOM_NOT_FOUND) {
747						showSnackbar(R.string.conference_not_found,
748								R.string.leave, leaveMuc);
749					}
750				}
751			}
752			getActivity().invalidateOptionsMenu();
753			updateChatMsgHint();
754			if (!activity.shouldPaneBeOpen()) {
755				activity.xmppConnectionService.markRead(conversation);
756				// TODO update notifications
757				UIHelper.updateNotification(getActivity(),
758						activity.getConversationList(), null, false);
759				activity.updateConversationList();
760			}
761		}
762	}
763
764	private void messageSent() {
765		int size = this.messageList.size();
766		if (size >= 1) {
767			messagesView.setSelection(size - 1);
768		}
769		chatMsg.setText("");
770	}
771
772	protected void updateStatusMessages() {
773		boolean addedStatusMsg = false;
774		if (conversation.getMode() == Conversation.MODE_SINGLE) {
775			for (int i = this.messageList.size() - 1; i >= 0; --i) {
776				if (addedStatusMsg) {
777					if (this.messageList.get(i).getType() == Message.TYPE_STATUS) {
778						this.messageList.remove(i);
779						--i;
780					}
781				} else {
782					if (this.messageList.get(i).getStatus() == Message.STATUS_RECIEVED) {
783						addedStatusMsg = true;
784					} else {
785						if (this.messageList.get(i).getStatus() == Message.STATUS_SEND_DISPLAYED) {
786							this.messageList.add(i + 1,
787									Message.createStatusMessage(conversation));
788							addedStatusMsg = true;
789						}
790					}
791				}
792			}
793		}
794	}
795
796	protected void makeFingerprintWarning(int latestEncryption) {
797		Set<String> knownFingerprints = conversation.getContact()
798				.getOtrFingerprints();
799		if ((latestEncryption == Message.ENCRYPTION_OTR)
800				&& (conversation.hasValidOtrSession()
801						&& (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints
802							.contains(conversation.getOtrFingerprint())))) {
803			showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify,
804					new OnClickListener() {
805
806						@Override
807						public void onClick(View v) {
808							if (conversation.getOtrFingerprint() != null) {
809								AlertDialog dialog = UIHelper
810										.getVerifyFingerprintDialog(
811												(ConversationActivity) getActivity(),
812												conversation, snackbar);
813								dialog.show();
814							}
815						}
816					});
817		}
818	}
819
820	protected void showSnackbar(int message, int action,
821			OnClickListener clickListener) {
822		snackbar.setVisibility(View.VISIBLE);
823		snackbarMessage.setText(message);
824		snackbarAction.setText(action);
825		snackbarAction.setOnClickListener(clickListener);
826	}
827
828	protected void hideSnackbar() {
829		snackbar.setVisibility(View.GONE);
830	}
831
832	protected void sendPlainTextMessage(Message message) {
833		ConversationActivity activity = (ConversationActivity) getActivity();
834		activity.xmppConnectionService.sendMessage(message);
835		messageSent();
836	}
837
838	protected void sendPgpMessage(final Message message) {
839		final ConversationActivity activity = (ConversationActivity) getActivity();
840		final XmppConnectionService xmppService = activity.xmppConnectionService;
841		final Contact contact = message.getConversation().getContact();
842		if (activity.hasPgp()) {
843			if (conversation.getMode() == Conversation.MODE_SINGLE) {
844				if (contact.getPgpKeyId() != 0) {
845					xmppService.getPgpEngine().hasKey(contact,
846							new UiCallback<Contact>() {
847
848								@Override
849								public void userInputRequried(PendingIntent pi,
850										Contact contact) {
851									activity.runIntent(
852											pi,
853											ConversationActivity.REQUEST_ENCRYPT_MESSAGE);
854								}
855
856								@Override
857								public void success(Contact contact) {
858									messageSent();
859									activity.encryptTextMessage(message);
860								}
861
862								@Override
863								public void error(int error, Contact contact) {
864
865								}
866							});
867
868				} else {
869					showNoPGPKeyDialog(false,
870							new DialogInterface.OnClickListener() {
871
872								@Override
873								public void onClick(DialogInterface dialog,
874										int which) {
875									conversation
876											.setNextEncryption(Message.ENCRYPTION_NONE);
877									message.setEncryption(Message.ENCRYPTION_NONE);
878									xmppService.sendMessage(message);
879									messageSent();
880								}
881							});
882				}
883			} else {
884				if (conversation.getMucOptions().pgpKeysInUse()) {
885					if (!conversation.getMucOptions().everybodyHasKeys()) {
886						Toast warning = Toast
887								.makeText(getActivity(),
888										R.string.missing_public_keys,
889										Toast.LENGTH_LONG);
890						warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
891						warning.show();
892					}
893					activity.encryptTextMessage(message);
894					messageSent();
895				} else {
896					showNoPGPKeyDialog(true,
897							new DialogInterface.OnClickListener() {
898
899								@Override
900								public void onClick(DialogInterface dialog,
901										int which) {
902									conversation
903											.setNextEncryption(Message.ENCRYPTION_NONE);
904									message.setEncryption(Message.ENCRYPTION_NONE);
905									xmppService.sendMessage(message);
906									messageSent();
907								}
908							});
909				}
910			}
911		} else {
912			activity.showInstallPgpDialog();
913		}
914	}
915
916	public void showNoPGPKeyDialog(boolean plural,
917			DialogInterface.OnClickListener listener) {
918		AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
919		builder.setIconAttribute(android.R.attr.alertDialogIcon);
920		if (plural) {
921			builder.setTitle(getString(R.string.no_pgp_keys));
922			builder.setMessage(getText(R.string.contacts_have_no_pgp_keys));
923		} else {
924			builder.setTitle(getString(R.string.no_pgp_key));
925			builder.setMessage(getText(R.string.contact_has_no_pgp_key));
926		}
927		builder.setNegativeButton(getString(R.string.cancel), null);
928		builder.setPositiveButton(getString(R.string.send_unencrypted),
929				listener);
930		builder.create().show();
931	}
932
933	protected void sendOtrMessage(final Message message) {
934		final ConversationActivity activity = (ConversationActivity) getActivity();
935		final XmppConnectionService xmppService = activity.xmppConnectionService;
936		if (conversation.hasValidOtrSession()) {
937			activity.xmppConnectionService.sendMessage(message);
938			messageSent();
939		} else {
940			activity.selectPresence(message.getConversation(),
941					new OnPresenceSelected() {
942
943						@Override
944						public void onPresenceSelected() {
945							message.setPresence(conversation.getNextPresence());
946							xmppService.sendMessage(message);
947							messageSent();
948						}
949					});
950		}
951	}
952
953	private static class ViewHolder {
954
955		protected LinearLayout message_box;
956		protected Button download_button;
957		protected ImageView image;
958		protected ImageView indicator;
959		protected TextView time;
960		protected TextView messageBody;
961		protected ImageView contact_picture;
962
963	}
964
965	private class BitmapCache {
966		private HashMap<String, Bitmap> bitmaps = new HashMap<String, Bitmap>();
967
968		public Bitmap get(String name, Contact contact, Context context) {
969			if (bitmaps.containsKey(name)) {
970				return bitmaps.get(name);
971			} else {
972				Bitmap bm;
973				if (contact != null) {
974					bm = UIHelper
975							.getContactPicture(contact, 48, context, false);
976				} else {
977					bm = UIHelper.getContactPicture(name, 48, context, false);
978				}
979				bitmaps.put(name, bm);
980				return bm;
981			}
982		}
983	}
984
985	public void setText(String text) {
986		this.pastedText = text;
987	}
988
989	public void clearInputField() {
990		this.chatMsg.setText("");
991	}
992}