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;
236
237		if (showPhoneSelfContactPicture) {
238			Uri selfiUri = PhoneHelper.getSefliUri(getActivity());
239			try {
240				self = BitmapFactory.decodeStream(getActivity()
241						.getContentResolver().openInputStream(selfiUri));
242			} catch (FileNotFoundException e) {
243				self = UIHelper.getUnknownContactPicture(conversation
244						.getAccount().getJid(), 200);
245			}
246		} else {
247			self = UIHelper.getUnknownContactPicture(conversation.getAccount()
248					.getJid(), 200);
249		}
250
251		final Bitmap selfBitmap = self;
252		return selfBitmap;
253	}
254
255	@Override
256	public void onStart() {
257		super.onStart();
258		ConversationActivity activity = (ConversationActivity) getActivity();
259
260		if (activity.xmppConnectionServiceBound) {
261			this.onBackendConnected();
262		}
263	}
264
265	public void onBackendConnected() {
266		final ConversationActivity activity = (ConversationActivity) getActivity();
267		this.conversation = activity.getSelectedConversation();
268		this.selfBitmap = findSelfPicture();
269		updateMessages();
270		// rendering complete. now go tell activity to close pane
271		if (!activity.shouldPaneBeOpen()) {
272			activity.getSlidingPaneLayout().closePane();
273			activity.getActionBar().setDisplayHomeAsUpEnabled(true);
274			activity.getActionBar().setTitle(conversation.getName());
275			activity.invalidateOptionsMenu();
276			if (!conversation.isRead()) {
277				conversation.markRead();
278				activity.updateConversationList();
279			}
280		}
281	}
282
283	public void updateMessages() {
284		ConversationActivity activity = (ConversationActivity) getActivity();
285		this.messageList.clear();
286		this.messageList.addAll(this.conversation.getMessages());
287		this.messageListAdapter.notifyDataSetChanged();
288		if (messageList.size() >= 1) {
289			int latestEncryption = this.conversation.getLatestMessage()
290					.getEncryption();
291			conversation.nextMessageEncryption = latestEncryption;
292			makeFingerprintWarning(latestEncryption);
293		}
294		getActivity().invalidateOptionsMenu();
295		updateChatMsgHint();
296		int size = this.messageList.size();
297		if (size >= 1)
298			messagesView.setSelection(size - 1);
299		if (!activity.shouldPaneBeOpen()) {
300			conversation.markRead();
301			activity.updateConversationList();
302		}
303	}
304
305	protected void makeFingerprintWarning(int latestEncryption) {
306		final LinearLayout fingerprintWarning = (LinearLayout) getView()
307				.findViewById(R.id.new_fingerprint);
308		if (conversation.getContact() != null) {
309			Set<String> knownFingerprints = conversation.getContact()
310					.getOtrFingerprints();
311			if ((latestEncryption == Message.ENCRYPTION_OTR)
312					&& (conversation.hasValidOtrSession()
313							&& (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints
314								.contains(conversation.getOtrFingerprint())))) {
315				fingerprintWarning.setVisibility(View.VISIBLE);
316				TextView fingerprint = (TextView) getView().findViewById(
317						R.id.otr_fingerprint);
318				fingerprint.setText(conversation.getOtrFingerprint());
319				fingerprintWarning.setOnClickListener(new OnClickListener() {
320
321					@Override
322					public void onClick(View v) {
323						AlertDialog dialog = UIHelper
324								.getVerifyFingerprintDialog(
325										(ConversationActivity) getActivity(),
326										conversation, fingerprintWarning);
327						dialog.show();
328					}
329				});
330			} else {
331				fingerprintWarning.setVisibility(View.GONE);
332			}
333		} else {
334			fingerprintWarning.setVisibility(View.GONE);
335		}
336	}
337
338	protected void sendPlainTextMessage(Message message) {
339		ConversationActivity activity = (ConversationActivity) getActivity();
340		activity.xmppConnectionService.sendMessage(conversation.getAccount(),
341				message, null);
342		chatMsg.setText("");
343	}
344
345	protected void sendOtrMessage(final Message message) {
346		ConversationActivity activity = (ConversationActivity) getActivity();
347		final XmppConnectionService xmppService = activity.xmppConnectionService;
348		if (conversation.hasValidOtrSession()) {
349			activity.xmppConnectionService.sendMessage(
350					conversation.getAccount(), message, null);
351			chatMsg.setText("");
352		} else {
353			Hashtable<String, Integer> presences;
354			if (conversation.getContact() != null) {
355				presences = conversation.getContact().getPresences();
356			} else {
357				presences = null;
358			}
359			if ((presences == null) || (presences.size() == 0)) {
360				AlertDialog.Builder builder = new AlertDialog.Builder(
361						getActivity());
362				builder.setTitle("Contact is offline");
363				builder.setIconAttribute(android.R.attr.alertDialogIcon);
364				builder.setMessage("Sending OTR encrypted messages to an offline contact is impossible.");
365				builder.setPositiveButton("Send plain text",
366						new DialogInterface.OnClickListener() {
367
368							@Override
369							public void onClick(DialogInterface dialog,
370									int which) {
371								conversation.nextMessageEncryption = Message.ENCRYPTION_NONE;
372								message.setEncryption(Message.ENCRYPTION_NONE);
373								xmppService.sendMessage(
374										conversation.getAccount(), message,
375										null);
376								chatMsg.setText("");
377							}
378						});
379				builder.setNegativeButton("Cancel", null);
380				builder.create().show();
381			} else if (presences.size() == 1) {
382				xmppService.sendMessage(conversation.getAccount(), message,
383						(String) presences.keySet().toArray()[0]);
384				chatMsg.setText("");
385			} else {
386				AlertDialog.Builder builder = new AlertDialog.Builder(
387						getActivity());
388				builder.setTitle("Choose Presence");
389				final String[] presencesArray = new String[presences.size()];
390				presences.keySet().toArray(presencesArray);
391				builder.setItems(presencesArray,
392						new DialogInterface.OnClickListener() {
393
394							@Override
395							public void onClick(DialogInterface dialog,
396									int which) {
397								xmppService.sendMessage(
398										conversation.getAccount(), message,
399										presencesArray[which]);
400								chatMsg.setText("");
401							}
402						});
403				builder.create().show();
404			}
405		}
406	}
407
408	private static class ViewHolder {
409
410		protected TextView time;
411		protected TextView messageBody;
412		protected ImageView imageView;
413
414	}
415
416	private class BitmapCache {
417		private HashMap<String, Bitmap> bitmaps = new HashMap<String, Bitmap>();
418		private Bitmap error = null;
419
420		public Bitmap get(String name, Uri uri) {
421			if (bitmaps.containsKey(name)) {
422				return bitmaps.get(name);
423			} else {
424				Bitmap bm;
425				if (uri != null) {
426					try {
427						bm = BitmapFactory.decodeStream(getActivity()
428								.getContentResolver().openInputStream(uri));
429					} catch (FileNotFoundException e) {
430						bm = UIHelper.getUnknownContactPicture(name, 200);
431					}
432				} else {
433					bm = UIHelper.getUnknownContactPicture(name, 200);
434				}
435				bitmaps.put(name, bm);
436				return bm;
437			}
438		}
439		
440		public Bitmap getError() {
441			if (error == null) {
442				error = UIHelper.getErrorPicture(200);
443			}
444			return error;
445		}
446	}
447}