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.getContact(), conv.getName(useSubject), 200,
168 activity.getApplicationContext()));
169 return view;
170 }
171
172 };
173
174 listView.setAdapter(this.listAdapter);
175
176 listView.setOnItemClickListener(new OnItemClickListener() {
177
178 @Override
179 public void onItemClick(AdapterView<?> arg0, View clickedView,
180 int position, long arg3) {
181 paneShouldBeOpen = false;
182 if (selectedConversation != conversationList.get(position)) {
183 selectedConversation = conversationList.get(position);
184 swapConversationFragment(); // .onBackendConnected(conversationList.get(position));
185 } else {
186 spl.closePane();
187 }
188 }
189 });
190 spl = (SlidingPaneLayout) findViewById(R.id.slidingpanelayout);
191 spl.setParallaxDistance(150);
192 spl.setShadowResource(R.drawable.es_slidingpane_shadow);
193 spl.setSliderFadeColor(0);
194 spl.setPanelSlideListener(new PanelSlideListener() {
195
196 @Override
197 public void onPanelOpened(View arg0) {
198 paneShouldBeOpen = true;
199 getActionBar().setDisplayHomeAsUpEnabled(false);
200 getActionBar().setTitle(R.string.app_name);
201 invalidateOptionsMenu();
202 hideKeyboard();
203 }
204
205 @Override
206 public void onPanelClosed(View arg0) {
207 paneShouldBeOpen = false;
208 if ((conversationList.size() > 0)
209 && (getSelectedConversation() != null)) {
210 getActionBar().setDisplayHomeAsUpEnabled(true);
211 getActionBar().setTitle(
212 getSelectedConversation().getName(useSubject));
213 invalidateOptionsMenu();
214 if (!getSelectedConversation().isRead()) {
215 getSelectedConversation().markRead();
216 UIHelper.updateNotification(getApplicationContext(),
217 getConversationList(), null, false);
218 listView.invalidateViews();
219 }
220 }
221 }
222
223 @Override
224 public void onPanelSlide(View arg0, float arg1) {
225 // TODO Auto-generated method stub
226
227 }
228 });
229 }
230
231 @Override
232 public boolean onCreateOptionsMenu(Menu menu) {
233 getMenuInflater().inflate(R.menu.conversations, menu);
234 MenuItem menuSecure = (MenuItem) menu.findItem(R.id.action_security);
235 MenuItem menuArchive = (MenuItem) menu.findItem(R.id.action_archive);
236 MenuItem menuMucDetails = (MenuItem) menu
237 .findItem(R.id.action_muc_details);
238 MenuItem menuContactDetails = (MenuItem) menu
239 .findItem(R.id.action_contact_details);
240 MenuItem menuInviteContacts = (MenuItem) menu
241 .findItem(R.id.action_invite);
242 MenuItem menuAttach = (MenuItem) menu.findItem(R.id.action_attach_file);
243
244 if ((spl.isOpen() && (spl.isSlideable()))) {
245 menuArchive.setVisible(false);
246 menuMucDetails.setVisible(false);
247 menuContactDetails.setVisible(false);
248 menuSecure.setVisible(false);
249 menuInviteContacts.setVisible(false);
250 menuAttach.setVisible(false);
251 } else {
252 ((MenuItem) menu.findItem(R.id.action_add)).setVisible(!spl
253 .isSlideable());
254 if (this.getSelectedConversation() != null) {
255 if (this.getSelectedConversation().getMode() == Conversation.MODE_MULTI) {
256 menuMucDetails.setVisible(true);
257 menuContactDetails.setVisible(false);
258 menuSecure.setVisible(false);
259 menuInviteContacts.setVisible(true);
260 menuAttach.setVisible(false);
261 } else {
262 menuContactDetails.setVisible(true);
263 menuMucDetails.setVisible(false);
264 menuInviteContacts.setVisible(false);
265 menuAttach.setVisible(true);
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, "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 Conversation conv = getSelectedConversation();
302 conv.setStatus(Conversation.STATUS_ARCHIVED);
303 paneShouldBeOpen = true;
304 spl.openPane();
305 xmppConnectionService.archiveConversation(conv);
306 if (conversationList.size() > 0) {
307 selectedConversation = conversationList.get(0);
308 } else {
309 selectedConversation = null;
310 }
311 break;
312 case R.id.action_contact_details:
313 Contact contact = this.getSelectedConversation().getContact();
314 if (contact != null) {
315 Intent intent = new Intent(this, ContactDetailsActivity.class);
316 intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
317 intent.putExtra("uuid", contact.getUuid());
318 startActivity(intent);
319 } else {
320 showAddToRosterDialog(getSelectedConversation());
321 }
322 break;
323 case R.id.action_muc_details:
324 Intent intent = new Intent(this, MucDetailsActivity.class);
325 intent.setAction(MucDetailsActivity.ACTION_VIEW_MUC);
326 intent.putExtra("uuid", getSelectedConversation().getUuid());
327 startActivity(intent);
328 break;
329 case R.id.action_invite:
330 Intent inviteIntent = new Intent(getApplicationContext(),
331 ContactsActivity.class);
332 inviteIntent.setAction("invite");
333 inviteIntent.putExtra("uuid", selectedConversation.getUuid());
334 startActivity(inviteIntent);
335 break;
336 case R.id.action_security:
337 final Conversation selConv = getSelectedConversation();
338 View menuItemView = findViewById(R.id.action_security);
339 PopupMenu popup = new PopupMenu(this, menuItemView);
340 final ConversationFragment fragment = (ConversationFragment) getFragmentManager()
341 .findFragmentByTag("conversation");
342 if (fragment != null) {
343 popup.setOnMenuItemClickListener(new OnMenuItemClickListener() {
344
345 @Override
346 public boolean onMenuItemClick(MenuItem item) {
347 switch (item.getItemId()) {
348 case R.id.encryption_choice_none:
349 selConv.nextMessageEncryption = Message.ENCRYPTION_NONE;
350 item.setChecked(true);
351 break;
352 case R.id.encryption_choice_otr:
353 selConv.nextMessageEncryption = Message.ENCRYPTION_OTR;
354 item.setChecked(true);
355 break;
356 case R.id.encryption_choice_pgp:
357 selConv.nextMessageEncryption = Message.ENCRYPTION_PGP;
358 item.setChecked(true);
359 break;
360 default:
361 selConv.nextMessageEncryption = Message.ENCRYPTION_NONE;
362 break;
363 }
364 fragment.updateChatMsgHint();
365 return true;
366 }
367 });
368 popup.inflate(R.menu.encryption_choices);
369 switch (selConv.nextMessageEncryption) {
370 case Message.ENCRYPTION_NONE:
371 popup.getMenu().findItem(R.id.encryption_choice_none)
372 .setChecked(true);
373 break;
374 case Message.ENCRYPTION_OTR:
375 popup.getMenu().findItem(R.id.encryption_choice_otr)
376 .setChecked(true);
377 break;
378 case Message.ENCRYPTION_PGP:
379 popup.getMenu().findItem(R.id.encryption_choice_pgp)
380 .setChecked(true);
381 break;
382 case Message.ENCRYPTION_DECRYPTED:
383 popup.getMenu().findItem(R.id.encryption_choice_pgp)
384 .setChecked(true);
385 break;
386 default:
387 popup.getMenu().findItem(R.id.encryption_choice_none)
388 .setChecked(true);
389 break;
390 }
391 popup.show();
392 }
393
394 break;
395 default:
396 break;
397 }
398 return super.onOptionsItemSelected(item);
399 }
400
401 protected ConversationFragment swapConversationFragment() {
402 ConversationFragment selectedFragment = new ConversationFragment();
403
404 FragmentTransaction transaction = getFragmentManager()
405 .beginTransaction();
406 transaction.replace(R.id.selected_conversation, selectedFragment,
407 "conversation");
408 transaction.commit();
409 return selectedFragment;
410 }
411
412 @Override
413 public boolean onKeyDown(int keyCode, KeyEvent event) {
414 if (keyCode == KeyEvent.KEYCODE_BACK) {
415 if (!spl.isOpen()) {
416 spl.openPane();
417 return false;
418 }
419 }
420 return super.onKeyDown(keyCode, event);
421 }
422
423 @Override
424 public void onStart() {
425 super.onStart();
426 SharedPreferences preferences = PreferenceManager
427 .getDefaultSharedPreferences(this);
428 this.useSubject = preferences.getBoolean("use_subject_in_muc", true);
429 if (this.xmppConnectionServiceBound) {
430 this.onBackendConnected();
431 }
432 if (conversationList.size() >= 1) {
433 onConvChanged.onConversationListChanged();
434 }
435 }
436
437 @Override
438 protected void onStop() {
439 if (xmppConnectionServiceBound) {
440 xmppConnectionService.removeOnConversationListChangedListener();
441 }
442 super.onStop();
443 }
444
445 @Override
446 void onBackendConnected() {
447 this.registerListener();
448 if (conversationList.size() == 0) {
449 updateConversationList();
450 }
451
452 if ((getIntent().getAction() != null)
453 && (getIntent().getAction().equals(Intent.ACTION_VIEW) && (!handledViewIntent))) {
454 if (getIntent().getType().equals(
455 ConversationActivity.VIEW_CONVERSATION)) {
456 handledViewIntent = true;
457
458 String convToView = (String) getIntent().getExtras().get(
459 CONVERSATION);
460
461 for (int i = 0; i < conversationList.size(); ++i) {
462 if (conversationList.get(i).getUuid().equals(convToView)) {
463 selectedConversation = conversationList.get(i);
464 }
465 }
466 paneShouldBeOpen = false;
467 String text = getIntent().getExtras().getString(TEXT, null);
468 swapConversationFragment().setText(text);
469 }
470 } else {
471 if (xmppConnectionService.getAccounts().size() == 0) {
472 startActivity(new Intent(this, ManageAccountActivity.class));
473 finish();
474 } else if (conversationList.size() <= 0) {
475 // add no history
476 startActivity(new Intent(this, ContactsActivity.class));
477 finish();
478 } else {
479 spl.openPane();
480 // find currently loaded fragment
481 ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
482 .findFragmentByTag("conversation");
483 if (selectedFragment != null) {
484 selectedFragment.onBackendConnected();
485 } else {
486 selectedConversation = conversationList.get(0);
487 swapConversationFragment();
488 }
489 ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
490 }
491 }
492 }
493
494 public void registerListener() {
495 if (xmppConnectionServiceBound) {
496 xmppConnectionService
497 .setOnConversationListChangedListener(this.onConvChanged);
498 }
499 }
500
501 @Override
502 protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
503 super.onActivityResult(requestCode, resultCode, data);
504 if (resultCode == RESULT_OK) {
505 if (requestCode == REQUEST_DECRYPT_PGP) {
506 ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
507 .findFragmentByTag("conversation");
508 if (selectedFragment != null) {
509 selectedFragment.hidePgpPassphraseBox();
510 }
511 } else if (requestCode == ATTACH_FILE) {
512 Conversation conversation = getSelectedConversation();
513 String presence = conversation.getNextPresence();
514 xmppConnectionService.attachImageToConversation(conversation, presence, data.getData());
515
516 }
517 }
518 }
519
520 public void updateConversationList() {
521 conversationList.clear();
522 conversationList.addAll(xmppConnectionService.getConversations());
523 listView.invalidateViews();
524 }
525
526 public void selectPresence(final Conversation conversation, final OnPresenceSelected listener) {
527 Contact contact = conversation.getContact();
528 if (contact==null) {
529 showAddToRosterDialog(conversation);
530 listener.onPresenceSelected(false,null);
531 } else {
532 Hashtable<String, Integer> presences = contact.getPresences();
533 if (presences.size() == 0) {
534 listener.onPresenceSelected(false, null);
535 } else if (presences.size() == 1) {
536 String presence = (String) presences.keySet().toArray()[0];
537 conversation.setNextPresence(presence);
538 listener.onPresenceSelected(true, presence);
539 } else {
540 AlertDialog.Builder builder = new AlertDialog.Builder(this);
541 builder.setTitle("Choose Presence");
542 final String[] presencesArray = new String[presences.size()];
543 presences.keySet().toArray(presencesArray);
544 builder.setItems(presencesArray,
545 new DialogInterface.OnClickListener() {
546
547 @Override
548 public void onClick(DialogInterface dialog,
549 int which) {
550 String presence = presencesArray[which];
551 conversation.setNextPresence(presence);
552 listener.onPresenceSelected(true,presence);
553 }
554 });
555 builder.create().show();
556 }
557 }
558 }
559
560 private void showAddToRosterDialog(final Conversation conversation) {
561 String jid = conversation.getContactJid();
562 AlertDialog.Builder builder = new AlertDialog.Builder(this);
563 builder.setTitle(jid);
564 builder.setMessage(getString(R.string.not_in_roster));
565 builder.setNegativeButton(getString(R.string.cancel), null);
566 builder.setPositiveButton(getString(R.string.add_contact), new DialogInterface.OnClickListener() {
567
568 @Override
569 public void onClick(DialogInterface dialog, int which) {
570 String jid = conversation.getContactJid();
571 Account account = getSelectedConversation().getAccount();
572 String name = jid.split("@")[0];
573 Contact contact = new Contact(account, name, jid, null);
574 xmppConnectionService.createContact(contact);
575 }
576 });
577 builder.create().show();
578 }
579}