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.FragmentTransaction;
18import android.content.Context;
19import android.content.DialogInterface;
20import android.content.Intent;
21import android.content.SharedPreferences;
22import android.graphics.Color;
23import android.graphics.Typeface;
24import android.support.v4.widget.SlidingPaneLayout;
25import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener;
26import android.view.KeyEvent;
27import android.view.LayoutInflater;
28import android.view.Menu;
29import android.view.MenuItem;
30import android.view.View;
31import android.view.ViewGroup;
32import android.widget.AdapterView;
33import android.widget.AdapterView.OnItemClickListener;
34import android.widget.ArrayAdapter;
35import android.widget.ListView;
36import android.widget.PopupMenu;
37import android.widget.PopupMenu.OnMenuItemClickListener;
38import android.widget.TextView;
39import android.widget.ImageView;
40
41public class ConversationActivity extends XmppActivity {
42
43 public static final String VIEW_CONVERSATION = "viewConversation";
44 public static final String CONVERSATION = "conversationUuid";
45 public static final String TEXT = "text";
46 public static final String PRESENCE = "eu.siacs.conversations.presence";
47
48 public static final int REQUEST_SEND_MESSAGE = 0x75441;
49 public static final int REQUEST_DECRYPT_PGP = 0x76783;
50 private static final int ATTACH_FILE = 0x48502;
51
52 protected SlidingPaneLayout spl;
53
54 private List<Conversation> conversationList = new ArrayList<Conversation>();
55 private Conversation selectedConversation = null;
56 private ListView listView;
57
58 private boolean paneShouldBeOpen = true;
59 private boolean useSubject = true;
60 private ArrayAdapter<Conversation> listAdapter;
61
62 private OnConversationListChangedListener onConvChanged = new OnConversationListChangedListener() {
63
64 @Override
65 public void onConversationListChanged() {
66 runOnUiThread(new Runnable() {
67
68 @Override
69 public void run() {
70 updateConversationList();
71 if (paneShouldBeOpen) {
72 if (conversationList.size() >= 1) {
73 swapConversationFragment();
74 } else {
75 startActivity(new Intent(getApplicationContext(),
76 ContactsActivity.class));
77 finish();
78 }
79 }
80 ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
81 .findFragmentByTag("conversation");
82 if (selectedFragment != null) {
83 selectedFragment.updateMessages();
84 }
85 }
86 });
87 }
88 };
89
90 protected ConversationActivity activity = this;
91
92 public List<Conversation> getConversationList() {
93 return this.conversationList;
94 }
95
96 public Conversation getSelectedConversation() {
97 return this.selectedConversation;
98 }
99
100 public ListView getConversationListView() {
101 return this.listView;
102 }
103
104 public SlidingPaneLayout getSlidingPaneLayout() {
105 return this.spl;
106 }
107
108 public boolean shouldPaneBeOpen() {
109 return paneShouldBeOpen;
110 }
111
112 @Override
113 protected void onCreate(Bundle savedInstanceState) {
114
115 super.onCreate(savedInstanceState);
116
117 setContentView(R.layout.fragment_conversations_overview);
118
119 listView = (ListView) findViewById(R.id.list);
120
121 this.listAdapter = new ArrayAdapter<Conversation>(this,
122 R.layout.conversation_list_row, conversationList) {
123 @Override
124 public View getView(int position, View view, ViewGroup parent) {
125 if (view == null) {
126 LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
127 view = (View) inflater.inflate(
128 R.layout.conversation_list_row, null);
129 }
130 Conversation conv;
131 if (conversationList.size() > position) {
132 conv = getItem(position);
133 } else {
134 return view;
135 }
136 if (!spl.isSlideable()) {
137 if (conv == getSelectedConversation()) {
138 view.setBackgroundColor(0xffdddddd);
139 } else {
140 view.setBackgroundColor(Color.TRANSPARENT);
141 }
142 } else {
143 view.setBackgroundColor(Color.TRANSPARENT);
144 }
145 TextView convName = (TextView) view
146 .findViewById(R.id.conversation_name);
147 convName.setText(conv.getName(useSubject));
148 TextView convLastMsg = (TextView) view
149 .findViewById(R.id.conversation_lastmsg);
150 convLastMsg.setText(conv.getLatestMessage().getBody());
151
152 if (!conv.isRead()) {
153 convName.setTypeface(null, Typeface.BOLD);
154 convLastMsg.setTypeface(null, Typeface.BOLD);
155 } else {
156 convName.setTypeface(null, Typeface.NORMAL);
157 convLastMsg.setTypeface(null, Typeface.NORMAL);
158 }
159
160 ((TextView) view.findViewById(R.id.conversation_lastupdate))
161 .setText(UIHelper.readableTimeDifference(conv
162 .getLatestMessage().getTimeSent()));
163
164 ImageView imageView = (ImageView) view
165 .findViewById(R.id.conversation_image);
166 imageView.setImageBitmap(UIHelper.getContactPicture(
167 conv, 56, activity.getApplicationContext(), false));
168 return view;
169 }
170
171 };
172
173 listView.setAdapter(this.listAdapter);
174
175 listView.setOnItemClickListener(new OnItemClickListener() {
176
177 @Override
178 public void onItemClick(AdapterView<?> arg0, View clickedView,
179 int position, long arg3) {
180 paneShouldBeOpen = false;
181 if (selectedConversation != conversationList.get(position)) {
182 selectedConversation = conversationList.get(position);
183 swapConversationFragment(); // .onBackendConnected(conversationList.get(position));
184 } else {
185 spl.closePane();
186 }
187 }
188 });
189 spl = (SlidingPaneLayout) findViewById(R.id.slidingpanelayout);
190 spl.setParallaxDistance(150);
191 spl.setShadowResource(R.drawable.es_slidingpane_shadow);
192 spl.setSliderFadeColor(0);
193 spl.setPanelSlideListener(new PanelSlideListener() {
194
195 @Override
196 public void onPanelOpened(View arg0) {
197 paneShouldBeOpen = true;
198 getActionBar().setDisplayHomeAsUpEnabled(false);
199 getActionBar().setTitle(R.string.app_name);
200 invalidateOptionsMenu();
201 hideKeyboard();
202 }
203
204 @Override
205 public void onPanelClosed(View arg0) {
206 paneShouldBeOpen = false;
207 if ((conversationList.size() > 0)
208 && (getSelectedConversation() != null)) {
209 getActionBar().setDisplayHomeAsUpEnabled(true);
210 getActionBar().setTitle(
211 getSelectedConversation().getName(useSubject));
212 invalidateOptionsMenu();
213 if (!getSelectedConversation().isRead()) {
214 getSelectedConversation().markRead();
215 UIHelper.updateNotification(getApplicationContext(),
216 getConversationList(), null, false);
217 listView.invalidateViews();
218 }
219 }
220 }
221
222 @Override
223 public void onPanelSlide(View arg0, float arg1) {
224 // TODO Auto-generated method stub
225
226 }
227 });
228 }
229
230 @Override
231 public boolean onCreateOptionsMenu(Menu menu) {
232 getMenuInflater().inflate(R.menu.conversations, menu);
233 MenuItem menuSecure = (MenuItem) menu.findItem(R.id.action_security);
234 MenuItem menuArchive = (MenuItem) menu.findItem(R.id.action_archive);
235 MenuItem menuMucDetails = (MenuItem) menu
236 .findItem(R.id.action_muc_details);
237 MenuItem menuContactDetails = (MenuItem) menu
238 .findItem(R.id.action_contact_details);
239 MenuItem menuInviteContacts = (MenuItem) menu
240 .findItem(R.id.action_invite);
241 MenuItem menuAttach = (MenuItem) menu.findItem(R.id.action_attach_file);
242
243 if ((spl.isOpen() && (spl.isSlideable()))) {
244 menuArchive.setVisible(false);
245 menuMucDetails.setVisible(false);
246 menuContactDetails.setVisible(false);
247 menuSecure.setVisible(false);
248 menuInviteContacts.setVisible(false);
249 menuAttach.setVisible(false);
250 } else {
251 ((MenuItem) menu.findItem(R.id.action_add)).setVisible(!spl
252 .isSlideable());
253 if (this.getSelectedConversation() != null) {
254 if (this.getSelectedConversation().getMode() == Conversation.MODE_MULTI) {
255 menuMucDetails.setVisible(true);
256 menuContactDetails.setVisible(false);
257 menuSecure.setVisible(false);
258 menuInviteContacts.setVisible(true);
259 menuAttach.setVisible(false);
260 } else {
261 menuContactDetails.setVisible(true);
262 menuMucDetails.setVisible(false);
263 menuInviteContacts.setVisible(false);
264 menuAttach.setVisible(true);
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 default:
395 break;
396 }
397 return super.onOptionsItemSelected(item);
398 }
399
400 protected ConversationFragment swapConversationFragment() {
401 ConversationFragment selectedFragment = new ConversationFragment();
402
403 FragmentTransaction transaction = getFragmentManager()
404 .beginTransaction();
405 transaction.replace(R.id.selected_conversation, selectedFragment,
406 "conversation");
407 transaction.commit();
408 return selectedFragment;
409 }
410
411 @Override
412 public boolean onKeyDown(int keyCode, KeyEvent event) {
413 if (keyCode == KeyEvent.KEYCODE_BACK) {
414 if (!spl.isOpen()) {
415 spl.openPane();
416 return false;
417 }
418 }
419 return super.onKeyDown(keyCode, event);
420 }
421
422 @Override
423 public void onStart() {
424 super.onStart();
425 SharedPreferences preferences = PreferenceManager
426 .getDefaultSharedPreferences(this);
427 this.useSubject = preferences.getBoolean("use_subject_in_muc", true);
428 if (this.xmppConnectionServiceBound) {
429 this.onBackendConnected();
430 }
431 if (conversationList.size() >= 1) {
432 onConvChanged.onConversationListChanged();
433 }
434 }
435
436 @Override
437 protected void onStop() {
438 if (xmppConnectionServiceBound) {
439 xmppConnectionService.removeOnConversationListChangedListener();
440 }
441 super.onStop();
442 }
443
444 @Override
445 void onBackendConnected() {
446 this.registerListener();
447 if (conversationList.size() == 0) {
448 updateConversationList();
449 }
450
451 if ((getIntent().getAction() != null)
452 && (getIntent().getAction().equals(Intent.ACTION_VIEW) && (!handledViewIntent))) {
453 if (getIntent().getType().equals(
454 ConversationActivity.VIEW_CONVERSATION)) {
455 handledViewIntent = true;
456
457 String convToView = (String) getIntent().getExtras().get(
458 CONVERSATION);
459
460 for (int i = 0; i < conversationList.size(); ++i) {
461 if (conversationList.get(i).getUuid().equals(convToView)) {
462 selectedConversation = conversationList.get(i);
463 }
464 }
465 paneShouldBeOpen = false;
466 String text = getIntent().getExtras().getString(TEXT, null);
467 swapConversationFragment().setText(text);
468 }
469 } else {
470 if (xmppConnectionService.getAccounts().size() == 0) {
471 startActivity(new Intent(this, ManageAccountActivity.class));
472 finish();
473 } else if (conversationList.size() <= 0) {
474 // add no history
475 startActivity(new Intent(this, ContactsActivity.class));
476 finish();
477 } else {
478 spl.openPane();
479 // find currently loaded fragment
480 ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
481 .findFragmentByTag("conversation");
482 if (selectedFragment != null) {
483 selectedFragment.onBackendConnected();
484 } else {
485 selectedConversation = conversationList.get(0);
486 swapConversationFragment();
487 }
488 ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
489 }
490 }
491 }
492
493 public void registerListener() {
494 if (xmppConnectionServiceBound) {
495 xmppConnectionService
496 .setOnConversationListChangedListener(this.onConvChanged);
497 }
498 }
499
500 @Override
501 protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
502 super.onActivityResult(requestCode, resultCode, data);
503 if (resultCode == RESULT_OK) {
504 if (requestCode == REQUEST_DECRYPT_PGP) {
505 ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
506 .findFragmentByTag("conversation");
507 if (selectedFragment != null) {
508 selectedFragment.hidePgpPassphraseBox();
509 }
510 } else if (requestCode == ATTACH_FILE) {
511 Conversation conversation = getSelectedConversation();
512 String presence = conversation.getNextPresence();
513 xmppConnectionService.attachImageToConversation(conversation, presence, data.getData());
514
515 }
516 }
517 }
518
519 public void updateConversationList() {
520 conversationList.clear();
521 conversationList.addAll(xmppConnectionService.getConversations());
522 listView.invalidateViews();
523 }
524
525 public void selectPresence(final Conversation conversation, final OnPresenceSelected listener) {
526 Contact contact = conversation.getContact();
527 if (contact==null) {
528 showAddToRosterDialog(conversation);
529 listener.onPresenceSelected(false,null);
530 } else {
531 Hashtable<String, Integer> presences = contact.getPresences();
532 if (presences.size() == 0) {
533 listener.onPresenceSelected(false, null);
534 } else if (presences.size() == 1) {
535 String presence = (String) presences.keySet().toArray()[0];
536 conversation.setNextPresence(presence);
537 listener.onPresenceSelected(true, presence);
538 } else {
539 AlertDialog.Builder builder = new AlertDialog.Builder(this);
540 builder.setTitle("Choose Presence");
541 final String[] presencesArray = new String[presences.size()];
542 presences.keySet().toArray(presencesArray);
543 builder.setItems(presencesArray,
544 new DialogInterface.OnClickListener() {
545
546 @Override
547 public void onClick(DialogInterface dialog,
548 int which) {
549 String presence = presencesArray[which];
550 conversation.setNextPresence(presence);
551 listener.onPresenceSelected(true,presence);
552 }
553 });
554 builder.create().show();
555 }
556 }
557 }
558
559 private void showAddToRosterDialog(final Conversation conversation) {
560 String jid = conversation.getContactJid();
561 AlertDialog.Builder builder = new AlertDialog.Builder(this);
562 builder.setTitle(jid);
563 builder.setMessage(getString(R.string.not_in_roster));
564 builder.setNegativeButton(getString(R.string.cancel), null);
565 builder.setPositiveButton(getString(R.string.add_contact), new DialogInterface.OnClickListener() {
566
567 @Override
568 public void onClick(DialogInterface dialog, int which) {
569 String jid = conversation.getContactJid();
570 Account account = getSelectedConversation().getAccount();
571 String name = jid.split("@")[0];
572 Contact contact = new Contact(account, name, jid, null);
573 xmppConnectionService.createContact(contact);
574 }
575 });
576 builder.create().show();
577 }
578}