ConversationFragment.java

  1package de.gultsch.chat.ui;
  2
  3import java.io.FileNotFoundException;
  4import java.util.ArrayList;
  5import java.util.HashMap;
  6import java.util.HashSet;
  7import java.util.Hashtable;
  8import java.util.List;
  9import java.util.Set;
 10
 11import javax.crypto.spec.PSource;
 12
 13import net.java.otr4j.OtrException;
 14import net.java.otr4j.session.SessionStatus;
 15
 16import de.gultsch.chat.R;
 17import de.gultsch.chat.entities.Contact;
 18import de.gultsch.chat.entities.Conversation;
 19import de.gultsch.chat.entities.Message;
 20import de.gultsch.chat.services.XmppConnectionService;
 21import de.gultsch.chat.utils.PhoneHelper;
 22import de.gultsch.chat.utils.UIHelper;
 23import android.app.AlertDialog;
 24import android.app.Fragment;
 25import android.content.DialogInterface;
 26import android.content.SharedPreferences;
 27import android.graphics.Bitmap;
 28import android.graphics.BitmapFactory;
 29import android.graphics.Typeface;
 30import android.net.Uri;
 31import android.os.Bundle;
 32import android.preference.PreferenceManager;
 33import android.util.Log;
 34import android.view.LayoutInflater;
 35import android.view.View;
 36import android.view.View.OnClickListener;
 37import android.view.View.OnLayoutChangeListener;
 38import android.view.ViewGroup;
 39import android.widget.ArrayAdapter;
 40import android.widget.EditText;
 41import android.widget.LinearLayout;
 42import android.widget.ListView;
 43import android.widget.ImageButton;
 44import android.widget.ImageView;
 45import android.widget.TextView;
 46
 47public class ConversationFragment extends Fragment {
 48
 49	protected Conversation conversation;
 50	protected ListView messagesView;
 51	protected LayoutInflater inflater;
 52	protected List<Message> messageList = new ArrayList<Message>();
 53	protected ArrayAdapter<Message> messageListAdapter;
 54	protected Contact contact;
 55	protected BitmapCache mBitmapCache = new BitmapCache();
 56
 57	private EditText chatMsg;
 58	
 59	protected Bitmap selfBitmap;
 60
 61	private OnClickListener sendMsgListener = new OnClickListener() {
 62
 63		@Override
 64		public void onClick(View v) {
 65			ConversationActivity activity = (ConversationActivity) getActivity();
 66			final XmppConnectionService xmppService = activity.xmppConnectionService;
 67			if (chatMsg.getText().length() < 1)
 68				return;
 69			Message message = new Message(conversation, chatMsg.getText()
 70					.toString(), conversation.nextMessageEncryption);
 71			if (conversation.nextMessageEncryption == Message.ENCRYPTION_OTR) {
 72				sendOtrMessage(message);
 73			} else {
 74				sendPlainTextMessage(message);
 75			}
 76		}
 77	};
 78
 79	public void updateChatMsgHint() {
 80		if (conversation.getMode() == Conversation.MODE_MULTI) {
 81			chatMsg.setHint("Send message to conference");
 82		} else {
 83			switch (conversation.nextMessageEncryption) {
 84			case Message.ENCRYPTION_NONE:
 85				chatMsg.setHint("Send plain text message");
 86				break;
 87			case Message.ENCRYPTION_OTR:
 88				chatMsg.setHint("Send OTR encrypted message");
 89				break;
 90			case Message.ENCRYPTION_PGP:
 91				chatMsg.setHint("Send openPGP encryted messeage");
 92			default:
 93				break;
 94			}
 95		}
 96	}
 97
 98	@Override
 99	public View onCreateView(final LayoutInflater inflater,
100			ViewGroup container, Bundle savedInstanceState) {
101
102		this.inflater = inflater;
103
104		final View view = inflater.inflate(R.layout.fragment_conversation,
105				container, false);
106		chatMsg = (EditText) view.findViewById(R.id.textinput);
107		ImageButton sendButton = (ImageButton) view
108				.findViewById(R.id.textSendButton);
109		sendButton.setOnClickListener(this.sendMsgListener);
110
111		messagesView = (ListView) view.findViewById(R.id.messages_view);
112		
113		messageListAdapter = new ArrayAdapter<Message>(this.getActivity()
114				.getApplicationContext(), R.layout.message_sent,
115				this.messageList) {
116
117			private static final int SENT = 0;
118			private static final int RECIEVED = 1;
119			private static final int ERROR = 2;
120
121			@Override
122			public int getViewTypeCount() {
123				return 3;
124			}
125
126			@Override
127			public int getItemViewType(int position) {
128				if (getItem(position).getStatus() == Message.STATUS_RECIEVED) {
129					return RECIEVED;
130				} else if (getItem(position).getStatus() == Message.STATUS_ERROR) {
131					return ERROR;
132				} else {
133					return SENT;
134				}
135			}
136
137			@Override
138			public View getView(int position, View view, ViewGroup parent) {
139				Message item = getItem(position);
140				int type = getItemViewType(position);
141				ViewHolder viewHolder;
142				if (view == null) {
143					viewHolder = new ViewHolder();
144					switch (type) {
145					case SENT:
146						view = (View) inflater.inflate(R.layout.message_sent,
147								null);
148						viewHolder.imageView = (ImageView) view
149								.findViewById(R.id.message_photo);
150						viewHolder.imageView.setImageBitmap(selfBitmap);
151						break;
152					case RECIEVED:
153						view = (View) inflater.inflate(
154								R.layout.message_recieved, null);
155						viewHolder.imageView = (ImageView) view
156								.findViewById(R.id.message_photo);
157						if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
158							Uri uri = item.getConversation().getProfilePhotoUri();
159							if (uri != null) {
160								viewHolder.imageView
161										.setImageBitmap(mBitmapCache.get(item
162												.getConversation().getName(), uri));
163							} else {
164								viewHolder.imageView
165										.setImageBitmap(mBitmapCache.get(item
166												.getConversation().getName(), null));
167							}
168						}
169						break;
170					case ERROR:
171						view = (View) inflater.inflate(R.layout.message_error, null);
172						viewHolder.imageView = (ImageView) view
173								.findViewById(R.id.message_photo);
174						viewHolder.imageView.setImageBitmap(mBitmapCache.getError());
175						break;
176					default:
177						viewHolder = null;
178						break;
179					}
180					viewHolder.messageBody = (TextView) view
181							.findViewById(R.id.message_body);
182					viewHolder.time = (TextView) view
183							.findViewById(R.id.message_time);
184					view.setTag(viewHolder);
185				} else {
186					viewHolder = (ViewHolder) view.getTag();
187				}
188				if (type == RECIEVED) {
189					 if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
190						if (item.getCounterpart() != null) {
191							viewHolder.imageView.setImageBitmap(mBitmapCache
192									.get(item.getCounterpart(), null));
193						} else {
194							viewHolder.imageView
195									.setImageBitmap(mBitmapCache.get(item
196											.getConversation().getName(), null));
197						}
198					}
199				}
200				String body = item.getBody();
201				if (body != null) {
202					viewHolder.messageBody.setText(body.trim());
203				}
204				if (item.getStatus() == Message.STATUS_UNSEND) {
205					viewHolder.time.setTypeface(null, Typeface.ITALIC);
206					viewHolder.time.setText("sending\u2026");
207				} else {
208					viewHolder.time.setTypeface(null, Typeface.NORMAL);
209					if ((item.getConversation().getMode() == Conversation.MODE_SINGLE)
210							|| (type != RECIEVED)) {
211						viewHolder.time.setText(UIHelper
212								.readableTimeDifference(item.getTimeSent()));
213					} else {
214						viewHolder.time.setText(item.getCounterpart()
215								+ " \u00B7 "
216								+ UIHelper.readableTimeDifference(item
217										.getTimeSent()));
218					}
219				}
220				return view;
221			}
222		};
223		messagesView.setAdapter(messageListAdapter);
224
225		return view;
226	}
227
228	protected Bitmap findSelfPicture() {
229		SharedPreferences sharedPref = PreferenceManager
230				.getDefaultSharedPreferences(getActivity()
231						.getApplicationContext());
232		boolean showPhoneSelfContactPicture = sharedPref.getBoolean(
233				"show_phone_selfcontact_picture", true);
234
235		Bitmap self = null;
236
237		if (showPhoneSelfContactPicture) {
238			Uri selfiUri = PhoneHelper.getSefliUri(getActivity());
239			if (selfiUri!=null) {
240				try {
241					self = BitmapFactory.decodeStream(getActivity()
242							.getContentResolver().openInputStream(selfiUri));
243				} catch (FileNotFoundException e) {
244					self = null;
245				}
246			}
247		}
248		if (self==null){
249			self = UIHelper.getUnknownContactPicture(conversation.getAccount()
250					.getJid(), 200);
251		}
252
253		final Bitmap selfBitmap = self;
254		return selfBitmap;
255	}
256
257	@Override
258	public void onStart() {
259		super.onStart();
260		ConversationActivity activity = (ConversationActivity) getActivity();
261
262		if (activity.xmppConnectionServiceBound) {
263			this.onBackendConnected();
264		}
265	}
266
267	public void onBackendConnected() {
268		final ConversationActivity activity = (ConversationActivity) getActivity();
269		this.conversation = activity.getSelectedConversation();
270		this.selfBitmap = findSelfPicture();
271		updateMessages();
272		// rendering complete. now go tell activity to close pane
273		if (!activity.shouldPaneBeOpen()) {
274			activity.getSlidingPaneLayout().closePane();
275			activity.getActionBar().setDisplayHomeAsUpEnabled(true);
276			activity.getActionBar().setTitle(conversation.getName());
277			activity.invalidateOptionsMenu();
278			if (!conversation.isRead()) {
279				conversation.markRead();
280				activity.updateConversationList();
281			}
282		}
283	}
284
285	public void updateMessages() {
286		ConversationActivity activity = (ConversationActivity) getActivity();
287		this.messageList.clear();
288		this.messageList.addAll(this.conversation.getMessages());
289		this.messageListAdapter.notifyDataSetChanged();
290		if (messageList.size() >= 1) {
291			int latestEncryption = this.conversation.getLatestMessage()
292					.getEncryption();
293			conversation.nextMessageEncryption = latestEncryption;
294			makeFingerprintWarning(latestEncryption);
295		}
296		getActivity().invalidateOptionsMenu();
297		updateChatMsgHint();
298		int size = this.messageList.size();
299		if (size >= 1)
300			messagesView.setSelection(size - 1);
301		if (!activity.shouldPaneBeOpen()) {
302			conversation.markRead();
303			activity.updateConversationList();
304		}
305	}
306
307	protected void makeFingerprintWarning(int latestEncryption) {
308		final LinearLayout fingerprintWarning = (LinearLayout) getView()
309				.findViewById(R.id.new_fingerprint);
310		if (conversation.getContact() != null) {
311			Set<String> knownFingerprints = conversation.getContact()
312					.getOtrFingerprints();
313			if ((latestEncryption == Message.ENCRYPTION_OTR)
314					&& (conversation.hasValidOtrSession()
315							&& (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints
316								.contains(conversation.getOtrFingerprint())))) {
317				fingerprintWarning.setVisibility(View.VISIBLE);
318				TextView fingerprint = (TextView) getView().findViewById(
319						R.id.otr_fingerprint);
320				fingerprint.setText(conversation.getOtrFingerprint());
321				fingerprintWarning.setOnClickListener(new OnClickListener() {
322
323					@Override
324					public void onClick(View v) {
325						AlertDialog dialog = UIHelper
326								.getVerifyFingerprintDialog(
327										(ConversationActivity) getActivity(),
328										conversation, fingerprintWarning);
329						dialog.show();
330					}
331				});
332			} else {
333				fingerprintWarning.setVisibility(View.GONE);
334			}
335		} else {
336			fingerprintWarning.setVisibility(View.GONE);
337		}
338	}
339
340	protected void sendPlainTextMessage(Message message) {
341		ConversationActivity activity = (ConversationActivity) getActivity();
342		activity.xmppConnectionService.sendMessage(conversation.getAccount(),
343				message, null);
344		chatMsg.setText("");
345	}
346
347	protected void sendOtrMessage(final Message message) {
348		ConversationActivity activity = (ConversationActivity) getActivity();
349		final XmppConnectionService xmppService = activity.xmppConnectionService;
350		if (conversation.hasValidOtrSession()) {
351			activity.xmppConnectionService.sendMessage(
352					conversation.getAccount(), message, null);
353			chatMsg.setText("");
354		} else {
355			Hashtable<String, Integer> presences;
356			if (conversation.getContact() != null) {
357				presences = conversation.getContact().getPresences();
358			} else {
359				presences = null;
360			}
361			if ((presences == null) || (presences.size() == 0)) {
362				AlertDialog.Builder builder = new AlertDialog.Builder(
363						getActivity());
364				builder.setTitle("Contact is offline");
365				builder.setIconAttribute(android.R.attr.alertDialogIcon);
366				builder.setMessage("Sending OTR encrypted messages to an offline contact is impossible.");
367				builder.setPositiveButton("Send plain text",
368						new DialogInterface.OnClickListener() {
369
370							@Override
371							public void onClick(DialogInterface dialog,
372									int which) {
373								conversation.nextMessageEncryption = Message.ENCRYPTION_NONE;
374								message.setEncryption(Message.ENCRYPTION_NONE);
375								xmppService.sendMessage(
376										conversation.getAccount(), message,
377										null);
378								chatMsg.setText("");
379							}
380						});
381				builder.setNegativeButton("Cancel", null);
382				builder.create().show();
383			} else if (presences.size() == 1) {
384				xmppService.sendMessage(conversation.getAccount(), message,
385						(String) presences.keySet().toArray()[0]);
386				chatMsg.setText("");
387			} else {
388				AlertDialog.Builder builder = new AlertDialog.Builder(
389						getActivity());
390				builder.setTitle("Choose Presence");
391				final String[] presencesArray = new String[presences.size()];
392				presences.keySet().toArray(presencesArray);
393				builder.setItems(presencesArray,
394						new DialogInterface.OnClickListener() {
395
396							@Override
397							public void onClick(DialogInterface dialog,
398									int which) {
399								xmppService.sendMessage(
400										conversation.getAccount(), message,
401										presencesArray[which]);
402								chatMsg.setText("");
403							}
404						});
405				builder.create().show();
406			}
407		}
408	}
409
410	private static class ViewHolder {
411
412		protected TextView time;
413		protected TextView messageBody;
414		protected ImageView imageView;
415
416	}
417
418	private class BitmapCache {
419		private HashMap<String, Bitmap> bitmaps = new HashMap<String, Bitmap>();
420		private Bitmap error = null;
421
422		public Bitmap get(String name, Uri uri) {
423			if (bitmaps.containsKey(name)) {
424				return bitmaps.get(name);
425			} else {
426				Bitmap bm;
427				if (uri != null) {
428					try {
429						bm = BitmapFactory.decodeStream(getActivity()
430								.getContentResolver().openInputStream(uri));
431					} catch (FileNotFoundException e) {
432						bm = UIHelper.getUnknownContactPicture(name, 200);
433					}
434				} else {
435					bm = UIHelper.getUnknownContactPicture(name, 200);
436				}
437				bitmaps.put(name, bm);
438				return bm;
439			}
440		}
441		
442		public Bitmap getError() {
443			if (error == null) {
444				error = UIHelper.getErrorPicture(200);
445			}
446			return error;
447		}
448	}
449}