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