diff options
25 files changed, 471 insertions, 162 deletions
diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 1035a6a6c..5b3b2f9df 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -87,7 +87,7 @@ public final class Config { public static final boolean DISABLE_HTTP_UPLOAD = false; public static final boolean DISABLE_STRING_PREP = false; // setting to true might increase startup performance public static final boolean EXTENDED_SM_LOGGING = false; // log stanza counts - public static final boolean EXTENDED_IQ_LOGGING = true; // log iq requests + public static final boolean BACKGROUND_STANZA_LOGGING = true; public static final boolean RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE = true; //setting to true might increase power consumption public static final boolean ENCRYPT_ON_HTTP_UPLOADED = false; diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index ae1b3a03f..aac814433 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -216,6 +216,20 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { private static class FetchStatusMap extends AxolotlAddressMap<FetchStatus> { + public void clearErrorFor(Jid jid) { + synchronized (MAP_LOCK) { + Map<Integer, FetchStatus> devices = this.map.get(jid.toBareJid().toString()); + if (devices == null) { + return; + } + for(Map.Entry<Integer, FetchStatus> entry : devices.entrySet()) { + if (entry.getValue() == FetchStatus.ERROR) { + Log.d(Config.LOGTAG,"resetting error for "+jid.toBareJid()+"("+entry.getKey()+")"); + entry.setValue(FetchStatus.TIMEOUT); + } + } + } + } } public static String getLogprefix(Account account) { @@ -320,6 +334,10 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { numPublishTriesOnEmptyPep = 0; } + public void clearErrorsInFetchStatusMap(Jid jid) { + fetchStatusMap.clearErrorFor(jid); + } + public void regenerateKeys(boolean wipeOther) { axolotlStore.regenerate(); sessions.clear(); @@ -643,7 +661,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { jids = Arrays.asList(conversation.getJid().toBareJid()); } else { jids = conversation.getMucOptions().getMembers(); - jids.remove(account.getJid().toBareJid()); } return jids; } diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java index 979a2845d..4eb73313d 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java @@ -90,16 +90,18 @@ public class SQLiteAxolotlStore implements AxolotlStore { // -------------------------------------- private IdentityKeyPair loadIdentityKeyPair() { - IdentityKeyPair ownKey = mXmppConnectionService.databaseBackend.loadOwnIdentityKeyPair(account); + synchronized (mXmppConnectionService) { + IdentityKeyPair ownKey = mXmppConnectionService.databaseBackend.loadOwnIdentityKeyPair(account); - if (ownKey != null) { + if (ownKey != null) { + return ownKey; + } else { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve own IdentityKeyPair"); + ownKey = generateIdentityKeyPair(); + mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownKey); + } return ownKey; - } else { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve own IdentityKeyPair"); - ownKey = generateIdentityKeyPair(); - mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownKey); } - return ownKey; } private int loadRegistrationId() { diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index a86002862..e29ebb52c 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -655,23 +655,6 @@ public class Conversation extends AbstractEntity implements Blockable { return this.nextCounterpart; } - private int getMostRecentlyUsedOutgoingEncryption() { - synchronized (this.messages) { - for(int i = this.messages.size() -1; i >= 0; --i) { - final Message m = this.messages.get(i); - if (!m.isCarbon() && m.getStatus() != Message.STATUS_RECEIVED) { - final int e = m.getEncryption(); - if (e == Message.ENCRYPTION_DECRYPTED || e == Message.ENCRYPTION_DECRYPTION_FAILED) { - return Message.ENCRYPTION_PGP; - } else { - return e; - } - } - } - } - return Message.ENCRYPTION_NONE; - } - private int getMostRecentlyUsedIncomingEncryption() { synchronized (this.messages) { for(int i = this.messages.size() -1; i >= 0; --i) { @@ -700,12 +683,8 @@ public class Conversation extends AbstractEntity implements Blockable { && getAccount().getSelfContact().getPresences().allOrNonSupport(AxolotlService.PEP_DEVICE_LIST_NOTIFY) && getContact().getPresences().allOrNonSupport(AxolotlService.PEP_DEVICE_LIST_NOTIFY)) { return Message.ENCRYPTION_AXOLOTL; - } - int outgoing = this.getMostRecentlyUsedOutgoingEncryption(); - if (outgoing == Message.ENCRYPTION_NONE) { - next = this.getMostRecentlyUsedIncomingEncryption(); } else { - next = outgoing; + next = this.getMostRecentlyUsedIncomingEncryption(); } } diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 225d05315..818ac1d65 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -492,6 +492,9 @@ public class Message extends AbstractEntity { Message current = this; while(current.mergeable(current.next())) { current = current.next(); + if (current == null) { + break; + } body.append(MERGE_SEPARATOR); body.append(current.getBody().trim()); } @@ -507,6 +510,9 @@ public class Message extends AbstractEntity { Message current = this; while(current.mergeable(current.next())) { current = current.next(); + if (current == null) { + break; + } status = current.status; } return status; @@ -517,6 +523,9 @@ public class Message extends AbstractEntity { Message current = this; while(current.mergeable(current.next())) { current = current.next(); + if (current == null) { + break; + } time = current.timeSent; } return time; diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index 1309956eb..d4a15ea7a 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -5,9 +5,7 @@ import android.annotation.SuppressLint; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.Set; import eu.siacs.conversations.R; @@ -28,6 +26,19 @@ public class MucOptions { this.self = user; } + public void changeAffiliation(Jid jid, Affiliation affiliation) { + User user = findUserByRealJid(jid); + synchronized (users) { + if (user != null && user.getRole() == Role.NONE) { + users.remove(user); + if (affiliation.ranks(Affiliation.MEMBER)) { + user.affiliation = affiliation; + users.add(user); + } + } + } + } + public enum Affiliation { OWNER("owner", 4, R.string.owner), ADMIN("admin", 3, R.string.admin), @@ -124,10 +135,10 @@ public class MucOptions { } - public static class User { + public static class User implements Comparable<User> { private Role role = Role.NONE; private Affiliation affiliation = Affiliation.NONE; - private Jid jid; + private Jid realJid; private Jid fullJid; private long pgpKeyId = 0; private Avatar avatar; @@ -139,15 +150,11 @@ public class MucOptions { } public String getName() { - return this.fullJid.getResourcepart(); + return fullJid == null ? null : fullJid.getResourcepart(); } - public void setJid(Jid jid) { - this.jid = jid; - } - - public Jid getJid() { - return this.jid; + public void setRealJid(Jid jid) { + this.realJid = jid != null ? jid.toBareJid() : null; } public Role getRole() { @@ -155,6 +162,10 @@ public class MucOptions { } public void setRole(String role) { + if (role == null) { + this.role = Role.NONE; + return; + } role = role.toLowerCase(); switch (role) { case "moderator": @@ -172,26 +183,15 @@ public class MucOptions { } } - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } else if (!(other instanceof User)) { - return false; - } else { - User o = (User) other; - return getName() != null && getName().equals(o.getName()) - && jid != null && jid.equals(o.jid) - && affiliation == o.affiliation - && role == o.role; - } - } - public Affiliation getAffiliation() { return this.affiliation; } public void setAffiliation(String affiliation) { + if (affiliation == null) { + this.affiliation = Affiliation.NONE; + return; + } affiliation = affiliation.toLowerCase(); switch (affiliation) { case "admin": @@ -220,7 +220,13 @@ public class MucOptions { } public Contact getContact() { - return getAccount().getRoster().getContactFromRoster(getJid()); + if (fullJid != null) { + return getAccount().getRoster().getContactFromRoster(realJid); + } else if (realJid != null){ + return getAccount().getRoster().getContact(realJid); + } else { + return null; + } } public boolean setAvatar(Avatar avatar) { @@ -243,11 +249,62 @@ public class MucOptions { public Jid getFullJid() { return fullJid; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + User user = (User) o; + + if (role != user.role) return false; + if (affiliation != user.affiliation) return false; + if (realJid != null ? !realJid.equals(user.realJid) : user.realJid != null) + return false; + return fullJid != null ? fullJid.equals(user.fullJid) : user.fullJid == null; + + } + + @Override + public int hashCode() { + int result = role != null ? role.hashCode() : 0; + result = 31 * result + (affiliation != null ? affiliation.hashCode() : 0); + result = 31 * result + (realJid != null ? realJid.hashCode() : 0); + result = 31 * result + (fullJid != null ? fullJid.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "[fulljid:"+String.valueOf(fullJid)+",realjid:"+String.valueOf(realJid)+",affiliation"+affiliation.toString()+"]"; + } + + public boolean realJidMatchesAccount() { + return realJid != null && realJid.equals(options.account.getJid().toBareJid()); + } + + @Override + public int compareTo(User another) { + Contact ourContact = getContact(); + Contact anotherContact = another.getContact(); + if (ourContact != null && anotherContact != null) { + return ourContact.compareTo(anotherContact); + } else if (ourContact == null && anotherContact != null) { + return getName().compareToIgnoreCase(anotherContact.getDisplayName()); + } else if (ourContact != null) { + return ourContact.getDisplayName().compareToIgnoreCase(another.getName()); + } else { + return getName().compareToIgnoreCase(another.getName()); + } + } + + public Jid getRealJid() { + return realJid; + } } private Account account; - private final Map<String, User> users = Collections.synchronizedMap(new LinkedHashMap<String, User>()); - private final Set<Jid> members = Collections.synchronizedSet(new HashSet<Jid>()); + private final Set<User> users = new HashSet<>(); private final List<String> features = new ArrayList<>(); private Data form = new Data(); private Conversation conversation; @@ -315,20 +372,82 @@ public class MucOptions { return hasFeature("muc_moderated"); } - public User deleteUser(String name) { - return this.users.remove(name); + public User deleteUser(Jid jid) { + User user = findUserByFullJid(jid); + if (user != null) { + synchronized (users) { + users.remove(user); + if (user.affiliation.ranks(Affiliation.MEMBER) && user.realJid != null) { + user.role = Role.NONE; + user.avatar = null; + user.fullJid = null; + users.add(user); + } + } + } + return user; } public void addUser(User user) { - this.users.put(user.getName(), user); + User old; + if (user.fullJid == null && user.realJid != null) { + old = findUserByRealJid(user.realJid); + if (old != null) { + if (old.fullJid != null) { + return; //don't add. user already exists + } else { + synchronized (users) { + users.remove(old); + } + } + } + } else if (user.realJid != null) { + old = findUserByRealJid(user.realJid); + synchronized (users) { + if (old != null && old.fullJid == null) { + users.remove(old); + } + } + } + old = findUserByFullJid(user.getFullJid()); + synchronized (this.users) { + if (old != null) { + users.remove(old); + } + this.users.add(user); + } } - public User findUser(String name) { - return this.users.get(name); + public User findUserByFullJid(Jid jid) { + if (jid == null) { + return null; + } + synchronized (users) { + for (User user : users) { + if (jid.equals(user.getFullJid())) { + return user; + } + } + } + return null; } - public boolean isUserInRoom(String name) { - return findUser(name) != null; + public User findUserByRealJid(Jid jid) { + if (jid == null) { + return null; + } + synchronized (users) { + for (User user : users) { + if (jid.equals(user.realJid)) { + return user; + } + } + } + return null; + } + + public boolean isUserInRoom(Jid jid) { + return findUserByFullJid(jid) != null; } public void setError(Error error) { @@ -341,7 +460,23 @@ public class MucOptions { } public ArrayList<User> getUsers() { - return new ArrayList<>(users.values()); + return getUsers(true); + } + + public ArrayList<User> getUsers(boolean includeOffline) { + synchronized (users) { + if (includeOffline) { + return new ArrayList<>(users); + } else { + ArrayList<User> onlineUsers = new ArrayList<>(); + for (User user : users) { + if (user.getRole().ranks(Role.PARTICIPANT)) { + onlineUsers.add(user); + } + } + return onlineUsers; + } + } } public List<User> getUsers(int max) { @@ -350,7 +485,9 @@ public class MucOptions { } public int getUserCount() { - return this.users.size(); + synchronized (users) { + return users.size(); + } } public String getProposedNick() { @@ -386,7 +523,9 @@ public class MucOptions { } public void setOffline() { - this.users.clear(); + synchronized (users) { + this.users.clear(); + } this.error = Error.NO_RESPONSE; this.isOnline = false; } @@ -410,7 +549,7 @@ public class MucOptions { Contact contact = user.getContact(); if (contact != null && !contact.getDisplayName().isEmpty()) { names.add(contact.getDisplayName().split("\\s+")[0]); - } else { + } else if (user.getName() != null){ names.add(user.getName()); } } @@ -429,7 +568,7 @@ public class MucOptions { public long[] getPgpKeyIds() { List<Long> ids = new ArrayList<>(); - for (User user : this.users.values()) { + for (User user : this.users) { if (user.getPgpKeyId() != 0) { ids.add(user.getPgpKeyId()); } @@ -443,18 +582,22 @@ public class MucOptions { } public boolean pgpKeysInUse() { - for (User user : this.users.values()) { - if (user.getPgpKeyId() != 0) { - return true; + synchronized (users) { + for (User user : users) { + if (user.getPgpKeyId() != 0) { + return true; + } } } return false; } public boolean everybodyHasKeys() { - for (User user : this.users.values()) { - if (user.getPgpKeyId() == 0) { - return false; + synchronized (users) { + for (User user : users) { + if (user.getPgpKeyId() == 0) { + return false; + } } } return true; @@ -468,12 +611,12 @@ public class MucOptions { } } - public Jid getTrueCounterpart(String name) { - if (name.equals(getSelf().getName())) { + public Jid getTrueCounterpart(Jid jid) { + if (jid.equals(getSelf().getFullJid())) { return account.getJid().toBareJid(); } - User user = findUser(name); - return user == null ? null : user.getJid(); + User user = findUserByFullJid(jid); + return user == null ? null : user.realJid; } public String getPassword() { @@ -499,11 +642,15 @@ public class MucOptions { return this.conversation; } - public void putMember(Jid jid) { - members.add(jid); - } - public List<Jid> getMembers() { - return new ArrayList<>(members); + ArrayList<Jid> members = new ArrayList<>(); + synchronized (users) { + for (User user : users) { + if (user.affiliation.ranks(Affiliation.MEMBER) && user.realJid != null) { + members.add(user.realJid); + } + } + } + return members; } } diff --git a/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java b/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java index 9accec95d..9ee1d180e 100644 --- a/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java +++ b/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java @@ -146,7 +146,7 @@ public class ServiceDiscoveryResult { this.hash = hash; this.ver = ver; - JSONArray identities = o.optJSONArray("identites"); + JSONArray identities = o.optJSONArray("identities"); if (identities != null) { for (int i = 0; i < identities.length(); i++) { this.identities.add(new Identity(identities.getJSONObject(i))); @@ -158,6 +158,54 @@ public class ServiceDiscoveryResult { this.features.add(features.getString(i)); } } + JSONArray forms = o.optJSONArray("forms"); + if (forms != null) { + for(int i = 0; i < forms.length(); i++) { + this.forms.add(createFormFromJSONObject(forms.getJSONObject(i))); + } + } + } + + private static Data createFormFromJSONObject(JSONObject o) { + Data data = new Data(); + JSONArray names = o.names(); + for(int i = 0; i < names.length(); ++i) { + try { + String name = names.getString(i); + JSONArray jsonValues = o.getJSONArray(name); + ArrayList<String> values = new ArrayList<>(jsonValues.length()); + for(int j = 0; j < jsonValues.length(); ++j) { + values.add(jsonValues.getString(j)); + } + data.put(name, values); + } catch (Exception e) { + e.printStackTrace(); + } + } + return data; + } + + private static JSONObject createJSONFromForm(Data data) { + JSONObject object = new JSONObject(); + for(Field field : data.getFields()) { + try { + JSONArray jsonValues = new JSONArray(); + for(String value : field.getValues()) { + jsonValues.put(value); + } + object.put(field.getFieldName(), jsonValues); + } catch(Exception e) { + e.printStackTrace(); + } + } + try { + JSONArray jsonValues = new JSONArray(); + jsonValues.put(data.getFormType()); + object.put(Data.FORM_TYPE, jsonValues); + } catch(Exception e) { + e.printStackTrace(); + } + return object; } public String getVer() { @@ -274,10 +322,16 @@ public class ServiceDiscoveryResult { for(Identity id : this.getIdentities()) { ids.put(id.toJSON()); } - o.put("identites", ids); + o.put("identities", ids); o.put("features", new JSONArray(this.getFeatures())); + JSONArray forms = new JSONArray(); + for(Data data : this.forms) { + forms.put(createJSONFromForm(data)); + } + o.put("forms", forms); + return o; } catch(JSONException e) { return null; diff --git a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java index 7370465c8..c14a7eb0d 100644 --- a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java +++ b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java @@ -8,8 +8,11 @@ import java.util.Locale; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; @@ -70,4 +73,24 @@ public abstract class AbstractParser { } return item.findChildContent("data", "urn:xmpp:avatar:data"); } + + public static MucOptions.User parseItem(Conversation conference, Element item) { + final String local = conference.getJid().getLocalpart(); + final String domain = conference.getJid().getDomainpart(); + String affiliation = item.getAttribute("affiliation"); + String role = item.getAttribute("role"); + String nick = item.getAttribute("nick"); + Jid fullJid; + try { + fullJid = nick != null ? Jid.fromParts(local, domain, nick) : null; + } catch (InvalidJidException e) { + fullJid = null; + } + Jid realJid = item.getAttributeAsJid("jid"); + MucOptions.User user = new MucOptions.User(conference.getMucOptions(), nick == null ? null : fullJid); + user.setRealJid(realJid); + user.setAffiliation(affiliation); + user.setRole(role); + return user; + } } diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java index afbc04124..189df4a7b 100644 --- a/src/main/java/eu/siacs/conversations/parser/IqParser.java +++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java @@ -54,6 +54,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { final String name = item.getAttribute("name"); final String subscription = item.getAttribute("subscription"); final Contact contact = account.getRoster().getContact(jid); + boolean bothPre = contact.getOption(Contact.Options.TO) && contact.getOption(Contact.Options.FROM); if (!contact.getOption(Contact.Options.DIRTY_PUSH)) { contact.setServerName(name); contact.parseGroupsFromElement(item); @@ -69,6 +70,14 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { contact.parseSubscriptionFromElement(item); } } + boolean both = contact.getOption(Contact.Options.TO) && contact.getOption(Contact.Options.FROM); + if ((both != bothPre) && both) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": gained mutual presence subscription with "+contact.getJid()); + AxolotlService axolotlService = account.getAxolotlService(); + if (axolotlService != null) { + axolotlService.clearErrorsInFetchStatusMap(contact.getJid()); + } + } mXmppConnectionService.getAvatarService().clear(contact); } } @@ -268,7 +277,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { @Override public void onIqPacketReceived(final Account account, final IqPacket packet) { - if (Config.EXTENDED_IQ_LOGGING && (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET)) { + if (Config.BACKGROUND_STANZA_LOGGING && (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET)) { Element first = packet.getChildren().size() > 0 ? packet.getChildren().get(0) : null; Log.d(Config.LOGTAG,account.getJid().toBareJid()+": IQ request from "+packet.getFrom()+(first == null ? "" : " "+first)); } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 3d07c0fad..cd1c55986 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -402,7 +402,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } else if (axolotlEncrypted != null && Config.supportOmemo()) { Jid origin; if (conversation.getMode() == Conversation.MODE_MULTI) { - origin = conversation.getMucOptions().getTrueCounterpart(counterpart.getResourcepart()); + origin = conversation.getMucOptions().getTrueCounterpart(counterpart); if (origin == null) { Log.d(Config.LOGTAG,"axolotl message in non anonymous conference received"); return; @@ -430,7 +430,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece message.setOob(isOob); message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0"); if (conversation.getMode() == Conversation.MODE_MULTI) { - Jid trueCounterpart = conversation.getMucOptions().getTrueCounterpart(counterpart.getResourcepart()); + Jid trueCounterpart = conversation.getMucOptions().getTrueCounterpart(counterpart); message.setTrueCounterpart(trueCounterpart); if (trueCounterpart != null) { updateLastseen(timestamp, account, trueCounterpart, false); @@ -536,8 +536,11 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } } } else if (!packet.hasChild("body")){ //no body + if (Config.BACKGROUND_STANZA_LOGGING && !mXmppConnectionService.checkListeners()) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": " + original); + } + Conversation conversation = mXmppConnectionService.find(account, from.toBareJid()); if (isTypeGroupChat) { - Conversation conversation = mXmppConnectionService.find(account, from.toBareJid()); if (packet.hasChild("subject")) { if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { conversation.setHasMessagesLeftOnServer(conversation.countMessages() > 0); @@ -553,18 +556,33 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece return; } } - - if (conversation != null && isMucStatusMessage) { + } + if (conversation != null && mucUserElement != null && from.isBareJid()) { + if (mucUserElement.hasChild("status")) { for (Element child : mucUserElement.getChildren()) { if (child.getName().equals("status") && MucOptions.STATUS_CODE_ROOM_CONFIG_CHANGED.equals(child.getAttribute("code"))) { mXmppConnectionService.fetchConferenceConfiguration(conversation); } } + } else if (mucUserElement.hasChild("item")) { + for(Element child : mucUserElement.getChildren()) { + if ("item".equals(child.getName())) { + MucOptions.User user = AbstractParser.parseItem(conversation,child); + Log.d(Config.LOGTAG,account.getJid()+": changing affiliation for " + +user.getRealJid()+" to "+user.getAffiliation()+" in " + +conversation.getJid().toBareJid()); + if (!user.realJidMatchesAccount()) { + conversation.getMucOptions().addUser(user); + } + } + } } } } + + Element received = packet.findChild("received", "urn:xmpp:chat-markers:0"); if (received == null) { received = packet.findChild("received", "urn:xmpp:receipts"); diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java index 7664dee61..ae5d576a6 100644 --- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java +++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java @@ -14,15 +14,12 @@ import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.Presence; -import eu.siacs.conversations.entities.ServiceDiscoveryResult; import eu.siacs.conversations.generator.PresenceGenerator; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.OnPresencePacketReceived; import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.pep.Avatar; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; import eu.siacs.conversations.xmpp.stanzas.PresencePacket; public class PresenceParser extends AbstractParser implements @@ -67,11 +64,7 @@ public class PresenceParser extends AbstractParser implements MucOptions.User user = new MucOptions.User(mucOptions, from); user.setAffiliation(item.getAttribute("affiliation")); user.setRole(item.getAttribute("role")); - Jid real = item.getAttributeAsJid("jid"); - if (real != null) { - user.setJid(real); - mucOptions.putMember(real.toBareJid()); - } + user.setRealJid(item.getAttributeAsJid("jid")); if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE) || packet.getFrom().equals(mucOptions.getConversation().getJid())) { mucOptions.setOnline(); mucOptions.setSelf(user); @@ -127,7 +120,7 @@ public class PresenceParser extends AbstractParser implements Log.d(Config.LOGTAG, "unknown error in conference: " + packet); } } else if (!from.isBareJid()){ - MucOptions.User user = mucOptions.deleteUser(from.getResourcepart()); + MucOptions.User user = mucOptions.deleteUser(from); if (user != null) { mXmppConnectionService.getAvatarService().clear(user); } @@ -147,7 +140,7 @@ public class PresenceParser extends AbstractParser implements } else if (error != null && error.hasChild("forbidden")) { mucOptions.setError(MucOptions.Error.BANNED); } else if (error != null && error.hasChild("registration-required")) { - mucOptions.setError(MucOptions.Error.BANNED); + mucOptions.setError(MucOptions.Error.MEMBERS_ONLY); } } } diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index dde385bd9..f1155b07d 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -52,7 +52,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { private static DatabaseBackend instance = null; private static final String DATABASE_NAME = "history"; - private static final int DATABASE_VERSION = 26; + private static final int DATABASE_VERSION = 27; private static String CREATE_CONTATCS_STATEMENT = "create table " + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " @@ -402,6 +402,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (oldVersion < 26 && newVersion >= 26) { db.execSQL(CREATE_PRESENCE_TEMPLATES_STATEMENT); } + + if (oldVersion < 27 && newVersion >= 27) { + db.execSQL("DELETE FROM "+ServiceDiscoveryResult.TABLENAME); + } } public static synchronized DatabaseBackend getInstance(Context context) { diff --git a/src/main/java/eu/siacs/conversations/services/AvatarService.java b/src/main/java/eu/siacs/conversations/services/AvatarService.java index 4cf9ade14..0ab0db2de 100644 --- a/src/main/java/eu/siacs/conversations/services/AvatarService.java +++ b/src/main/java/eu/siacs/conversations/services/AvatarService.java @@ -246,7 +246,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded { if (c != null && (c.getProfilePhoto() != null || c.getAvatar() != null)) { return get(c, size, cachedOnly); } else if (message.getConversation().getMode() == Conversation.MODE_MULTI){ - MucOptions.User user = conversation.getMucOptions().findUser(message.getCounterpart().getResourcepart()); + MucOptions.User user = conversation.getMucOptions().findUserByFullJid(message.getCounterpart()); if (user != null) { return getImpl(user,size,cachedOnly); } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 18c72d0ca..5d1f87fb6 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -83,6 +83,7 @@ import eu.siacs.conversations.generator.IqGenerator; import eu.siacs.conversations.generator.MessageGenerator; import eu.siacs.conversations.generator.PresenceGenerator; import eu.siacs.conversations.http.HttpConnectionManager; +import eu.siacs.conversations.parser.AbstractParser; import eu.siacs.conversations.parser.IqParser; import eu.siacs.conversations.parser.MessageParser; import eu.siacs.conversations.parser.PresenceParser; @@ -1762,7 +1763,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } - private boolean checkListeners() { + public boolean checkListeners() { return (this.mOnAccountUpdate == null && this.mOnConversationUpdate == null && this.mOnRosterUpdate == null @@ -1894,11 +1895,15 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa @Override public void onIqPacketReceived(Account account, IqPacket packet) { + Element query = packet.query("http://jabber.org/protocol/muc#admin"); if (packet.getType() == IqPacket.TYPE.RESULT && query != null) { for(Element child : query.getChildren()) { if ("item".equals(child.getName())) { - conversation.getMucOptions().putMember(child.getAttributeAsJid("jid")); + MucOptions.User user = AbstractParser.parseItem(conversation,child); + if (!user.realJidMatchesAccount()) { + conversation.getMucOptions().addUser(user); + } } } } else { @@ -2170,13 +2175,15 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } - public void changeAffiliationInConference(final Conversation conference, Jid user, MucOptions.Affiliation affiliation, final OnAffiliationChanged callback) { + public void changeAffiliationInConference(final Conversation conference, Jid user, final MucOptions.Affiliation affiliation, final OnAffiliationChanged callback) { final Jid jid = user.toBareJid(); IqPacket request = this.mIqGenerator.changeAffiliation(conference, jid, affiliation.toString()); sendIqPacket(conference.getAccount(), request, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { if (packet.getType() == IqPacket.TYPE.RESULT) { + conference.getMucOptions().changeAffiliation(jid, affiliation); + getAvatarService().clear(conference); callback.onAffiliationChangedSuccessful(jid); } else { callback.onAffiliationChangeFailed(jid, R.string.could_not_change_affiliation); @@ -2188,8 +2195,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public void changeAffiliationsInConference(final Conversation conference, MucOptions.Affiliation before, MucOptions.Affiliation after) { List<Jid> jids = new ArrayList<>(); for (MucOptions.User user : conference.getMucOptions().getUsers()) { - if (user.getAffiliation() == before && user.getJid() != null) { - jids.add(user.getJid()); + if (user.getAffiliation() == before && user.getRealJid() != null) { + jids.add(user.getRealJid()); } } IqPacket request = this.mIqGenerator.changeAffiliation(conference, jids, after.toString()); @@ -2570,7 +2577,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } else { Conversation conversation = find(account, avatar.owner.toBareJid()); if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { - MucOptions.User user = conversation.getMucOptions().findUser(avatar.owner.getResourcepart()); + MucOptions.User user = conversation.getMucOptions().findUserByFullJid(avatar.owner); if (user != null) { if (user.setAvatar(avatar)) { getAvatarService().clear(user); @@ -3169,7 +3176,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } - private ServiceDiscoveryResult getCachedServiceDiscoveryResult(Pair<String,String> key) { + public ServiceDiscoveryResult getCachedServiceDiscoveryResult(Pair<String, String> key) { ServiceDiscoveryResult result = discoCache.get(key); if (result != null) { return result; diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index 0ddc8b3d8..ee6d9417c 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -1,6 +1,5 @@ package eu.siacs.conversations.ui; -import android.annotation.TargetApi; import android.app.AlertDialog; import android.app.PendingIntent; import android.content.ActivityNotFoundException; @@ -8,7 +7,6 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentSender.SendIntentException; -import android.os.Build; import android.os.Bundle; import android.view.ContextMenu; import android.view.LayoutInflater; @@ -366,13 +364,13 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers final Contact contact = user.getContact(); if (contact != null) { name = contact.getDisplayName(); - } else if (user.getJid() != null){ - name = user.getJid().toBareJid().toString(); + } else if (user.getRealJid() != null){ + name = user.getRealJid().toBareJid().toString(); } else { name = user.getName(); } menu.setHeaderTitle(name); - if (user.getJid() != null) { + if (user.getRealJid() != null) { MenuItem showContactDetails = menu.findItem(R.id.action_contact_details); MenuItem startConversation = menu.findItem(R.id.start_conversation); MenuItem giveMembership = menu.findItem(R.id.give_membership); @@ -425,23 +423,25 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers startConversation(mSelectedUser); return true; case R.id.give_admin_privileges: - xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getJid(), MucOptions.Affiliation.ADMIN,this); + xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getRealJid(), MucOptions.Affiliation.ADMIN,this); return true; case R.id.give_membership: - xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getJid(), MucOptions.Affiliation.MEMBER,this); + xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getRealJid(), MucOptions.Affiliation.MEMBER,this); return true; case R.id.remove_membership: - xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getJid(), MucOptions.Affiliation.NONE,this); + xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getRealJid(), MucOptions.Affiliation.NONE,this); return true; case R.id.remove_admin_privileges: - xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getJid(), MucOptions.Affiliation.MEMBER,this); + xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getRealJid(), MucOptions.Affiliation.MEMBER,this); return true; case R.id.remove_from_room: removeFromRoom(mSelectedUser); return true; case R.id.ban_from_conference: - xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getJid(), MucOptions.Affiliation.OUTCAST,this); - xmppConnectionService.changeRoleInConference(mConversation,mSelectedUser.getName(), MucOptions.Role.NONE,this); + xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getRealJid(), MucOptions.Affiliation.OUTCAST,this); + if (mSelectedUser.getRole() != MucOptions.Role.NONE) { + xmppConnectionService.changeRoleInConference(mConversation, mSelectedUser.getName(), MucOptions.Role.NONE, this); + } return true; case R.id.send_private_message: privateMsgInMuc(mConversation,mSelectedUser.getName()); @@ -453,8 +453,10 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers private void removeFromRoom(final User user) { if (mConversation.getMucOptions().membersOnly()) { - xmppConnectionService.changeAffiliationInConference(mConversation,user.getJid(), MucOptions.Affiliation.NONE,this); - xmppConnectionService.changeRoleInConference(mConversation,mSelectedUser.getName(), MucOptions.Role.NONE,ConferenceDetailsActivity.this); + xmppConnectionService.changeAffiliationInConference(mConversation,user.getRealJid(), MucOptions.Affiliation.NONE,this); + if (user.getRole() != MucOptions.Role.NONE) { + xmppConnectionService.changeRoleInConference(mConversation, mSelectedUser.getName(), MucOptions.Role.NONE, ConferenceDetailsActivity.this); + } } else { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.ban_from_conference); @@ -463,8 +465,10 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers builder.setPositiveButton(R.string.ban_now,new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - xmppConnectionService.changeAffiliationInConference(mConversation,user.getJid(), MucOptions.Affiliation.OUTCAST,ConferenceDetailsActivity.this); - xmppConnectionService.changeRoleInConference(mConversation,mSelectedUser.getName(), MucOptions.Role.NONE,ConferenceDetailsActivity.this); + xmppConnectionService.changeAffiliationInConference(mConversation,user.getRealJid(), MucOptions.Affiliation.OUTCAST,ConferenceDetailsActivity.this); + if (user.getRole() != MucOptions.Role.NONE) { + xmppConnectionService.changeRoleInConference(mConversation, mSelectedUser.getName(), MucOptions.Role.NONE, ConferenceDetailsActivity.this); + } } }); builder.create().show(); @@ -472,8 +476,8 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } protected void startConversation(User user) { - if (user.getJid() != null) { - Conversation conversation = xmppConnectionService.findOrCreateConversation(this.mConversation.getAccount(),user.getJid().toBareJid(),false); + if (user.getRealJid() != null) { + Conversation conversation = xmppConnectionService.findOrCreateConversation(this.mConversation.getAccount(),user.getRealJid().toBareJid(),false); switchToConversation(conversation); } } @@ -576,12 +580,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); membersView.removeAllViews(); final ArrayList<User> users = mucOptions.getUsers(); - Collections.sort(users,new Comparator<User>() { - @Override - public int compare(User lhs, User rhs) { - return lhs.getName().compareToIgnoreCase(rhs.getName()); - } - }); + Collections.sort(users); for (final User user : users) { View view = inflater.inflate(R.layout.contact, membersView,false); this.setListItemBackgroundOnView(view); @@ -608,11 +607,12 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers tvKey.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId())); } Contact contact = user.getContact(); + String name = user.getName(); if (contact != null) { tvDisplayName.setText(contact.getDisplayName()); - tvStatus.setText(user.getName() + " \u2022 " + getStatus(user)); + tvStatus.setText((name != null ? name+ " \u2022 " : "") + getStatus(user)); } else { - tvDisplayName.setText(user.getName()); + tvDisplayName.setText(name == null ? "" : name); tvStatus.setText(getStatus(user)); } @@ -658,7 +658,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers @Override public void onAffiliationChangedSuccessful(Jid jid) { - + refreshUi(); } @Override diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 6d3571492..04c3d82df 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -472,12 +472,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa public void onContactPictureClicked(Message message) { if (message.getStatus() <= Message.STATUS_RECEIVED) { if (message.getConversation().getMode() == Conversation.MODE_MULTI) { - if (message.getCounterpart() != null) { - String user = message.getCounterpart().isBareJid() ? message.getCounterpart().toString() : message.getCounterpart().getResourcepart(); + Jid user = message.getCounterpart(); + if (user != null && !user.isBareJid()) { if (!message.getConversation().getMucOptions().isUserInRoom(user)) { - Toast.makeText(activity,activity.getString(R.string.user_has_left_conference,user),Toast.LENGTH_SHORT).show(); + Toast.makeText(activity,activity.getString(R.string.user_has_left_conference,user.getResourcepart()),Toast.LENGTH_SHORT).show(); } - highlightInConference(user); + highlightInConference(user.getResourcepart()); } } else { if (!message.getContact().isSelf()) { @@ -506,14 +506,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa public void onContactPictureLongClicked(Message message) { if (message.getStatus() <= Message.STATUS_RECEIVED) { if (message.getConversation().getMode() == Conversation.MODE_MULTI) { - if (message.getCounterpart() != null) { - String user = message.getCounterpart().getResourcepart(); - if (user != null) { - if (message.getConversation().getMucOptions().isUserInRoom(user)) { - privateMessageWith(message.getCounterpart()); - } else { - Toast.makeText(activity, activity.getString(R.string.user_has_left_conference, user), Toast.LENGTH_SHORT).show(); - } + Jid user = message.getCounterpart(); + if (user != null && !user.isBareJid()) { + if (message.getConversation().getMucOptions().isUserInRoom(user)) { + privateMessageWith(user); + } else { + Toast.makeText(activity, activity.getString(R.string.user_has_left_conference, user.getResourcepart()), Toast.LENGTH_SHORT).show(); } } } diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 57d3f9550..15701c950 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -507,6 +507,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate final MenuItem showBlocklist = menu.findItem(R.id.action_show_block_list); final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more); final MenuItem changePassword = menu.findItem(R.id.action_change_password_on_server); + final MenuItem showPassword = menu.findItem(R.id.action_show_password); final MenuItem clearDevices = menu.findItem(R.id.action_clear_devices); final MenuItem renewCertificate = menu.findItem(R.id.action_renew_certificate); final MenuItem mamPrefs = menu.findItem(R.id.action_mam_prefs); @@ -535,6 +536,13 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate mamPrefs.setVisible(false); changePresence.setVisible(false); } + + if (mAccount != null) { + showPassword.setVisible(mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) + && !mAccount.isOptionSet(Account.OPTION_REGISTER)); + } else { + showPassword.setVisible(false); + } return super.onCreateOptionsMenu(menu); } @@ -651,6 +659,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate case R.id.action_change_presence: changePresence(); break; + case R.id.action_show_password: + showPassword(); + break; } return super.onOptionsItemSelected(item); } @@ -909,6 +920,17 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate xmppConnectionService.fetchMamPreferences(mAccount, this); } + private void showPassword() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + View view = getLayoutInflater().inflate(R.layout.dialog_show_password, null); + TextView password = (TextView) view.findViewById(R.id.password); + password.setText(mAccount.getPassword()); + builder.setTitle(R.string.password); + builder.setView(view); + builder.setPositiveButton(R.string.cancel, null); + builder.create().show(); + } + @Override public void onKeyStatusUpdated(AxolotlService.FetchStatus report) { refreshUi(); diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 5d249d863..f3385b950 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -485,8 +485,8 @@ public abstract class XmppActivity extends Activity { ChooseContactActivity.class); List<String> contacts = new ArrayList<>(); if (conversation.getMode() == Conversation.MODE_MULTI) { - for (MucOptions.User user : conversation.getMucOptions().getUsers()) { - Jid jid = user.getJid(); + for (MucOptions.User user : conversation.getMucOptions().getUsers(false)) { + Jid jid = user.getRealJid(); if (jid != null) { contacts.add(jid.toBareJid().toString()); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index c4d5bfdf1..b42be17b9 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -1040,7 +1040,7 @@ public class XmppConnection implements Runnable { final String ver = caps == null ? null : caps.getAttribute("ver"); ServiceDiscoveryResult discoveryResult = null; if (hash != null && ver != null) { - discoveryResult = mXmppConnectionService.databaseBackend.findDiscoveryResult(hash, ver); + discoveryResult = mXmppConnectionService.getCachedServiceDiscoveryResult(new Pair<>(hash, ver)); } if (discoveryResult == null) { sendServiceDiscoveryInfo(account.getServer()); @@ -1435,7 +1435,7 @@ public class XmppConnection implements Runnable { } public int getTimeToNextAttempt() { - final int interval = (int) (25 * Math.pow(1.5, attempt)); + final int interval = Math.min((int) (25 * Math.pow(1.3, attempt)), 300); final int secondsSinceLast = (int) ((SystemClock.elapsedRealtime() - this.lastConnect) / 1000); return interval - secondsSinceLast; } diff --git a/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java b/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java index 8dabcb5be..c81c5a1f3 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java +++ b/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java @@ -9,7 +9,7 @@ import eu.siacs.conversations.xml.Element; public class Data extends Element { - private static final String FORM_TYPE = "FORM_TYPE"; + public static final String FORM_TYPE = "FORM_TYPE"; public Data() { super("x"); diff --git a/src/main/res/layout/dialog_show_password.xml b/src/main/res/layout/dialog_show_password.xml new file mode 100644 index 000000000..dba0690cb --- /dev/null +++ b/src/main/res/layout/dialog_show_password.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:padding="16dp" > + + <TextView + android:id="@+id/password" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="monospace" + android:textSize="?attr/TextSizeHeadline" + android:fontFamily="monospace" + android:layout_gravity="center_horizontal" + android:textColor="@color/black87" > + </TextView> + +</LinearLayout>
\ No newline at end of file diff --git a/src/main/res/layout/message_received.xml b/src/main/res/layout/message_received.xml index 14aea096f..60a566187 100644 --- a/src/main/res/layout/message_received.xml +++ b/src/main/res/layout/message_received.xml @@ -59,6 +59,7 @@ android:autoLink="web" android:textColorLink="@color/dark" android:textColor="@color/dark" + android:maxLines="256" android:textColorHighlight="@color/grey800" android:textSize="?attr/TextSizeBody" /> diff --git a/src/main/res/layout/message_sent.xml b/src/main/res/layout/message_sent.xml index 2617c1212..26c24509e 100644 --- a/src/main/res/layout/message_sent.xml +++ b/src/main/res/layout/message_sent.xml @@ -60,6 +60,7 @@ android:autoLink="web" android:textColorLink="@color/black54" android:textColor="@color/black54" + android:maxLines="256" android:textColorHighlight="@color/grey500" android:textSize="?attr/TextSizeBody" /> diff --git a/src/main/res/menu/editaccount.xml b/src/main/res/menu/editaccount.xml index b28edf1ed..4ff3752f7 100644 --- a/src/main/res/menu/editaccount.xml +++ b/src/main/res/menu/editaccount.xml @@ -37,6 +37,11 @@ android:title="@string/mam_prefs"/> <item + android:id="@+id/action_show_password" + android:showAsAction="never" + android:title="@string/show_password"/> + + <item android:id="@+id/action_change_password_on_server" android:showAsAction="always" android:icon="@drawable/ic_vpn_key_white_24dp" diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index ce626b760..5185f37aa 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -419,7 +419,7 @@ <string name="password_should_not_be_empty">Password should not be empty</string> <string name="perform_action_with">Perform action with</string> <string name="no_affiliation">No affiliation</string> - <string name="no_role">No role</string> + <string name="no_role">Offline</string> <string name="outcast">Outcast</string> <string name="member">Member</string> <string name="advanced_mode">Advanced mode</string> @@ -520,7 +520,7 @@ <string name="download_started">Download started</string> <string name="account_status_tor_unavailable">Tor network unavailable</string> <string name="account_status_bind_failure">Bind failure</string> - <string name="account_status_host_unknown">Domain not recognized</string> + <string name="account_status_host_unknown">Host unknown</string> <string name="server_info_broken">Broken</string> <string name="pref_presence_settings">Presence</string> <string name="pref_away_when_screen_off">Away when screen is off</string> @@ -624,13 +624,14 @@ <string name="presence_dnd">Busy</string> <string name="secure_password_generated">A secure password has been generated</string> <string name="device_does_not_support_battery_op">Your device does not support opting out of battery optimization</string> - <string name="share">Share</string> + <string name="share">Share</string> <string name="share_location">Share location</string> - <string name="location_sharing_disabled">Location sharing is disabled in settings</string> - <string name="locating">Locating…</string> + <string name="location_sharing_disabled">Location sharing is disabled in settings</string> + <string name="locating">Locating…</string> <string name="uninstall_plugins">We have integrated the VoiceRecorder and ShareLocation plugin directly into the messenger. You should uninstall the plugins.</string> <string name="uninstall">Uninstall</string> <string name="action_end_conversation_muc">Leave conference</string> <string name="leave_conference_warning">Do you really want to leave this conference? You will no longer be notified of new messages until joining the conference again.</string> + <string name="show_password">Show password</string> </resources> |