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