From 07786d457664efee11afced9df65213cbfbf1626 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 2 Nov 2019 09:43:37 +0100 Subject: [PATCH] optionally search local muc rooms instead of jabber.network --- .../eu/siacs/conversations/entities/Room.java | 90 +++++++ .../conversations/generator/IqGenerator.java | 14 ++ .../http/services/MuclumbusService.java | 56 +---- .../siacs/conversations/parser/IqParser.java | 55 ++++ .../conversations/services/AvatarService.java | 7 +- .../services/ChannelDiscoveryService.java | 148 ++++++++++- .../services/XmppConnectionService.java | 14 +- .../ui/ChannelDiscoveryActivity.java | 53 ++-- .../adapter/ChannelSearchResultAdapter.java | 23 +- .../conversations/utils/AccountUtils.java | 8 +- .../eu/siacs/conversations/xml/Namespace.java | 2 + .../res/menu/channel_discovery_activity.xml | 18 +- src/main/res/values/arrays.xml | 10 + src/main/res/values/defaults.xml | 1 + src/main/res/values/strings.xml | 5 + src/main/res/xml/preferences.xml | 235 +++++++++--------- 16 files changed, 513 insertions(+), 226 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/entities/Room.java diff --git a/src/main/java/eu/siacs/conversations/entities/Room.java b/src/main/java/eu/siacs/conversations/entities/Room.java new file mode 100644 index 000000000..74ce07c91 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/entities/Room.java @@ -0,0 +1,90 @@ +package eu.siacs.conversations.entities; + +import com.google.common.base.Objects; +import com.google.common.base.Strings; +import com.google.common.collect.ComparisonChain; + +import java.util.Comparator; + +import eu.siacs.conversations.services.AvatarService; +import eu.siacs.conversations.utils.LanguageUtils; +import eu.siacs.conversations.utils.UIHelper; +import rocks.xmpp.addr.Jid; + +public class Room implements AvatarService.Avatarable, Comparable { + + public String address; + public String name; + public String description; + public String language; + public int nusers; + + public Room(String address, String name, String description, String language, int nusers) { + this.address = address; + this.name = name; + this.description = description; + this.language = language; + this.nusers = nusers; + } + + public Room() { + + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public Jid getRoom() { + try { + return Jid.of(address); + } catch (IllegalArgumentException e) { + return null; + } + } + + public String getLanguage() { + return LanguageUtils.convert(language); + } + + @Override + public int getAvatarBackgroundColor() { + Jid room = getRoom(); + return UIHelper.getColorForName(room != null ? room.asBareJid().toEscapedString() : name); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Room room = (Room) o; + return Objects.equal(address, room.address) && + Objects.equal(name, room.name) && + Objects.equal(description, room.description); + } + + @Override + public int hashCode() { + return Objects.hashCode(address, name, description); + } + + + public boolean contains(String needle) { + return Strings.nullToEmpty(name).contains(needle) + || Strings.nullToEmpty(description).contains(needle) + || Strings.nullToEmpty(address).contains(needle); + } + + @Override + public int compareTo(Room o) { + return ComparisonChain.start() + .compare(o.nusers, nusers) + .compare(Strings.nullToEmpty(name), Strings.nullToEmpty(o.name)) + .compare(Strings.nullToEmpty(address), Strings.nullToEmpty(o.address)) + .result(); + } +} \ No newline at end of file diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 7c7456776..1f9de7e6c 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -565,4 +565,18 @@ public class IqGenerator extends AbstractGenerator { } return packet; } + + public IqPacket queryDiscoItems(Jid jid) { + IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + packet.setTo(jid); + packet.addChild("query",Namespace.DISCO_ITEMS); + return packet; + } + + public IqPacket queryDiscoInfo(Jid jid) { + IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + packet.setTo(jid); + packet.addChild("query",Namespace.DISCO_INFO); + return packet; + } } diff --git a/src/main/java/eu/siacs/conversations/http/services/MuclumbusService.java b/src/main/java/eu/siacs/conversations/http/services/MuclumbusService.java index 89a8e0ec4..9fae92319 100644 --- a/src/main/java/eu/siacs/conversations/http/services/MuclumbusService.java +++ b/src/main/java/eu/siacs/conversations/http/services/MuclumbusService.java @@ -1,20 +1,15 @@ package eu.siacs.conversations.http.services; -import com.google.common.base.Objects; - import java.util.Collections; import java.util.List; import java.util.Set; -import eu.siacs.conversations.services.AvatarService; -import eu.siacs.conversations.utils.LanguageUtils; -import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.entities.Room; import retrofit2.Call; import retrofit2.http.Body; import retrofit2.http.GET; import retrofit2.http.POST; import retrofit2.http.Query; -import rocks.xmpp.addr.Jid; public interface MuclumbusService { @@ -31,55 +26,6 @@ public interface MuclumbusService { public List items; } - class Room implements AvatarService.Avatarable { - - public String address; - public String name; - public String description; - public String language; - - public String getName() { - return name; - } - - public String getDescription() { - return description; - } - - public Jid getRoom() { - try { - return Jid.of(address); - } catch (IllegalArgumentException e) { - return null; - } - } - - public String getLanguage() { - return LanguageUtils.convert(language); - } - - @Override - public int getAvatarBackgroundColor() { - Jid room = getRoom(); - return UIHelper.getColorForName(room != null ? room.asBareJid().toEscapedString() : name); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Room room = (Room) o; - return Objects.equal(address, room.address) && - Objects.equal(name, room.name) && - Objects.equal(description, room.description); - } - - @Override - public int hashCode() { - return Objects.hashCode(address, name, description); - } - } - class SearchRequest { public final Set keywords; diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java index 3042e510f..e5ef662bb 100644 --- a/src/main/java/eu/siacs/conversations/parser/IqParser.java +++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java @@ -1,6 +1,7 @@ package eu.siacs.conversations.parser; import android.support.annotation.NonNull; +import android.text.TextUtils; import android.util.Base64; import android.util.Log; import android.util.Pair; @@ -27,12 +28,15 @@ import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Room; +import eu.siacs.conversations.services.ChannelDiscoveryService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; +import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.stanzas.IqPacket; import rocks.xmpp.addr.Jid; @@ -417,4 +421,55 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { } } + + public static List items(IqPacket packet) { + ArrayList items = new ArrayList<>(); + final Element query = packet.findChild("query", Namespace.DISCO_ITEMS); + if (query == null) { + return items; + } + for(Element child : query.getChildren()) { + if ("item".equals(child.getName())) { + Jid jid = child.getAttributeAsJid("jid"); + if (jid != null) { + items.add(jid); + } + } + } + return items; + } + + public static Room parseRoom(IqPacket packet) { + final Element query = packet.findChild("query", Namespace.DISCO_INFO); + if(query == null) { + return null; + } + final Element x = query.findChild("x"); + if (x == null) { + return null; + } + final Element identity = query.findChild("identity"); + Data data = Data.parse(x); + String address = packet.getFrom().toEscapedString(); + String name = identity == null ? null : identity.getAttribute("name"); + String roomName = data.getValue("muc#roomconfig_roomname");; + String description = data.getValue("muc#roominfo_description"); + String language = data.getValue("muc#roominfo_lang"); + String occupants = data.getValue("muc#roominfo_occupants"); + int nusers; + try { + nusers = occupants == null ? 0 : Integer.parseInt(occupants); + } catch (NumberFormatException e) { + nusers = 0; + } + + return new Room( + address, + TextUtils.isEmpty(roomName) ? name : roomName, + description, + language, + nusers + ); + } + } diff --git a/src/main/java/eu/siacs/conversations/services/AvatarService.java b/src/main/java/eu/siacs/conversations/services/AvatarService.java index 0e349e508..ca2d534b2 100644 --- a/src/main/java/eu/siacs/conversations/services/AvatarService.java +++ b/src/main/java/eu/siacs/conversations/services/AvatarService.java @@ -38,6 +38,7 @@ import eu.siacs.conversations.entities.ListItem; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.RawBlockable; +import eu.siacs.conversations.entities.Room; import eu.siacs.conversations.http.services.MuclumbusService; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; @@ -81,14 +82,14 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded { return get((ListItem) avatarable, size, cachedOnly); } else if (avatarable instanceof MucOptions.User) { return get((MucOptions.User) avatarable, size, cachedOnly); - } else if (avatarable instanceof MuclumbusService.Room) { - return get((MuclumbusService.Room) avatarable, size, cachedOnly); + } else if (avatarable instanceof Room) { + return get((Room) avatarable, size, cachedOnly); } throw new AssertionError("AvatarService does not know how to generate avatar from "+avatarable.getClass().getName()); } - private Bitmap get(final MuclumbusService.Room result, final int size, boolean cacheOnly) { + private Bitmap get(final Room result, final int size, boolean cacheOnly) { final Jid room = result.getRoom(); Conversation conversation = room != null ? mXmppConnectionService.findFirstMuc(room) : null; if (conversation != null) { diff --git a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java index f5dd08485..f01bd72cc 100644 --- a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java +++ b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java @@ -7,14 +7,27 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import eu.siacs.conversations.Config; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Room; import eu.siacs.conversations.http.HttpConnectionManager; import eu.siacs.conversations.http.services.MuclumbusService; +import eu.siacs.conversations.parser.IqParser; +import eu.siacs.conversations.utils.LanguageUtils; +import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xmpp.OnIqPacketReceived; +import eu.siacs.conversations.xmpp.XmppConnection; +import eu.siacs.conversations.xmpp.stanzas.IqPacket; import okhttp3.OkHttpClient; import okhttp3.ResponseBody; import retrofit2.Call; @@ -22,6 +35,7 @@ import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; +import rocks.xmpp.addr.Jid; public class ChannelDiscoveryService { @@ -30,7 +44,7 @@ public class ChannelDiscoveryService { private MuclumbusService muclumbusService; - private final Cache> cache; + private final Cache> cache; ChannelDiscoveryService(XmppConnectionService service) { this.service = service; @@ -56,21 +70,28 @@ public class ChannelDiscoveryService { this.muclumbusService = retrofit.create(MuclumbusService.class); } - void discover(String query, OnChannelSearchResultsFound onChannelSearchResultsFound) { - final boolean all = query == null || query.trim().isEmpty(); - List result = cache.getIfPresent(all ? "" : query); + void cleanCache() { + cache.invalidateAll(); + } + + void discover(@NonNull final String query, Method method, OnChannelSearchResultsFound onChannelSearchResultsFound) { + List result = cache.getIfPresent(key(method, query)); if (result != null) { onChannelSearchResultsFound.onChannelSearchResultsFound(result); return; } - if (all) { - discoverChannels(onChannelSearchResultsFound); + if (method == Method.LOCAL_SERVER) { + discoverChannelsLocalServers(query, onChannelSearchResultsFound); } else { - discoverChannels(query, onChannelSearchResultsFound); + if (query.isEmpty()) { + discoverChannelsJabberNetwork(onChannelSearchResultsFound); + } else { + discoverChannelsJabberNetwork(query, onChannelSearchResultsFound); + } } } - private void discoverChannels(OnChannelSearchResultsFound listener) { + private void discoverChannelsJabberNetwork(OnChannelSearchResultsFound listener) { Call call = muclumbusService.getRooms(1); try { call.enqueue(new Callback() { @@ -82,7 +103,7 @@ public class ChannelDiscoveryService { logError(response); return; } - cache.put("", body.items); + cache.put(key(Method.JABBER_NETWORK, ""), body.items); listener.onChannelSearchResultsFound(body.items); } @@ -97,7 +118,7 @@ public class ChannelDiscoveryService { } } - private void discoverChannels(final String query, OnChannelSearchResultsFound listener) { + private void discoverChannelsJabberNetwork(final String query, OnChannelSearchResultsFound listener) { MuclumbusService.SearchRequest searchRequest = new MuclumbusService.SearchRequest(query); Call searchResultCall = muclumbusService.search(searchRequest); @@ -110,7 +131,7 @@ public class ChannelDiscoveryService { logError(response); return; } - cache.put(query, body.result.items); + cache.put(key(Method.JABBER_NETWORK, query), body.result.items); listener.onChannelSearchResultsFound(body.result.items); } @@ -122,6 +143,102 @@ public class ChannelDiscoveryService { }); } + private void discoverChannelsLocalServers(final String query, final OnChannelSearchResultsFound listener) { + final Map localMucService = getLocalMucServices(); + Log.d(Config.LOGTAG, "checking with " + localMucService.size() + " muc services"); + if (localMucService.size() == 0) { + listener.onChannelSearchResultsFound(Collections.emptyList()); + return; + } + if (!query.isEmpty()) { + final List cached = cache.getIfPresent(key(Method.LOCAL_SERVER, "")); + if (cached != null) { + final List results = copyMatching(cached, query); + cache.put(key(Method.LOCAL_SERVER, query), results); + listener.onChannelSearchResultsFound(results); + } + } + final AtomicInteger queriesInFlight = new AtomicInteger(); + final List rooms = new ArrayList<>(); + for (Map.Entry entry : localMucService.entrySet()) { + IqPacket itemsRequest = service.getIqGenerator().queryDiscoItems(entry.getKey()); + queriesInFlight.incrementAndGet(); + service.sendIqPacket(entry.getValue(), itemsRequest, (account, itemsResponse) -> { + if (itemsResponse.getType() == IqPacket.TYPE.RESULT) { + final List items = IqParser.items(itemsResponse); + for (Jid item : items) { + IqPacket infoRequest = service.getIqGenerator().queryDiscoInfo(item); + queriesInFlight.incrementAndGet(); + service.sendIqPacket(account, infoRequest, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket infoResponse) { + if (infoResponse.getType() == IqPacket.TYPE.RESULT) { + final Room room = IqParser.parseRoom(infoResponse); + if (room != null) { + rooms.add(room); + } + if (queriesInFlight.decrementAndGet() <= 0) { + finishDiscoSearch(rooms, query, listener); + } + } else { + queriesInFlight.decrementAndGet(); + } + } + }); + } + } + if (queriesInFlight.decrementAndGet() <= 0) { + finishDiscoSearch(rooms, query, listener); + } + }); + } + } + + private void finishDiscoSearch(List rooms, String query, OnChannelSearchResultsFound listener) { + Collections.sort(rooms); + cache.put(key(Method.LOCAL_SERVER, ""), rooms); + if (query.isEmpty()) { + listener.onChannelSearchResultsFound(rooms); + } else { + List results = copyMatching(rooms, query); + cache.put(key(Method.LOCAL_SERVER, query), results); + listener.onChannelSearchResultsFound(rooms); + } + } + + private static List copyMatching(List haystack, String needle) { + ArrayList result = new ArrayList<>(); + for (Room room : haystack) { + if (room.contains(needle)) { + result.add(room); + } + } + return result; + } + + private Map getLocalMucServices() { + final HashMap localMucServices = new HashMap<>(); + for (Account account : service.getAccounts()) { + if (account.isEnabled()) { + final XmppConnection xmppConnection = account.getXmppConnection(); + if (xmppConnection == null) { + continue; + } + for (final String mucService : xmppConnection.getMucServers()) { + Jid jid = Jid.of(mucService); + if (!localMucServices.containsKey(jid)) { + localMucServices.put(jid, account); + } + } + } + } + return localMucServices; + } + + private static String key(Method method, String query) { + return String.format("%s\00%s", method, query); + } + private static void logError(final Response response) { final ResponseBody errorBody = response.errorBody(); Log.d(Config.LOGTAG, "code from muclumbus=" + response.code()); @@ -129,13 +246,18 @@ public class ChannelDiscoveryService { return; } try { - Log.d(Config.LOGTAG,"error body="+errorBody.string()); + Log.d(Config.LOGTAG, "error body=" + errorBody.string()); } catch (IOException e) { //ignored } } public interface OnChannelSearchResultsFound { - void onChannelSearchResultsFound(List results); + void onChannelSearchResultsFound(List results); + } + + public enum Method { + JABBER_NETWORK, + LOCAL_SERVER } } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index dbf6a1af4..e79a0bfaa 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -34,6 +34,7 @@ import android.provider.ContactsContract; import android.security.KeyChain; import android.support.annotation.BoolRes; import android.support.annotation.IntegerRes; +import android.support.annotation.NonNull; import android.support.v4.app.RemoteInput; import android.support.v4.content.ContextCompat; import android.text.TextUtils; @@ -42,6 +43,8 @@ import android.util.Log; import android.util.LruCache; import android.util.Pair; +import com.google.common.base.Strings; + import org.conscrypt.Conscrypt; import org.openintents.openpgp.IOpenPgpService2; import org.openintents.openpgp.util.OpenPgpApi; @@ -857,8 +860,8 @@ public class XmppConnectionService extends Service { mChannelDiscoveryService.initializeMuclumbusService(); } - public void discoverChannels(String query, ChannelDiscoveryService.OnChannelSearchResultsFound onChannelSearchResultsFound) { - mChannelDiscoveryService.discover(query, onChannelSearchResultsFound); + public void discoverChannels(String query, ChannelDiscoveryService.Method method, ChannelDiscoveryService.OnChannelSearchResultsFound onChannelSearchResultsFound) { + mChannelDiscoveryService.discover(Strings.nullToEmpty(query).trim(), method, onChannelSearchResultsFound); } public boolean isDataSaverDisabled() { @@ -2244,6 +2247,7 @@ public class XmppConnectionService extends Service { getNotificationService().updateErrorNotification(); toggleForegroundService(); syncEnabledAccountSetting(); + mChannelDiscoveryService.cleanCache(); return true; } else { return false; @@ -3086,9 +3090,7 @@ public class XmppConnectionService extends Service { } public void fetchConferenceConfiguration(final Conversation conversation, final OnConferenceConfigurationFetched callback) { - IqPacket request = new IqPacket(IqPacket.TYPE.GET); - request.setTo(conversation.getJid().asBareJid()); - request.query("http://jabber.org/protocol/disco#info"); + IqPacket request = mIqGenerator.queryDiscoInfo(conversation.getJid().asBareJid()); sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { @@ -4437,7 +4439,7 @@ public class XmppConnectionService extends Service { request.setTo(jid); final String node = presence.getNode(); final String ver = presence.getVer(); - final Element query = request.query("http://jabber.org/protocol/disco#info"); + final Element query = request.query(Namespace.DISCO_INFO); if (node != null && ver != null) { query.setAttribute("node", node + "#" + ver); } diff --git a/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java b/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java index 8834e22eb..f09263ea3 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java @@ -7,6 +7,7 @@ import android.content.SharedPreferences; import android.databinding.DataBindingUtil; import android.net.Uri; import android.os.Bundle; +import android.preference.PreferenceManager; import android.support.v7.widget.Toolbar; import android.text.Html; import android.view.KeyEvent; @@ -27,7 +28,7 @@ import eu.siacs.conversations.databinding.ActivityChannelDiscoveryBinding; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Bookmark; import eu.siacs.conversations.entities.Conversation; -import eu.siacs.conversations.http.services.MuclumbusService; +import eu.siacs.conversations.entities.Room; import eu.siacs.conversations.services.ChannelDiscoveryService; import eu.siacs.conversations.ui.adapter.ChannelSearchResultAdapter; import eu.siacs.conversations.ui.util.PendingItem; @@ -46,6 +47,8 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O private MenuItem mMenuSearchView; private EditText mSearchEditText; + private ChannelDiscoveryService.Method method = ChannelDiscoveryService.Method.LOCAL_SERVER; + private boolean optedIn = false; @Override @@ -55,14 +58,15 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O @Override void onBackendConnected() { - if (optedIn) { - String query; + if (optedIn || method == ChannelDiscoveryService.Method.LOCAL_SERVER) { + final String query; if (mMenuSearchView != null && mMenuSearchView.isActionViewExpanded()) { query = mSearchEditText.getText().toString(); } else { query = mInitialSearchValue.peek(); } - xmppConnectionService.discoverChannels(query, this); + toggleLoadingScreen(); + xmppConnectionService.discoverChannels(query, this.method, this); } } @@ -74,29 +78,39 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O configureActionBar(getSupportActionBar(), true); binding.list.setAdapter(this.adapter); this.adapter.setOnChannelSearchResultSelectedListener(this); - optedIn = getPreferences().getBoolean(CHANNEL_DISCOVERY_OPT_IN, false); + this.optedIn = getPreferences().getBoolean(CHANNEL_DISCOVERY_OPT_IN, false); final String search = savedInstanceState == null ? null : savedInstanceState.getString("search"); if (search != null) { mInitialSearchValue.push(search); } + } + private static ChannelDiscoveryService.Method getMethod(final Context c) { + final SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(c); + final String m = p.getString("channel_discovery_method", c.getString(R.string.default_channel_discovery)); + try { + return ChannelDiscoveryService.Method.valueOf(m); + } catch (IllegalArgumentException e) { + return ChannelDiscoveryService.Method.JABBER_NETWORK; + } } @Override public boolean onCreateOptionsMenu(final Menu menu) { - getMenuInflater().inflate(R.menu.muc_users_activity, menu); + getMenuInflater().inflate(R.menu.channel_discovery_activity, menu); + AccountUtils.showHideMenuItems(menu); mMenuSearchView = menu.findItem(R.id.action_search); final View mSearchView = mMenuSearchView.getActionView(); mSearchEditText = mSearchView.findViewById(R.id.search_field); mSearchEditText.setHint(R.string.search_channels); - String initialSearchValue = mInitialSearchValue.pop(); + final String initialSearchValue = mInitialSearchValue.pop(); if (initialSearchValue != null) { mMenuSearchView.expandActionView(); mSearchEditText.append(initialSearchValue); mSearchEditText.requestFocus(); - if (optedIn && xmppConnectionService != null) { - xmppConnectionService.discoverChannels(initialSearchValue, this); + if ((optedIn || method == ChannelDiscoveryService.Method.LOCAL_SERVER) && xmppConnectionService != null) { + xmppConnectionService.discoverChannels(initialSearchValue, this.method, this); } } mSearchEditText.setOnEditorActionListener(this); @@ -120,8 +134,8 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O imm.hideSoftInputFromWindow(mSearchEditText.getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY); mSearchEditText.setText(""); toggleLoadingScreen(); - if (optedIn) { - xmppConnectionService.discoverChannels(null, this); + if (optedIn || method == ChannelDiscoveryService.Method.LOCAL_SERVER) { + xmppConnectionService.discoverChannels(null, this.method, this); } return true; } @@ -135,7 +149,8 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O @Override public void onStart() { super.onStart(); - if (!optedIn) { + this.method = getMethod(this); + if (!optedIn && method == ChannelDiscoveryService.Method.JABBER_NETWORK) { final AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.channel_discovery_opt_in_title); builder.setMessage(Html.fromHtml(getString(R.string.channel_discover_opt_in_message))); @@ -160,21 +175,21 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O SharedPreferences preferences = getPreferences(); preferences.edit().putBoolean(CHANNEL_DISCOVERY_OPT_IN, true).apply(); optedIn = true; - xmppConnectionService.discoverChannels(null, this); + xmppConnectionService.discoverChannels(null, this.method, this); } @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (optedIn) { + if (optedIn || method == ChannelDiscoveryService.Method.LOCAL_SERVER) { toggleLoadingScreen(); SoftKeyboardUtils.hideSoftKeyboard(this); - xmppConnectionService.discoverChannels(v.getText().toString(), this); + xmppConnectionService.discoverChannels(v.getText().toString(), this.method, this); } return true; } @Override - public void onChannelSearchResultsFound(final List results) { + public void onChannelSearchResultsFound(final List results) { runOnUiThread(() -> { adapter.submitList(results); binding.progressBar.setVisibility(View.GONE); @@ -188,7 +203,7 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O } @Override - public void onChannelSearchResult(final MuclumbusService.Room result) { + public void onChannelSearchResult(final Room result) { List accounts = AccountUtils.getEnabledAccounts(xmppConnectionService); if (accounts.size() == 1) { joinChannelSearchResult(accounts.get(0), result); @@ -206,7 +221,7 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O @Override public boolean onContextItemSelected(MenuItem item) { - final MuclumbusService.Room room = adapter.getCurrent(); + final Room room = adapter.getCurrent(); if (room != null) { switch (item.getItemId()) { case R.id.share_with: @@ -224,7 +239,7 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O return false; } - public void joinChannelSearchResult(String selectedAccount, MuclumbusService.Room result) { + public void joinChannelSearchResult(String selectedAccount, Room result) { final Jid jid = Config.DOMAIN_LOCK == null ? Jid.of(selectedAccount) : Jid.of(selectedAccount, Config.DOMAIN_LOCK, null); final boolean syncAutoJoin = getBooleanPreference("autojoin", R.bool.autojoin); final Account account = xmppConnectionService.findAccountByJid(jid); diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ChannelSearchResultAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ChannelSearchResultAdapter.java index 5ba28c446..0f452e0b1 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/ChannelSearchResultAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/ChannelSearchResultAdapter.java @@ -16,26 +16,26 @@ import java.util.Locale; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.SearchResultItemBinding; -import eu.siacs.conversations.http.services.MuclumbusService; +import eu.siacs.conversations.entities.Room; import eu.siacs.conversations.ui.XmppActivity; import eu.siacs.conversations.ui.util.AvatarWorkerTask; import rocks.xmpp.addr.Jid; -public class ChannelSearchResultAdapter extends ListAdapter implements View.OnCreateContextMenuListener { +public class ChannelSearchResultAdapter extends ListAdapter implements View.OnCreateContextMenuListener { - private static final DiffUtil.ItemCallback DIFF = new DiffUtil.ItemCallback() { + private static final DiffUtil.ItemCallback DIFF = new DiffUtil.ItemCallback() { @Override - public boolean areItemsTheSame(@NonNull MuclumbusService.Room a, @NonNull MuclumbusService.Room b) { + public boolean areItemsTheSame(@NonNull Room a, @NonNull Room b) { return a.address != null && a.address.equals(b.address); } @Override - public boolean areContentsTheSame(@NonNull MuclumbusService.Room a, @NonNull MuclumbusService.Room b) { + public boolean areContentsTheSame(@NonNull Room a, @NonNull Room b) { return a.equals(b); } }; private OnChannelSearchResultSelected listener; - private MuclumbusService.Room current; + private Room current; public ChannelSearchResultAdapter() { super(DIFF); @@ -49,7 +49,7 @@ public class ChannelSearchResultAdapter extends ListAdapter + xmlns:app="http://schemas.android.com/apk/res-auto"> + android:title="@string/search" + app:actionLayout="@layout/actionview_search" + app:showAsAction="collapseActionView|always" /> + + diff --git a/src/main/res/values/arrays.xml b/src/main/res/values/arrays.xml index 54e122dad..e59254b18 100644 --- a/src/main/res/values/arrays.xml +++ b/src/main/res/values/arrays.xml @@ -124,4 +124,14 @@ @string/video_720p @string/video_original + + + @string/jabber_network + @string/local_server + + + + JABBER_NETWORK + LOCAL_SERVER + diff --git a/src/main/res/values/defaults.xml b/src/main/res/values/defaults.xml index b5e32a639..d17c8c277 100644 --- a/src/main/res/values/defaults.xml +++ b/src/main/res/values/defaults.xml @@ -43,4 +43,5 @@ false false 360 + JABBER_NETWORK diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 3674aab53..8c03dcada 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -879,4 +879,9 @@ Unable to perform this action Join public channel… The sharing application did not grant permission to access this file. + + jabber.network + Local server + Most users should choose ‘jabber.network’ for better suggestions from the entirety of the public XMPP ecosystem. + Channel discovery method diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml index 4b93528f4..efea33a2c 100644 --- a/src/main/res/xml/preferences.xml +++ b/src/main/res/xml/preferences.xml @@ -16,6 +16,22 @@ + + + + - - - - - - - - - - + android:key="notification_category" + android:title="@string/pref_notification_settings"> - + - - - + + + - + + + + + + + - + - + + + + - - - - + + + - - - @@ -212,11 +210,6 @@ android:key="btbv" android:summary="@string/pref_blind_trust_before_verification_summary" android:title="@string/pref_blind_trust_before_verification" /> - - + + + android:key="group_chats" + android:title="@string/group_chats_and_channels"> + + + + + + - - - +