diff --git a/src/main/java/eu/siacs/conversations/entities/KnownConference.java b/src/main/java/eu/siacs/conversations/entities/KnownConference.java new file mode 100644 index 0000000000..07bf91891d --- /dev/null +++ b/src/main/java/eu/siacs/conversations/entities/KnownConference.java @@ -0,0 +1,134 @@ +package eu.siacs.conversations.entities; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.jid.Jid; + +public class KnownConference extends Element implements ListItem { + + private Conversation mJoinedConversation; + + public KnownConference(final Jid jid) { + super("conference"); + this.setAttribute("jid", jid.toString()); + } + + @Override + public int compareTo(final ListItem another) { + return this.getDisplayName().compareToIgnoreCase( + another.getDisplayName()); + } + + @Override + public String getDisplayName() { + if (this.mJoinedConversation != null + && (this.mJoinedConversation.getMucOptions().getSubject() != null)) { + return this.mJoinedConversation.getMucOptions().getSubject(); + } else if (getName() != null) { + return getName(); + } else { + return this.getJid().getLocalpart(); + } + } + + @Override + public Jid getJid() { + return this.getAttributeAsJid("jid"); + } + + @Override + public List getTags() { + ArrayList tags = new ArrayList(); + for (Element element : getChildren()) { + if (element.getName().equals("group") && element.getContent() != null) { + String group = element.getContent(); + tags.add(new Tag(group, UIHelper.getColorForName(group))); + } + } + return tags; + } + + public String getNick() { + return this.findChildContent("nick"); + } + + public void setNick(String nick) { + Element element = this.findChild("nick"); + if (element == null) { + element = this.addChild("nick"); + } + element.setContent(nick); + } + + public boolean autojoin() { + return this.getAttributeAsBoolean("autojoin"); + } + + public String getPassword() { + return this.findChildContent("password"); + } + + public void setPassword(String password) { + Element element = this.findChild("password"); + if (element != null) { + element.setContent(password); + } + } + + public boolean match(String needle) { + if (needle == null) { + return true; + } + needle = needle.toLowerCase(Locale.US); + final Jid jid = getJid(); + return (jid != null && jid.toString().contains(needle)) || + getDisplayName().toLowerCase(Locale.US).contains(needle) || + matchInTag(needle); + } + + private boolean matchInTag(String needle) { + needle = needle.toLowerCase(Locale.US); + for (Tag tag : getTags()) { + if (tag.getName().toLowerCase(Locale.US).contains(needle)) { + return true; + } + } + return false; + } + + public Conversation getConversation() { + return this.mJoinedConversation; + } + + public void setConversation(Conversation conversation) { + this.mJoinedConversation = conversation; + } + + public String getName() { + return this.getAttribute("name"); + } + + public void setName(String name) { + this.name = name; + } + + public void unregisterConversation() { + if (this.mJoinedConversation != null) { + this.mJoinedConversation.deregisterWithBookmark(); + } + } + + @Override + public boolean equals(Object o) { + if (o instanceof KnownConference) { + if (getJid().toString().equals(((KnownConference) o).getJid().toString())) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 1168e0403c..3e1e25660b 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -24,6 +24,7 @@ import android.provider.ContactsContract; import android.util.Log; import android.util.LruCache; +import android.widget.ArrayAdapter; import net.java.otr4j.OtrException; import net.java.otr4j.session.Session; @@ -90,6 +91,7 @@ import eu.siacs.conversations.xmpp.OnPresencePacketReceived; import eu.siacs.conversations.xmpp.OnStatusChanged; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; +import eu.siacs.conversations.xmpp.OnUpdateFoundConferences; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.chatstate.ChatState; import eu.siacs.conversations.xmpp.forms.Data; @@ -105,7 +107,7 @@ import eu.siacs.conversations.xmpp.stanzas.PresencePacket; import me.leolin.shortcutbadger.ShortcutBadger; -public class XmppConnectionService extends Service implements OnPhoneContactsLoadedListener { +public class XmppConnectionService extends Service implements OnPhoneContactsLoadedListener, OnUpdateFoundConferences { public static final String ACTION_CLEAR_NOTIFICATION = "clear_notification"; public static final String ACTION_DISABLE_FOREGROUND = "disable_foreground"; @@ -313,7 +315,7 @@ public void onStatusChanged(Account account) { private PowerManager pm; private LruCache mBitmapCache; private Thread mPhoneContactMergerThread; - + private OnUpdateFoundConferences mOnUpdateFoundConferencesListener = null; private boolean mRestoredFromDatabase = false; public boolean areMessagesInitialized() { return this.mRestoredFromDatabase; @@ -662,6 +664,7 @@ public XmppConnection createConnection(final Account account) { connection.setOnJinglePacketReceivedListener(this.jingleListener); connection.setOnBindListener(this.mOnBindListener); connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener); + connection.setOnKnownConferenceNamesUpdatedListener(this); connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService); return connection; } @@ -1121,6 +1124,15 @@ public Conversation findOrCreateConversation(final Account account, final Jid ji public Conversation findOrCreateConversation(final Account account, final Jid jid, final boolean muc, final MessageArchiveService.Query query) { synchronized (this.conversations) { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + String roomDiscoverySettingAllServers = getResources().getString(R.string.pref_muc_discovery_all_servers); + String roomDiscoverySettingConnectedMucServers = getResources().getString(R.string.pref_muc_discovery_connected_muc_servers); + String roomDiscoverySetting = preferences.getString("room_discovery", roomDiscoverySettingConnectedMucServers); + if ( muc && (roomDiscoverySetting.equals(roomDiscoverySettingConnectedMucServers) || roomDiscoverySetting.equals(roomDiscoverySettingAllServers) ) && account.getJid().getDomainpartAsJid() != jid.getDomainpartAsJid() ) { + account.getXmppConnection().sendSverviceDiscoveryToAlienServer(jid.getDomainpartAsJid(), true); + } else if (!muc && roomDiscoverySetting.equals(roomDiscoverySettingAllServers) && account.getJid().getDomainpartAsJid() != jid.getDomainpartAsJid()) { + account.getXmppConnection().sendSverviceDiscoveryToAlienServer(jid.getDomainpartAsJid(), true); + } Conversation conversation = find(account, jid); if (conversation != null) { return conversation; @@ -1366,6 +1378,10 @@ public void setOnUpdateBlocklistListener(final OnUpdateBlocklist listener) { } } + public void setOnUpdateFoundConferencesListener(final OnUpdateFoundConferences listener) { + this.mOnUpdateFoundConferencesListener = listener; + } + public void removeOnUpdateBlocklistListener() { synchronized (this) { this.updateBlocklistListenerCount--; @@ -1403,7 +1419,6 @@ public void removeOnNewKeysAvailableListener() { } } } - public void setOnMucRosterUpdateListener(OnMucRosterUpdate listener) { synchronized (this) { if (checkListeners()) { @@ -1477,6 +1492,11 @@ private void connectMultiModeConversations(Account account) { } } + public CopyOnWriteArrayList getConferenceNames(Jid jid, Jid serverJid) { + Account account = findAccountByJid(jid); + CopyOnWriteArrayList knownConferences = account.getXmppConnection().getKnownConferenceNames(serverJid); + return knownConferences; + } public void joinMuc(Conversation conversation) { Account account = conversation.getAccount(); @@ -2440,9 +2460,11 @@ public List getKnownConferenceHosts() { final ArrayList mucServers = new ArrayList<>(); for (final Account account : accounts) { if (account.getXmppConnection() != null) { - final String server = account.getXmppConnection().getMucServer(); - if (server != null && !mucServers.contains(server)) { - mucServers.add(server); + final List servers = account.getXmppConnection().getMucServers(); + for (String server : servers) { + if (server != null && !mucServers.contains(server)) { + mucServers.add(server); + } } } } @@ -2584,6 +2606,22 @@ public void onIqPacketReceived(final Account account, final IqPacket packet) { } } + @Override + public void onUpdateFoundConferences() { + if (mOnUpdateFoundConferencesListener != null) { + mOnUpdateFoundConferencesListener.onUpdateFoundConferences(); + } + } + + public void searchForConferenceRoomsOnAlienServer(Jid server) { + for (Account acc : accounts) { + if (acc.isOnlineAndConnected()) { + acc.getXmppConnection().sendSverviceDiscoveryToAlienServer(server, true); + return; + } + } + } + public interface OnMoreMessagesLoaded { public void onMoreMessagesLoaded(int count, Conversation conversation); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index e0824575f9..ed612ecb45 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -191,7 +191,21 @@ protected void onCreate(final Bundle savedInstanceState) { public void onItemClick(AdapterView arg0, View clickedView, int position, long arg3) { if (getSelectedConversation() != conversationList.get(position)) { - setSelectedConversation(conversationList.get(position)); + Conversation conversationToShow = conversationList.get(position); + Jid ownServer = conversationToShow.getAccount().getJid().getDomainpartAsJid(); + Jid remoteServer = conversationToShow.getJid().getDomainpartAsJid(); + if (!ownServer.equals(remoteServer)) { + conversationToShow.getAccount().getXmppConnection().sendSverviceDiscoveryToAlienServer(remoteServer, false); + if (!remoteServer.getDomainpart().toString().startsWith("conference")) { + try { + Jid remoteConferenceServerGuess = Jid.fromString("conference." + remoteServer.getDomainpart()); + conversationToShow.getAccount().getXmppConnection().sendSverviceDiscoveryToAlienServer(remoteConferenceServerGuess, false); + } catch (InvalidJidException e) { + //bad guess, ignore + } + } + } + setSelectedConversation(conversationToShow); ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation()); conversationWasSelectedByKeyboard = false; } diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index 74621fc4c9..28ea7d4c74 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -42,6 +42,7 @@ import android.widget.EditText; import android.widget.ListView; import android.widget.Spinner; +import android.widget.TextView; import android.widget.Toast; import com.google.zxing.integration.android.IntentIntegrator; @@ -59,6 +60,7 @@ import eu.siacs.conversations.entities.Bookmark; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.KnownConference; import eu.siacs.conversations.entities.ListItem; import eu.siacs.conversations.entities.Presences; import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate; @@ -66,23 +68,29 @@ import eu.siacs.conversations.ui.adapter.ListItemAdapter; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; +import eu.siacs.conversations.xmpp.OnUpdateFoundConferences; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; -public class StartConversationActivity extends XmppActivity implements OnRosterUpdate, OnUpdateBlocklist { +public class StartConversationActivity extends XmppActivity implements OnRosterUpdate, OnUpdateBlocklist, OnUpdateFoundConferences { public int conference_context_id; + public int conference_roomsearch_context_id; public int contact_context_id; private Tab mContactsTab; private Tab mConferencesTab; + private Tab mConferenceRoomSearchTab; private ViewPager mViewPager; private MyListFragment mContactsListFragment = new MyListFragment(); - private List contacts = new ArrayList<>(); - private ArrayAdapter mContactsAdapter; private MyListFragment mConferenceListFragment = new MyListFragment(); + private MyListFragment mConferenceRoomSearchFragment = new MyListFragment(); + private List contacts = new ArrayList<>(); private List conferences = new ArrayList(); + private List knownConferences = new ArrayList(); + private ArrayAdapter mContactsAdapter; private ArrayAdapter mConferenceAdapter; + private ArrayAdapter mConferenceRoomSearchAdapter; private List mActivatedAccounts = new ArrayList(); private List mKnownHosts; private List mKnownConferenceHosts; @@ -193,27 +201,44 @@ public void onCreate(Bundle savedInstanceState) { .setTabListener(mTabListener); mConferencesTab = actionBar.newTab().setText(R.string.conferences) .setTabListener(mTabListener); + mConferenceRoomSearchTab = actionBar.newTab().setText(R.string.conference_roomsearch) + .setTabListener(mTabListener); actionBar.addTab(mContactsTab); actionBar.addTab(mConferencesTab); + actionBar.addTab(mConferenceRoomSearchTab); mViewPager.setOnPageChangeListener(mOnPageChangeListener); mViewPager.setAdapter(new FragmentPagerAdapter(getFragmentManager()) { @Override public int getCount() { - return 2; + return 3; } @Override public Fragment getItem(int position) { if (position == 0) { return mContactsListFragment; - } else { + } else if (position == 1) { return mConferenceListFragment; + } else { + return mConferenceRoomSearchFragment; } } }); + mConferenceRoomSearchAdapter = new ListItemAdapter(this, knownConferences); + mConferenceRoomSearchFragment.setListAdapter(mConferenceRoomSearchAdapter); + mConferenceRoomSearchFragment.setContextMenu(R.menu.conference_roomsearch_context); + mConferenceRoomSearchFragment.setOnListItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView adapterView, View view, int i, long l) { + String conferenceAddress = ((TextView) view.findViewById(R.id.contact_jid)).getText().toString(); + showJoinConferenceDialog(conferenceAddress); + } + }); + + mConferenceAdapter = new ListItemAdapter(this, conferences); mConferenceListFragment.setListAdapter(mConferenceAdapter); mConferenceListFragment.setContextMenu(R.menu.conference_context); @@ -369,7 +394,7 @@ public void onClick(final View v) { final Jid accountJid; try { if (Config.DOMAIN_LOCK != null) { - accountJid = Jid.fromParts((String) spinner.getSelectedItem(),Config.DOMAIN_LOCK,null); + accountJid = Jid.fromParts((String) spinner.getSelectedItem(), Config.DOMAIN_LOCK, null); } else { accountJid = Jid.fromString((String) spinner.getSelectedItem()); } @@ -506,6 +531,7 @@ public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.start_conversation, menu); MenuItem menuCreateContact = menu.findItem(R.id.action_create_contact); MenuItem menuCreateConference = menu.findItem(R.id.action_join_conference); + MenuItem menuAddConferenceServer = menu.findItem(R.id.action_add_conference_server); MenuItem menuHideOffline = menu.findItem(R.id.action_hide_offline); menuHideOffline.setChecked(this.mHideOfflineContacts); mMenuSearchView = menu.findItem(R.id.action_search); @@ -516,8 +542,16 @@ public boolean onCreateOptionsMenu(Menu menu) { mSearchEditText.addTextChangedListener(mSearchTextWatcher); if (getActionBar().getSelectedNavigationIndex() == 0) { menuCreateConference.setVisible(false); + menuAddConferenceServer.setVisible(false); + menuCreateContact.setVisible(true); + } else if (getActionBar().getSelectedNavigationIndex() == 1) { + menuCreateContact.setVisible(false); + menuCreateConference.setVisible(true); + menuAddConferenceServer.setVisible(false); } else { menuCreateContact.setVisible(false); + menuAddConferenceServer.setVisible(true); + menuCreateConference.setVisible(false); } if (mInitialJid != null) { mMenuSearchView.expandActionView(); @@ -536,6 +570,9 @@ public boolean onOptionsItemSelected(MenuItem item) { case R.id.action_join_conference: showJoinConferenceDialog(null); return true; + case R.id.action_add_conference_server: + showAddConferenceServerDialog(null); + return true; case R.id.action_scan_qr_code: new IntentIntegrator(this).initiateScan(); return true; @@ -550,6 +587,38 @@ public boolean onOptionsItemSelected(MenuItem item) { return super.onOptionsItemSelected(item); } + private void showAddConferenceServerDialog(String server) { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.query_conference_server); + final View dialogView = getLayoutInflater().inflate(R.layout.add_conference_server_dialog, null); + final AutoCompleteTextView jid = (AutoCompleteTextView) dialogView.findViewById(R.id.jid); + jid.setAdapter(new KnownHostsAdapter(this,android.R.layout.simple_list_item_1, mKnownConferenceHosts)); + if (server != null && !server.isEmpty()) { + jid.setText(server); + } + builder.setView(dialogView); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.add, null); + final AlertDialog dialog = builder.create(); + dialog.show(); + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener( + new View.OnClickListener() { + + @Override + public void onClick(final View v) { + if (!xmppConnectionServiceBound) { + return; + } + try { + Jid serverJid = Jid.fromString(jid.getText().toString()); + xmppConnectionService.searchForConferenceRoomsOnAlienServer(serverJid); + } catch (InvalidJidException e) { + } + dialog.dismiss(); + } + }); + } + @Override public boolean onKeyUp(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_SEARCH && !event.isLongPress()) { @@ -684,6 +753,7 @@ protected void filter(String needle) { if (xmppConnectionServiceBound) { this.filterContacts(needle); this.filterConferences(needle); + this.filterKnownConferences(needle); } } @@ -719,6 +789,31 @@ protected void filterConferences(String needle) { mConferenceAdapter.notifyDataSetChanged(); } + protected void filterKnownConferences(String needle) { + this.knownConferences.clear(); + for (Account account : xmppConnectionService.getAccounts()) { + for (String conferenceHost : xmppConnectionService.getKnownConferenceHosts()) { + try { + List conferenceNames = xmppConnectionService.getConferenceNames(account.getJid(), Jid.fromString(conferenceHost)); + for (String conference : conferenceNames) { + if (needle == null || needle.isEmpty() || + conference.toLowerCase().contains(needle.toLowerCase()) || + conferenceHost.toLowerCase().contains(needle.toLowerCase())) { + KnownConference newConference = new KnownConference(Jid.fromParts(conference, conferenceHost, "")); + if (!this.knownConferences.contains(newConference)) { + this.knownConferences.add(newConference); + } + } + } + } catch (InvalidJidException e) { + e.printStackTrace(); + } + } + } + Collections.sort(this.knownConferences); + mConferenceRoomSearchAdapter.notifyDataSetChanged(); + } + private void onTabChanged() { invalidateOptionsMenu(); } @@ -735,6 +830,20 @@ protected void refreshUiReal() { } } + @Override + public void onUpdateFoundConferences() { + runOnUiThread(new Runnable() { + @Override + public void run() { + if (mSearchEditText != null) { + filter(mSearchEditText.getText().toString()); + } else { + filter(null); + } + } + }); + } + public static class MyListFragment extends ListFragment { private AdapterView.OnItemClickListener mOnItemClickListener; private int mResContextMenu; @@ -768,9 +877,8 @@ public void onCreateContextMenu(final ContextMenu menu, final View v, final StartConversationActivity activity = (StartConversationActivity) getActivity(); activity.getMenuInflater().inflate(mResContextMenu, menu); final AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo; - if (mResContextMenu == R.menu.conference_context) { - activity.conference_context_id = acmi.position; - } else { + + if (mResContextMenu == R.menu.contact_context) { activity.contact_context_id = acmi.position; final Blockable contact = (Contact) activity.contacts.get(acmi.position); final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock); @@ -784,6 +892,10 @@ public void onCreateContextMenu(final ContextMenu menu, final View v, } else { blockUnblockItem.setVisible(false); } + } else if (mResContextMenu == R.menu.conference_context) { + activity.conference_context_id = acmi.position; + } else { + activity.conference_roomsearch_context_id = acmi.position; } } @@ -808,11 +920,32 @@ public boolean onContextItemSelected(final MenuItem item) { break; case R.id.context_delete_conference: activity.deleteConference(); + break; + case R.id.context_join_searched_conference: + activity.showJoinConferenceDialogFromContextMenu(); + break; + case R.id.context_add_conference_server: + activity.showAddConferenceServerDialogFromContext(); + break; } return true; } } + private void showAddConferenceServerDialogFromContext() { + int position = conference_roomsearch_context_id; + ListItem confrence = knownConferences.get(position); + xmppConnectionService.searchForConferenceRoomsOnAlienServer(confrence.getJid().getDomainpartAsJid()); + String toastString = getResources().getString(R.string.searching_on_conference_server) + " " + confrence.getJid().getDomainpart(); + Toast.makeText(this, toastString, Toast.LENGTH_LONG).show(); + } + + private void showJoinConferenceDialogFromContextMenu() { + int position = conference_roomsearch_context_id; + ListItem confrence = knownConferences.get(position); + showJoinConferenceDialog(confrence.getJid().toString()); + } + private class Invite extends XmppUri { public Invite(final Uri uri) { diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 967efec92d..9b8c0e1561 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -83,6 +83,7 @@ import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; +import eu.siacs.conversations.xmpp.OnUpdateFoundConferences; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; @@ -296,6 +297,9 @@ protected void registerListeners() { if (this instanceof OnKeyStatusUpdated) { this.xmppConnectionService.setOnKeyStatusUpdatedListener((OnKeyStatusUpdated) this); } + if (this instanceof OnUpdateFoundConferences) { + this.xmppConnectionService.setOnUpdateFoundConferencesListener((OnUpdateFoundConferences) this); + } } protected void unregisterListeners() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/OnUpdateFoundConferences.java b/src/main/java/eu/siacs/conversations/xmpp/OnUpdateFoundConferences.java new file mode 100644 index 0000000000..8cddcd2d10 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/xmpp/OnUpdateFoundConferences.java @@ -0,0 +1,15 @@ +package eu.siacs.conversations.xmpp; + +import android.widget.ArrayAdapter; + +import java.util.ArrayList; + +import eu.siacs.conversations.xmpp.jid.Jid; + +/** + * Created by philip on 16.07.15. + */ +public interface OnUpdateFoundConferences { + @SuppressWarnings("MethodNameSameAsClassName") + void onUpdateFoundConferences(); +} diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index a65f51fefb..872a807d71 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -1,10 +1,13 @@ package eu.siacs.conversations.xmpp; +import android.app.Activity; +import android.content.SharedPreferences; import android.os.Bundle; import android.os.Parcelable; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.SystemClock; +import android.preference.PreferenceManager; import android.util.Log; import android.util.Pair; import android.util.SparseArray; @@ -29,12 +32,15 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; @@ -43,6 +49,7 @@ import javax.net.ssl.X509TrustManager; import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.sasl.DigestMd5; import eu.siacs.conversations.crypto.sasl.Plain; import eu.siacs.conversations.crypto.sasl.SaslMechanism; @@ -88,7 +95,7 @@ public class XmppConnection implements Runnable { private boolean needsBinding = true; private boolean shouldAuthenticate = true; private Element streamFeatures; - private final HashMap disco = new HashMap<>(); + private final ConcurrentHashMap disco = new ConcurrentHashMap<>(); private String streamId = null; private int smVersion = 3; @@ -108,6 +115,7 @@ public class XmppConnection implements Runnable { private OnMessagePacketReceived messageListener = null; private OnStatusChanged statusListener = null; private OnBindListener bindListener = null; + private OnUpdateFoundConferences updateKnownConferenceNamesListener = null; private final ArrayList advancedStreamFeaturesLoadedListeners = new ArrayList<>(); private OnMessageAcknowledged acknowledgedListener = null; private XmppConnectionService mXmppConnectionService = null; @@ -801,12 +809,22 @@ private void sendPostBindInitialization() { } } + private void sendServiceDiscoveryInfo(final Jid jid, final boolean forceUpdate) { + if (forceUpdate) { + if (disco.containsKey(jid)) { + disco.remove(jid); + } + } + sendServiceDiscoveryInfo(jid); + } + private void sendServiceDiscoveryInfo(final Jid jid) { if (disco.containsKey(jid)) { if (account.getServer().equals(jid)) { enableAdvancedStreamFeatures(); } - } else { + } + if (!disco.containsKey(jid)) { final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); iq.setTo(jid); iq.query("http://jabber.org/protocol/disco#info"); @@ -817,15 +835,42 @@ public void onIqPacketReceived(final Account account, final IqPacket packet) { if (packet.getType() == IqPacket.TYPE.RESULT) { final List elements = packet.query().getChildren(); final Info info = new Info(); - for (final Element element : elements) { - if (element.getName().equals("identity")) { - String type = element.getAttribute("type"); - String category = element.getAttribute("category"); - if (type != null && category != null) { + for (final Element element : elements) { + if (element.getName().equals("identity")) { + String type = element.getAttribute("type"); + String category = element.getAttribute("category"); + if (type != null && category != null) { info.identities.add(new Pair<>(category, type)); + if (category.equals("conference")) { + if (jid.hasLocalpart()) { + try { + Jid conferenceServerJid = Jid.fromString(jid.getDomainpart()); + Info inf = disco.get(conferenceServerJid); + if (inf == null) { + disco.put(conferenceServerJid, new Info()); + inf = disco.get(conferenceServerJid); + } + CopyOnWriteArrayList c = inf.hostedConferences; + c.add(jid.getLocalpart()); + if (updateKnownConferenceNamesListener != null) { + updateKnownConferenceNamesListener.onUpdateFoundConferences(); + } + Log.d(Config.LOGTAG, "Discovered conference: " + jid.toString()); + } catch (InvalidJidException e) { + //wont happen;) + } + } else { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mXmppConnectionService.getApplicationContext()); + String mucDiscoverySettingOff = mXmppConnectionService.getResources().getString(R.string.pref_muc_discovery_off); + String mucDiscoverySettingOn = mXmppConnectionService.getResources().getString(R.string.pref_muc_discovery_own_server); + String mucDiscoverySetting = preferences.getString("room_discovery", mucDiscoverySettingOn); + if (!mucDiscoverySetting.equals(mucDiscoverySettingOff)) { + info.features.add("http://jabber.org/protocol/muc"); + sendServiceDiscoveryItems(jid); + } + } + } } - } else if (element.getName().equals("feature")) { - info.features.add(element.getAttribute("var")); } } disco.put(jid, info); @@ -836,19 +881,20 @@ public void onIqPacketReceived(final Account account, final IqPacket packet) { } } } else { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not query disco info for "+jid.toString()); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not query disco info for " + jid.toString()); } } }); } } + private void enableAdvancedStreamFeatures() { if (getFeatures().carbons() && !features.carbonsEnabled) { sendEnableCarbons(); } if (getFeatures().blocking() && !features.blockListRequested) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": Requesting block list"); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": Requesting block list"); this.sendIqPacket(getIqGenerator().generateGetBlockList(), mXmppConnectionService.getIqParser()); } } @@ -953,6 +999,10 @@ public void sendPresencePacket(final PresencePacket packet) { this.sendPacket(packet); } + public void sendSverviceDiscoveryToAlienServer(Jid server, boolean forceUpdate) { + sendServiceDiscoveryInfo(server, forceUpdate); + } + private synchronized void sendPacket(final AbstractStanza packet) { if (stanzasSent == Integer.MAX_VALUE) { resetStreamId(); @@ -996,6 +1046,9 @@ public void setOnUnregisteredIqPacketReceivedListener( this.unregisteredIqListener = listener; } + public void setOnKnownConferenceNamesUpdatedListener(final OnUpdateFoundConferences listener) { + this.updateKnownConferenceNamesListener = listener; + } public void setOnPresencePacketReceivedListener( final OnPresencePacketReceived listener) { this.presenceListener = listener; @@ -1080,6 +1133,13 @@ public List findDiscoItemsByFeature(final String feature) { return items; } + public CopyOnWriteArrayList getKnownConferenceNames(Jid jid){ + if (!disco.containsKey(jid)) { + return new CopyOnWriteArrayList(); + } + return disco.get(jid).hostedConferences; + } + public Jid findDiscoItemByFeature(final String feature) { final List items = findDiscoItemsByFeature(feature); if (items.size() >= 1) { @@ -1093,15 +1153,30 @@ public void r() { } public String getMucServer() { + for (final Entry cursor : disco.entrySet()) { + final Info value = cursor.getValue(); + if (cursor.getKey().equals(account.getJid().getDomainpartAsJid())) { + if (value.features.contains("http://jabber.org/protocol/muc") + && !value.features.contains("jabber:iq:gateway") + && !value.identities.contains(new Pair<>("conference","irc"))) { + return cursor.getKey().toString(); + } + } + } + return null; + } + + public ArrayList getMucServers() { + ArrayList mucServres = new ArrayList(); for (final Entry cursor : disco.entrySet()) { final Info value = cursor.getValue(); if (value.features.contains("http://jabber.org/protocol/muc") && !value.features.contains("jabber:iq:gateway") && !value.identities.contains(new Pair<>("conference","irc"))) { - return cursor.getKey().toString(); + mucServres.add(cursor.getKey().toString()); } } - return null; + return mucServres; } public int getTimeToNextAttempt() { @@ -1151,6 +1226,7 @@ public void resetAttemptCount() { private class Info { public final ArrayList features = new ArrayList<>(); public final ArrayList> identities = new ArrayList<>(); + public final CopyOnWriteArrayList hostedConferences = new CopyOnWriteArrayList<>(); } private class UnauthorizedException extends IOException { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java b/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java index f989c0c25e..c07670d590 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java @@ -33,6 +33,17 @@ public String getDomainpart() { return IDN.toUnicode(domainpart); } + public Jid getDomainpartAsJid() { + Jid returnJid = null; + try { + returnJid = new Jid(domainpart, true); + } catch (InvalidJidException wonthHappen) { + //wont happen + } + return returnJid; + } + + public String getResourcepart() { return resourcepart; } diff --git a/src/main/res/layout/add_conference_server_dialog.xml b/src/main/res/layout/add_conference_server_dialog.xml new file mode 100644 index 0000000000..8d81845e72 --- /dev/null +++ b/src/main/res/layout/add_conference_server_dialog.xml @@ -0,0 +1,26 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/res/menu/conference_roomsearch_context.xml b/src/main/res/menu/conference_roomsearch_context.xml new file mode 100644 index 0000000000..c7cfb66ce9 --- /dev/null +++ b/src/main/res/menu/conference_roomsearch_context.xml @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/res/menu/start_conversation.xml b/src/main/res/menu/start_conversation.xml index a89d71ffd6..6ecabaa3bc 100644 --- a/src/main/res/menu/start_conversation.xml +++ b/src/main/res/menu/start_conversation.xml @@ -17,6 +17,11 @@ android:icon="?attr/icon_add_group" android:showAsAction="always" android:title="@string/join_conference" /> + Erstellen Der Kontakt existiert bereits Beitreten - Konferenz-Adresse - raum@conference.domain.de + Konferenzserver-Adresse + conference.domain.de + Raumname + Raumname Zur Kontaktliste hinzufügen Von Kontaktliste entfernen Die Konferenz befindet sich bereits auf deiner Kontaktliste diff --git a/src/main/res/values/arrays.xml b/src/main/res/values/arrays.xml index 5be352d1ca..dba76368b1 100644 --- a/src/main/res/values/arrays.xml +++ b/src/main/res/values/arrays.xml @@ -8,6 +8,12 @@ Conversations Android + + @string/pref_muc_discovery_off + @string/pref_muc_discovery_own_server + @string/pref_muc_discovery_connected_muc_servers + @string/pref_muc_discovery_all_servers + @string/never 256 KiB diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 5f3a587ab0..f2c85d6989 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -219,9 +219,11 @@ Verify Decrypt Conferences + Search conferences Search Create Contact Join Conference + Add conference server Delete Contact View contact details Block contact @@ -273,6 +275,8 @@ Ignore Warning: Sending this without mutual presence updates could cause unexpected problems.\n\nGo to contact details to verify your presence subscriptions. Encryption settings + Select where Conversations searches for chatrooms + Chatroom discovery Force end-to-end encryption Always send messages encrypted (except for conferences) Don’t save encrypted messages @@ -512,6 +516,12 @@ Download failed: Could not connect to host Use white background Show received messages as black text on a white background + Off + Own server + Connected chat servers + All servers + Search more conferences + Searching for additional conferences on Timeout in DNS Broken diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml index 6663c22b70..34be6393e9 100644 --- a/src/main/res/xml/preferences.xml +++ b/src/main/res/xml/preferences.xml @@ -22,6 +22,13 @@ android:key="auto_accept_file_size" android:summary="@string/pref_accept_files_summary" android:title="@string/pref_accept_files"/> +