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