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