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}