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 switch (type) {
161 case SENT:
162 viewHolder = new ViewHolder();
163 view = (View) inflater.inflate(R.layout.message_sent,
164 null);
165 viewHolder.imageView = (ImageView) view
166 .findViewById(R.id.message_photo);
167 viewHolder.messageBody = (TextView) view
168 .findViewById(R.id.message_body);
169 viewHolder.time = (TextView) view
170 .findViewById(R.id.message_time);
171 view.setTag(viewHolder);
172 break;
173 case RECIEVED:
174 viewHolder = new ViewHolder();
175 view = (View) inflater.inflate(
176 R.layout.message_recieved, null);
177 viewHolder.imageView = (ImageView) view
178 .findViewById(R.id.message_photo);
179 viewHolder.messageBody = (TextView) view
180 .findViewById(R.id.message_body);
181 viewHolder.time = (TextView) view
182 .findViewById(R.id.message_time);
183 view.setTag(viewHolder);
184 break;
185 default:
186 viewHolder = null;
187 break;
188 }
189 } else {
190 viewHolder = (ViewHolder) view.getTag();
191 }
192 if (type == RECIEVED) {
193 if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
194 Uri uri = item.getConversation().getProfilePhotoUri();
195 if (uri != null) {
196 viewHolder.imageView.setImageBitmap(mBitmapCache.get(item.getConversation().getName(), uri));
197 } else {
198 viewHolder.imageView.setImageBitmap(mBitmapCache.get(item.getConversation().getName(),null));
199 }
200 } else if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
201 if (item.getCounterpart() != null) {
202 viewHolder.imageView.setImageBitmap(mBitmapCache.get(item.getCounterpart(),null));
203 } else {
204 viewHolder.imageView.setImageBitmap(mBitmapCache.get(item.getConversation().getName(),null));
205 }
206 }
207 } else {
208 viewHolder.imageView.setImageBitmap(selfBitmap);
209 }
210 String body = item.getBody();
211 if (body != null) {
212 viewHolder.messageBody.setText(body.trim());
213 }
214 if (item.getStatus() == Message.STATUS_UNSEND) {
215 viewHolder.time.setTypeface(null, Typeface.ITALIC);
216 viewHolder.time.setText("sending\u2026");
217 } else {
218 viewHolder.time.setTypeface(null, Typeface.NORMAL);
219 if ((item.getConversation().getMode() == Conversation.MODE_SINGLE)
220 || (type != RECIEVED)) {
221 viewHolder.time.setText(UIHelper
222 .readableTimeDifference(item.getTimeSent()));
223 } else {
224 viewHolder.time.setText(item.getCounterpart()
225 + " \u00B7 "
226 + UIHelper.readableTimeDifference(item
227 .getTimeSent()));
228 }
229 }
230 return view;
231 }
232 };
233 messagesView.setAdapter(messageListAdapter);
234
235 return view;
236 }
237
238 @Override
239 public void onStart() {
240 super.onStart();
241 final ConversationActivity activity = (ConversationActivity) getActivity();
242
243 if (activity.xmppConnectionServiceBound) {
244 this.conversation = activity.getSelectedConversation();
245 updateMessages();
246 // rendering complete. now go tell activity to close pane
247 if (!activity.shouldPaneBeOpen()) {
248 activity.getSlidingPaneLayout().closePane();
249 activity.getActionBar().setDisplayHomeAsUpEnabled(true);
250 activity.getActionBar().setTitle(conversation.getName());
251 activity.invalidateOptionsMenu();
252 if (!conversation.isRead()) {
253 conversation.markRead();
254 activity.updateConversationList();
255 }
256 }
257 }
258 }
259
260 public void onBackendConnected() {
261 final ConversationActivity activity = (ConversationActivity) getActivity();
262 this.conversation = activity.getSelectedConversation();
263 updateMessages();
264 // rendering complete. now go tell activity to close pane
265 if (!activity.shouldPaneBeOpen()) {
266 activity.getSlidingPaneLayout().closePane();
267 activity.getActionBar().setDisplayHomeAsUpEnabled(true);
268 activity.getActionBar().setTitle(conversation.getName());
269 activity.invalidateOptionsMenu();
270 if (!conversation.isRead()) {
271 conversation.markRead();
272 activity.updateConversationList();
273 }
274 }
275 }
276
277 public void updateMessages() {
278 ConversationActivity activity = (ConversationActivity) getActivity();
279 this.messageList.clear();
280 this.messageList.addAll(this.conversation.getMessages());
281 this.messageListAdapter.notifyDataSetChanged();
282 if (messageList.size() >= 1) {
283 int latestEncryption = this.conversation.getLatestMessage()
284 .getEncryption();
285 conversation.nextMessageEncryption = latestEncryption;
286 makeFingerprintWarning(latestEncryption);
287 }
288 getActivity().invalidateOptionsMenu();
289 updateChatMsgHint();
290 int size = this.messageList.size();
291 if (size >= 1)
292 messagesView.setSelection(size - 1);
293 if (!activity.shouldPaneBeOpen()) {
294 conversation.markRead();
295 activity.updateConversationList();
296 }
297 }
298
299 protected void makeFingerprintWarning(int latestEncryption) {
300 final LinearLayout fingerprintWarning = (LinearLayout) getView()
301 .findViewById(R.id.new_fingerprint);
302 if (conversation.getContact() != null) {
303 Set<String> knownFingerprints = conversation.getContact()
304 .getOtrFingerprints();
305 if ((latestEncryption == Message.ENCRYPTION_OTR)
306 && (conversation.hasValidOtrSession()
307 && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints
308 .contains(conversation.getOtrFingerprint())))) {
309 fingerprintWarning.setVisibility(View.VISIBLE);
310 TextView fingerprint = (TextView) getView().findViewById(
311 R.id.otr_fingerprint);
312 fingerprint.setText(conversation.getOtrFingerprint());
313 fingerprintWarning.setOnClickListener(new OnClickListener() {
314
315 @Override
316 public void onClick(View v) {
317 AlertDialog dialog = UIHelper
318 .getVerifyFingerprintDialog(
319 (ConversationActivity) getActivity(),
320 conversation, fingerprintWarning);
321 dialog.show();
322 }
323 });
324 } else {
325 fingerprintWarning.setVisibility(View.GONE);
326 }
327 } else {
328 fingerprintWarning.setVisibility(View.GONE);
329 }
330 }
331
332 protected void sendPlainTextMessage(Message message) {
333 ConversationActivity activity = (ConversationActivity) getActivity();
334 activity.xmppConnectionService.sendMessage(conversation.getAccount(),
335 message, null);
336 chatMsg.setText("");
337 }
338
339 protected void sendOtrMessage(final Message message) {
340 ConversationActivity activity = (ConversationActivity) getActivity();
341 final XmppConnectionService xmppService = activity.xmppConnectionService;
342 if (conversation.hasValidOtrSession()) {
343 activity.xmppConnectionService.sendMessage(
344 conversation.getAccount(), message, null);
345 chatMsg.setText("");
346 } else {
347 Hashtable<String, Integer> presences;
348 if (conversation.getContact() != null) {
349 presences = conversation.getContact().getPresences();
350 } else {
351 presences = null;
352 }
353 if ((presences != null) && (presences.size() == 0)) {
354 AlertDialog.Builder builder = new AlertDialog.Builder(
355 getActivity());
356 builder.setTitle("Contact is offline");
357 builder.setIconAttribute(android.R.attr.alertDialogIcon);
358 builder.setMessage("Sending OTR encrypted messages to an offline contact is impossible.");
359 builder.setPositiveButton("Send plain text",
360 new DialogInterface.OnClickListener() {
361
362 @Override
363 public void onClick(DialogInterface dialog,
364 int which) {
365 conversation.nextMessageEncryption = Message.ENCRYPTION_NONE;
366 message.setEncryption(Message.ENCRYPTION_NONE);
367 xmppService.sendMessage(
368 conversation.getAccount(), message,
369 null);
370 chatMsg.setText("");
371 }
372 });
373 builder.setNegativeButton("Cancel", null);
374 builder.create().show();
375 } else if (presences.size() == 1) {
376 xmppService.sendMessage(conversation.getAccount(), message,
377 (String) presences.keySet().toArray()[0]);
378 chatMsg.setText("");
379 } else {
380 AlertDialog.Builder builder = new AlertDialog.Builder(
381 getActivity());
382 builder.setTitle("Choose Presence");
383 final String[] presencesArray = new String[presences.size()];
384 presences.keySet().toArray(presencesArray);
385 builder.setItems(presencesArray,
386 new DialogInterface.OnClickListener() {
387
388 @Override
389 public void onClick(DialogInterface dialog,
390 int which) {
391 xmppService.sendMessage(
392 conversation.getAccount(), message,
393 presencesArray[which]);
394 chatMsg.setText("");
395 }
396 });
397 builder.create().show();
398 }
399 }
400 }
401
402 private static class ViewHolder {
403
404 protected TextView time;
405 protected TextView messageBody;
406 protected ImageView imageView;
407
408 }
409
410 private class BitmapCache {
411 private HashMap<String, Bitmap> bitmaps = new HashMap<String, Bitmap>();
412 public Bitmap get(String name, Uri uri) {
413 if (bitmaps.containsKey(name)) {
414 return bitmaps.get(name);
415 } else {
416 Bitmap bm;
417 if (uri!=null) {
418 try {
419 bm = BitmapFactory.decodeStream(getActivity()
420 .getContentResolver().openInputStream(uri));
421 } catch (FileNotFoundException e) {
422 bm = UIHelper.getUnknownContactPicture(name, 200);
423 }
424 } else {
425 bm = UIHelper.getUnknownContactPicture(name, 200);
426 }
427 bitmaps.put(name, bm);
428 return bm;
429 }
430 }
431 }
432}