1package eu.siacs.conversations.ui;
2
3import java.util.ArrayList;
4import java.util.HashMap;
5import java.util.List;
6import java.util.Set;
7
8import net.java.otr4j.session.SessionStatus;
9import eu.siacs.conversations.R;
10import eu.siacs.conversations.crypto.PgpEngine;
11import eu.siacs.conversations.entities.Account;
12import eu.siacs.conversations.entities.Contact;
13import eu.siacs.conversations.entities.Conversation;
14import eu.siacs.conversations.entities.Message;
15import eu.siacs.conversations.entities.MucOptions;
16import eu.siacs.conversations.services.ImageProvider;
17import eu.siacs.conversations.services.XmppConnectionService;
18import eu.siacs.conversations.ui.XmppActivity.OnPresenceSelected;
19import eu.siacs.conversations.utils.UIHelper;
20import eu.siacs.conversations.xmpp.jingle.JingleConnection;
21import android.app.AlertDialog;
22import android.app.Fragment;
23import android.app.PendingIntent;
24import android.content.Context;
25import android.content.DialogInterface;
26import android.content.Intent;
27import android.content.IntentSender;
28import android.content.SharedPreferences;
29import android.content.IntentSender.SendIntentException;
30import android.graphics.Bitmap;
31import android.graphics.Typeface;
32import android.os.Bundle;
33import android.preference.PreferenceManager;
34import android.text.Editable;
35import android.text.Selection;
36import android.util.DisplayMetrics;
37import android.view.Gravity;
38import android.view.LayoutInflater;
39import android.view.View;
40import android.view.View.OnClickListener;
41import android.view.View.OnLongClickListener;
42import android.view.ViewGroup;
43import android.widget.AbsListView.OnScrollListener;
44import android.widget.AbsListView;
45import android.widget.ArrayAdapter;
46import android.widget.Button;
47import android.widget.EditText;
48import android.widget.LinearLayout;
49import android.widget.ListView;
50import android.widget.ImageButton;
51import android.widget.ImageView;
52import android.widget.RelativeLayout;
53import android.widget.TextView;
54import android.widget.Toast;
55
56public class ConversationFragment extends Fragment {
57
58 protected Conversation conversation;
59 protected ListView messagesView;
60 protected LayoutInflater inflater;
61 protected List<Message> messageList = new ArrayList<Message>();
62 protected ArrayAdapter<Message> messageListAdapter;
63 protected Contact contact;
64 protected BitmapCache mBitmapCache = new BitmapCache();
65
66 protected int mPrimaryTextColor;
67 protected int mSecondaryTextColor;
68
69 protected String queuedPqpMessage = null;
70
71 private EditText chatMsg;
72 private String pastedText = null;
73 private RelativeLayout snackbar;
74 private TextView snackbarMessage;
75 private TextView snackbarAction;
76
77 protected Bitmap selfBitmap;
78
79 private boolean useSubject = true;
80 private boolean messagesLoaded = false;
81
82 private IntentSender askForPassphraseIntent = null;
83
84 private OnClickListener sendMsgListener = new OnClickListener() {
85
86 @Override
87 public void onClick(View v) {
88 if (chatMsg.getText().length() < 1)
89 return;
90 Message message = new Message(conversation, chatMsg.getText()
91 .toString(), conversation.getNextEncryption());
92 if (conversation.getNextEncryption() == Message.ENCRYPTION_OTR) {
93 sendOtrMessage(message);
94 } else if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
95 sendPgpMessage(message);
96 } else {
97 sendPlainTextMessage(message);
98 }
99 }
100 };
101 protected OnClickListener clickToDecryptListener = new OnClickListener() {
102
103 @Override
104 public void onClick(View v) {
105 if (activity.hasPgp() && askForPassphraseIntent != null) {
106 try {
107 getActivity().startIntentSenderForResult(
108 askForPassphraseIntent,
109 ConversationActivity.REQUEST_DECRYPT_PGP, null, 0,
110 0, 0);
111 } catch (SendIntentException e) {
112 //
113 }
114 }
115 }
116 };
117
118 private OnClickListener clickToMuc = new OnClickListener() {
119
120 @Override
121 public void onClick(View v) {
122 Intent intent = new Intent(getActivity(),
123 ConferenceDetailsActivity.class);
124 intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC);
125 intent.putExtra("uuid", conversation.getUuid());
126 startActivity(intent);
127 }
128 };
129
130 private OnClickListener leaveMuc = new OnClickListener() {
131
132 @Override
133 public void onClick(View v) {
134 activity.endConversation(conversation);
135 }
136 };
137
138 private OnScrollListener mOnScrollListener = new OnScrollListener() {
139
140 @Override
141 public void onScrollStateChanged(AbsListView view, int scrollState) {
142 // TODO Auto-generated method stub
143
144 }
145
146 @Override
147 public void onScroll(AbsListView view, int firstVisibleItem,
148 int visibleItemCount, int totalItemCount) {
149 if (firstVisibleItem == 0 && messagesLoaded) {
150 long timestamp = messageList.get(0).getTimeSent();
151 messagesLoaded = false;
152 List<Message> messages = activity.xmppConnectionService
153 .getMoreMessages(conversation, timestamp);
154 messageList.addAll(0, messages);
155 messageListAdapter.notifyDataSetChanged();
156 if (messages.size() != 0) {
157 messagesLoaded = true;
158 }
159 messagesView.setSelectionFromTop(messages.size() + 1, 0);
160 }
161 }
162 };
163
164 private ConversationActivity activity;
165
166 public void updateChatMsgHint() {
167 switch (conversation.getNextEncryption()) {
168 case Message.ENCRYPTION_NONE:
169 chatMsg.setHint(getString(R.string.send_plain_text_message));
170 break;
171 case Message.ENCRYPTION_OTR:
172 chatMsg.setHint(getString(R.string.send_otr_message));
173 break;
174 case Message.ENCRYPTION_PGP:
175 chatMsg.setHint(getString(R.string.send_pgp_message));
176 break;
177 default:
178 break;
179 }
180 }
181
182 @Override
183 public View onCreateView(final LayoutInflater inflater,
184 ViewGroup container, Bundle savedInstanceState) {
185
186 final DisplayMetrics metrics = getResources().getDisplayMetrics();
187
188 this.inflater = inflater;
189
190 mPrimaryTextColor = getResources().getColor(R.color.primarytext);
191 mSecondaryTextColor = getResources().getColor(R.color.secondarytext);
192
193 final View view = inflater.inflate(R.layout.fragment_conversation,
194 container, false);
195 chatMsg = (EditText) view.findViewById(R.id.textinput);
196 chatMsg.setOnClickListener(new OnClickListener() {
197
198 @Override
199 public void onClick(View v) {
200 if (activity.getSlidingPaneLayout().isSlideable()) {
201 activity.getSlidingPaneLayout().closePane();
202 }
203 }
204 });
205
206 ImageButton sendButton = (ImageButton) view
207 .findViewById(R.id.textSendButton);
208 sendButton.setOnClickListener(this.sendMsgListener);
209
210 snackbar = (RelativeLayout) view.findViewById(R.id.snackbar);
211 snackbarMessage = (TextView) view.findViewById(R.id.snackbar_message);
212 snackbarAction = (TextView) view.findViewById(R.id.snackbar_action);
213
214 messagesView = (ListView) view.findViewById(R.id.messages_view);
215 messagesView.setOnScrollListener(mOnScrollListener);
216 messagesView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL);
217
218 messageListAdapter = new ArrayAdapter<Message>(this.getActivity()
219 .getApplicationContext(), R.layout.message_sent,
220 this.messageList) {
221
222 private static final int SENT = 0;
223 private static final int RECIEVED = 1;
224 private static final int STATUS = 2;
225
226 @Override
227 public int getViewTypeCount() {
228 return 3;
229 }
230
231 @Override
232 public int getItemViewType(int position) {
233 if (getItem(position).getType() == Message.TYPE_STATUS) {
234 return STATUS;
235 } else if (getItem(position).getStatus() <= Message.STATUS_RECIEVED) {
236 return RECIEVED;
237 } else {
238 return SENT;
239 }
240 }
241
242 private void displayStatus(ViewHolder viewHolder, Message message) {
243 String filesize = null;
244 String info = null;
245 boolean error = false;
246 boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
247 && message.getStatus() <= Message.STATUS_RECIEVED;
248 if (message.getType() == Message.TYPE_IMAGE) {
249 String[] fileParams = message.getBody().split(",");
250 try {
251 long size = Long.parseLong(fileParams[0]);
252 filesize = size / 1024 + " KB";
253 } catch (NumberFormatException e) {
254 filesize = "0 KB";
255 }
256 }
257 switch (message.getStatus()) {
258 case Message.STATUS_WAITING:
259 info = getString(R.string.waiting);
260 break;
261 case Message.STATUS_UNSEND:
262 info = getString(R.string.sending);
263 break;
264 case Message.STATUS_OFFERED:
265 info = getString(R.string.offering);
266 break;
267 case Message.STATUS_SEND_FAILED:
268 info = getString(R.string.send_failed);
269 error = true;
270 break;
271 case Message.STATUS_SEND_REJECTED:
272 info = getString(R.string.send_rejected);
273 error = true;
274 break;
275 case Message.STATUS_RECEPTION_FAILED:
276 info = getString(R.string.reception_failed);
277 error = true;
278 default:
279 if (multiReceived) {
280 info = message.getCounterpart();
281 }
282 break;
283 }
284 if (error) {
285 viewHolder.time.setTextColor(0xFFe92727);
286 } else {
287 viewHolder.time.setTextColor(mSecondaryTextColor);
288 }
289 if (message.getEncryption() == Message.ENCRYPTION_NONE) {
290 viewHolder.indicator.setVisibility(View.GONE);
291 } else {
292 viewHolder.indicator.setVisibility(View.VISIBLE);
293 }
294
295 String formatedTime = UIHelper.readableTimeDifference(
296 getContext(), message.getTimeSent());
297 if (message.getStatus() <= Message.STATUS_RECIEVED) {
298 if ((filesize != null) && (info != null)) {
299 viewHolder.time.setText(filesize + " \u00B7 " + info);
300 } else if ((filesize == null) && (info != null)) {
301 viewHolder.time.setText(formatedTime + " \u00B7 "
302 + info);
303 } else if ((filesize != null) && (info == null)) {
304 viewHolder.time.setText(formatedTime + " \u00B7 "
305 + filesize);
306 } else {
307 viewHolder.time.setText(formatedTime);
308 }
309 } else {
310 if ((filesize != null) && (info != null)) {
311 viewHolder.time.setText(filesize + " \u00B7 " + info);
312 } else if ((filesize == null) && (info != null)) {
313 if (error) {
314 viewHolder.time.setText(info + " \u00B7 "
315 + formatedTime);
316 } else {
317 viewHolder.time.setText(info);
318 }
319 } else if ((filesize != null) && (info == null)) {
320 viewHolder.time.setText(filesize + " \u00B7 "
321 + formatedTime);
322 } else {
323 viewHolder.time.setText(formatedTime);
324 }
325 }
326 }
327
328 private void displayInfoMessage(ViewHolder viewHolder, int r) {
329 if (viewHolder.download_button != null) {
330 viewHolder.download_button.setVisibility(View.GONE);
331 }
332 viewHolder.image.setVisibility(View.GONE);
333 viewHolder.messageBody.setVisibility(View.VISIBLE);
334 viewHolder.messageBody.setText(getString(r));
335 viewHolder.messageBody.setTextColor(0xff33B5E5);
336 viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
337 viewHolder.messageBody.setTextIsSelectable(false);
338 }
339
340 private void displayDecryptionFailed(ViewHolder viewHolder) {
341 if (viewHolder.download_button != null) {
342 viewHolder.download_button.setVisibility(View.GONE);
343 }
344 viewHolder.image.setVisibility(View.GONE);
345 viewHolder.messageBody.setVisibility(View.VISIBLE);
346 viewHolder.messageBody
347 .setText(getString(R.string.decryption_failed));
348 viewHolder.messageBody.setTextColor(0xFFe92727);
349 viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
350 viewHolder.messageBody.setTextIsSelectable(false);
351 }
352
353 private void displayTextMessage(ViewHolder viewHolder, String text) {
354 if (viewHolder.download_button != null) {
355 viewHolder.download_button.setVisibility(View.GONE);
356 }
357 viewHolder.image.setVisibility(View.GONE);
358 viewHolder.messageBody.setVisibility(View.VISIBLE);
359 if (text != null) {
360 viewHolder.messageBody.setText(text.trim());
361 } else {
362 viewHolder.messageBody.setText("");
363 }
364 viewHolder.messageBody.setTextColor(mPrimaryTextColor);
365 viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
366 viewHolder.messageBody.setTextIsSelectable(true);
367 }
368
369 private void displayImageMessage(ViewHolder viewHolder,
370 final Message message) {
371 if (viewHolder.download_button != null) {
372 viewHolder.download_button.setVisibility(View.GONE);
373 }
374 viewHolder.messageBody.setVisibility(View.GONE);
375 viewHolder.image.setVisibility(View.VISIBLE);
376 String[] fileParams = message.getBody().split(",");
377 if (fileParams.length == 3) {
378 double target = metrics.density * 288;
379 int w = Integer.parseInt(fileParams[1]);
380 int h = Integer.parseInt(fileParams[2]);
381 int scalledW;
382 int scalledH;
383 if (w <= h) {
384 scalledW = (int) (w / ((double) h / target));
385 scalledH = (int) target;
386 } else {
387 scalledW = (int) target;
388 scalledH = (int) (h / ((double) w / target));
389 }
390 viewHolder.image
391 .setLayoutParams(new LinearLayout.LayoutParams(
392 scalledW, scalledH));
393 }
394 activity.loadBitmap(message, viewHolder.image);
395 viewHolder.image.setOnClickListener(new OnClickListener() {
396
397 @Override
398 public void onClick(View v) {
399 Intent intent = new Intent(Intent.ACTION_VIEW);
400 intent.setDataAndType(
401 ImageProvider.getContentUri(message), "image/*");
402 startActivity(intent);
403 }
404 });
405 viewHolder.image
406 .setOnLongClickListener(new OnLongClickListener() {
407
408 @Override
409 public boolean onLongClick(View v) {
410 Intent shareIntent = new Intent();
411 shareIntent.setAction(Intent.ACTION_SEND);
412 shareIntent.putExtra(Intent.EXTRA_STREAM,
413 ImageProvider.getContentUri(message));
414 shareIntent.setType("image/webp");
415 startActivity(Intent.createChooser(shareIntent,
416 getText(R.string.share_with)));
417 return true;
418 }
419 });
420 }
421
422 @Override
423 public View getView(int position, View view, ViewGroup parent) {
424 final Message item = getItem(position);
425 int type = getItemViewType(position);
426 ViewHolder viewHolder;
427 if (view == null) {
428 viewHolder = new ViewHolder();
429 switch (type) {
430 case SENT:
431 view = (View) inflater.inflate(R.layout.message_sent,
432 null);
433 viewHolder.message_box = (LinearLayout) view
434 .findViewById(R.id.message_box);
435 viewHolder.contact_picture = (ImageView) view
436 .findViewById(R.id.message_photo);
437 viewHolder.contact_picture.setImageBitmap(selfBitmap);
438 viewHolder.indicator = (ImageView) view
439 .findViewById(R.id.security_indicator);
440 viewHolder.image = (ImageView) view
441 .findViewById(R.id.message_image);
442 viewHolder.messageBody = (TextView) view
443 .findViewById(R.id.message_body);
444 viewHolder.time = (TextView) view
445 .findViewById(R.id.message_time);
446 view.setTag(viewHolder);
447 break;
448 case RECIEVED:
449 view = (View) inflater.inflate(
450 R.layout.message_recieved, null);
451 viewHolder.message_box = (LinearLayout) view
452 .findViewById(R.id.message_box);
453 viewHolder.contact_picture = (ImageView) view
454 .findViewById(R.id.message_photo);
455
456 viewHolder.download_button = (Button) view
457 .findViewById(R.id.download_button);
458
459 if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
460
461 viewHolder.contact_picture
462 .setImageBitmap(mBitmapCache.get(
463 item.getConversation().getName(
464 useSubject), item
465 .getConversation()
466 .getContact(),
467 getActivity()
468 .getApplicationContext()));
469
470 }
471 viewHolder.indicator = (ImageView) view
472 .findViewById(R.id.security_indicator);
473 viewHolder.image = (ImageView) view
474 .findViewById(R.id.message_image);
475 viewHolder.messageBody = (TextView) view
476 .findViewById(R.id.message_body);
477 viewHolder.time = (TextView) view
478 .findViewById(R.id.message_time);
479 view.setTag(viewHolder);
480 break;
481 case STATUS:
482 view = (View) inflater.inflate(R.layout.message_status,
483 null);
484 viewHolder.contact_picture = (ImageView) view
485 .findViewById(R.id.message_photo);
486 if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
487
488 viewHolder.contact_picture
489 .setImageBitmap(mBitmapCache.get(
490 item.getConversation().getName(
491 useSubject), item
492 .getConversation()
493 .getContact(),
494 getActivity()
495 .getApplicationContext()));
496 viewHolder.contact_picture.setAlpha(128);
497 viewHolder.contact_picture.setOnClickListener(new OnClickListener() {
498
499 @Override
500 public void onClick(View v) {
501 Toast.makeText(getActivity(), R.string.contact_has_read_up_to_this_point, Toast.LENGTH_SHORT).show();
502 }
503 });
504
505 }
506 break;
507 default:
508 viewHolder = null;
509 break;
510 }
511 } else {
512 viewHolder = (ViewHolder) view.getTag();
513 }
514
515 if (type == STATUS) {
516 return view;
517 }
518
519 if (type == RECIEVED) {
520 if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
521 viewHolder.contact_picture.setImageBitmap(mBitmapCache
522 .get(item.getCounterpart(), null, getActivity()
523 .getApplicationContext()));
524 viewHolder.contact_picture
525 .setOnClickListener(new OnClickListener() {
526
527 @Override
528 public void onClick(View v) {
529 highlightInConference(item
530 .getCounterpart());
531 }
532 });
533 }
534 }
535
536 if (item.getType() == Message.TYPE_IMAGE) {
537 if (item.getStatus() == Message.STATUS_RECIEVING) {
538 displayInfoMessage(viewHolder, R.string.receiving_image);
539 } else if (item.getStatus() == Message.STATUS_RECEIVED_OFFER) {
540 viewHolder.image.setVisibility(View.GONE);
541 viewHolder.messageBody.setVisibility(View.GONE);
542 viewHolder.download_button.setVisibility(View.VISIBLE);
543 viewHolder.download_button
544 .setOnClickListener(new OnClickListener() {
545
546 @Override
547 public void onClick(View v) {
548 JingleConnection connection = item
549 .getJingleConnection();
550 if (connection != null) {
551 connection.accept();
552 }
553 }
554 });
555 } else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED)
556 || (item.getEncryption() == Message.ENCRYPTION_NONE)
557 || (item.getEncryption() == Message.ENCRYPTION_OTR)) {
558 displayImageMessage(viewHolder, item);
559 } else if (item.getEncryption() == Message.ENCRYPTION_PGP) {
560 displayInfoMessage(viewHolder,
561 R.string.encrypted_message);
562 } else {
563 displayDecryptionFailed(viewHolder);
564 }
565 } else {
566 if (item.getEncryption() == Message.ENCRYPTION_PGP) {
567 if (activity.hasPgp()) {
568 displayInfoMessage(viewHolder,
569 R.string.encrypted_message);
570 } else {
571 displayInfoMessage(viewHolder,
572 R.string.install_openkeychain);
573 viewHolder.message_box
574 .setOnClickListener(new OnClickListener() {
575
576 @Override
577 public void onClick(View v) {
578 activity.showInstallPgpDialog();
579 }
580 });
581 }
582 } else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
583 displayDecryptionFailed(viewHolder);
584 } else {
585 displayTextMessage(viewHolder, item.getBody());
586 }
587 }
588
589 displayStatus(viewHolder, item);
590
591 return view;
592 }
593 };
594 messagesView.setAdapter(messageListAdapter);
595
596 return view;
597 }
598
599 protected void highlightInConference(String nick) {
600 String oldString = chatMsg.getText().toString().trim();
601 if (oldString.isEmpty()) {
602 chatMsg.setText(nick + ": ");
603 } else {
604 chatMsg.setText(oldString + " " + nick + " ");
605 }
606 int position = chatMsg.length();
607 Editable etext = chatMsg.getText();
608 Selection.setSelection(etext, position);
609 }
610
611 protected Bitmap findSelfPicture() {
612 SharedPreferences sharedPref = PreferenceManager
613 .getDefaultSharedPreferences(getActivity()
614 .getApplicationContext());
615 boolean showPhoneSelfContactPicture = sharedPref.getBoolean(
616 "show_phone_selfcontact_picture", true);
617
618 return UIHelper.getSelfContactPicture(conversation.getAccount(), 48,
619 showPhoneSelfContactPicture, getActivity());
620 }
621
622 @Override
623 public void onStart() {
624 super.onStart();
625 this.activity = (ConversationActivity) getActivity();
626 SharedPreferences preferences = PreferenceManager
627 .getDefaultSharedPreferences(activity);
628 this.useSubject = preferences.getBoolean("use_subject_in_muc", true);
629 if (activity.xmppConnectionServiceBound) {
630 this.onBackendConnected();
631 }
632 }
633
634 @Override
635 public void onStop() {
636 super.onStop();
637 if (this.conversation != null) {
638 this.conversation.setNextMessage(chatMsg.getText().toString());
639 }
640 }
641
642 public void onBackendConnected() {
643 this.conversation = activity.getSelectedConversation();
644 if (this.conversation == null) {
645 return;
646 }
647 String oldString = conversation.getNextMessage().trim();
648 if (this.pastedText == null) {
649 this.chatMsg.setText(oldString);
650 } else {
651
652 if (oldString.isEmpty()) {
653 chatMsg.setText(pastedText);
654 } else {
655 chatMsg.setText(oldString + " " + pastedText);
656 }
657 pastedText = null;
658 }
659 int position = chatMsg.length();
660 Editable etext = chatMsg.getText();
661 Selection.setSelection(etext, position);
662 this.selfBitmap = findSelfPicture();
663 updateMessages();
664 if (activity.getSlidingPaneLayout().isSlideable()) {
665 if (!activity.shouldPaneBeOpen()) {
666 activity.getSlidingPaneLayout().closePane();
667 activity.getActionBar().setDisplayHomeAsUpEnabled(true);
668 activity.getActionBar().setHomeButtonEnabled(true);
669 activity.getActionBar().setTitle(
670 conversation.getName(useSubject));
671 activity.invalidateOptionsMenu();
672 }
673 }
674 }
675
676 private void decryptMessage(Message message) {
677 PgpEngine engine = activity.xmppConnectionService.getPgpEngine();
678 if (engine != null) {
679 engine.decrypt(message, new UiCallback<Message>() {
680
681 @Override
682 public void userInputRequried(PendingIntent pi, Message message) {
683 askForPassphraseIntent = pi.getIntentSender();
684 showSnackbar(R.string.openpgp_messages_found,
685 R.string.decrypt, clickToDecryptListener);
686 }
687
688 @Override
689 public void success(Message message) {
690 activity.xmppConnectionService.databaseBackend
691 .updateMessage(message);
692 updateMessages();
693 }
694
695 @Override
696 public void error(int error, Message message) {
697 message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
698 // updateMessages();
699 }
700 });
701 }
702 }
703
704 public void updateMessages() {
705 if (getView() == null) {
706 return;
707 }
708 hideSnackbar();
709 final ConversationActivity activity = (ConversationActivity) getActivity();
710 if (this.conversation != null) {
711 final Contact contact = this.conversation.getContact();
712 if (!contact.showInRoster() && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
713 showSnackbar(R.string.contact_added_you, R.string.add_back, new OnClickListener() {
714
715 @Override
716 public void onClick(View v) {
717 activity.xmppConnectionService.createContact(contact);
718 activity.switchToContactDetails(contact);
719 }
720 });
721 }
722 for (Message message : this.conversation.getMessages()) {
723 if ((message.getEncryption() == Message.ENCRYPTION_PGP)
724 && ((message.getStatus() == Message.STATUS_RECIEVED) || (message
725 .getStatus() == Message.STATUS_SEND))) {
726 decryptMessage(message);
727 break;
728 }
729 }
730 if (this.conversation.getMessages().size() == 0) {
731 this.messageList.clear();
732 messagesLoaded = false;
733 } else {
734 for (Message message : this.conversation.getMessages()) {
735 if (!this.messageList.contains(message)) {
736 this.messageList.add(message);
737 }
738 }
739 messagesLoaded = true;
740 updateStatusMessages();
741 }
742 this.messageListAdapter.notifyDataSetChanged();
743 if (conversation.getMode() == Conversation.MODE_SINGLE) {
744 if (messageList.size() >= 1) {
745 makeFingerprintWarning(conversation.getLatestEncryption());
746 }
747 } else {
748 if (!conversation.getMucOptions().online()
749 && conversation.getAccount().getStatus() == Account.STATUS_ONLINE) {
750 if (conversation.getMucOptions().getError() == MucOptions.ERROR_NICK_IN_USE) {
751 showSnackbar(R.string.nick_in_use, R.string.edit,
752 clickToMuc);
753 } else if (conversation.getMucOptions().getError() == MucOptions.ERROR_ROOM_NOT_FOUND) {
754 showSnackbar(R.string.conference_not_found,
755 R.string.leave, leaveMuc);
756 }
757 }
758 }
759 getActivity().invalidateOptionsMenu();
760 updateChatMsgHint();
761 if (!activity.shouldPaneBeOpen()) {
762 activity.xmppConnectionService.markRead(conversation);
763 // TODO update notifications
764 UIHelper.updateNotification(getActivity(),
765 activity.getConversationList(), null, false);
766 activity.updateConversationList();
767 }
768 }
769 }
770
771 private void messageSent() {
772 int size = this.messageList.size();
773 if (size >= 1) {
774 messagesView.setSelection(size - 1);
775 }
776 chatMsg.setText("");
777 }
778
779 protected void updateStatusMessages() {
780 boolean addedStatusMsg = false;
781 if (conversation.getMode() == Conversation.MODE_SINGLE) {
782 for (int i = this.messageList.size() - 1; i >= 0; --i) {
783 if (addedStatusMsg) {
784 if (this.messageList.get(i).getType() == Message.TYPE_STATUS) {
785 this.messageList.remove(i);
786 --i;
787 }
788 } else {
789 if (this.messageList.get(i).getStatus() == Message.STATUS_RECIEVED) {
790 addedStatusMsg = true;
791 } else {
792 if (this.messageList.get(i).getStatus() == Message.STATUS_SEND_DISPLAYED) {
793 this.messageList.add(i + 1,
794 Message.createStatusMessage(conversation));
795 addedStatusMsg = true;
796 }
797 }
798 }
799 }
800 }
801 }
802
803 protected void makeFingerprintWarning(int latestEncryption) {
804 Set<String> knownFingerprints = conversation.getContact()
805 .getOtrFingerprints();
806 if ((latestEncryption == Message.ENCRYPTION_OTR)
807 && (conversation.hasValidOtrSession()
808 && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints
809 .contains(conversation.getOtrFingerprint())))) {
810 showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify,
811 new OnClickListener() {
812
813 @Override
814 public void onClick(View v) {
815 if (conversation.getOtrFingerprint() != null) {
816 AlertDialog dialog = UIHelper
817 .getVerifyFingerprintDialog(
818 (ConversationActivity) getActivity(),
819 conversation, snackbar);
820 dialog.show();
821 }
822 }
823 });
824 }
825 }
826
827 protected void showSnackbar(int message, int action,
828 OnClickListener clickListener) {
829 snackbar.setVisibility(View.VISIBLE);
830 snackbarMessage.setText(message);
831 snackbarAction.setText(action);
832 snackbarAction.setOnClickListener(clickListener);
833 }
834
835 protected void hideSnackbar() {
836 snackbar.setVisibility(View.GONE);
837 }
838
839 protected void sendPlainTextMessage(Message message) {
840 ConversationActivity activity = (ConversationActivity) getActivity();
841 activity.xmppConnectionService.sendMessage(message);
842 messageSent();
843 }
844
845 protected void sendPgpMessage(final Message message) {
846 final ConversationActivity activity = (ConversationActivity) getActivity();
847 final XmppConnectionService xmppService = activity.xmppConnectionService;
848 final Contact contact = message.getConversation().getContact();
849 if (activity.hasPgp()) {
850 if (conversation.getMode() == Conversation.MODE_SINGLE) {
851 if (contact.getPgpKeyId() != 0) {
852 xmppService.getPgpEngine().hasKey(contact,
853 new UiCallback<Contact>() {
854
855 @Override
856 public void userInputRequried(PendingIntent pi,
857 Contact contact) {
858 activity.runIntent(
859 pi,
860 ConversationActivity.REQUEST_ENCRYPT_MESSAGE);
861 }
862
863 @Override
864 public void success(Contact contact) {
865 messageSent();
866 activity.encryptTextMessage(message);
867 }
868
869 @Override
870 public void error(int error, Contact contact) {
871
872 }
873 });
874
875 } else {
876 showNoPGPKeyDialog(false,
877 new DialogInterface.OnClickListener() {
878
879 @Override
880 public void onClick(DialogInterface dialog,
881 int which) {
882 conversation
883 .setNextEncryption(Message.ENCRYPTION_NONE);
884 message.setEncryption(Message.ENCRYPTION_NONE);
885 xmppService.sendMessage(message);
886 messageSent();
887 }
888 });
889 }
890 } else {
891 if (conversation.getMucOptions().pgpKeysInUse()) {
892 if (!conversation.getMucOptions().everybodyHasKeys()) {
893 Toast warning = Toast
894 .makeText(getActivity(),
895 R.string.missing_public_keys,
896 Toast.LENGTH_LONG);
897 warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
898 warning.show();
899 }
900 activity.encryptTextMessage(message);
901 messageSent();
902 } else {
903 showNoPGPKeyDialog(true,
904 new DialogInterface.OnClickListener() {
905
906 @Override
907 public void onClick(DialogInterface dialog,
908 int which) {
909 conversation
910 .setNextEncryption(Message.ENCRYPTION_NONE);
911 message.setEncryption(Message.ENCRYPTION_NONE);
912 xmppService.sendMessage(message);
913 messageSent();
914 }
915 });
916 }
917 }
918 } else {
919 activity.showInstallPgpDialog();
920 }
921 }
922
923 public void showNoPGPKeyDialog(boolean plural,
924 DialogInterface.OnClickListener listener) {
925 AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
926 builder.setIconAttribute(android.R.attr.alertDialogIcon);
927 if (plural) {
928 builder.setTitle(getString(R.string.no_pgp_keys));
929 builder.setMessage(getText(R.string.contacts_have_no_pgp_keys));
930 } else {
931 builder.setTitle(getString(R.string.no_pgp_key));
932 builder.setMessage(getText(R.string.contact_has_no_pgp_key));
933 }
934 builder.setNegativeButton(getString(R.string.cancel), null);
935 builder.setPositiveButton(getString(R.string.send_unencrypted),
936 listener);
937 builder.create().show();
938 }
939
940 protected void sendOtrMessage(final Message message) {
941 final ConversationActivity activity = (ConversationActivity) getActivity();
942 final XmppConnectionService xmppService = activity.xmppConnectionService;
943 if (conversation.hasValidOtrSession()) {
944 activity.xmppConnectionService.sendMessage(message);
945 messageSent();
946 } else {
947 activity.selectPresence(message.getConversation(),
948 new OnPresenceSelected() {
949
950 @Override
951 public void onPresenceSelected() {
952 message.setPresence(conversation.getNextPresence());
953 xmppService.sendMessage(message);
954 messageSent();
955 }
956 });
957 }
958 }
959
960 private static class ViewHolder {
961
962 protected LinearLayout message_box;
963 protected Button download_button;
964 protected ImageView image;
965 protected ImageView indicator;
966 protected TextView time;
967 protected TextView messageBody;
968 protected ImageView contact_picture;
969
970 }
971
972 private class BitmapCache {
973 private HashMap<String, Bitmap> bitmaps = new HashMap<String, Bitmap>();
974
975 public Bitmap get(String name, Contact contact, Context context) {
976 if (bitmaps.containsKey(name)) {
977 return bitmaps.get(name);
978 } else {
979 Bitmap bm;
980 if (contact != null) {
981 bm = UIHelper
982 .getContactPicture(contact, 48, context, false);
983 } else {
984 bm = UIHelper.getContactPicture(name, 48, context, false);
985 }
986 bitmaps.put(name, bm);
987 return bm;
988 }
989 }
990 }
991
992 public void setText(String text) {
993 this.pastedText = text;
994 }
995
996 public void clearInputField() {
997 this.chatMsg.setText("");
998 }
999}