From 8cd59bb944ed9373eb04420a32f3b7cfce0a8956 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 15 Mar 2014 15:13:35 +0100 Subject: better muc invitations. clearified the creation of ad hoc mucs with an alert dialog --- AndroidManifest.xml | 4 +- res/menu/conversations.xml | 4 + res/menu/newconversation_context.xml | 4 + res/values/strings.xml | 11 +- .../services/XmppConnectionService.java | 30 +- .../siacs/conversations/ui/ContactsActivity.java | 608 +++++++++++++++++++++ .../conversations/ui/ConversationActivity.java | 17 +- .../conversations/ui/ConversationFragment.java | 3 +- .../conversations/ui/ManageAccountActivity.java | 2 +- .../siacs/conversations/ui/MucDetailsActivity.java | 2 +- .../conversations/ui/NewConversationActivity.java | 532 ------------------ src/eu/siacs/conversations/utils/CryptoHelper.java | 22 + .../siacs/conversations/xmpp/XmppConnection.java | 10 +- 13 files changed, 687 insertions(+), 562 deletions(-) create mode 100644 src/eu/siacs/conversations/ui/ContactsActivity.java delete mode 100644 src/eu/siacs/conversations/ui/NewConversationActivity.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 3b598426..e248145d 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -62,8 +62,8 @@ android:windowSoftInputMode="stateHidden" > + + diff --git a/res/values/strings.xml b/res/values/strings.xml index f04fca96..eb38e39f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -10,7 +10,7 @@ Conferenece details Secure conversation Add account - New Conversation + Contacts just now sending… Renew PGP announcement @@ -31,4 +31,13 @@ No OTR Fingerprint generated. Just go ahead an start an encrypted conversation Start Conversation Invite Contacts + Invite to existing conference + Create new conference + Cancel + Create \u0026 Invite + Do you want to create a new conference with a randomly generated address and invite the selected contacts to it? + No existing conferences + Invitation sent + Account offline + You have to be online to invite people to conferences diff --git a/src/eu/siacs/conversations/services/XmppConnectionService.java b/src/eu/siacs/conversations/services/XmppConnectionService.java index f2233130..815e06af 100644 --- a/src/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/eu/siacs/conversations/services/XmppConnectionService.java @@ -72,7 +72,7 @@ public class XmppConnectionService extends Service { private static final int PING_MAX_INTERVAL = 300; private static final int PING_MIN_INTERVAL = 10; - private static final int PING_TIMEOUT = 2; + private static final int PING_TIMEOUT = 5; private static final int CONNECT_TIMEOUT = 60; private List accounts; @@ -160,7 +160,7 @@ public class XmppConnectionService extends Service { } } else { - Log.d(LOGTAG, "unparsed message " + packet.toString()); + //Log.d(LOGTAG, "unparsed message " + packet.toString()); } } if ((message == null)||(message.getBody() == null)) { @@ -199,19 +199,6 @@ public class XmppConnectionService extends Service { accountChangedListener.onAccountListChangedListener(); } if (account.getStatus() == Account.STATUS_ONLINE) { - if (account.getXmppConnection().hasFeatureRosterManagment()) { - updateRoster(account, null); - } - connectMultiModeConversations(account); - List conversations = getConversations(); - for (int i = 0; i < conversations.size(); ++i) { - if (conversations.get(i).getAccount() == account) { - sendUnsendMessages(conversations.get(i)); - } - } - if (convChangedListener != null) { - convChangedListener.onConversationListChanged(); - } scheduleWakeupCall(PING_MAX_INTERVAL, true); } else if (account.getStatus() == Account.STATUS_OFFLINE) { if (!account.isOptionSet(Account.OPTION_DISABLED)) { @@ -558,6 +545,19 @@ public class XmppConnectionService extends Service { @Override public void onBind(Account account) { databaseBackend.clearPresences(account); + if (account.getXmppConnection().hasFeatureRosterManagment()) { + updateRoster(account, null); + } + connectMultiModeConversations(account); + List conversations = getConversations(); + for (int i = 0; i < conversations.size(); ++i) { + if (conversations.get(i).getAccount() == account) { + sendUnsendMessages(conversations.get(i)); + } + } + if (convChangedListener != null) { + convChangedListener.onConversationListChanged(); + } } }); return connection; diff --git a/src/eu/siacs/conversations/ui/ContactsActivity.java b/src/eu/siacs/conversations/ui/ContactsActivity.java new file mode 100644 index 00000000..1bd6e72f --- /dev/null +++ b/src/eu/siacs/conversations/ui/ContactsActivity.java @@ -0,0 +1,608 @@ +package eu.siacs.conversations.ui; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.utils.CryptoHelper; +import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.utils.Validator; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.util.SparseBooleanArray; +import android.view.ActionMode; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AbsListView; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.ImageView; +import android.widget.Toast; +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; + +public class ContactsActivity extends XmppActivity { + + protected List rosterContacts = new ArrayList(); + protected List aggregatedContacts = new ArrayList(); + protected ListView contactsView; + protected ArrayAdapter contactsAdapter; + + protected EditText search; + protected String searchString = ""; + private TextView contactsHeader; + private List accounts; + private List selectedContacts = new ArrayList(); + + private ContactsActivity activity = this; + + private boolean useSubject = true; + private boolean isActionMode = false; + private boolean inviteIntent = false; + private ActionMode actionMode = null; + private AbsListView.MultiChoiceModeListener actionModeCallback = new AbsListView.MultiChoiceModeListener() { + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + menu.clear(); + MenuInflater inflater = mode.getMenuInflater(); + inflater.inflate(R.menu.newconversation_context, menu); + SparseBooleanArray checkedItems = contactsView + .getCheckedItemPositions(); + selectedContacts.clear(); + for (int i = 0; i < aggregatedContacts.size(); ++i) { + if (checkedItems.get(i, false)) { + selectedContacts.add(aggregatedContacts.get(i)); + } + } + if (selectedContacts.size() == 0) { + menu.findItem(R.id.action_start_conversation).setVisible(false); + menu.findItem(R.id.action_contact_details).setVisible(false); + menu.findItem(R.id.action_invite).setVisible(false); + menu.findItem(R.id.action_invite_to_existing).setVisible(false); + } else if ((selectedContacts.size() == 1) && (!inviteIntent)) { + menu.findItem(R.id.action_start_conversation).setVisible(true); + menu.findItem(R.id.action_contact_details).setVisible(true); + menu.findItem(R.id.action_invite).setVisible(false); + menu.findItem(R.id.action_invite_to_existing).setVisible(true); + } else if (!inviteIntent) { + menu.findItem(R.id.action_start_conversation).setVisible(true); + menu.findItem(R.id.action_contact_details).setVisible(false); + menu.findItem(R.id.action_invite).setVisible(false); + menu.findItem(R.id.action_invite_to_existing).setVisible(true); + } else { + menu.findItem(R.id.action_invite).setVisible(true); + menu.findItem(R.id.action_start_conversation).setVisible(false); + menu.findItem(R.id.action_contact_details).setVisible(false); + menu.findItem(R.id.action_invite_to_existing).setVisible(false); + } + return true; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + // TODO Auto-generated method stub + + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + return true; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + switch (item.getItemId()) { + case R.id.action_start_conversation: + if (selectedContacts.size() == 1) { + startConversation(selectedContacts.get(0)); + } else { + startConference(); + } + break; + case R.id.action_contact_details: + Intent intent = new Intent(getApplicationContext(), + ContactDetailsActivity.class); + intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT); + intent.putExtra("uuid", selectedContacts.get(0).getUuid()); + startActivity(intent); + break; + case R.id.action_invite: + invite(); + break; + case R.id.action_invite_to_existing: + final List mucs = new ArrayList(); + for(Conversation conv : xmppConnectionService.getConversations()) { + if (conv.getMode() == Conversation.MODE_MULTI) { + mucs.add(conv); + } + } + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle(getString(R.string.invite_contacts_to_existing)); + if (mucs.size() >= 1) { + String[] options = new String[mucs.size()]; + for(int i = 0; i < options.length; ++i) { + options[i] = mucs.get(i).getName(useSubject); + } + builder.setItems(options, new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + Conversation conversation = mucs.get(which); + if (isOnline(conversation.getAccount())) { + xmppConnectionService.inviteToConference(conversation, selectedContacts); + Toast.makeText(activity, getString(R.string.invitation_sent), Toast.LENGTH_SHORT).show(); + actionMode.finish(); + } + } + }); + } else { + builder.setMessage(getString(R.string.no_open_mucs)); + } + builder.setNegativeButton(getString(R.string.cancel),null); + builder.create().show(); + break; + default: + break; + } + return false; + } + + @Override + public void onItemCheckedStateChanged(ActionMode mode, int position, + long id, boolean checked) { + } + }; + + private boolean isOnline(Account account) { + if (account.getStatus() == Account.STATUS_ONLINE) { + return true; + } else { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(getString(R.string.account_offline)); + builder.setMessage(getString(R.string.cant_invite_while_offline)); + builder.setNegativeButton("OK", null); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.create().show(); + return false; + } + } + + private void invite() { + List conversations = xmppConnectionService + .getConversations(); + Conversation conversation = null; + for (Conversation tmpConversation : conversations) { + if (tmpConversation.getUuid().equals( + getIntent().getStringExtra("uuid"))) { + conversation = tmpConversation; + break; + } + } + if (conversation != null) { + xmppConnectionService.inviteToConference(conversation, + selectedContacts); + } + finish(); + } + + private void startConference() { + if (accounts.size() > 1) { + getAccountChooser(new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + startConference(accounts.get(which)); + } + }).show(); + } else { + startConference(accounts.get(0)); + } + + } + + private void startConference(final Account account) { + if (isOnline(account)) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(getString(R.string.new_conference)); + builder.setMessage(getString(R.string.new_conference_explained)); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setPositiveButton(getString(R.string.create_invite), + new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + String mucName = CryptoHelper.randomMucName(); + String serverName = account.getXmppConnection() + .getMucServer(); + String jid = mucName + "@" + serverName; + Conversation conversation = xmppConnectionService + .findOrCreateConversation(account, jid, true); + StringBuilder subject = new StringBuilder(); + subject.append(account.getUsername() + ", "); + for (int i = 0; i < selectedContacts.size(); ++i) { + if (i + 1 != selectedContacts.size()) { + subject.append(selectedContacts.get(i) + .getDisplayName() + ", "); + } else { + subject.append(selectedContacts.get(i) + .getDisplayName()); + } + } + xmppConnectionService.sendConversationSubject( + conversation, subject.toString()); + xmppConnectionService.inviteToConference(conversation, + selectedContacts); + switchToConversation(conversation, null); + } + }); + builder.create().show(); + } + } + + protected void updateAggregatedContacts() { + + aggregatedContacts.clear(); + for (Contact contact : rosterContacts) { + if (contact.match(searchString)) + aggregatedContacts.add(contact); + } + + Collections.sort(aggregatedContacts, new Comparator() { + + @SuppressLint("DefaultLocale") + @Override + public int compare(Contact lhs, Contact rhs) { + return lhs.getDisplayName().toLowerCase() + .compareTo(rhs.getDisplayName().toLowerCase()); + } + }); + + if (aggregatedContacts.size() == 0) { + + if (Validator.isValidJid(searchString)) { + String name = searchString.split("@")[0]; + Contact newContact = new Contact(null, name, searchString, null); + newContact.flagAsNotInRoster(); + aggregatedContacts.add(newContact); + contactsHeader.setText("Create new contact"); + } else { + contactsHeader.setText("Contacts"); + } + } else { + contactsHeader.setText("Contacts"); + } + + contactsAdapter.notifyDataSetChanged(); + contactsView.setScrollX(0); + } + + private OnItemLongClickListener onLongClickListener = new OnItemLongClickListener() { + + @Override + public boolean onItemLongClick(AdapterView arg0, View view, + int position, long arg3) { + if (!isActionMode) { + contactsView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + contactsView.setItemChecked(position, true); + actionMode = contactsView.startActionMode(actionModeCallback); + } + return true; + } + }; + + @Override + protected void onStart() { + super.onStart(); + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); + this.useSubject = preferences.getBoolean("use_subject_in_muc", true); + inviteIntent = "invite".equals(getIntent().getAction()); + if (inviteIntent) { + contactsHeader.setVisibility(View.GONE); + actionMode = contactsView.startActionMode(actionModeCallback); + search.setVisibility(View.GONE); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_new_conversation); + + contactsHeader = (TextView) findViewById(R.id.contacts_header); + + search = (EditText) findViewById(R.id.new_conversation_search); + search.addTextChangedListener(new TextWatcher() { + + @Override + public void onTextChanged(CharSequence s, int start, int before, + int count) { + searchString = search.getText().toString(); + updateAggregatedContacts(); + } + + @Override + public void afterTextChanged(Editable s) { + // TODO Auto-generated method stub + + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + // TODO Auto-generated method stub + + } + }); + + contactsView = (ListView) findViewById(R.id.contactList); + contactsAdapter = new ArrayAdapter(getApplicationContext(), + R.layout.contact, aggregatedContacts) { + @Override + public View getView(int position, View view, ViewGroup parent) { + LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); + Contact contact = getItem(position); + if (view == null) { + view = (View) inflater.inflate(R.layout.contact, null); + } + + ((TextView) view.findViewById(R.id.contact_display_name)) + .setText(getItem(position).getDisplayName()); + TextView contactJid = (TextView) view + .findViewById(R.id.contact_jid); + contactJid.setText(contact.getJid()); + ImageView imageView = (ImageView) view + .findViewById(R.id.contact_photo); + imageView.setImageBitmap(UIHelper.getContactPicture(contact, + null, 90, this.getContext())); + return view; + } + }; + contactsView.setAdapter(contactsAdapter); + contactsView.setMultiChoiceModeListener(actionModeCallback); + contactsView.setOnItemClickListener(new OnItemClickListener() { + + @Override + public void onItemClick(AdapterView arg0, final View view, + int pos, long arg3) { + if (!isActionMode) { + Contact clickedContact = aggregatedContacts.get(pos); + startConversation(clickedContact); + + } else { + actionMode.invalidate(); + } + } + }); + contactsView.setOnItemLongClickListener(this.onLongClickListener); + } + + public void startConversation(final Contact contact) { + if ((contact.getAccount() == null) && (accounts.size() > 1)) { + getAccountChooser(new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + contact.setAccount(accounts.get(which)); + showIsMucDialogIfNeeded(contact); + } + }).show(); + } else { + if (contact.getAccount() == null) { + contact.setAccount(accounts.get(0)); + } + showIsMucDialogIfNeeded(contact); + } + } + + protected AlertDialog getAccountChooser(OnClickListener listener) { + String[] accountList = new String[accounts.size()]; + for (int i = 0; i < accounts.size(); ++i) { + accountList[i] = accounts.get(i).getJid(); + } + + AlertDialog.Builder accountChooser = new AlertDialog.Builder(this); + accountChooser.setTitle("Choose account"); + accountChooser.setItems(accountList, listener); + return accountChooser.create(); + } + + public void showIsMucDialogIfNeeded(final Contact clickedContact) { + if (clickedContact.couldBeMuc()) { + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + dialog.setTitle("Multi User Conference"); + dialog.setMessage("Are you trying to join a conference?"); + dialog.setPositiveButton("Yes", new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + startConversation(clickedContact, + clickedContact.getAccount(), true); + } + }); + dialog.setNegativeButton("No", new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + startConversation(clickedContact, + clickedContact.getAccount(), false); + } + }); + dialog.create().show(); + } else { + startConversation(clickedContact, clickedContact.getAccount(), + false); + } + } + + public void startConversation(Contact contact, Account account, boolean muc) { + if (!contact.isInRoster()) { + xmppConnectionService.createContact(contact); + } + Conversation conversation = xmppConnectionService + .findOrCreateConversation(account, contact.getJid(), muc); + + switchToConversation(conversation, null); + } + + @Override + void onBackendConnected() { + this.accounts = xmppConnectionService.getAccounts(); + if (Intent.ACTION_SENDTO.equals(getIntent().getAction())) { + getActionBar().setDisplayHomeAsUpEnabled(false); + getActionBar().setHomeButtonEnabled(false); + String jid; + try { + jid = URLDecoder.decode(getIntent().getData().getEncodedPath(), + "UTF-8").split("/")[1]; + } catch (UnsupportedEncodingException e) { + jid = null; + } + if (jid != null) { + final String finalJid = jid; + if (this.accounts.size() > 1) { + getAccountChooser(new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + Conversation conversation = xmppConnectionService + .findOrCreateConversation( + accounts.get(which), finalJid, + false); + switchToConversation(conversation, null); + finish(); + } + }).show(); + } else { + Conversation conversation = xmppConnectionService + .findOrCreateConversation(this.accounts.get(0), + jid, false); + switchToConversation(conversation, null); + finish(); + } + } + } + + if (xmppConnectionService.getConversationCount() == 0) { + getActionBar().setDisplayHomeAsUpEnabled(false); + getActionBar().setHomeButtonEnabled(false); + } + this.rosterContacts.clear(); + for (int i = 0; i < accounts.size(); ++i) { + rosterContacts.addAll(xmppConnectionService.getRoster(accounts + .get(i))); + } + updateAggregatedContacts(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.newconversation, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_refresh_contacts: + refreshContacts(); + break; + default: + break; + } + return super.onOptionsItemSelected(item); + } + + private void refreshContacts() { + final ProgressBar progress = (ProgressBar) findViewById(R.id.progressBar1); + final EditText searchBar = (EditText) findViewById(R.id.new_conversation_search); + final TextView contactsHeader = (TextView) findViewById(R.id.contacts_header); + final ListView contactList = (ListView) findViewById(R.id.contactList); + searchBar.setVisibility(View.GONE); + contactsHeader.setVisibility(View.GONE); + contactList.setVisibility(View.GONE); + progress.setVisibility(View.VISIBLE); + this.accounts = xmppConnectionService.getAccounts(); + this.rosterContacts.clear(); + for (int i = 0; i < accounts.size(); ++i) { + if (accounts.get(i).getStatus() == Account.STATUS_ONLINE) { + xmppConnectionService.updateRoster(accounts.get(i), + new OnRosterFetchedListener() { + + @Override + public void onRosterFetched( + final List roster) { + runOnUiThread(new Runnable() { + + @Override + public void run() { + rosterContacts.addAll(roster); + progress.setVisibility(View.GONE); + searchBar.setVisibility(View.VISIBLE); + contactList.setVisibility(View.VISIBLE); + contactList.setVisibility(View.VISIBLE); + updateAggregatedContacts(); + } + }); + } + }); + } + } + } + + @Override + public void onActionModeStarted(ActionMode mode) { + super.onActionModeStarted(mode); + this.isActionMode = true; + search.setEnabled(false); + } + + @Override + public void onActionModeFinished(ActionMode mode) { + super.onActionModeFinished(mode); + if (inviteIntent) { + finish(); + } else { + this.isActionMode = false; + contactsView.clearChoices(); + contactsView.requestLayout(); + contactsView.post(new Runnable() { + @Override + public void run() { + contactsView.setChoiceMode(ListView.CHOICE_MODE_NONE); + } + }); + search.setEnabled(true); + } + } + +} diff --git a/src/eu/siacs/conversations/ui/ConversationActivity.java b/src/eu/siacs/conversations/ui/ConversationActivity.java index ad643c31..a08e0727 100644 --- a/src/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/eu/siacs/conversations/ui/ConversationActivity.java @@ -74,7 +74,7 @@ public class ConversationActivity extends XmppActivity { if (conversationList.size() >= 1) { swapConversationFragment(); } else { - startActivity(new Intent(getApplicationContext(), NewConversationActivity.class)); + startActivity(new Intent(getApplicationContext(), ContactsActivity.class)); finish(); } } @@ -249,12 +249,14 @@ public class ConversationActivity extends XmppActivity { MenuItem menuArchive = (MenuItem) menu.findItem(R.id.action_archive); MenuItem menuMucDetails = (MenuItem) menu.findItem(R.id.action_muc_details); MenuItem menuContactDetails = (MenuItem) menu.findItem(R.id.action_contact_details); + MenuItem menuInviteContacts = (MenuItem) menu.findItem(R.id.action_invite); if ((spl.isOpen()&&(spl.isSlideable()))) { menuArchive.setVisible(false); menuMucDetails.setVisible(false); menuContactDetails.setVisible(false); menuSecure.setVisible(false); + menuInviteContacts.setVisible(false); } else { ((MenuItem) menu.findItem(R.id.action_add)).setVisible(!spl.isSlideable()); if (this.getSelectedConversation()!=null) { @@ -263,9 +265,11 @@ public class ConversationActivity extends XmppActivity { menuContactDetails.setVisible(false); menuSecure.setVisible(false); menuArchive.setTitle("Leave conference"); + menuInviteContacts.setVisible(true); } else { menuContactDetails.setVisible(true); menuMucDetails.setVisible(false); + menuInviteContacts.setVisible(false); if (this.getSelectedConversation().getLatestMessage().getEncryption() != Message.ENCRYPTION_NONE) { menuSecure.setIcon(R.drawable.ic_action_secure); } @@ -282,7 +286,7 @@ public class ConversationActivity extends XmppActivity { spl.openPane(); break; case R.id.action_add: - startActivity(new Intent(this, NewConversationActivity.class)); + startActivity(new Intent(this, ContactsActivity.class)); break; case R.id.action_archive: Conversation conv = getSelectedConversation(); @@ -319,6 +323,13 @@ public class ConversationActivity extends XmppActivity { intent.putExtra("uuid", getSelectedConversation().getUuid()); startActivity(intent); break; + case R.id.action_invite: + Intent inviteIntent = new Intent(getApplicationContext(), + ContactsActivity.class); + inviteIntent.setAction("invite"); + inviteIntent.putExtra("uuid",selectedConversation.getUuid()); + startActivity(inviteIntent); + break; case R.id.action_security: final Conversation selConv = getSelectedConversation(); View menuItemView = findViewById(R.id.action_security); @@ -451,7 +462,7 @@ public class ConversationActivity extends XmppActivity { finish(); } else if (conversationList.size() <= 0) { //add no history - startActivity(new Intent(this, NewConversationActivity.class)); + startActivity(new Intent(this, ContactsActivity.class)); finish(); } else { spl.openPane(); diff --git a/src/eu/siacs/conversations/ui/ConversationFragment.java b/src/eu/siacs/conversations/ui/ConversationFragment.java index d72dae8e..4ae85dc2 100644 --- a/src/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/eu/siacs/conversations/ui/ConversationFragment.java @@ -326,7 +326,8 @@ public class ConversationFragment extends Fragment { public void onStart() { super.onStart(); ConversationActivity activity = (ConversationActivity) getActivity(); - + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); + this.useSubject = preferences.getBoolean("use_subject_in_muc", true); if (activity.xmppConnectionServiceBound) { this.onBackendConnected(); } diff --git a/src/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/eu/siacs/conversations/ui/ManageAccountActivity.java index 8798a74c..b9ed4102 100644 --- a/src/eu/siacs/conversations/ui/ManageAccountActivity.java +++ b/src/eu/siacs/conversations/ui/ManageAccountActivity.java @@ -200,7 +200,7 @@ public class ManageAccountActivity extends XmppActivity { if ((account.getStatus() == Account.STATUS_OFFLINE)||(account.getStatus() == Account.STATUS_TLS_ERROR)) { activity.xmppConnectionService.reconnectAccount(accountList.get(position),true); } else if (account.getStatus() == Account.STATUS_ONLINE) { - activity.startActivity(new Intent(activity.getApplicationContext(),NewConversationActivity.class)); + activity.startActivity(new Intent(activity.getApplicationContext(),ContactsActivity.class)); } else if (account.isOptionSet(Account.OPTION_REGISTER)) { editAccount(account); } diff --git a/src/eu/siacs/conversations/ui/MucDetailsActivity.java b/src/eu/siacs/conversations/ui/MucDetailsActivity.java index 73c18240..8d90fd56 100644 --- a/src/eu/siacs/conversations/ui/MucDetailsActivity.java +++ b/src/eu/siacs/conversations/ui/MucDetailsActivity.java @@ -70,7 +70,7 @@ public class MucDetailsActivity extends XmppActivity { @Override public void onClick(View v) { Intent intent = new Intent(getApplicationContext(), - NewConversationActivity.class); + ContactsActivity.class); intent.setAction("invite"); intent.putExtra("uuid",conversation.getUuid()); startActivity(intent); diff --git a/src/eu/siacs/conversations/ui/NewConversationActivity.java b/src/eu/siacs/conversations/ui/NewConversationActivity.java deleted file mode 100644 index 9f8f23a4..00000000 --- a/src/eu/siacs/conversations/ui/NewConversationActivity.java +++ /dev/null @@ -1,532 +0,0 @@ -package eu.siacs.conversations.ui; - -import java.io.UnsupportedEncodingException; -import java.math.BigInteger; -import java.net.URLDecoder; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -import eu.siacs.conversations.R; -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.entities.Contact; -import eu.siacs.conversations.entities.Conversation; -import eu.siacs.conversations.utils.UIHelper; -import eu.siacs.conversations.utils.Validator; -import android.os.Bundle; -import android.text.Editable; -import android.text.TextWatcher; -import android.util.Log; -import android.util.SparseBooleanArray; -import android.view.ActionMode; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AbsListView; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.AdapterView.OnItemLongClickListener; -import android.widget.ArrayAdapter; -import android.widget.EditText; -import android.widget.ListView; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.ImageView; -import android.annotation.SuppressLint; -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; - -public class NewConversationActivity extends XmppActivity { - - protected List rosterContacts = new ArrayList(); - protected List aggregatedContacts = new ArrayList(); - protected ListView contactsView; - protected ArrayAdapter contactsAdapter; - - protected EditText search; - protected String searchString = ""; - private TextView contactsHeader; - private List accounts; - private List selectedContacts = new ArrayList(); - - private boolean isActionMode = false; - private boolean inviteIntent = false; - private ActionMode actionMode = null; - private AbsListView.MultiChoiceModeListener actionModeCallback = new AbsListView.MultiChoiceModeListener() { - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - menu.clear(); - MenuInflater inflater = mode.getMenuInflater(); - inflater.inflate(R.menu.newconversation_context, menu); - SparseBooleanArray checkedItems = contactsView - .getCheckedItemPositions(); - selectedContacts.clear(); - for (int i = 0; i < aggregatedContacts.size(); ++i) { - if (checkedItems.get(i, false)) { - selectedContacts.add(aggregatedContacts.get(i)); - } - } - if (selectedContacts.size() == 0) { - menu.findItem(R.id.action_start_conversation).setVisible(false); - menu.findItem(R.id.action_contact_details).setVisible(false); - menu.findItem(R.id.action_invite).setVisible(false); - } else if ((selectedContacts.size() == 1)&&(!inviteIntent)) { - menu.findItem(R.id.action_start_conversation).setVisible(true); - menu.findItem(R.id.action_contact_details).setVisible(true); - menu.findItem(R.id.action_invite).setVisible(false); - } else if (!inviteIntent){ - menu.findItem(R.id.action_start_conversation).setVisible(true); - menu.findItem(R.id.action_contact_details).setVisible(false); - menu.findItem(R.id.action_invite).setVisible(false); - } else { - menu.findItem(R.id.action_invite).setVisible(true); - menu.findItem(R.id.action_start_conversation).setVisible(false); - menu.findItem(R.id.action_contact_details).setVisible(false); - } - return true; - } - - @Override - public void onDestroyActionMode(ActionMode mode) { - // TODO Auto-generated method stub - - } - - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - return true; - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - switch (item.getItemId()) { - case R.id.action_start_conversation: - if (selectedContacts.size() == 1) { - startConversation(selectedContacts.get(0)); - } else { - startConference(); - } - break; - case R.id.action_contact_details: - Intent intent = new Intent(getApplicationContext(), - ContactDetailsActivity.class); - intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT); - intent.putExtra("uuid", selectedContacts.get(0).getUuid()); - startActivity(intent); - break; - case R.id.action_invite: - invite(); - break; - default: - break; - } - // TODO Auto-generated method stub - return false; - } - - @Override - public void onItemCheckedStateChanged(ActionMode mode, int position, - long id, boolean checked) { - } - }; - - private void invite() { - List conversations = xmppConnectionService - .getConversations(); - Conversation conversation = null; - for (Conversation tmpConversation : conversations) { - if (tmpConversation.getUuid().equals( - getIntent().getStringExtra("uuid"))) { - conversation = tmpConversation; - break; - } - } - if (conversation != null) { - xmppConnectionService.inviteToConference(conversation, selectedContacts); - } - finish(); - } - - private void startConference() { - if (accounts.size() > 1) { - getAccountChooser(new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - startConference(accounts.get(which), selectedContacts); - } - }).show(); - } else { - startConference(accounts.get(0), selectedContacts); - } - - } - - private void startConference(Account account, List contacts) { - SecureRandom random = new SecureRandom(); - String mucName = new BigInteger(100, random).toString(32); - String serverName = account.getXmppConnection().getMucServer(); - String jid = mucName + "@" + serverName; - Conversation conversation = xmppConnectionService - .findOrCreateConversation(account, jid, true); - StringBuilder subject = new StringBuilder(); - for (int i = 0; i < selectedContacts.size(); ++i) { - if (i + 1 != selectedContacts.size()) { - subject.append(selectedContacts.get(i).getDisplayName() + ", "); - } else { - subject.append(selectedContacts.get(i).getDisplayName()); - } - } - xmppConnectionService.sendConversationSubject(conversation, - subject.toString()); - xmppConnectionService.inviteToConference(conversation, contacts); - switchToConversation(conversation, null); - } - - protected void updateAggregatedContacts() { - - aggregatedContacts.clear(); - for (Contact contact : rosterContacts) { - if (contact.match(searchString)) - aggregatedContacts.add(contact); - } - - Collections.sort(aggregatedContacts, new Comparator() { - - @SuppressLint("DefaultLocale") - @Override - public int compare(Contact lhs, Contact rhs) { - return lhs.getDisplayName().toLowerCase() - .compareTo(rhs.getDisplayName().toLowerCase()); - } - }); - - if (aggregatedContacts.size() == 0) { - - if (Validator.isValidJid(searchString)) { - String name = searchString.split("@")[0]; - Contact newContact = new Contact(null, name, searchString, null); - newContact.flagAsNotInRoster(); - aggregatedContacts.add(newContact); - contactsHeader.setText("Create new contact"); - } else { - contactsHeader.setText("Contacts"); - } - } else { - contactsHeader.setText("Contacts"); - } - - contactsAdapter.notifyDataSetChanged(); - contactsView.setScrollX(0); - } - - private OnItemLongClickListener onLongClickListener = new OnItemLongClickListener() { - - @Override - public boolean onItemLongClick(AdapterView arg0, View view, - int position, long arg3) { - if (!isActionMode) { - contactsView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); - contactsView.setItemChecked(position, true); - actionMode = contactsView.startActionMode(actionModeCallback); - } - return true; - } - }; - - @Override - protected void onStart() { - super.onStart(); - inviteIntent = "invite".equals(getIntent().getAction()); - if (inviteIntent) { - contactsHeader.setVisibility(View.GONE); - actionMode = contactsView.startActionMode(actionModeCallback); - search.setVisibility(View.GONE); - } - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - - super.onCreate(savedInstanceState); - - setContentView(R.layout.activity_new_conversation); - - contactsHeader = (TextView) findViewById(R.id.contacts_header); - - search = (EditText) findViewById(R.id.new_conversation_search); - search.addTextChangedListener(new TextWatcher() { - - @Override - public void onTextChanged(CharSequence s, int start, int before, - int count) { - searchString = search.getText().toString(); - updateAggregatedContacts(); - } - - @Override - public void afterTextChanged(Editable s) { - // TODO Auto-generated method stub - - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, - int after) { - // TODO Auto-generated method stub - - } - }); - - contactsView = (ListView) findViewById(R.id.contactList); - contactsAdapter = new ArrayAdapter(getApplicationContext(), - R.layout.contact, aggregatedContacts) { - @Override - public View getView(int position, View view, ViewGroup parent) { - LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); - Contact contact = getItem(position); - if (view == null) { - view = (View) inflater.inflate(R.layout.contact, null); - } - - ((TextView) view.findViewById(R.id.contact_display_name)) - .setText(getItem(position).getDisplayName()); - TextView contactJid = (TextView) view - .findViewById(R.id.contact_jid); - contactJid.setText(contact.getJid()); - ImageView imageView = (ImageView) view - .findViewById(R.id.contact_photo); - imageView.setImageBitmap(UIHelper.getContactPicture(contact, - null, 90, this.getContext())); - return view; - } - }; - contactsView.setAdapter(contactsAdapter); - contactsView.setMultiChoiceModeListener(actionModeCallback); - contactsView.setOnItemClickListener(new OnItemClickListener() { - - @Override - public void onItemClick(AdapterView arg0, final View view, - int pos, long arg3) { - if (!isActionMode) { - Contact clickedContact = aggregatedContacts.get(pos); - startConversation(clickedContact); - - } else { - actionMode.invalidate(); - } - } - }); - contactsView.setOnItemLongClickListener(this.onLongClickListener); - } - - public void startConversation(final Contact contact) { - if ((contact.getAccount() == null) && (accounts.size() > 1)) { - getAccountChooser(new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - contact.setAccount(accounts.get(which)); - showIsMucDialogIfNeeded(contact); - } - }).show(); - } else { - if (contact.getAccount() == null) { - contact.setAccount(accounts.get(0)); - } - showIsMucDialogIfNeeded(contact); - } - } - - protected AlertDialog getAccountChooser(OnClickListener listener) { - String[] accountList = new String[accounts.size()]; - for (int i = 0; i < accounts.size(); ++i) { - accountList[i] = accounts.get(i).getJid(); - } - - AlertDialog.Builder accountChooser = new AlertDialog.Builder(this); - accountChooser.setTitle("Choose account"); - accountChooser.setItems(accountList, listener); - return accountChooser.create(); - } - - public void showIsMucDialogIfNeeded(final Contact clickedContact) { - if (clickedContact.couldBeMuc()) { - AlertDialog.Builder dialog = new AlertDialog.Builder(this); - dialog.setTitle("Multi User Conference"); - dialog.setMessage("Are you trying to join a conference?"); - dialog.setPositiveButton("Yes", new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - startConversation(clickedContact, - clickedContact.getAccount(), true); - } - }); - dialog.setNegativeButton("No", new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - startConversation(clickedContact, - clickedContact.getAccount(), false); - } - }); - dialog.create().show(); - } else { - startConversation(clickedContact, clickedContact.getAccount(), - false); - } - } - - public void startConversation(Contact contact, Account account, boolean muc) { - if (!contact.isInRoster()) { - xmppConnectionService.createContact(contact); - } - Conversation conversation = xmppConnectionService - .findOrCreateConversation(account, contact.getJid(), muc); - - switchToConversation(conversation, null); - } - - @Override - void onBackendConnected() { - this.accounts = xmppConnectionService.getAccounts(); - if (Intent.ACTION_SENDTO.equals(getIntent().getAction())) { - getActionBar().setDisplayHomeAsUpEnabled(false); - getActionBar().setHomeButtonEnabled(false); - String jid; - try { - jid = URLDecoder.decode(getIntent().getData().getEncodedPath(), - "UTF-8").split("/")[1]; - } catch (UnsupportedEncodingException e) { - jid = null; - } - if (jid != null) { - final String finalJid = jid; - if (this.accounts.size() > 1) { - getAccountChooser(new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - Conversation conversation = xmppConnectionService - .findOrCreateConversation( - accounts.get(which), finalJid, - false); - switchToConversation(conversation, null); - finish(); - } - }).show(); - } else { - Conversation conversation = xmppConnectionService - .findOrCreateConversation(this.accounts.get(0), - jid, false); - switchToConversation(conversation, null); - finish(); - } - } - } - - if (xmppConnectionService.getConversationCount() == 0) { - getActionBar().setDisplayHomeAsUpEnabled(false); - getActionBar().setHomeButtonEnabled(false); - } - this.rosterContacts.clear(); - for (int i = 0; i < accounts.size(); ++i) { - rosterContacts.addAll(xmppConnectionService.getRoster(accounts - .get(i))); - } - updateAggregatedContacts(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.newconversation, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_refresh_contacts: - refreshContacts(); - break; - default: - break; - } - return super.onOptionsItemSelected(item); - } - - private void refreshContacts() { - final ProgressBar progress = (ProgressBar) findViewById(R.id.progressBar1); - final EditText searchBar = (EditText) findViewById(R.id.new_conversation_search); - final TextView contactsHeader = (TextView) findViewById(R.id.contacts_header); - final ListView contactList = (ListView) findViewById(R.id.contactList); - searchBar.setVisibility(View.GONE); - contactsHeader.setVisibility(View.GONE); - contactList.setVisibility(View.GONE); - progress.setVisibility(View.VISIBLE); - this.accounts = xmppConnectionService.getAccounts(); - this.rosterContacts.clear(); - for (int i = 0; i < accounts.size(); ++i) { - if (accounts.get(i).getStatus() == Account.STATUS_ONLINE) { - xmppConnectionService.updateRoster(accounts.get(i), - new OnRosterFetchedListener() { - - @Override - public void onRosterFetched( - final List roster) { - runOnUiThread(new Runnable() { - - @Override - public void run() { - rosterContacts.addAll(roster); - progress.setVisibility(View.GONE); - searchBar.setVisibility(View.VISIBLE); - contactList.setVisibility(View.VISIBLE); - contactList.setVisibility(View.VISIBLE); - updateAggregatedContacts(); - } - }); - } - }); - } - } - } - - @Override - public void onActionModeStarted(ActionMode mode) { - super.onActionModeStarted(mode); - this.isActionMode = true; - search.setEnabled(false); - } - - @Override - public void onActionModeFinished(ActionMode mode) { - super.onActionModeFinished(mode); - if (inviteIntent) { - finish(); - } else { - this.isActionMode = false; - contactsView.clearChoices(); - contactsView.requestLayout(); - contactsView.post(new Runnable() { - @Override - public void run() { - contactsView.setChoiceMode(ListView.CHOICE_MODE_NONE); - } - }); - search.setEnabled(true); - } - } - -} diff --git a/src/eu/siacs/conversations/utils/CryptoHelper.java b/src/eu/siacs/conversations/utils/CryptoHelper.java index ebbbd967..550639d2 100644 --- a/src/eu/siacs/conversations/utils/CryptoHelper.java +++ b/src/eu/siacs/conversations/utils/CryptoHelper.java @@ -1,9 +1,14 @@ package eu.siacs.conversations.utils; +import java.security.SecureRandom; +import java.util.Random; + import android.util.Base64; public class CryptoHelper { final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); + final protected static char[] vowels = "aeiou".toCharArray(); + final protected static char[] consonants ="bcdfghjklmnpqrstvwxyz".toCharArray(); public static String bytesToHex(byte[] bytes) { char[] hexChars = new char[bytes.length * 2]; for ( int j = 0; j < bytes.length; j++ ) { @@ -31,4 +36,21 @@ public class CryptoHelper { return Base64.encodeToString(saslBytes, Base64.DEFAULT); } + + public static String randomMucName() { + Random random = new SecureRandom(); + return randomWord(3,random)+"."+randomWord(7,random); + } + + protected static String randomWord(int lenght,Random random) { + StringBuilder builder = new StringBuilder(lenght); + for(int i = 0; i < lenght; ++i) { + if (i % 2 == 0) { + builder.append(consonants[random.nextInt(consonants.length)]); + } else { + builder.append(vowels[random.nextInt(vowels.length)]); + } + } + return builder.toString(); + } } diff --git a/src/eu/siacs/conversations/xmpp/XmppConnection.java b/src/eu/siacs/conversations/xmpp/XmppConnection.java index dadd310a..0fbd6f77 100644 --- a/src/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/eu/siacs/conversations/xmpp/XmppConnection.java @@ -220,6 +220,7 @@ public class XmppConnection implements Runnable { tagWriter.writeStanzaAsync(r); } else if (nextTag.isStart("resumed")) { tagReader.readElement(nextTag); + sendPing(); changeStatus(Account.STATUS_ONLINE); Log.d(LOGTAG,account.getJid()+": session resumed"); } else if (nextTag.isStart("r")) { @@ -543,10 +544,6 @@ public class XmppConnection implements Runnable { String resource = packet.findChild("bind").findChild("jid") .getContent().split("/")[1]; account.setResource(resource); - if (bindListener !=null) { - bindListener.onBind(account); - } - account.setStatus(Account.STATUS_ONLINE); if (streamFeatures.hasChild("sm")) { EnablePacket enable = new EnablePacket(); tagWriter.writeStanzaAsync(enable); @@ -554,9 +551,10 @@ public class XmppConnection implements Runnable { sendInitialPresence(); sendServiceDiscoveryInfo(); sendServiceDiscoveryItems(); - if (statusListener != null) { - statusListener.onStatusChanged(account); + if (bindListener !=null) { + bindListener.onBind(account); } + account.setStatus(Account.STATUS_ONLINE); } }); } -- cgit v1.2.3