1package eu.siacs.conversations.ui;
2
3import java.util.ArrayList;
4import java.util.Hashtable;
5import java.util.List;
6
7import eu.siacs.conversations.R;
8import eu.siacs.conversations.entities.Account;
9import eu.siacs.conversations.entities.Contact;
10import eu.siacs.conversations.entities.Conversation;
11import eu.siacs.conversations.entities.Message;
12import eu.siacs.conversations.utils.ExceptionHelper;
13import eu.siacs.conversations.utils.UIHelper;
14import android.os.Bundle;
15import android.preference.PreferenceManager;
16import android.app.AlertDialog;
17import android.app.AlertDialog.Builder;
18import android.app.FragmentTransaction;
19import android.content.Context;
20import android.content.DialogInterface;
21import android.content.DialogInterface.OnClickListener;
22import android.content.Intent;
23import android.content.SharedPreferences;
24import android.graphics.Color;
25import android.graphics.Typeface;
26import android.support.v4.widget.SlidingPaneLayout;
27import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener;
28import android.view.KeyEvent;
29import android.view.LayoutInflater;
30import android.view.Menu;
31import android.view.MenuItem;
32import android.view.View;
33import android.view.ViewGroup;
34import android.widget.AdapterView;
35import android.widget.AdapterView.OnItemClickListener;
36import android.widget.ArrayAdapter;
37import android.widget.CheckBox;
38import android.widget.ListView;
39import android.widget.PopupMenu;
40import android.widget.PopupMenu.OnMenuItemClickListener;
41import android.widget.TextView;
42import android.widget.ImageView;
43
44public class ConversationActivity extends XmppActivity {
45
46 public static final String VIEW_CONVERSATION = "viewConversation";
47 public static final String CONVERSATION = "conversationUuid";
48 public static final String TEXT = "text";
49 public static final String PRESENCE = "eu.siacs.conversations.presence";
50
51 public static final int REQUEST_SEND_MESSAGE = 0x75441;
52 public static final int REQUEST_DECRYPT_PGP = 0x76783;
53 private static final int ATTACH_FILE = 0x48502;
54
55 protected SlidingPaneLayout spl;
56
57 private List<Conversation> conversationList = new ArrayList<Conversation>();
58 private Conversation selectedConversation = null;
59 private ListView listView;
60
61 private boolean paneShouldBeOpen = true;
62 private boolean useSubject = true;
63 private ArrayAdapter<Conversation> listAdapter;
64
65 private OnConversationListChangedListener onConvChanged = new OnConversationListChangedListener() {
66
67 @Override
68 public void onConversationListChanged() {
69 runOnUiThread(new Runnable() {
70
71 @Override
72 public void run() {
73 updateConversationList();
74 if (paneShouldBeOpen) {
75 if (conversationList.size() >= 1) {
76 swapConversationFragment();
77 } else {
78 startActivity(new Intent(getApplicationContext(),
79 ContactsActivity.class));
80 finish();
81 }
82 }
83 ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
84 .findFragmentByTag("conversation");
85 if (selectedFragment != null) {
86 selectedFragment.updateMessages();
87 }
88 }
89 });
90 }
91 };
92
93 protected ConversationActivity activity = this;
94
95 public List<Conversation> getConversationList() {
96 return this.conversationList;
97 }
98
99 public Conversation getSelectedConversation() {
100 return this.selectedConversation;
101 }
102
103 public ListView getConversationListView() {
104 return this.listView;
105 }
106
107 public SlidingPaneLayout getSlidingPaneLayout() {
108 return this.spl;
109 }
110
111 public boolean shouldPaneBeOpen() {
112 return paneShouldBeOpen;
113 }
114
115 @Override
116 protected void onCreate(Bundle savedInstanceState) {
117
118 super.onCreate(savedInstanceState);
119
120 setContentView(R.layout.fragment_conversations_overview);
121
122 listView = (ListView) findViewById(R.id.list);
123
124 this.listAdapter = new ArrayAdapter<Conversation>(this,
125 R.layout.conversation_list_row, conversationList) {
126 @Override
127 public View getView(int position, View view, ViewGroup parent) {
128 if (view == null) {
129 LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
130 view = (View) inflater.inflate(
131 R.layout.conversation_list_row, null);
132 }
133 Conversation conv;
134 if (conversationList.size() > position) {
135 conv = getItem(position);
136 } else {
137 return view;
138 }
139 if (!spl.isSlideable()) {
140 if (conv == getSelectedConversation()) {
141 view.setBackgroundColor(0xffdddddd);
142 } else {
143 view.setBackgroundColor(Color.TRANSPARENT);
144 }
145 } else {
146 view.setBackgroundColor(Color.TRANSPARENT);
147 }
148 TextView convName = (TextView) view
149 .findViewById(R.id.conversation_name);
150 convName.setText(conv.getName(useSubject));
151 TextView convLastMsg = (TextView) view
152 .findViewById(R.id.conversation_lastmsg);
153 convLastMsg.setText(conv.getLatestMessage().getBody());
154
155 if (!conv.isRead()) {
156 convName.setTypeface(null, Typeface.BOLD);
157 convLastMsg.setTypeface(null, Typeface.BOLD);
158 } else {
159 convName.setTypeface(null, Typeface.NORMAL);
160 convLastMsg.setTypeface(null, Typeface.NORMAL);
161 }
162
163 ((TextView) view.findViewById(R.id.conversation_lastupdate))
164 .setText(UIHelper.readableTimeDifference(conv
165 .getLatestMessage().getTimeSent()));
166
167 ImageView imageView = (ImageView) view
168 .findViewById(R.id.conversation_image);
169 imageView.setImageBitmap(UIHelper.getContactPicture(
170 conv, 56, activity.getApplicationContext(), false));
171 return view;
172 }
173
174 };
175
176 listView.setAdapter(this.listAdapter);
177
178 listView.setOnItemClickListener(new OnItemClickListener() {
179
180 @Override
181 public void onItemClick(AdapterView<?> arg0, View clickedView,
182 int position, long arg3) {
183 paneShouldBeOpen = false;
184 if (selectedConversation != conversationList.get(position)) {
185 selectedConversation = conversationList.get(position);
186 swapConversationFragment(); // .onBackendConnected(conversationList.get(position));
187 } else {
188 spl.closePane();
189 }
190 }
191 });
192 spl = (SlidingPaneLayout) findViewById(R.id.slidingpanelayout);
193 spl.setParallaxDistance(150);
194 spl.setShadowResource(R.drawable.es_slidingpane_shadow);
195 spl.setSliderFadeColor(0);
196 spl.setPanelSlideListener(new PanelSlideListener() {
197
198 @Override
199 public void onPanelOpened(View arg0) {
200 paneShouldBeOpen = true;
201 getActionBar().setDisplayHomeAsUpEnabled(false);
202 getActionBar().setTitle(R.string.app_name);
203 invalidateOptionsMenu();
204 hideKeyboard();
205 }
206
207 @Override
208 public void onPanelClosed(View arg0) {
209 paneShouldBeOpen = false;
210 if ((conversationList.size() > 0)
211 && (getSelectedConversation() != null)) {
212 getActionBar().setDisplayHomeAsUpEnabled(true);
213 getActionBar().setTitle(
214 getSelectedConversation().getName(useSubject));
215 invalidateOptionsMenu();
216 if (!getSelectedConversation().isRead()) {
217 getSelectedConversation().markRead();
218 UIHelper.updateNotification(getApplicationContext(),
219 getConversationList(), null, false);
220 listView.invalidateViews();
221 }
222 }
223 }
224
225 @Override
226 public void onPanelSlide(View arg0, float arg1) {
227 // TODO Auto-generated method stub
228
229 }
230 });
231 }
232
233 @Override
234 public boolean onCreateOptionsMenu(Menu menu) {
235 getMenuInflater().inflate(R.menu.conversations, menu);
236 MenuItem menuSecure = (MenuItem) menu.findItem(R.id.action_security);
237 MenuItem menuArchive = (MenuItem) menu.findItem(R.id.action_archive);
238 MenuItem menuMucDetails = (MenuItem) menu
239 .findItem(R.id.action_muc_details);
240 MenuItem menuContactDetails = (MenuItem) menu
241 .findItem(R.id.action_contact_details);
242 MenuItem menuInviteContacts = (MenuItem) menu
243 .findItem(R.id.action_invite);
244 MenuItem menuAttach = (MenuItem) menu.findItem(R.id.action_attach_file);
245 MenuItem menuClearHistory = (MenuItem) menu.findItem(R.id.action_clear_history);
246
247 if ((spl.isOpen() && (spl.isSlideable()))) {
248 menuArchive.setVisible(false);
249 menuMucDetails.setVisible(false);
250 menuContactDetails.setVisible(false);
251 menuSecure.setVisible(false);
252 menuInviteContacts.setVisible(false);
253 menuAttach.setVisible(false);
254 menuClearHistory.setVisible(false);
255 } else {
256 ((MenuItem) menu.findItem(R.id.action_add)).setVisible(!spl
257 .isSlideable());
258 if (this.getSelectedConversation() != null) {
259 if (this.getSelectedConversation().getMode() == Conversation.MODE_MULTI) {
260 menuContactDetails.setVisible(false);
261 menuSecure.setVisible(false);
262 menuAttach.setVisible(false);
263 } else {
264 menuMucDetails.setVisible(false);
265 menuInviteContacts.setVisible(false);
266 if (this.getSelectedConversation().getLatestMessage()
267 .getEncryption() != Message.ENCRYPTION_NONE) {
268 menuSecure.setIcon(R.drawable.ic_action_secure);
269 }
270 }
271 }
272 }
273 return true;
274 }
275
276 @Override
277 public boolean onOptionsItemSelected(MenuItem item) {
278 switch (item.getItemId()) {
279 case android.R.id.home:
280 spl.openPane();
281 break;
282 case R.id.action_attach_file:
283 selectPresence(getSelectedConversation(), new OnPresenceSelected() {
284
285 @Override
286 public void onPresenceSelected(boolean success, String presence) {
287 if (success) {
288 Intent attachFileIntent = new Intent();
289 attachFileIntent.setType("image/*");
290 attachFileIntent.setAction(Intent.ACTION_GET_CONTENT);
291 Intent chooser = Intent.createChooser(attachFileIntent, getString(R.string.attach_file));
292 startActivityForResult(chooser, ATTACH_FILE);
293 }
294 }
295 });
296 break;
297 case R.id.action_add:
298 startActivity(new Intent(this, ContactsActivity.class));
299 break;
300 case R.id.action_archive:
301 this.endConversation(getSelectedConversation());
302 break;
303 case R.id.action_contact_details:
304 Contact contact = this.getSelectedConversation().getContact();
305 if (contact != null) {
306 Intent intent = new Intent(this, ContactDetailsActivity.class);
307 intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
308 intent.putExtra("uuid", contact.getUuid());
309 startActivity(intent);
310 } else {
311 showAddToRosterDialog(getSelectedConversation());
312 }
313 break;
314 case R.id.action_muc_details:
315 Intent intent = new Intent(this, MucDetailsActivity.class);
316 intent.setAction(MucDetailsActivity.ACTION_VIEW_MUC);
317 intent.putExtra("uuid", getSelectedConversation().getUuid());
318 startActivity(intent);
319 break;
320 case R.id.action_invite:
321 Intent inviteIntent = new Intent(getApplicationContext(),
322 ContactsActivity.class);
323 inviteIntent.setAction("invite");
324 inviteIntent.putExtra("uuid", selectedConversation.getUuid());
325 startActivity(inviteIntent);
326 break;
327 case R.id.action_security:
328 final Conversation selConv = getSelectedConversation();
329 View menuItemView = findViewById(R.id.action_security);
330 PopupMenu popup = new PopupMenu(this, menuItemView);
331 final ConversationFragment fragment = (ConversationFragment) getFragmentManager()
332 .findFragmentByTag("conversation");
333 if (fragment != null) {
334 popup.setOnMenuItemClickListener(new OnMenuItemClickListener() {
335
336 @Override
337 public boolean onMenuItemClick(MenuItem item) {
338 switch (item.getItemId()) {
339 case R.id.encryption_choice_none:
340 selConv.nextMessageEncryption = Message.ENCRYPTION_NONE;
341 item.setChecked(true);
342 break;
343 case R.id.encryption_choice_otr:
344 selConv.nextMessageEncryption = Message.ENCRYPTION_OTR;
345 item.setChecked(true);
346 break;
347 case R.id.encryption_choice_pgp:
348 selConv.nextMessageEncryption = Message.ENCRYPTION_PGP;
349 item.setChecked(true);
350 break;
351 default:
352 selConv.nextMessageEncryption = Message.ENCRYPTION_NONE;
353 break;
354 }
355 fragment.updateChatMsgHint();
356 return true;
357 }
358 });
359 popup.inflate(R.menu.encryption_choices);
360 switch (selConv.nextMessageEncryption) {
361 case Message.ENCRYPTION_NONE:
362 popup.getMenu().findItem(R.id.encryption_choice_none)
363 .setChecked(true);
364 break;
365 case Message.ENCRYPTION_OTR:
366 popup.getMenu().findItem(R.id.encryption_choice_otr)
367 .setChecked(true);
368 break;
369 case Message.ENCRYPTION_PGP:
370 popup.getMenu().findItem(R.id.encryption_choice_pgp)
371 .setChecked(true);
372 break;
373 case Message.ENCRYPTION_DECRYPTED:
374 popup.getMenu().findItem(R.id.encryption_choice_pgp)
375 .setChecked(true);
376 break;
377 default:
378 popup.getMenu().findItem(R.id.encryption_choice_none)
379 .setChecked(true);
380 break;
381 }
382 popup.show();
383 }
384
385 break;
386 case R.id.action_clear_history:
387 clearHistoryDialog(getSelectedConversation());
388 break;
389 default:
390 break;
391 }
392 return super.onOptionsItemSelected(item);
393 }
394
395 private void endConversation(Conversation conversation) {
396 conversation.setStatus(Conversation.STATUS_ARCHIVED);
397 paneShouldBeOpen = true;
398 spl.openPane();
399 xmppConnectionService.archiveConversation(conversation);
400 if (conversationList.size() > 0) {
401 selectedConversation = conversationList.get(0);
402 } else {
403 selectedConversation = null;
404 }
405 }
406
407 protected void clearHistoryDialog(final Conversation conversation) {
408 AlertDialog.Builder builder = new AlertDialog.Builder(this);
409 builder.setTitle(getString(R.string.clear_conversation_history));
410 View dialogView = getLayoutInflater().inflate(R.layout.dialog_clear_history, null);
411 final CheckBox endConversationCheckBox = (CheckBox) dialogView.findViewById(R.id.end_conversation_checkbox);
412 builder.setView(dialogView);
413 builder.setNegativeButton(getString(R.string.cancel), null);
414 builder.setPositiveButton(getString(R.string.delete_messages), new OnClickListener() {
415
416 @Override
417 public void onClick(DialogInterface dialog, int which) {
418 activity.xmppConnectionService.clearConversationHistory(conversation);
419 if (endConversationCheckBox.isChecked()) {
420 endConversation(conversation);
421 }
422 }
423 });
424 builder.create().show();
425 }
426
427 protected ConversationFragment swapConversationFragment() {
428 ConversationFragment selectedFragment = new ConversationFragment();
429
430 FragmentTransaction transaction = getFragmentManager()
431 .beginTransaction();
432 transaction.replace(R.id.selected_conversation, selectedFragment,
433 "conversation");
434 transaction.commit();
435 return selectedFragment;
436 }
437
438 @Override
439 public boolean onKeyDown(int keyCode, KeyEvent event) {
440 if (keyCode == KeyEvent.KEYCODE_BACK) {
441 if (!spl.isOpen()) {
442 spl.openPane();
443 return false;
444 }
445 }
446 return super.onKeyDown(keyCode, event);
447 }
448
449 @Override
450 public void onStart() {
451 super.onStart();
452 SharedPreferences preferences = PreferenceManager
453 .getDefaultSharedPreferences(this);
454 this.useSubject = preferences.getBoolean("use_subject_in_muc", true);
455 if (this.xmppConnectionServiceBound) {
456 this.onBackendConnected();
457 }
458 if (conversationList.size() >= 1) {
459 onConvChanged.onConversationListChanged();
460 }
461 }
462
463 @Override
464 protected void onStop() {
465 if (xmppConnectionServiceBound) {
466 xmppConnectionService.removeOnConversationListChangedListener();
467 }
468 super.onStop();
469 }
470
471 @Override
472 void onBackendConnected() {
473 this.registerListener();
474 if (conversationList.size() == 0) {
475 updateConversationList();
476 }
477
478 if ((getIntent().getAction() != null)
479 && (getIntent().getAction().equals(Intent.ACTION_VIEW) && (!handledViewIntent))) {
480 if (getIntent().getType().equals(
481 ConversationActivity.VIEW_CONVERSATION)) {
482 handledViewIntent = true;
483
484 String convToView = (String) getIntent().getExtras().get(
485 CONVERSATION);
486
487 for (int i = 0; i < conversationList.size(); ++i) {
488 if (conversationList.get(i).getUuid().equals(convToView)) {
489 selectedConversation = conversationList.get(i);
490 }
491 }
492 paneShouldBeOpen = false;
493 String text = getIntent().getExtras().getString(TEXT, null);
494 swapConversationFragment().setText(text);
495 }
496 } else {
497 if (xmppConnectionService.getAccounts().size() == 0) {
498 startActivity(new Intent(this, ManageAccountActivity.class));
499 finish();
500 } else if (conversationList.size() <= 0) {
501 // add no history
502 startActivity(new Intent(this, ContactsActivity.class));
503 finish();
504 } else {
505 spl.openPane();
506 // find currently loaded fragment
507 ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
508 .findFragmentByTag("conversation");
509 if (selectedFragment != null) {
510 selectedFragment.onBackendConnected();
511 } else {
512 selectedConversation = conversationList.get(0);
513 swapConversationFragment();
514 }
515 ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
516 }
517 }
518 }
519
520 public void registerListener() {
521 if (xmppConnectionServiceBound) {
522 xmppConnectionService
523 .setOnConversationListChangedListener(this.onConvChanged);
524 }
525 }
526
527 @Override
528 protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
529 super.onActivityResult(requestCode, resultCode, data);
530 if (resultCode == RESULT_OK) {
531 if (requestCode == REQUEST_DECRYPT_PGP) {
532 ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
533 .findFragmentByTag("conversation");
534 if (selectedFragment != null) {
535 selectedFragment.hidePgpPassphraseBox();
536 }
537 } else if (requestCode == ATTACH_FILE) {
538 Conversation conversation = getSelectedConversation();
539 String presence = conversation.getNextPresence();
540 xmppConnectionService.attachImageToConversation(conversation, presence, data.getData());
541
542 }
543 }
544 }
545
546 public void updateConversationList() {
547 conversationList.clear();
548 conversationList.addAll(xmppConnectionService.getConversations());
549 listView.invalidateViews();
550 }
551
552 public void selectPresence(final Conversation conversation, final OnPresenceSelected listener) {
553 Contact contact = conversation.getContact();
554 if (contact==null) {
555 showAddToRosterDialog(conversation);
556 listener.onPresenceSelected(false,null);
557 } else {
558 Hashtable<String, Integer> presences = contact.getPresences();
559 if (presences.size() == 0) {
560 listener.onPresenceSelected(false, null);
561 } else if (presences.size() == 1) {
562 String presence = (String) presences.keySet().toArray()[0];
563 conversation.setNextPresence(presence);
564 listener.onPresenceSelected(true, presence);
565 } else {
566 AlertDialog.Builder builder = new AlertDialog.Builder(this);
567 builder.setTitle(getString(R.string.choose_presence));
568 final String[] presencesArray = new String[presences.size()];
569 presences.keySet().toArray(presencesArray);
570 builder.setItems(presencesArray,
571 new DialogInterface.OnClickListener() {
572
573 @Override
574 public void onClick(DialogInterface dialog,
575 int which) {
576 String presence = presencesArray[which];
577 conversation.setNextPresence(presence);
578 listener.onPresenceSelected(true,presence);
579 }
580 });
581 builder.create().show();
582 }
583 }
584 }
585
586 private void showAddToRosterDialog(final Conversation conversation) {
587 String jid = conversation.getContactJid();
588 AlertDialog.Builder builder = new AlertDialog.Builder(this);
589 builder.setTitle(jid);
590 builder.setMessage(getString(R.string.not_in_roster));
591 builder.setNegativeButton(getString(R.string.cancel), null);
592 builder.setPositiveButton(getString(R.string.add_contact), new DialogInterface.OnClickListener() {
593
594 @Override
595 public void onClick(DialogInterface dialog, int which) {
596 String jid = conversation.getContactJid();
597 Account account = getSelectedConversation().getAccount();
598 String name = jid.split("@")[0];
599 Contact contact = new Contact(account, name, jid, null);
600 xmppConnectionService.createContact(contact);
601 }
602 });
603 builder.create().show();
604 }
605}