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}