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