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