diff options
Diffstat (limited to 'src/main/java/eu/siacs')
44 files changed, 2102 insertions, 1414 deletions
diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index f403701c..81a68008 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -31,6 +31,34 @@ public final class Config { public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY / 2; public static final int MAM_MAX_MESSAGES = 500; + public static final String ENABLED_CIPHERS[] = { + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_AES_128_SHA", + "TLS_ECDHE_RSA_AES_256_SHA", + + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA384", + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA256", + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + + "TLS_DHE_RSA_WITH_CAMELLIA_256_SHA", + + // Fallback. + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_128_GCM_SHA384", + "TLS_RSA_WITH_AES_256_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_128_CBC_SHA256", + "TLS_RSA_WITH_AES_128_CBC_SHA384", + "TLS_RSA_WITH_AES_256_CBC_SHA256", + "TLS_RSA_WITH_AES_256_CBC_SHA384", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA", + }; + private Config() { } diff --git a/src/main/java/eu/siacs/conversations/crypto/OtrEngine.java b/src/main/java/eu/siacs/conversations/crypto/OtrEngine.java index 7f7b350c..d5c45465 100644 --- a/src/main/java/eu/siacs/conversations/crypto/OtrEngine.java +++ b/src/main/java/eu/siacs/conversations/crypto/OtrEngine.java @@ -33,6 +33,7 @@ import net.java.otr4j.crypto.OtrCryptoEngineImpl; import net.java.otr4j.crypto.OtrCryptoException; import net.java.otr4j.session.InstanceTag; import net.java.otr4j.session.SessionID; +import net.java.otr4j.session.FragmenterInstructions; public class OtrEngine extends OtrCryptoEngineImpl implements OtrEngineHost { @@ -269,4 +270,9 @@ public class OtrEngine extends OtrCryptoEngineImpl implements OtrEngineHost { } } + @Override + public FragmenterInstructions getFragmenterInstructions(SessionID sessionID) { + return null; + } + } diff --git a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java index 83d9b7b2..ad5a4132 100644 --- a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java +++ b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java @@ -19,6 +19,7 @@ import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.http.HttpConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.UiCallback; import android.app.PendingIntent; @@ -56,9 +57,11 @@ public class PgpEngine { if (message.getEncryption() == Message.ENCRYPTION_PGP) { message.setBody(os.toString()); message.setEncryption(Message.ENCRYPTION_DECRYPTED); - if (message.trusted() && message.bodyContainsDownloadable()) { - mXmppConnectionService.getHttpConnectionManager() - .createNewConnection(message); + final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager(); + if (message.trusted() + && message.bodyContainsDownloadable() + && manager.getAutoAcceptFileSize() > 0) { + manager.createNewConnection(message); } callback.success(message); } diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 470bd290..036acf63 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -108,9 +108,9 @@ public class Conversation extends AbstractEntity implements Blockable { } } - public void findMessagesWithFiles(OnMessageFound onMessageFound) { + public void findMessagesWithFiles(final OnMessageFound onMessageFound) { synchronized (this.messages) { - for (Message message : this.messages) { + for (final Message message : this.messages) { if ((message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) && message.getEncryption() != Message.ENCRYPTION_PGP) { onMessageFound.onMessageFound(message); @@ -119,14 +119,14 @@ public class Conversation extends AbstractEntity implements Blockable { } } - public Message findMessageWithFileAndUuid(String uuid) { + public Message findMessageWithFileAndUuid(final String uuid) { synchronized (this.messages) { - for (Message message : this.messages) { - if (message.getType() == Message.TYPE_IMAGE + for (final Message message : this.messages) { + if ((message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) && message.getEncryption() != Message.ENCRYPTION_PGP && message.getUuid().equals(uuid)) { return message; - } + } } } return null; diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index b5a1897d..8112f5de 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -36,17 +36,19 @@ public class Message extends AbstractEntity { public static final int TYPE_STATUS = 3; public static final int TYPE_PRIVATE = 4; - public static String CONVERSATION = "conversationUuid"; - public static String COUNTERPART = "counterpart"; - public static String TRUE_COUNTERPART = "trueCounterpart"; - public static String BODY = "body"; - public static String TIME_SENT = "timeSent"; - public static String ENCRYPTION = "encryption"; - public static String STATUS = "status"; - public static String TYPE = "type"; - public static String REMOTE_MSG_ID = "remoteMsgId"; - public static String SERVER_MSG_ID = "serverMsgId"; - public static String RELATIVE_FILE_PATH = "relativeFilePath"; + public static final String CONVERSATION = "conversationUuid"; + public static final String COUNTERPART = "counterpart"; + public static final String TRUE_COUNTERPART = "trueCounterpart"; + public static final String BODY = "body"; + public static final String TIME_SENT = "timeSent"; + public static final String ENCRYPTION = "encryption"; + public static final String STATUS = "status"; + public static final String TYPE = "type"; + public static final String REMOTE_MSG_ID = "remoteMsgId"; + public static final String SERVER_MSG_ID = "serverMsgId"; + public static final String RELATIVE_FILE_PATH = "relativeFilePath"; + public static final String ME_COMMAND = "/me "; + public boolean markable = false; protected String conversationUuid; protected Jid counterpart; @@ -310,12 +312,17 @@ public class Message extends AbstractEntity { public boolean equals(Message message) { if (this.serverMsgId != null && message.getServerMsgId() != null) { return this.serverMsgId.equals(message.getServerMsgId()); + } else if (this.body == null || this.counterpart == null) { + return false; + } else if (message.getRemoteMsgId() != null) { + return (message.getRemoteMsgId().equals(this.remoteMsgId) || message.getRemoteMsgId().equals(this.uuid)) + && this.counterpart.equals(message.getCounterpart()) + && this.body.equals(message.getBody()); } else { - return this.body != null - && this.counterpart != null - && ((this.remoteMsgId != null && this.remoteMsgId.equals(message.getRemoteMsgId())) - || this.uuid.equals(message.getRemoteMsgId())) && this.body.equals(message.getBody()) - && this.counterpart.equals(message.getCounterpart()); + return this.remoteMsgId == null + && this.counterpart.equals(message.getCounterpart()) + && this.body.equals(message.getBody()) + && Math.abs(this.getTimeSent() - message.getTimeSent()) < Config.PING_TIMEOUT * 500; } } @@ -348,15 +355,34 @@ public class Message extends AbstractEntity { } public boolean mergeable(final Message message) { - return message != null && (message.getType() == Message.TYPE_TEXT && this.getDownloadable() == null && message.getDownloadable() == null && message.getEncryption() != Message.ENCRYPTION_PGP && this.getType() == message.getType() && this.getStatus() == message.getStatus() && this.getEncryption() == message.getEncryption() && this.getCounterpart() != null && this.getCounterpart().equals(message.getCounterpart()) && (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) && !message.bodyContainsDownloadable() && !this.bodyContainsDownloadable()); + return message != null && + (message.getType() == Message.TYPE_TEXT && + this.getDownloadable() == null && + message.getDownloadable() == null && + message.getEncryption() != Message.ENCRYPTION_PGP && + this.getType() == message.getType() && + this.getStatus() == message.getStatus() && + this.getEncryption() == message.getEncryption() && + this.getCounterpart() != null && + this.getCounterpart().equals(message.getCounterpart()) && + (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) && + !message.bodyContainsDownloadable() && + !this.bodyContainsDownloadable() && + !message.getBody().startsWith(ME_COMMAND) && + !this.getBody().startsWith(ME_COMMAND) + ); } public String getMergedBody() { - Message next = this.next(); + final Message next = this.next(); if (this.mergeable(next)) { - return body.trim() + '\n' + next.getMergedBody(); + return getBody() + '\n' + next.getMergedBody(); } - return body.trim(); + return getBody(); + } + + public boolean hasMeCommand() { + return getMergedBody().startsWith(ME_COMMAND); } public int getMergedStatus() { @@ -395,7 +421,7 @@ public class Message extends AbstractEntity { String[] pathParts = url.getPath().split("/"); String filename; if (pathParts.length > 0) { - filename = pathParts[pathParts.length - 1]; + filename = pathParts[pathParts.length - 1].toLowerCase(); } else { return false; } diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index 97a63532..27821c65 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -4,15 +4,77 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; 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.PresencePacket; + import android.annotation.SuppressLint; @SuppressLint("DefaultLocale") public class MucOptions { + + public enum Affiliation { + OWNER("owner", 4, R.string.owner), + ADMIN("admin", 3, R.string.admin), + MEMBER("member", 2, R.string.member), + OUTCAST("outcast", 0, R.string.outcast), + NONE("none", 1, R.string.no_affiliation); + + private Affiliation(String string, int rank, int resId) { + this.string = string; + this.resId = resId; + this.rank = rank; + } + + private String string; + private int resId; + private int rank; + + public int getResId() { + return resId; + } + + @Override + public String toString() { + return this.string; + } + + public boolean outranks(Affiliation affiliation) { + return rank > affiliation.rank; + } + + public boolean ranks(Affiliation affiliation) { + return rank >= affiliation.rank; + } + } + + public enum Role { + MODERATOR("moderator", R.string.moderator), + VISITOR("visitor", R.string.visitor), + PARTICIPANT("participant", R.string.participant), + NONE("none", R.string.no_role); + + private Role(String string, int resId) { + this.string = string; + this.resId = resId; + } + + private String string; + private int resId; + + public int getResId() { + return resId; + } + + @Override + public String toString() { + return this.string; + } + } + public static final int ERROR_NO_ERROR = 0; public static final int ERROR_NICK_IN_USE = 1; public static final int ERROR_UNKNOWN = 2; @@ -22,6 +84,7 @@ public class MucOptions { public static final int KICKED_FROM_ROOM = 9; + public static final String STATUS_CODE_ROOM_CONFIG_CHANGED = "104"; public static final String STATUS_CODE_SELF_PRESENCE = "110"; public static final String STATUS_CODE_BANNED = "301"; public static final String STATUS_CODE_CHANGED_NICK = "303"; @@ -30,6 +93,7 @@ public class MucOptions { private interface OnEventListener { public void onSuccess(); + public void onFailure(); } @@ -42,18 +106,8 @@ public class MucOptions { } public class User { - public static final int ROLE_MODERATOR = 3; - public static final int ROLE_NONE = 0; - public static final int ROLE_PARTICIPANT = 2; - public static final int ROLE_VISITOR = 1; - public static final int AFFILIATION_ADMIN = 4; - public static final int AFFILIATION_OWNER = 3; - public static final int AFFILIATION_MEMBER = 2; - public static final int AFFILIATION_OUTCAST = 1; - public static final int AFFILIATION_NONE = 0; - - private int role; - private int affiliation; + private Role role = Role.NONE; + private Affiliation affiliation = Affiliation.NONE; private String name; private Jid jid; private long pgpKeyId = 0; @@ -74,7 +128,7 @@ public class MucOptions { return this.jid; } - public int getRole() { + public Role getRole() { return this.role; } @@ -82,35 +136,41 @@ public class MucOptions { role = role.toLowerCase(); switch (role) { case "moderator": - this.role = ROLE_MODERATOR; + this.role = Role.MODERATOR; break; case "participant": - this.role = ROLE_PARTICIPANT; + this.role = Role.PARTICIPANT; break; case "visitor": - this.role = ROLE_VISITOR; + this.role = Role.VISITOR; break; default: - this.role = ROLE_NONE; + this.role = Role.NONE; break; } } - public int getAffiliation() { + public Affiliation getAffiliation() { return this.affiliation; } public void setAffiliation(String affiliation) { - if (affiliation.equalsIgnoreCase("admin")) { - this.affiliation = AFFILIATION_ADMIN; - } else if (affiliation.equalsIgnoreCase("owner")) { - this.affiliation = AFFILIATION_OWNER; - } else if (affiliation.equalsIgnoreCase("member")) { - this.affiliation = AFFILIATION_MEMBER; - } else if (affiliation.equalsIgnoreCase("outcast")) { - this.affiliation = AFFILIATION_OUTCAST; - } else { - this.affiliation = AFFILIATION_NONE; + affiliation = affiliation.toLowerCase(); + switch (affiliation) { + case "admin": + this.affiliation = Affiliation.ADMIN; + break; + case "owner": + this.affiliation = Affiliation.OWNER; + break; + case "member": + this.affiliation = Affiliation.MEMBER; + break; + case "outcast": + this.affiliation = Affiliation.OUTCAST; + break; + default: + this.affiliation = Affiliation.NONE; } } @@ -129,6 +189,7 @@ public class MucOptions { private Account account; private List<User> users = new CopyOnWriteArrayList<>(); + private List<String> features = new ArrayList<>(); private Conversation conversation; private boolean isOnline = false; private int error = ERROR_UNKNOWN; @@ -144,6 +205,31 @@ public class MucOptions { this.conversation = conversation; } + public void updateFeatures(ArrayList<String> features) { + this.features.clear(); + this.features.addAll(features); + } + + public boolean hasFeature(String feature) { + return this.features.contains(feature); + } + + public boolean canInvite() { + return !membersOnly() || self.getAffiliation().ranks(Affiliation.ADMIN); + } + + public boolean membersOnly() { + return hasFeature("muc_membersonly"); + } + + public boolean nonanonymous() { + return hasFeature("muc_nonanonymous"); + } + + public boolean persistent() { + return hasFeature("muc_persistent"); + } + public void deleteUser(String name) { for (int i = 0; i < users.size(); ++i) { if (users.get(i).getName().equals(name)) { @@ -168,7 +254,7 @@ public class MucOptions { if (!from.isBareJid()) { final String name = from.getResourcepart(); final String type = packet.getAttribute("type"); - final Element x = packet.findChild("x","http://jabber.org/protocol/muc#user"); + final Element x = packet.findChild("x", "http://jabber.org/protocol/muc#user"); final List<String> codes = getStatusCodes(x); if (type == null) { User user = new User(); @@ -204,7 +290,7 @@ public class MucOptions { msg = ""; } user.setPgpKeyId(pgp.fetchKeyId(account, msg, - signed.getContent())); + signed.getContent())); } } } @@ -259,12 +345,12 @@ public class MucOptions { } private List<String> getStatusCodes(Element x) { - List<String> codes = new ArrayList<String>(); + List<String> codes = new ArrayList<>(); if (x != null) { - for(Element child : x.getChildren()) { + for (Element child : x.getChildren()) { if (child.getName().equals("status")) { String code = child.getAttribute("code"); - if (code!=null) { + if (code != null) { codes.add(code); } } @@ -389,7 +475,7 @@ public class MucOptions { public Jid createJoinJid(String nick) { try { - return Jid.fromString(this.conversation.getJid().toBareJid().toString() + "/"+nick); + return Jid.fromString(this.conversation.getJid().toBareJid().toString() + "/" + nick); } catch (final InvalidJidException e) { return null; } diff --git a/src/main/java/eu/siacs/conversations/entities/Roster.java b/src/main/java/eu/siacs/conversations/entities/Roster.java index 1a81a419..ce058004 100644 --- a/src/main/java/eu/siacs/conversations/entities/Roster.java +++ b/src/main/java/eu/siacs/conversations/entities/Roster.java @@ -1,14 +1,14 @@ package eu.siacs.conversations.entities; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; -import java.util.concurrent.ConcurrentHashMap; import eu.siacs.conversations.xmpp.jid.Jid; public class Roster { final Account account; - final ConcurrentHashMap<String, Contact> contacts = new ConcurrentHashMap<>(); + final HashMap<String, Contact> contacts = new HashMap<>(); private String version = null; public Roster(Account account) { @@ -19,23 +19,27 @@ public class Roster { if (jid == null) { return null; } - final Contact contact = contacts.get(jid.toBareJid().toString()); - if (contact != null && contact.showInRoster()) { - return contact; - } else { - return null; + synchronized (this.contacts) { + Contact contact = contacts.get(jid.toBareJid().toString()); + if (contact != null && contact.showInRoster()) { + return contact; + } else { + return null; + } } } public Contact getContact(final Jid jid) { - final Jid bareJid = jid.toBareJid(); - if (contacts.containsKey(bareJid.toString())) { - return contacts.get(bareJid.toString()); - } else { - final Contact contact = new Contact(bareJid); - contact.setAccount(account); - contacts.put(bareJid.toString(), contact); - return contact; + synchronized (this.contacts) { + final Jid bareJid = jid.toBareJid(); + if (contacts.containsKey(bareJid.toString())) { + return contacts.get(bareJid.toString()); + } else { + Contact contact = new Contact(bareJid); + contact.setAccount(account); + contacts.put(bareJid.toString(), contact); + return contact; + } } } @@ -46,13 +50,13 @@ public class Roster { } public void markAllAsNotInRoster() { - for (final Contact contact : getContacts()) { + for (Contact contact : getContacts()) { contact.resetOption(Contact.Options.IN_ROSTER); } } public void clearSystemAccounts() { - for (final Contact contact : getContacts()) { + for (Contact contact : getContacts()) { contact.setPhotoUri(null); contact.setSystemName(null); contact.setSystemAccount(null); @@ -60,13 +64,17 @@ public class Roster { } public List<Contact> getContacts() { - return new ArrayList<>(this.contacts.values()); + synchronized (this.contacts) { + return new ArrayList<>(this.contacts.values()); + } } public void initContact(final Contact contact) { contact.setAccount(account); contact.setOption(Contact.Options.IN_ROSTER); - contacts.put(contact.getJid().toBareJid().toString(), contact); + synchronized (this.contacts) { + contacts.put(contact.getJid().toBareJid().toString(), contact); + } } public void setVersion(String version) { diff --git a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java index 870ee757..526e5b19 100644 --- a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java @@ -5,6 +5,7 @@ import android.util.Base64; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -14,16 +15,22 @@ import java.util.TimeZone; import eu.siacs.conversations.services.XmppConnectionService; public abstract class AbstractGenerator { - public final String[] FEATURES = {"urn:xmpp:jingle:1", + private final String[] FEATURES = { + "urn:xmpp:jingle:1", "urn:xmpp:jingle:apps:file-transfer:3", "urn:xmpp:jingle:transports:s5b:1", - "urn:xmpp:jingle:transports:ibb:1", "urn:xmpp:receipts", - "urn:xmpp:chat-markers:0", "http://jabber.org/protocol/muc", - "jabber:x:conference", "http://jabber.org/protocol/caps", + "urn:xmpp:jingle:transports:ibb:1", + "http://jabber.org/protocol/muc", + "jabber:x:conference", + "http://jabber.org/protocol/caps", "http://jabber.org/protocol/disco#info", "urn:xmpp:avatar:metadata+notify", "urn:xmpp:ping"}; - public final String IDENTITY_NAME = "Conversations 0.10"; + private final String[] MESSAGE_CONFIRMATION_FEATURES = { + "urn:xmpp:chat-markers:0", + "urn:xmpp:receipts" + }; + public final String IDENTITY_NAME = "Conversations 1.0"; public final String IDENTITY_TYPE = "phone"; private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); @@ -43,9 +50,8 @@ public abstract class AbstractGenerator { } catch (NoSuchAlgorithmException e) { return null; } - List<String> features = Arrays.asList(FEATURES); - Collections.sort(features); - for (String feature : features) { + + for (String feature : getFeatures()) { s.append(feature + "<"); } byte[] sha1 = md.digest(s.toString().getBytes()); @@ -56,4 +62,14 @@ public abstract class AbstractGenerator { DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); return DATE_FORMAT.format(time); } + + public List<String> getFeatures() { + ArrayList<String> features = new ArrayList<>(); + features.addAll(Arrays.asList(FEATURES)); + if (mXmppConnectionService.confirmMessages()) { + features.addAll(Arrays.asList(MESSAGE_CONFIRMATION_FEATURES)); + } + Collections.sort(features); + return features; + } } diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index f94dc5d7..161e6f89 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -1,10 +1,11 @@ package eu.siacs.conversations.generator; -import java.util.Arrays; -import java.util.Collections; + +import java.util.ArrayList; import java.util.List; import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.Xmlns; @@ -21,7 +22,7 @@ public class IqGenerator extends AbstractGenerator { } public IqPacket discoResponse(final IqPacket request) { - final IqPacket packet = new IqPacket(IqPacket.TYPE_RESULT); + final IqPacket packet = new IqPacket(IqPacket.TYPE.RESULT); packet.setId(request.getId()); packet.setTo(request.getFrom()); final Element query = packet.addChild("query", @@ -31,16 +32,14 @@ public class IqGenerator extends AbstractGenerator { identity.setAttribute("category", "client"); identity.setAttribute("type", this.IDENTITY_TYPE); identity.setAttribute("name", IDENTITY_NAME); - final List<String> features = Arrays.asList(FEATURES); - Collections.sort(features); - for (final String feature : features) { + for (final String feature : getFeatures()) { query.addChild("feature").setAttribute("var", feature); } return packet; } protected IqPacket publish(final String node, final Element item) { - final IqPacket packet = new IqPacket(IqPacket.TYPE_SET); + final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub"); final Element publish = pubsub.addChild("publish"); @@ -50,7 +49,7 @@ public class IqGenerator extends AbstractGenerator { } protected IqPacket retrieve(String node, Element item) { - final IqPacket packet = new IqPacket(IqPacket.TYPE_GET); + final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); final Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub"); final Element items = pubsub.addChild("items"); @@ -100,12 +99,14 @@ public class IqGenerator extends AbstractGenerator { } public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) { - final IqPacket packet = new IqPacket(IqPacket.TYPE_SET); + final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final Element query = packet.query("urn:xmpp:mam:0"); query.setAttribute("queryid",mam.getQueryId()); final Data data = new Data(); data.setFormType("urn:xmpp:mam:0"); - if (mam.getWith()!=null) { + if (mam.muc()) { + packet.setTo(mam.getWith()); + } else if (mam.getWith()!=null) { data.put("with", mam.getWith().toString()); } data.put("start",getTimestamp(mam.getStart())); @@ -119,28 +120,28 @@ public class IqGenerator extends AbstractGenerator { return packet; } public IqPacket generateGetBlockList() { - final IqPacket iq = new IqPacket(IqPacket.TYPE_GET); + final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); iq.addChild("blocklist", Xmlns.BLOCKING); return iq; } public IqPacket generateSetBlockRequest(final Jid jid) { - final IqPacket iq = new IqPacket(IqPacket.TYPE_SET); + final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); final Element block = iq.addChild("block", Xmlns.BLOCKING); block.addChild("item").setAttribute("jid", jid.toBareJid().toString()); return iq; } public IqPacket generateSetUnblockRequest(final Jid jid) { - final IqPacket iq = new IqPacket(IqPacket.TYPE_SET); + final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); final Element block = iq.addChild("unblock", Xmlns.BLOCKING); block.addChild("item").setAttribute("jid", jid.toBareJid().toString()); return iq; } public IqPacket generateSetPassword(final Account account, final String newPassword) { - final IqPacket packet = new IqPacket(IqPacket.TYPE_SET); + final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); packet.setTo(account.getServer()); final Element query = packet.addChild("query", Xmlns.REGISTER); final Jid jid = account.getJid(); @@ -148,4 +149,33 @@ public class IqGenerator extends AbstractGenerator { query.addChild("password").setContent(newPassword); return packet; } + + public IqPacket changeAffiliation(Conversation conference, Jid jid, String affiliation) { + List<Jid> jids = new ArrayList<>(); + jids.add(jid); + return changeAffiliation(conference,jids,affiliation); + } + + public IqPacket changeAffiliation(Conversation conference, List<Jid> jids, String affiliation) { + IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + packet.setTo(conference.getJid().toBareJid()); + packet.setFrom(conference.getAccount().getJid()); + Element query = packet.query("http://jabber.org/protocol/muc#admin"); + for(Jid jid : jids) { + Element item = query.addChild("item"); + item.setAttribute("jid", jid.toString()); + item.setAttribute("affiliation", affiliation); + } + return packet; + } + + public IqPacket changeRole(Conversation conference, String nick, String role) { + IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + packet.setTo(conference.getJid().toBareJid()); + packet.setFrom(conference.getAccount().getJid()); + Element item = packet.query("http://jabber.org/protocol/muc#admin").addChild("item"); + item.setAttribute("nick", nick); + item.setAttribute("role", role); + return packet; + } } diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index 8e99888b..2ee636b5 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -68,7 +68,7 @@ public class MessageGenerator extends AbstractGenerator { packet.addChild("private", "urn:xmpp:carbons:2"); packet.addChild("no-copy", "urn:xmpp:hints"); try { - packet.setBody(otrSession.transformSending(message.getBody())); + packet.setBody(otrSession.transformSending(message.getBody())[0]); return packet; } catch (OtrException e) { return null; diff --git a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java index e3642f6b..1e896724 100644 --- a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java @@ -49,7 +49,7 @@ public class PresenceGenerator extends AbstractGenerator { Element cap = packet.addChild("c", "http://jabber.org/protocol/caps"); cap.setAttribute("hash", "sha-1"); - cap.setAttribute("node", "http://conversions.siacs.eu"); + cap.setAttribute("node", "http://conversions.im"); cap.setAttribute("ver", capHash); } return packet; diff --git a/src/main/java/eu/siacs/conversations/http/HttpConnection.java b/src/main/java/eu/siacs/conversations/http/HttpConnection.java index 5348e395..4bff5251 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpConnection.java @@ -14,11 +14,13 @@ import java.net.MalformedURLException; import java.net.URL; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; +import java.util.Arrays; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; import eu.siacs.conversations.Config; @@ -65,14 +67,22 @@ public class HttpConnection implements Downloadable { this.message.setDownloadable(this); try { mUrl = new URL(message.getBody()); - String path = mUrl.getPath(); - if (path != null && (path.endsWith(".pgp") || path.endsWith(".gpg"))) { + String[] parts = mUrl.getPath().toLowerCase().split("\\."); + String lastPart = parts.length >= 1 ? parts[parts.length - 1] : null; + String secondToLast = parts.length >= 2 ? parts[parts.length -2] : null; + if ("pgp".equals(lastPart) || "gpg".equals(lastPart)) { this.message.setEncryption(Message.ENCRYPTION_PGP); } else if (message.getEncryption() != Message.ENCRYPTION_OTR) { this.message.setEncryption(Message.ENCRYPTION_NONE); } - this.file = mXmppConnectionService.getFileBackend().getFile( - message, false); + String extension; + if (Arrays.asList(VALID_CRYPTO_EXTENSIONS).contains(lastPart)) { + extension = secondToLast; + } else { + extension = lastPart; + } + message.setRelativeFilePath(message.getUuid()+"."+extension); + this.file = mXmppConnectionService.getFileBackend().getFile(message, false); String reference = mUrl.getRef(); if (reference != null && reference.length() == 96) { this.file.setKey(CryptoHelper.hexToBytes(reference)); @@ -81,7 +91,7 @@ public class HttpConnection implements Downloadable { if (this.message.getEncryption() == Message.ENCRYPTION_OTR && this.file.getKey() == null) { this.message.setEncryption(Message.ENCRYPTION_NONE); - } + } checkFileSize(false); } catch (MalformedURLException e) { this.cancel(); @@ -115,33 +125,39 @@ public class HttpConnection implements Downloadable { mXmppConnectionService.updateConversationUi(); } - private void setupTrustManager(HttpsURLConnection connection, - boolean interactive) { - X509TrustManager trustManager; - HostnameVerifier hostnameVerifier; + private void setupTrustManager(final HttpsURLConnection connection, + final boolean interactive) { + final X509TrustManager trustManager; + final HostnameVerifier hostnameVerifier; if (interactive) { trustManager = mXmppConnectionService.getMemorizingTrustManager(); hostnameVerifier = mXmppConnectionService - .getMemorizingTrustManager().wrapHostnameVerifier( - new StrictHostnameVerifier()); + .getMemorizingTrustManager().wrapHostnameVerifier( + new StrictHostnameVerifier()); } else { trustManager = mXmppConnectionService.getMemorizingTrustManager() - .getNonInteractive(); + .getNonInteractive(); hostnameVerifier = mXmppConnectionService - .getMemorizingTrustManager() - .wrapHostnameVerifierNonInteractive( - new StrictHostnameVerifier()); + .getMemorizingTrustManager() + .wrapHostnameVerifierNonInteractive( + new StrictHostnameVerifier()); } try { - SSLContext sc = SSLContext.getInstance("TLS"); + final SSLContext sc = SSLContext.getInstance("TLS"); sc.init(null, new X509TrustManager[]{trustManager}, mXmppConnectionService.getRNG()); - connection.setSSLSocketFactory(sc.getSocketFactory()); + + final SSLSocketFactory sf = sc.getSocketFactory(); + final String[] cipherSuites = CryptoHelper.getSupportedCipherSuites( + sf.getSupportedCipherSuites()); + if (cipherSuites.length > 0) { + sc.getDefaultSSLParameters().setCipherSuites(cipherSuites); + + } + + connection.setSSLSocketFactory(sf); connection.setHostnameVerifier(hostnameVerifier); - } catch (KeyManagementException e) { - return; - } catch (NoSuchAlgorithmException e) { - return; + } catch (final KeyManagementException | NoSuchAlgorithmException ignored) { } } @@ -179,24 +195,24 @@ public class HttpConnection implements Downloadable { } private long retrieveFileSize() throws IOException, - SSLHandshakeException { - changeStatus(STATUS_CHECKING); - HttpURLConnection connection = (HttpURLConnection) mUrl - .openConnection(); - connection.setRequestMethod("HEAD"); - if (connection instanceof HttpsURLConnection) { - setupTrustManager((HttpsURLConnection) connection, interactive); - } - connection.connect(); - String contentLength = connection.getHeaderField("Content-Length"); - if (contentLength == null) { - throw new IOException(); - } - try { - return Long.parseLong(contentLength, 10); - } catch (NumberFormatException e) { - throw new IOException(); - } + SSLHandshakeException { + changeStatus(STATUS_CHECKING); + HttpURLConnection connection = (HttpURLConnection) mUrl + .openConnection(); + connection.setRequestMethod("HEAD"); + if (connection instanceof HttpsURLConnection) { + setupTrustManager((HttpsURLConnection) connection, interactive); + } + connection.connect(); + String contentLength = connection.getHeaderField("Content-Length"); + if (contentLength == null) { + throw new IOException(); + } + try { + return Long.parseLong(contentLength, 10); + } catch (NumberFormatException e) { + throw new IOException(); + } } } @@ -225,7 +241,7 @@ public class HttpConnection implements Downloadable { private void download() throws SSLHandshakeException, IOException { HttpURLConnection connection = (HttpURLConnection) mUrl - .openConnection(); + .openConnection(); if (connection instanceof HttpsURLConnection) { setupTrustManager((HttpsURLConnection) connection, interactive); } @@ -291,4 +307,4 @@ public class HttpConnection implements Downloadable { public String getMimeType() { return ""; } -}
\ No newline at end of file +} diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java index e84545fc..6430c296 100644 --- a/src/main/java/eu/siacs/conversations/parser/IqParser.java +++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java @@ -71,24 +71,17 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { return super.avatarData(items); } - public static boolean fromServer(final Account account, final IqPacket packet) { - return packet.getFrom() == null - || packet.getFrom().equals(account.getServer()) - || packet.getFrom().equals(account.getJid().toBareJid()) - || packet.getFrom().equals(account.getJid()); - } - @Override public void onIqPacketReceived(final Account account, final IqPacket packet) { - if (packet.hasChild("query", Xmlns.ROSTER) && fromServer(account, packet)) { + if (packet.hasChild("query", Xmlns.ROSTER) && packet.fromServer(account)) { final Element query = packet.findChild("query"); // If this is in response to a query for the whole roster: - if (packet.getType() == IqPacket.TYPE_RESULT) { + if (packet.getType() == IqPacket.TYPE.RESULT) { account.getRoster().markAllAsNotInRoster(); } this.rosterItems(account, query); } else if ((packet.hasChild("block", Xmlns.BLOCKING) || packet.hasChild("blocklist", Xmlns.BLOCKING)) && - fromServer(account, packet)) { + packet.fromServer(account)) { // Block list or block push. Log.d(Config.LOGTAG, "Received blocklist update from server"); final Element blocklist = packet.findChild("blocklist", Xmlns.BLOCKING); @@ -97,8 +90,9 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { (block != null ? block.getChildren() : null); // If this is a response to a blocklist query, clear the block list and replace with the new one. // Otherwise, just update the existing blocklist. - if (packet.getType() == IqPacket.TYPE_RESULT) { + if (packet.getType() == IqPacket.TYPE.RESULT) { account.clearBlocklist(); + account.getXmppConnection().getFeatures().setBlockListRequested(true); } if (items != null) { final Collection<Jid> jids = new ArrayList<>(items.size()); @@ -116,7 +110,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { // Update the UI mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED); } else if (packet.hasChild("unblock", Xmlns.BLOCKING) && - fromServer(account, packet) && packet.getType() == IqPacket.TYPE_SET) { + packet.fromServer(account) && packet.getType() == IqPacket.TYPE.SET) { Log.d(Config.LOGTAG, "Received unblock update from server"); final Collection<Element> items = packet.findChild("unblock", Xmlns.BLOCKING).getChildren(); if (items.size() == 0) { @@ -144,12 +138,12 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { .discoResponse(packet); account.getXmppConnection().sendIqPacket(response, null); } else if (packet.hasChild("ping", "urn:xmpp:ping")) { - final IqPacket response = packet.generateResponse(IqPacket.TYPE_RESULT); + final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); mXmppConnectionService.sendIqPacket(account, response, null); } else { - if ((packet.getType() == IqPacket.TYPE_GET) - || (packet.getType() == IqPacket.TYPE_SET)) { - final IqPacket response = packet.generateResponse(IqPacket.TYPE_ERROR); + if ((packet.getType() == IqPacket.TYPE.GET) + || (packet.getType() == IqPacket.TYPE.SET)) { + final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR); final Element error = response.addChild("error"); error.setAttribute("type", "cancel"); error.addChild("feature-not-implemented", diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 44cda261..49efb004 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -1,15 +1,14 @@ package eu.siacs.conversations.parser; -import android.util.Log; - import net.java.otr4j.session.Session; import net.java.otr4j.session.SessionStatus; -import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.entities.MucOptions; +import eu.siacs.conversations.http.HttpConnectionManager; import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; @@ -76,7 +75,7 @@ public class MessageParser extends AbstractParser implements } updateLastseen(packet, account, true); String body = packet.getBody(); - if (body.matches("^\\?OTRv\\d*\\?")) { + if (body.matches("^\\?OTRv\\d{1,2}\\?.*")) { conversation.endOtrIfNeeded(); } if (!conversation.hasValidOtrSession()) { @@ -103,8 +102,10 @@ public class MessageParser extends AbstractParser implements body = otrSession.transformReceiving(body); SessionStatus after = otrSession.getSessionStatus(); if ((before != after) && (after == SessionStatus.ENCRYPTED)) { + conversation.setNextEncryption(Message.ENCRYPTION_OTR); mXmppConnectionService.onOtrSessionEstablished(conversation); } else if ((before != after) && (after == SessionStatus.FINISHED)) { + conversation.setNextEncryption(Message.ENCRYPTION_NONE); conversation.resetOtrSession(); mXmppConnectionService.updateConversationUi(); } @@ -142,17 +143,30 @@ public class MessageParser extends AbstractParser implements Conversation conversation = mXmppConnectionService .findOrCreateConversation(account, from.toBareJid(), true); if (packet.hasChild("subject")) { - conversation.getMucOptions().setSubject( - packet.findChild("subject").getContent()); + conversation.setHasMessagesLeftOnServer(true); + conversation.getMucOptions().setSubject(packet.findChild("subject").getContent()); mXmppConnectionService.updateConversationUi(); return null; } - if (from.isBareJid()) { + + final Element x = packet.findChild("x", "http://jabber.org/protocol/muc#user"); + if (from.isBareJid() && (x == null || !x.hasChild("status"))) { + return null; + } else if (from.isBareJid() && x.hasChild("status")) { + for(Element child : x.getChildren()) { + if (child.getName().equals("status")) { + String code = child.getAttribute("code"); + if (code.contains(MucOptions.STATUS_CODE_ROOM_CONFIG_CHANGED)) { + mXmppConnectionService.fetchConferenceConfiguration(conversation); + } + } + } return null; } + if (from.getResourcepart().equals(conversation.getMucOptions().getActualNick())) { if (mXmppConnectionService.markMessage(conversation, - packet.getId(), Message.STATUS_SEND)) { + packet.getId(), Message.STATUS_SEND_RECEIVED)) { return null; } else if (packet.getId() == null) { Message message = conversation.findSentMessageWithBody(packet.getBody()); @@ -245,6 +259,10 @@ public class MessageParser extends AbstractParser implements return null; } } + if (message.hasChild("x","http://jabber.org/protocol/muc#user") + && "chat".equals(message.getAttribute("type"))) { + return null; + } Conversation conversation = mXmppConnectionService .findOrCreateConversation(account, fullJid.toBareJid(), false); String pgpBody = getPgpBody(message); @@ -344,6 +362,17 @@ public class MessageParser extends AbstractParser implements private void parseNonMessage(Element packet, Account account) { final Jid from = packet.getAttributeAsJid("from"); + Element invite = extractInvite(packet); + if (invite != null) { + Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, from, true); + if (!conversation.getMucOptions().online()) { + Element password = invite.findChild("password"); + conversation.getMucOptions().setPassword(password == null ? null : password.getContent()); + mXmppConnectionService.databaseBackend.updateConversation(conversation); + mXmppConnectionService.joinMuc(conversation); + mXmppConnectionService.updateConversationUi(); + } + } if (packet.hasChild("event", "http://jabber.org/protocol/pubsub#event")) { Element event = packet.findChild("event", "http://jabber.org/protocol/pubsub#event"); @@ -370,42 +399,18 @@ public class MessageParser extends AbstractParser implements updateLastseen(packet, account, false); mXmppConnectionService.markMessage(account, from.toBareJid(), id, Message.STATUS_SEND_RECEIVED); - } else if (packet.hasChild("x", "http://jabber.org/protocol/muc#user")) { - Element x = packet.findChild("x", - "http://jabber.org/protocol/muc#user"); - if (x.hasChild("invite")) { - Conversation conversation = mXmppConnectionService - .findOrCreateConversation(account, - packet.getAttributeAsJid("from"), true); - if (!conversation.getMucOptions().online()) { - if (x.hasChild("password")) { - Element password = x.findChild("password"); - conversation.getMucOptions().setPassword( - password.getContent()); - mXmppConnectionService.databaseBackend - .updateConversation(conversation); - } - mXmppConnectionService.joinMuc(conversation); - mXmppConnectionService.updateConversationUi(); - } - } - } else if (packet.hasChild("x", "jabber:x:conference")) { - Element x = packet.findChild("x", "jabber:x:conference"); - Jid jid = x.getAttributeAsJid("jid"); - String password = x.getAttribute("password"); - if (jid != null) { - Conversation conversation = mXmppConnectionService - .findOrCreateConversation(account, jid, true); - if (!conversation.getMucOptions().online()) { - if (password != null) { - conversation.getMucOptions().setPassword(password); - mXmppConnectionService.databaseBackend - .updateConversation(conversation); - } - mXmppConnectionService.joinMuc(conversation); - mXmppConnectionService.updateConversationUi(); - } - } + } + } + + private Element extractInvite(Element message) { + Element x = message.findChild("x","http://jabber.org/protocol/muc#user"); + if (x == null) { + x = message.findChild("x","jabber:x:conference"); + } + if (x != null && x.hasChild("invite")) { + return x; + } else { + return null; } } @@ -482,7 +487,6 @@ public class MessageParser extends AbstractParser implements public void onMessagePacketReceived(Account account, MessagePacket packet) { Message message = null; this.parseNick(packet, account); - if ((packet.getType() == MessagePacket.TYPE_CHAT || packet.getType() == MessagePacket.TYPE_NORMAL)) { if ((packet.getBody() != null) && (packet.getBody().startsWith("?OTR"))) { @@ -490,9 +494,7 @@ public class MessageParser extends AbstractParser implements if (message != null) { message.markUnread(); } - } else if (packet.hasChild("body") - && !(packet.hasChild("x", - "http://jabber.org/protocol/muc#user"))) { + } else if (packet.hasChild("body") && extractInvite(packet) == null) { message = this.parseChat(packet, account); if (message != null) { message.markUnread(); @@ -578,9 +580,9 @@ public class MessageParser extends AbstractParser implements mXmppConnectionService.databaseBackend.createMessage(message); } } - if (message.trusted() && message.bodyContainsDownloadable()) { - this.mXmppConnectionService.getHttpConnectionManager() - .createNewConnection(message); + final HttpConnectionManager manager = this.mXmppConnectionService.getHttpConnectionManager(); + if (message.trusted() && message.bodyContainsDownloadable() && manager.getAutoAcceptFileSize() > 0) { + manager.createNewConnection(message); } else if (!message.isRead()) { mXmppConnectionService.getNotificationService().push(message); } diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 5fa61491..3ae3356d 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -220,10 +220,13 @@ public class DatabaseBackend extends SQLiteOpenHelper { public Conversation findConversation(final Account account, final Jid contactJid) { SQLiteDatabase db = this.getReadableDatabase(); - String[] selectionArgs = { account.getUuid(), contactJid.toBareJid().toString() + "%" }; + String[] selectionArgs = { account.getUuid(), + contactJid.toBareJid().toString() + "/%", + contactJid.toBareJid().toString() + }; Cursor cursor = db.query(Conversation.TABLENAME, null, - Conversation.ACCOUNT + "=? AND " + Conversation.CONTACTJID - + " like ?", selectionArgs, null, null, null); + Conversation.ACCOUNT + "=? AND (" + Conversation.CONTACTJID + + " like ? OR "+Conversation.CONTACTJID+"=?)", selectionArgs, null, null, null); if (cursor.getCount() == 0) return null; cursor.moveToFirst(); diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index f7defcdf..62987aaa 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -57,28 +57,33 @@ public class FileBackend { public DownloadableFile getFile(Message message, boolean decrypted) { String path = message.getRelativeFilePath(); - if (!decrypted && (message.getEncryption() == Message.ENCRYPTION_PGP || message.getEncryption() == Message.ENCRYPTION_DECRYPTED)) { - String extension; - if (path != null && !path.isEmpty()) { - String[] parts = path.split("\\."); - extension = "."+parts[parts.length - 1]; - } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_TEXT) { + String extension; + if (path != null && !path.isEmpty()) { + String[] parts = path.split("\\."); + extension = "."+parts[parts.length - 1]; + } else { + if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_TEXT) { extension = ".webp"; } else { extension = ""; } + path = message.getUuid()+extension; + } + final boolean encrypted = !decrypted + && (message.getEncryption() == Message.ENCRYPTION_PGP + || message.getEncryption() == Message.ENCRYPTION_DECRYPTED); + if (encrypted) { return new DownloadableFile(getConversationsFileDirectory()+message.getUuid()+extension+".pgp"); - } else if (path != null && !path.isEmpty()) { + } else { if (path.startsWith("/")) { return new DownloadableFile(path); } else { - return new DownloadableFile(getConversationsFileDirectory()+path); + if (message.getType() == Message.TYPE_FILE) { + return new DownloadableFile(getConversationsFileDirectory() + path); + } else { + return new DownloadableFile(getConversationsImageDirectory()+path); + } } - } else { - StringBuilder filename = new StringBuilder(); - filename.append(getConversationsImageDirectory()); - filename.append(message.getUuid()+".webp"); - return new DownloadableFile(filename.toString()); } } diff --git a/src/main/java/eu/siacs/conversations/services/AvatarService.java b/src/main/java/eu/siacs/conversations/services/AvatarService.java index 3ac4462d..f28dc24e 100644 --- a/src/main/java/eu/siacs/conversations/services/AvatarService.java +++ b/src/main/java/eu/siacs/conversations/services/AvatarService.java @@ -212,7 +212,8 @@ public class AvatarService { } bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); - final String letter = name.isEmpty() ? "X" : name.substring(0,1); + final String trimmedName = name.trim(); + final String letter = trimmedName.isEmpty() ? "X" : trimmedName.substring(0,1); final int color = UIHelper.getColorForName(name); drawTile(canvas, letter, color, 0, 0, size, size); mXmppConnectionService.getBitmapCache().put(KEY, bitmap); diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java index 82111243..f97077c4 100644 --- a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java +++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java @@ -111,7 +111,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { this.mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE_ERROR) { + if (packet.getType() == IqPacket.TYPE.ERROR) { Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": error executing mam: " + packet.toString()); finalizeQuery(query); } @@ -225,7 +225,6 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { private int messageCount = 0; private long start; private long end; - private Jid with = null; private String queryId; private String reference = null; private Account account; @@ -237,7 +236,6 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { public Query(Conversation conversation, long start, long end) { this(conversation.getAccount(), start, end); this.conversation = conversation; - this.with = conversation.getJid().toBareJid(); } public Query(Conversation conversation, long start, long end, PagingOrder order) { @@ -256,7 +254,6 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { Query query = new Query(this.account,this.start,this.end); query.reference = reference; query.conversation = conversation; - query.with = with; query.totalCount = totalCount; query.callback = callback; return query; @@ -287,7 +284,11 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } public Jid getWith() { - return with; + return conversation == null ? null : conversation.getJid().toBareJid(); + } + + public boolean muc() { + return conversation != null && conversation.getMode() == Conversation.MODE_MULTI; } public long getStart() { @@ -338,11 +339,15 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { @Override public String toString() { StringBuilder builder = new StringBuilder(); - builder.append("with="); - if (this.with==null) { - builder.append("*"); + if (this.muc()) { + builder.append("to="+this.getWith().toString()); } else { - builder.append(with.toString()); + builder.append("with="); + if (this.getWith() == null) { + builder.append("*"); + } else { + builder.append(getWith().toString()); + } } builder.append(", start="); builder.append(AbstractGenerator.getTimestamp(this.start)); diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index a30cf2f1..2ea0904f 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.services; +import android.annotation.SuppressLint; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -8,6 +9,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Bitmap; import android.net.Uri; +import android.os.Build; import android.os.PowerManager; import android.os.SystemClock; import android.support.v4.app.NotificationCompat; @@ -18,9 +20,13 @@ import android.text.Html; import android.util.DisplayMetrics; import android.util.Log; +import org.json.JSONArray; +import org.json.JSONObject; + import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.Calendar; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.regex.Matcher; @@ -30,16 +36,15 @@ import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; -import eu.siacs.conversations.entities.Downloadable; -import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.ui.ConversationActivity; import eu.siacs.conversations.ui.ManageAccountActivity; import eu.siacs.conversations.ui.TimePreference; +import eu.siacs.conversations.utils.UIHelper; public class NotificationService { - private XmppConnectionService mXmppConnectionService; + private final XmppConnectionService mXmppConnectionService; private final LinkedHashMap<String, ArrayList<Message>> notifications = new LinkedHashMap<>(); @@ -51,7 +56,7 @@ public class NotificationService { private boolean mIsInForeground; private long mLastNotification; - public NotificationService(XmppConnectionService service) { + public NotificationService(final XmppConnectionService service) { this.mXmppConnectionService = service; } @@ -65,6 +70,24 @@ public class NotificationService { ); } + public void notifyPebble(final Message message) { + final Intent i = new Intent("com.getpebble.action.SEND_NOTIFICATION"); + + final Conversation conversation = message.getConversation(); + final JSONObject jsonData = new JSONObject(new HashMap<String, String>(2) {{ + put("title", conversation.getName()); + put("body", message.getBody()); + }}); + final String notificationData = new JSONArray().put(jsonData).toString(); + + i.putExtra("messageType", "PEBBLE_ALERT"); + i.putExtra("sender", "Conversations"); /* XXX: Shouldn't be hardcoded, e.g., AbstractGenerator.APP_NAME); */ + i.putExtra("notificationData", notificationData); + + mXmppConnectionService.sendBroadcast(i); + } + + public boolean notificationsEnabled() { return mXmppConnectionService.getPreferences().getBoolean("show_notification", true); } @@ -88,18 +111,33 @@ public class NotificationService { return mXmppConnectionService.getPreferences().getBoolean("always_notify_in_conference", false); } + @SuppressLint("NewApi") + @SuppressWarnings("deprecation") + private boolean isInteractive() { + final PowerManager pm = (PowerManager) mXmppConnectionService + .getSystemService(Context.POWER_SERVICE); + + final boolean isScreenOn; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + isScreenOn = pm.isScreenOn(); + } else { + isScreenOn = pm.isInteractive(); + } + + return isScreenOn; + } + public void push(final Message message) { if (!notify(message)) { return; } - final PowerManager pm = (PowerManager) mXmppConnectionService - .getSystemService(Context.POWER_SERVICE); - final boolean isScreenOn = pm.isScreenOn(); - if (this.mIsInForeground && isScreenOn - && this.mOpenConversation == message.getConversation()) { + final boolean isScreenOn = isInteractive(); + + if (this.mIsInForeground && isScreenOn && this.mOpenConversation == message.getConversation()) { return; - } + } + synchronized (notifications) { final String conversationUuid = message.getConversationUuid(); if (notifications.containsKey(conversationUuid)) { @@ -110,11 +148,14 @@ public class NotificationService { notifications.put(conversationUuid, mList); } final Account account = message.getConversation().getAccount(); - updateNotification((!(this.mIsInForeground && this.mOpenConversation == null) || !isScreenOn) - && !account.inGracePeriod() - && !this.inMiniGracePeriod(account)); + final boolean doNotify = (!(this.mIsInForeground && this.mOpenConversation == null) || !isScreenOn) + && !account.inGracePeriod() + && !this.inMiniGracePeriod(account); + updateNotification(doNotify); + if (doNotify) { + notifyPebble(message); + } } - } public void clear() { @@ -131,6 +172,10 @@ public class NotificationService { } } + private void setNotificationColor(final Builder mBuilder) { + mBuilder.setColor(mXmppConnectionService.getResources().getColor(R.color.primary)); + } + private void updateNotification(final boolean notify) { final NotificationManager notificationManager = (NotificationManager) mXmppConnectionService .getSystemService(Context.NOTIFICATION_SERVICE); @@ -161,6 +206,10 @@ public class NotificationService { mBuilder.setSound(Uri.parse(ringtone)); } } + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + mBuilder.setCategory(Notification.CATEGORY_MESSAGE); + } + setNotificationColor(mBuilder); mBuilder.setSmallIcon(R.drawable.ic_notification); mBuilder.setDeleteIntent(createDeleteIntent()); mBuilder.setLights(0xffffffff, 2000, 4000); @@ -172,19 +221,19 @@ public class NotificationService { private Builder buildMultipleConversation() { final Builder mBuilder = new NotificationCompat.Builder( mXmppConnectionService); - NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(); + final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(); style.setBigContentTitle(notifications.size() + " " + mXmppConnectionService .getString(R.string.unread_conversations)); final StringBuilder names = new StringBuilder(); Conversation conversation = null; - for (ArrayList<Message> messages : notifications.values()) { + for (final ArrayList<Message> messages : notifications.values()) { if (messages.size() > 0) { conversation = messages.get(0).getConversation(); - String name = conversation.getName(); + final String name = conversation.getName(); style.addLine(Html.fromHtml("<b>" + name + "</b> " - + getReadableBody(messages.get(0)))); + + UIHelper.getMessagePreview(mXmppConnectionService,messages.get(0)).first)); names.append(name); names.append(", "); } @@ -199,8 +248,7 @@ public class NotificationService { mBuilder.setContentText(names.toString()); mBuilder.setStyle(style); if (conversation != null) { - mBuilder.setContentIntent(createContentIntent(conversation - .getUuid())); + mBuilder.setContentIntent(createContentIntent(conversation)); } return mBuilder; } @@ -214,14 +262,22 @@ public class NotificationService { mBuilder.setLargeIcon(mXmppConnectionService.getAvatarService() .get(conversation, getPixel(64))); mBuilder.setContentTitle(conversation.getName()); - final Message message; + Message message; if ((message = getImage(messages)) != null) { modifyForImage(mBuilder, message, messages, notify); } else { modifyForTextOnly(mBuilder, messages, notify); } - mBuilder.setContentIntent(createContentIntent(conversation - .getUuid())); + if ((message = getFirstDownloadableMessage(messages)) != null) { + mBuilder.addAction( + Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? + R.drawable.ic_file_download_white_24dp : R.drawable.ic_action_download, + mXmppConnectionService.getResources().getString(R.string.download_x_file, + UIHelper.getFileDescriptionString(mXmppConnectionService, message)), + createDownloadIntent(message) + ); + } + mBuilder.setContentIntent(createContentIntent(conversation)); } return mBuilder; } @@ -242,9 +298,11 @@ public class NotificationService { bigPictureStyle.bigPicture(bitmap); if (tmp.size() > 0) { bigPictureStyle.setSummaryText(getMergedBodies(tmp)); - builder.setContentText(getReadableBody(tmp.get(0))); + builder.setContentText(UIHelper.getMessagePreview(mXmppConnectionService,tmp.get(0)).first); } else { - builder.setContentText(mXmppConnectionService.getString(R.string.image_file)); + builder.setContentText(mXmppConnectionService.getString( + R.string.received_x_file, + UIHelper.getFileDescriptionString(mXmppConnectionService,message))); } builder.setStyle(bigPictureStyle); } catch (final FileNotFoundException e) { @@ -254,15 +312,14 @@ public class NotificationService { private void modifyForTextOnly(final Builder builder, final ArrayList<Message> messages, final boolean notify) { - builder.setStyle(new NotificationCompat.BigTextStyle() - .bigText(getMergedBodies(messages))); - builder.setContentText(getReadableBody(messages.get(0))); + builder.setStyle(new NotificationCompat.BigTextStyle().bigText(getMergedBodies(messages))); + builder.setContentText(UIHelper.getMessagePreview(mXmppConnectionService,messages.get(0)).first); if (notify) { - builder.setTicker(getReadableBody(messages.get(messages.size() - 1))); + builder.setTicker(UIHelper.getMessagePreview(mXmppConnectionService,messages.get(messages.size() - 1)).first); } } - private Message getImage(final ArrayList<Message> messages) { + private Message getImage(final Iterable<Message> messages) { for (final Message message : messages) { if (message.getType() == Message.TYPE_IMAGE && message.getDownloadable() == null @@ -273,10 +330,20 @@ public class NotificationService { return null; } - private String getMergedBodies(final ArrayList<Message> messages) { + private Message getFirstDownloadableMessage(final Iterable<Message> messages) { + for (final Message message : messages) { + if ((message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) && + message.getDownloadable() != null) { + return message; + } + } + return null; + } + + private CharSequence getMergedBodies(final ArrayList<Message> messages) { final StringBuilder text = new StringBuilder(); for (int i = 0; i < messages.size(); ++i) { - text.append(getReadableBody(messages.get(i))); + text.append(UIHelper.getMessagePreview(mXmppConnectionService,messages.get(i)).first); if (i != messages.size() - 1) { text.append("\n"); } @@ -284,52 +351,39 @@ public class NotificationService { return text.toString(); } - private String getReadableBody(final Message message) { - if (message.getDownloadable() != null - && (message.getDownloadable().getStatus() == Downloadable.STATUS_OFFER || message - .getDownloadable().getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE)) { - if (message.getType() == Message.TYPE_FILE) { - return mXmppConnectionService.getString(R.string.file_offered_for_download); - } else { - return mXmppConnectionService.getText( - R.string.image_offered_for_download).toString(); - } - } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { - return mXmppConnectionService.getText( - R.string.encrypted_message_received).toString(); - } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { - return mXmppConnectionService.getText(R.string.decryption_failed) - .toString(); - } else if (message.getType() == Message.TYPE_FILE) { - DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message); - return mXmppConnectionService.getString(R.string.file,file.getMimeType()); - } else if (message.getType() == Message.TYPE_IMAGE) { - return mXmppConnectionService.getText(R.string.image_file) - .toString(); - } else { - return message.getBody().trim(); - } - } - - private PendingIntent createContentIntent(final String conversationUuid) { + private PendingIntent createContentIntent(final String conversationUuid, final String downloadMessageUuid) { final TaskStackBuilder stackBuilder = TaskStackBuilder .create(mXmppConnectionService); stackBuilder.addParentStack(ConversationActivity.class); final Intent viewConversationIntent = new Intent(mXmppConnectionService, ConversationActivity.class); - viewConversationIntent.setAction(Intent.ACTION_VIEW); + if (downloadMessageUuid != null) { + viewConversationIntent.setAction(ConversationActivity.ACTION_DOWNLOAD); + } else { + viewConversationIntent.setAction(Intent.ACTION_VIEW); + } if (conversationUuid != null) { - viewConversationIntent.putExtra(ConversationActivity.CONVERSATION, - conversationUuid); + viewConversationIntent.putExtra(ConversationActivity.CONVERSATION, conversationUuid); viewConversationIntent.setType(ConversationActivity.VIEW_CONVERSATION); } + if (downloadMessageUuid != null) { + viewConversationIntent.putExtra(ConversationActivity.MESSAGE, downloadMessageUuid); + } stackBuilder.addNextIntent(viewConversationIntent); return stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); } + private PendingIntent createDownloadIntent(final Message message) { + return createContentIntent(message.getConversationUuid(), message.getUuid()); + } + + private PendingIntent createContentIntent(final Conversation conversation) { + return createContentIntent(conversation.getUuid(), null); + } + private PendingIntent createDeleteIntent() { final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class); @@ -393,15 +447,32 @@ public class NotificationService { public Notification createForegroundNotification() { final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService); - mBuilder.setSmallIcon(R.drawable.ic_stat_communication_import_export); + mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service)); - mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_disable)); - mBuilder.setContentIntent(createDisableForeground()); + mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_open_conversations)); + mBuilder.setContentIntent(createOpenConversationsIntent()); mBuilder.setWhen(0); mBuilder.setPriority(NotificationCompat.PRIORITY_MIN); + final int cancelIcon; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + mBuilder.setCategory(Notification.CATEGORY_SERVICE); + mBuilder.setSmallIcon(R.drawable.ic_import_export_white_48dp); + cancelIcon = R.drawable.ic_cancel_white_24dp; + } else { + mBuilder.setSmallIcon(R.drawable.ic_stat_communication_import_export); + cancelIcon = R.drawable.ic_action_cancel; + } + mBuilder.addAction(cancelIcon, + mXmppConnectionService.getString(R.string.disable_foreground_service), + createDisableForeground()); + setNotificationColor(mBuilder); return mBuilder.build(); } + private PendingIntent createOpenConversationsIntent() { + return PendingIntent.getActivity(mXmppConnectionService, 0, new Intent(mXmppConnectionService,ConversationActivity.class),0); + } + public void updateErrorNotification() { final NotificationManager mNotificationManager = (NotificationManager) mXmppConnectionService.getSystemService(Context.NOTIFICATION_SERVICE); final List<Account> errors = new ArrayList<>(); @@ -422,9 +493,13 @@ public class NotificationService { mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_fix)); } mBuilder.setOngoing(true); - mBuilder.setLights(0xffffffff, 2000, 4000); - mBuilder.setSmallIcon(R.drawable.ic_stat_alert_warning); - TaskStackBuilder stackBuilder = TaskStackBuilder.create(mXmppConnectionService); + //mBuilder.setLights(0xffffffff, 2000, 4000); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + mBuilder.setSmallIcon(R.drawable.ic_warning_white_36dp); + } else { + mBuilder.setSmallIcon(R.drawable.ic_stat_alert_warning); + } + final TaskStackBuilder stackBuilder = TaskStackBuilder.create(mXmppConnectionService); stackBuilder.addParentStack(ConversationActivity.class); final Intent manageAccountsIntent = new Intent(mXmppConnectionService,ManageAccountActivity.class); diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 6bdc55a1..e34f9bd7 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -84,7 +84,6 @@ import eu.siacs.conversations.xmpp.OnMessagePacketReceived; import eu.siacs.conversations.xmpp.OnPresencePacketReceived; import eu.siacs.conversations.xmpp.OnStatusChanged; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; -import eu.siacs.conversations.xmpp.PacketReceived; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.forms.Field; @@ -101,9 +100,8 @@ import eu.siacs.conversations.xmpp.stanzas.PresencePacket; public class XmppConnectionService extends Service implements OnPhoneContactsLoadedListener { public static final String ACTION_CLEAR_NOTIFICATION = "clear_notification"; - private static final String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts"; public static final String ACTION_DISABLE_FOREGROUND = "disable_foreground"; - + private static final String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts"; private ContentObserver contactObserver = new ContentObserver(null) { @Override public void onChange(boolean selfChange) { @@ -115,6 +113,56 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } }; private final IBinder mBinder = new XmppConnectionBinder(); + private final List<Conversation> conversations = new CopyOnWriteArrayList<>(); + private final FileObserver fileObserver = new FileObserver( + FileBackend.getConversationsImageDirectory()) { + + @Override + public void onEvent(int event, String path) { + if (event == FileObserver.DELETE) { + markFileDeleted(path.split("\\.")[0]); + } + } + }; + private final OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() { + + @Override + public void onJinglePacketReceived(Account account, JinglePacket packet) { + mJingleConnectionManager.deliverPacket(account, packet); + } + }; + private final OnBindListener mOnBindListener = new OnBindListener() { + + @Override + public void onBind(final Account account) { + account.getRoster().clearPresences(); + account.pendingConferenceJoins.clear(); + account.pendingConferenceLeaves.clear(); + fetchRosterFromServer(account); + fetchBookmarks(account); + sendPresence(account); + connectMultiModeConversations(account); + updateConversationUi(); + } + }; + private final OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() { + + @Override + public void onMessageAcknowledged(Account account, String uuid) { + for (final Conversation conversation : getConversations()) { + if (conversation.getAccount() == account) { + Message message = conversation.findUnsentMessageWithUuid(uuid); + if (message != null) { + markMessage(message, Message.STATUS_SEND); + if (conversation.setLastMessageTransmitted(System.currentTimeMillis())) { + databaseBackend.updateConversation(conversation); + } + } + } + } + } + }; + private final IqGenerator mIqGenerator = new IqGenerator(this); public DatabaseBackend databaseBackend; public OnContactStatusChanged onContactStatusChanged = new OnContactStatusChanged() { @@ -143,7 +191,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa private MessageGenerator mMessageGenerator = new MessageGenerator(this); private PresenceGenerator mPresenceGenerator = new PresenceGenerator(this); private List<Account> accounts; - private final List<Conversation> conversations = new CopyOnWriteArrayList<>(); private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager( this); private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager( @@ -189,12 +236,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } syncDirtyContacts(account); - scheduleWakeupCall(Config.PING_MAX_INTERVAL, true); + scheduleWakeUpCall(Config.PING_MAX_INTERVAL,account.getUuid().hashCode()); } else if (account.getStatus() == Account.State.OFFLINE) { resetSendingToWaiting(account); if (!account.isOptionSet(Account.OPTION_DISABLED)) { int timeToReconnect = mRandom.nextInt(50) + 10; - scheduleWakeupCall(timeToReconnect, false); + scheduleWakeUpCall(timeToReconnect,account.getUuid().hashCode()); } } else if (account.getStatus() == Account.State.REGISTRATION_SUCCESSFUL) { databaseBackend.updateAccount(account); @@ -207,13 +254,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa + ": error connecting account. try again in " + next + "s for the " + (connection.getAttempt() + 1) + " time"); - scheduleWakeupCall((int) (next * 1.2), false); + scheduleWakeUpCall(next,account.getUuid().hashCode()); } } getNotificationService().updateErrorNotification(); } }; - private int accountChangedListenerCount = 0; private OnRosterUpdate mOnRosterUpdate = null; private OnUpdateBlocklist mOnUpdateBlocklist = null; @@ -222,64 +268,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa private OnMucRosterUpdate mOnMucRosterUpdate = null; private int mucRosterChangedListenerCount = 0; private SecureRandom mRandom; - private final FileObserver fileObserver = new FileObserver( - FileBackend.getConversationsImageDirectory()) { - - @Override - public void onEvent(int event, String path) { - if (event == FileObserver.DELETE) { - markFileDeleted(path.split("\\.")[0]); - } - } - }; - private final OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() { - - @Override - public void onJinglePacketReceived(Account account, JinglePacket packet) { - mJingleConnectionManager.deliverPacket(account, packet); - } - }; - private OpenPgpServiceConnection pgpServiceConnection; private PgpEngine mPgpEngine = null; - private Intent pingIntent; - private PendingIntent pendingPingIntent = null; private WakeLock wakeLock; private PowerManager pm; - private final OnBindListener mOnBindListener = new OnBindListener() { - - @Override - public void onBind(final Account account) { - account.getRoster().clearPresences(); - account.pendingConferenceJoins.clear(); - account.pendingConferenceLeaves.clear(); - fetchRosterFromServer(account); - fetchBookmarks(account); - sendPresencePacket(account,mPresenceGenerator.sendPresence(account)); - connectMultiModeConversations(account); - updateConversationUi(); - } - }; - - private final OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() { - - @Override - public void onMessageAcknowledged(Account account, String uuid) { - for (final Conversation conversation : getConversations()) { - if (conversation.getAccount() == account) { - Message message = conversation.findUnsentMessageWithUuid(uuid); - if (message != null) { - markMessage(message, Message.STATUS_SEND); - if (conversation.setLastMessageTransmitted(System.currentTimeMillis())) { - databaseBackend.updateConversation(conversation); - } - } - } - } - } - }; private LruCache<String, Bitmap> mBitmapCache; - private final IqGenerator mIqGenerator = new IqGenerator(this); private Thread mPhoneContactMergerThread; public PgpEngine getPgpEngine() { @@ -389,18 +382,22 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa @Override public int onStartCommand(Intent intent, int flags, int startId) { - if (intent != null && intent.getAction() != null) { - if (intent.getAction().equals(ACTION_MERGE_PHONE_CONTACTS)) { - PhoneHelper.loadPhoneContacts(getApplicationContext(), new ArrayList<Bundle>(), this); - return START_STICKY; - } else if (intent.getAction().equals(Intent.ACTION_SHUTDOWN)) { - logoutAndSave(); - return START_NOT_STICKY; - } else if (intent.getAction().equals(ACTION_CLEAR_NOTIFICATION)) { - mNotificationService.clear(); - } else if (intent.getAction().equals(ACTION_DISABLE_FOREGROUND)) { - getPreferences().edit().putBoolean("keep_foreground_service",false).commit(); - toggleForegroundService(); + final String action = intent == null ? null : intent.getAction(); + if (action != null) { + switch (action) { + case ACTION_MERGE_PHONE_CONTACTS: + PhoneHelper.loadPhoneContacts(getApplicationContext(), new CopyOnWriteArrayList<Bundle>(), this); + return START_STICKY; + case Intent.ACTION_SHUTDOWN: + logoutAndSave(); + return START_NOT_STICKY; + case ACTION_CLEAR_NOTIFICATION: + mNotificationService.clear(); + break; + case ACTION_DISABLE_FOREGROUND: + getPreferences().edit().putBoolean("keep_foreground_service",false).commit(); + toggleForegroundService(); + break; } } this.wakeLock.acquire(); @@ -420,37 +417,39 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } if (account.getStatus() == Account.State.ONLINE) { - long lastReceived = account.getXmppConnection() - .getLastPacketReceived(); - long lastSent = account.getXmppConnection() - .getLastPingSent(); - if (lastSent - lastReceived >= Config.PING_TIMEOUT * 1000) { - Log.d(Config.LOGTAG, account.getJid() - + ": ping timeout"); + long lastReceived = account.getXmppConnection().getLastPacketReceived(); + long lastSent = account.getXmppConnection().getLastPingSent(); + long pingInterval = "ui".equals(action) ? Config.PING_MIN_INTERVAL * 1000 : Config.PING_MAX_INTERVAL * 1000; + long msToNextPing = (Math.max(lastReceived,lastSent) + pingInterval) - SystemClock.elapsedRealtime(); + if (lastSent > lastReceived && (lastSent + Config.PING_TIMEOUT * 1000) < SystemClock.elapsedRealtime()) { + Log.d(Config.LOGTAG, account.getJid().toBareJid()+ ": ping timeout"); this.reconnectAccount(account, true); - } else if (SystemClock.elapsedRealtime() - lastReceived >= Config.PING_MIN_INTERVAL * 1000) { + } else if (msToNextPing <= 0) { account.getXmppConnection().sendPing(); - this.scheduleWakeupCall(2, false); + Log.d(Config.LOGTAG, account.getJid().toBareJid()+" send ping"); + this.scheduleWakeUpCall(Config.PING_TIMEOUT,account.getUuid().hashCode()); + } else { + this.scheduleWakeUpCall((int) (msToNextPing / 1000), account.getUuid().hashCode()); } } else if (account.getStatus() == Account.State.OFFLINE) { if (account.getXmppConnection() == null) { - account.setXmppConnection(this - .createConnection(account)); + account.setXmppConnection(this.createConnection(account)); } new Thread(account.getXmppConnection()).start(); - } else if ((account.getStatus() == Account.State.CONNECTING) - && ((SystemClock.elapsedRealtime() - account - .getXmppConnection().getLastConnect()) / 1000 >= Config.CONNECT_TIMEOUT)) { - Log.d(Config.LOGTAG, account.getJid() - + ": time out during connect reconnecting"); - reconnectAccount(account, true); + } else if (account.getStatus() == Account.State.CONNECTING) { + long timeout = Config.CONNECT_TIMEOUT - ((SystemClock.elapsedRealtime() - account.getXmppConnection().getLastConnect()) / 1000); + if (timeout < 0) { + Log.d(Config.LOGTAG, account.getJid() + ": time out during connect reconnecting"); + reconnectAccount(account, true); + } else { + scheduleWakeUpCall((int) timeout,account.getUuid().hashCode()); + } } else { if (account.getXmppConnection().getTimeToNextAttempt() <= 0) { reconnectAccount(account, true); } } - // in any case. reschedule wakup call - this.scheduleWakeupCall(Config.PING_MAX_INTERVAL, true); + } if (mOnAccountUpdate != null) { mOnAccountUpdate.onAccountUpdate(); @@ -503,7 +502,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa this.databaseBackend.readRoster(account.getRoster()); } initConversations(); - PhoneHelper.loadPhoneContacts(getApplicationContext(),new ArrayList<Bundle>(), this); + PhoneHelper.loadPhoneContacts(getApplicationContext(),new CopyOnWriteArrayList<Bundle>(), this); getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactObserver); this.fileObserver.startWatching(); @@ -547,42 +546,16 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa stopSelf(); } - protected void scheduleWakeupCall(int seconds, boolean ping) { - long timeToWake = SystemClock.elapsedRealtime() + seconds * 1000; - Context context = getApplicationContext(); - AlarmManager alarmManager = (AlarmManager) context - .getSystemService(Context.ALARM_SERVICE); + protected void scheduleWakeUpCall(int seconds, int requestCode) { + final long timeToWake = SystemClock.elapsedRealtime() + (seconds + 1) * 1000; - if (ping) { - if (this.pingIntent == null) { - this.pingIntent = new Intent(context, EventReceiver.class); - this.pingIntent.setAction("ping"); - this.pingIntent.putExtra("time", timeToWake); - this.pendingPingIntent = PendingIntent.getBroadcast(context, 0, - this.pingIntent, 0); - alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, - timeToWake, pendingPingIntent); - } else { - long scheduledTime = this.pingIntent.getLongExtra("time", 0); - if (scheduledTime < SystemClock.elapsedRealtime() - || (scheduledTime > timeToWake)) { - this.pingIntent.putExtra("time", timeToWake); - alarmManager.cancel(this.pendingPingIntent); - this.pendingPingIntent = PendingIntent.getBroadcast( - context, 0, this.pingIntent, 0); - alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, - timeToWake, pendingPingIntent); - } - } - } else { - Intent intent = new Intent(context, EventReceiver.class); - intent.setAction("ping_check"); - PendingIntent alarmIntent = PendingIntent.getBroadcast(context, 0, - intent, 0); - alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeToWake, - alarmIntent); - } + Context context = getApplicationContext(); + AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + Intent intent = new Intent(context, EventReceiver.class); + intent.setAction("ping"); + PendingIntent alarmIntent = PendingIntent.getBroadcast(context, requestCode, intent, 0); + alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeToWake, alarmIntent); } public XmppConnection createConnection(final Account account) { @@ -787,7 +760,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public void fetchRosterFromServer(final Account account) { - final IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET); + final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.GET); if (!"".equals(account.getRosterVersion())) { Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": fetching roster version " + account.getRosterVersion()); @@ -800,10 +773,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public void fetchBookmarks(final Account account) { - final IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET); + final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.GET); final Element query = iqPacket.query("jabber:iq:private"); query.addChild("storage", "storage:bookmarks"); - final PacketReceived callback = new OnIqPacketReceived() { + final OnIqPacketReceived callback = new OnIqPacketReceived() { @Override public void onIqPacketReceived(final Account account, final IqPacket packet) { @@ -835,7 +808,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public void pushBookmarks(Account account) { - IqPacket iqPacket = new IqPacket(IqPacket.TYPE_SET); + IqPacket iqPacket = new IqPacket(IqPacket.TYPE.SET); Element query = iqPacket.query("jabber:iq:private"); Element storage = query.addChild("storage", "storage:bookmarks"); for (Bookmark bookmark : account.getBookmarks()) { @@ -970,6 +943,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa List<Message> messages = databaseBackend.getMessages(conversation, 50,timestamp); if (messages.size() > 0) { conversation.addAll(0, messages); + checkDeletedFiles(conversation); callback.onMoreMessagesLoaded(messages.size(), conversation); } else if (conversation.hasMessagesLeftOnServer() && account.isOnlineAndConnected() @@ -984,11 +958,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa }).start(); } - public interface OnMoreMessagesLoaded { - public void onMoreMessagesLoaded(int count,Conversation conversation); - public void informUser(int r); - } - public List<Account> getAccounts() { return this.accounts; } @@ -1003,23 +972,23 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public Conversation find(final Iterable<Conversation> haystack, final Account account, final Jid jid) { - if (jid == null ) { + if (jid == null) { return null; } for (final Conversation conversation : haystack) { if ((account == null || conversation.getAccount() == account) && (conversation.getJid().toBareJid().equals(jid.toBareJid()))) { return conversation; - } + } } return null; } - public Conversation findOrCreateConversation(final Account account, final Jid jid,final boolean muc) { - return this.findOrCreateConversation(account,jid,muc,null); + public Conversation findOrCreateConversation(final Account account, final Jid jid, final boolean muc) { + return this.findOrCreateConversation(account, jid, muc, null); } - public Conversation findOrCreateConversation(final Account account, final Jid jid,final boolean muc, final MessageArchiveService.Query query) { + public Conversation findOrCreateConversation(final Account account, final Jid jid, final boolean muc, final MessageArchiveService.Query query) { synchronized (this.conversations) { Conversation conversation = find(account, jid); if (conversation != null) { @@ -1031,9 +1000,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa conversation.setAccount(account); if (muc) { conversation.setMode(Conversation.MODE_MULTI); + conversation.setContactJid(jid); } else { conversation.setMode(Conversation.MODE_SINGLE); + conversation.setContactJid(jid.toBareJid()); } + conversation.setNextEncryption(-1); conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE)); this.databaseBackend.updateConversation(conversation); } else { @@ -1048,18 +1020,23 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa conversation = new Conversation(conversationName, account, jid, Conversation.MODE_MULTI); } else { - conversation = new Conversation(conversationName, account, jid, + conversation = new Conversation(conversationName, account, jid.toBareJid(), Conversation.MODE_SINGLE); } this.databaseBackend.createConversation(conversation); } - if (query == null) { - this.mMessageArchiveService.query(conversation); - } else { - if (query.getConversation() == null) { - this.mMessageArchiveService.query(conversation,query.getStart()); + if (account.getXmppConnection() != null + && account.getXmppConnection().getFeatures().mam() + && !muc) { + if (query == null) { + this.mMessageArchiveService.query(conversation); + } else { + if (query.getConversation() == null) { + this.mMessageArchiveService.query(conversation, query.getStart()); + } } } + checkDeletedFiles(conversation); this.conversations.add(conversation); updateConversationUi(); return conversation; @@ -1067,6 +1044,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public void archiveConversation(Conversation conversation) { + conversation.setStatus(Conversation.STATUS_ARCHIVED); + conversation.setNextEncryption(-1); synchronized (this.conversations) { if (conversation.getMode() == Conversation.MODE_MULTI) { if (conversation.getAccount().getStatus() == Account.State.ONLINE) { @@ -1107,7 +1086,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa sendIqPacket(account, iq, new OnIqPacketReceived() { @Override public void onIqPacketReceived(final Account account, final IqPacket packet) { - if (packet.getType() == IqPacket.TYPE_RESULT) { + if (packet.getType() == IqPacket.TYPE.RESULT) { account.setPassword(newPassword); databaseBackend.updateAccount(account); callback.onPasswordChangeSucceeded(); @@ -1118,11 +1097,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa }); } - public interface OnAccountPasswordChanged { - public void onPasswordChangeSucceeded(); - public void onPasswordChangeFailed(); - } - public void deleteAccount(final Account account) { synchronized (this.conversations) { for (final Conversation conversation : conversations) { @@ -1311,7 +1285,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa && (conversation.getAccount() == account)) { conversation.resetMucOptions(); joinMuc(conversation); - } + } } } @@ -1329,21 +1303,23 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa PresencePacket packet = new PresencePacket(); packet.setFrom(conversation.getAccount().getJid()); packet.setTo(joinJid); - Element x = packet.addChild("x","http://jabber.org/protocol/muc"); + Element x = packet.addChild("x", "http://jabber.org/protocol/muc"); if (conversation.getMucOptions().getPassword() != null) { x.addChild("password").setContent(conversation.getMucOptions().getPassword()); } - x.addChild("history").setAttribute("since",PresenceGenerator.getTimestamp(conversation.getLastMessageTransmitted())); + x.addChild("history").setAttribute("since", PresenceGenerator.getTimestamp(conversation.getLastMessageTransmitted())); String sig = account.getPgpSignature(); if (sig != null) { packet.addChild("status").setContent("online"); packet.addChild("x", "jabber:x:signed").setContent(sig); } sendPresencePacket(account, packet); + fetchConferenceConfiguration(conversation); if (!joinJid.equals(conversation.getJid())) { conversation.setContactJid(joinJid); databaseBackend.updateConversation(conversation); } + conversation.setHasMessagesLeftOnServer(false); } else { account.pendingConferenceJoins.add(conversation); } @@ -1437,7 +1413,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return server; } } - for(Account other : getAccounts()) { + for (Account other : getAccounts()) { if (other != account && other.getXmppConnection() != null) { server = other.getXmppConnection().getMucServer(); if (server != null) { @@ -1455,12 +1431,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa String server = findConferenceServer(account); if (server == null) { if (callback != null) { - callback.error(R.string.no_conference_server_found,null); + callback.error(R.string.no_conference_server_found, null); } return; } - String name = new BigInteger(75,getRNG()).toString(32); - Jid jid = Jid.fromParts(name,server,null); + String name = new BigInteger(75, getRNG()).toString(32); + Jid jid = Jid.fromParts(name, server, null); final Conversation conversation = findOrCreateConversation(account, jid, true); joinMuc(conversation); Bundle options = new Bundle(); @@ -1471,8 +1447,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa pushConferenceConfiguration(conversation, options, new OnConferenceOptionsPushed() { @Override public void onPushSucceeded() { - for(Jid invite : jids) { - invite(conversation,invite); + for (Jid invite : jids) { + invite(conversation, invite); } if (callback != null) { callback.success(conversation); @@ -1494,19 +1470,43 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } else { if (callback != null) { - callback.error(R.string.not_connected_try_again,null); + callback.error(R.string.not_connected_try_again, null); } } } - public void pushConferenceConfiguration(final Conversation conversation,final Bundle options, final OnConferenceOptionsPushed callback) { - IqPacket request = new IqPacket(IqPacket.TYPE_GET); + public void fetchConferenceConfiguration(final Conversation conversation) { + IqPacket request = new IqPacket(IqPacket.TYPE.GET); + request.setTo(conversation.getJid().toBareJid()); + request.query("http://jabber.org/protocol/disco#info"); + sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() != IqPacket.TYPE.ERROR) { + ArrayList<String> features = new ArrayList<>(); + for (Element child : packet.query().getChildren()) { + if (child != null && child.getName().equals("feature")) { + String var = child.getAttribute("var"); + if (var != null) { + features.add(var); + } + } + } + conversation.getMucOptions().updateFeatures(features); + updateConversationUi(); + } + } + }); + } + + public void pushConferenceConfiguration(final Conversation conversation, final Bundle options, final OnConferenceOptionsPushed callback) { + IqPacket request = new IqPacket(IqPacket.TYPE.GET); request.setTo(conversation.getJid().toBareJid()); request.query("http://jabber.org/protocol/muc#owner"); - sendIqPacket(conversation.getAccount(),request,new OnIqPacketReceived() { + sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() != IqPacket.TYPE_ERROR) { + if (packet.getType() != IqPacket.TYPE.ERROR) { Data data = Data.parse(packet.query().findChild("x", "jabber:x:data")); for (Field field : data.getFields()) { if (options.containsKey(field.getName())) { @@ -1514,13 +1514,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } data.submit(); - IqPacket set = new IqPacket(IqPacket.TYPE_SET); + IqPacket set = new IqPacket(IqPacket.TYPE.SET); set.setTo(conversation.getJid().toBareJid()); set.query("http://jabber.org/protocol/muc#owner").addChild(data); sendIqPacket(account, set, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE_RESULT) { + if (packet.getType() == IqPacket.TYPE.RESULT) { if (callback != null) { callback.onPushSucceeded(); } @@ -1540,6 +1540,60 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa }); } + public void pushSubjectToConference(final Conversation conference, final String subject) { + MessagePacket packet = this.getMessageGenerator().conferenceSubject(conference, subject); + this.sendMessagePacket(conference.getAccount(), packet); + final MucOptions mucOptions = conference.getMucOptions(); + final MucOptions.User self = mucOptions.getSelf(); + if (!mucOptions.persistent() && self.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) { + Bundle options = new Bundle(); + options.putString("muc#roomconfig_persistentroom", "1"); + this.pushConferenceConfiguration(conference, options, null); + } + } + + public void changeAffiliationInConference(final Conversation conference, Jid user, 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) { + callback.onAffiliationChangedSuccessful(jid); + } else { + callback.onAffiliationChangeFailed(jid, R.string.could_not_change_affiliation); + } + } + }); + } + + 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) { + jids.add(user.getJid()); + } + } + IqPacket request = this.mIqGenerator.changeAffiliation(conference, jids, after.toString()); + sendIqPacket(conference.getAccount(), request, null); + } + + public void changeRoleInConference(final Conversation conference, final String nick, MucOptions.Role role, final OnRoleChanged callback) { + IqPacket request = this.mIqGenerator.changeRole(conference, nick, role.toString()); + Log.d(Config.LOGTAG, request.toString()); + sendIqPacket(conference.getAccount(), request, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + Log.d(Config.LOGTAG, packet.toString()); + if (packet.getType() == IqPacket.TYPE.RESULT) { + callback.onRoleChangedSuccessful(nick); + } else { + callback.onRoleChangeFailed(nick, R.string.could_not_change_role); + } + } + }); + } + public void disconnect(Account account, boolean force) { if ((account.getStatus() == Account.State.ONLINE) || (account.getStatus() == Account.State.DISABLED)) { @@ -1560,7 +1614,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } account.getXmppConnection().disconnect(force); - } + } } @Override @@ -1599,8 +1653,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa final Session otrSession = conversation.getOtrSession(); Log.d(Config.LOGTAG, account.getJid().toBareJid() + " otr session established with " - + conversation.getJid() + "/" - + otrSession.getSessionID().getUserID()); + + conversation.getJid() + "/" + + otrSession.getSessionID().getUserID()); conversation.findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() { @Override @@ -1642,7 +1696,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa try { packet.setBody(otrSession .transformSending(CryptoHelper.FILETRANSFER - + CryptoHelper.bytesToHex(symmetricKey))); + + CryptoHelper.bytesToHex(symmetricKey))[0]); sendMessagePacket(account, packet); conversation.setSymmetricKey(symmetricKey); return true; @@ -1660,9 +1714,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (account.getStatus() == Account.State.ONLINE) { final boolean ask = contact.getOption(Contact.Options.ASKING); final boolean sendUpdates = contact - .getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST) - && contact.getOption(Contact.Options.PREEMPTIVE_GRANT); - final IqPacket iq = new IqPacket(IqPacket.TYPE_SET); + .getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST) + && contact.getOption(Contact.Options.PREEMPTIVE_GRANT); + final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); iq.query(Xmlns.ROSTER).addChild(contact.asElement()); account.getXmppConnection().sendIqPacket(iq, null); if (sendUpdates) { @@ -1677,12 +1731,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public void publishAvatar(final Account account, - final Uri image, - final UiCallback<Avatar> callback) { + final Uri image, + final UiCallback<Avatar> callback) { final Bitmap.CompressFormat format = Config.AVATAR_FORMAT; final int size = Config.AVATAR_SIZE; final Avatar avatar = getFileBackend() - .getPepAvatar(image, size, format); + .getPepAvatar(image, size, format); if (avatar != null) { avatar.height = size; avatar.width = size; @@ -1702,15 +1756,15 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa @Override public void onIqPacketReceived(Account account, IqPacket result) { - if (result.getType() == IqPacket.TYPE_RESULT) { + if (result.getType() == IqPacket.TYPE.RESULT) { final IqPacket packet = XmppConnectionService.this.mIqGenerator - .publishAvatarMetadata(avatar); + .publishAvatarMetadata(avatar); sendIqPacket(account, packet, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, - IqPacket result) { - if (result.getType() == IqPacket.TYPE_RESULT) { + IqPacket result) { + if (result.getType() == IqPacket.TYPE.RESULT) { if (account.setAvatar(avatar.getFilename())) { databaseBackend.updateAccount(account); } @@ -1739,15 +1793,15 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public void fetchAvatar(Account account, final Avatar avatar, - final UiCallback<Avatar> callback) { + final UiCallback<Avatar> callback) { IqPacket packet = this.mIqGenerator.retrieveAvatar(avatar); sendIqPacket(account, packet, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket result) { final String ERROR = account.getJid().toBareJid() - + ": fetching avatar for " + avatar.owner + " failed "; - if (result.getType() == IqPacket.TYPE_RESULT) { + + ": fetching avatar for " + avatar.owner + " failed "; + if (result.getType() == IqPacket.TYPE.RESULT) { avatar.image = mIqParser.avatarData(result); if (avatar.image != null) { if (getFileBackend().save(avatar)) { @@ -1760,7 +1814,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa updateAccountUi(); } else { Contact contact = account.getRoster() - .getContact(avatar.owner); + .getContact(avatar.owner); contact.setAvatar(avatar.getFilename()); getAvatarService().clear(contact); updateConversationUi(); @@ -1795,13 +1849,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public void checkForAvatar(Account account, - final UiCallback<Avatar> callback) { + final UiCallback<Avatar> callback) { IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null); this.sendIqPacket(account, packet, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE_RESULT) { + if (packet.getType() == IqPacket.TYPE.RESULT) { Element pubsub = packet.findChild("pubsub", "http://jabber.org/protocol/pubsub"); if (pubsub != null) { @@ -1835,7 +1889,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa contact.setOption(Contact.Options.DIRTY_DELETE); Account account = contact.getAccount(); if (account.getStatus() == Account.State.ONLINE) { - IqPacket iq = new IqPacket(IqPacket.TYPE_SET); + IqPacket iq = new IqPacket(IqPacket.TYPE.SET); Element item = iq.query(Xmlns.ROSTER).addChild("item"); item.setAttribute("jid", contact.getJid().toString()); item.setAttribute("subscription", "remove"); @@ -1861,8 +1915,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } Thread thread = new Thread(account.getXmppConnection()); thread.start(); - scheduleWakeupCall((int) (Config.CONNECT_TIMEOUT * 1.2), - false); + scheduleWakeUpCall(Config.CONNECT_TIMEOUT, account.getUuid().hashCode()); } else { account.getRoster().clearPresences(); account.setXmppConnection(null); @@ -1891,7 +1944,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public boolean markMessage(final Account account, final Jid recipient, final String uuid, - final int status) { + final int status) { if (uuid == null) { return false; } else { @@ -1899,20 +1952,20 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (conversation.getJid().equals(recipient) && conversation.getAccount().equals(account)) { return markMessage(conversation, uuid, status); - } + } } return false; } } public boolean markMessage(Conversation conversation, String uuid, - int status) { + int status) { if (uuid == null) { return false; } else { Message message = conversation.findSentMessageWithUuid(uuid); - if (message!=null) { - markMessage(message,status); + if (message != null) { + markMessage(message, status); return true; } else { return false; @@ -1923,9 +1976,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public void markMessage(Message message, int status) { if (status == Message.STATUS_SEND_FAILED && (message.getStatus() == Message.STATUS_SEND_RECEIVED || message - .getStatus() == Message.STATUS_SEND_DISPLAYED)) { + .getStatus() == Message.STATUS_SEND_DISPLAYED)) { return; - } + } message.setStatus(status); databaseBackend.updateMessage(message); updateConversationUi(); @@ -1933,7 +1986,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public SharedPreferences getPreferences() { return PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()); + .getDefaultSharedPreferences(getApplicationContext()); } public boolean forceEncryption() { @@ -2009,11 +2062,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa final Message markable = conversation.getLatestMarkableMessage(); this.markRead(conversation); if (confirmMessages() && markable != null && markable.getRemoteMsgId() != null) { - Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid()+ ": sending read marker to " + markable.getCounterpart().toString()); + Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": sending read marker to " + markable.getCounterpart().toString()); Account account = conversation.getAccount(); final Jid to = markable.getCounterpart(); MessagePacket packet = mMessageGenerator.confirm(account, to, markable.getRemoteMsgId()); - this.sendMessagePacket(conversation.getAccount(),packet); + this.sendMessagePacket(conversation.getAccount(), packet); } updateConversationUi(); } @@ -2090,13 +2143,17 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } - public void sendIqPacket(final Account account, final IqPacket packet, final PacketReceived callback) { + public void sendIqPacket(final Account account, final IqPacket packet, final OnIqPacketReceived callback) { final XmppConnection connection = account.getXmppConnection(); if (connection != null) { connection.sendIqPacket(packet, callback); } } + public void sendPresence(final Account account) { + sendPresencePacket(account, mPresenceGenerator.sendPresence(account)); + } + public MessageGenerator getMessageGenerator() { return this.mMessageGenerator; } @@ -2109,7 +2166,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return this.mIqGenerator; } - public IqParser getIqParser() { return this.mIqParser; } + public IqParser getIqParser() { + return this.mIqParser; + } public JingleConnectionManager getJingleConnectionManager() { return this.mJingleConnectionManager; @@ -2168,33 +2227,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa }).start(); } - public interface OnConversationUpdate { - public void onConversationUpdate(); - } - - public interface OnAccountUpdate { - public void onAccountUpdate(); - } - - public interface OnRosterUpdate { - public void onRosterUpdate(); - } - - public interface OnMucRosterUpdate { - public void onMucRosterUpdate(); - } - - private interface OnConferenceOptionsPushed { - public void onPushSucceeded(); - public void onPushFailed(); - } - - public class XmppConnectionBinder extends Binder { - public XmppConnectionService getService() { - return XmppConnectionService.this; - } - } - public void sendBlockRequest(final Blockable blockable) { if (blockable != null && blockable.getBlockedJid() != null) { final Jid jid = blockable.getBlockedJid(); @@ -2202,7 +2234,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa @Override public void onIqPacketReceived(final Account account, final IqPacket packet) { - if (packet.getType() == IqPacket.TYPE_RESULT) { + if (packet.getType() == IqPacket.TYPE.RESULT) { account.getBlocklist().add(jid); updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED); } @@ -2217,7 +2249,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa this.sendIqPacket(blockable.getAccount(), getIqGenerator().generateSetUnblockRequest(jid), new OnIqPacketReceived() { @Override public void onIqPacketReceived(final Account account, final IqPacket packet) { - if (packet.getType() == IqPacket.TYPE_RESULT) { + if (packet.getType() == IqPacket.TYPE.RESULT) { account.getBlocklist().remove(jid); updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); } @@ -2225,4 +2257,56 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa }); } } + + public interface OnMoreMessagesLoaded { + public void onMoreMessagesLoaded(int count, Conversation conversation); + + public void informUser(int r); + } + + public interface OnAccountPasswordChanged { + public void onPasswordChangeSucceeded(); + + public void onPasswordChangeFailed(); + } + + public interface OnAffiliationChanged { + public void onAffiliationChangedSuccessful(Jid jid); + + public void onAffiliationChangeFailed(Jid jid, int resId); + } + + public interface OnRoleChanged { + public void onRoleChangedSuccessful(String nick); + + public void onRoleChangeFailed(String nick, int resid); + } + + public interface OnConversationUpdate { + public void onConversationUpdate(); + } + + public interface OnAccountUpdate { + public void onAccountUpdate(); + } + + public interface OnRosterUpdate { + public void onRosterUpdate(); + } + + public interface OnMucRosterUpdate { + public void onMucRosterUpdate(); + } + + public interface OnConferenceOptionsPushed { + public void onPushSucceeded(); + + public void onPushFailed(); + } + + public class XmppConnectionBinder extends Binder { + public XmppConnectionService getService() { + return XmppConnectionService.this; + } + } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index eeb015f3..399d9fdf 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -1,8 +1,10 @@ package eu.siacs.conversations.ui; import android.annotation.TargetApi; +import android.app.AlertDialog; import android.app.PendingIntent; import android.content.Context; +import android.content.DialogInterface; import android.content.IntentSender.SendIntentException; import android.graphics.Bitmap; import android.os.Build; @@ -31,12 +33,14 @@ import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Bookmark; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.MucOptions.User; +import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService.OnMucRosterUpdate; import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdate; -import eu.siacs.conversations.xmpp.stanzas.MessagePacket; +import eu.siacs.conversations.xmpp.jid.Jid; -public class ConferenceDetailsActivity extends XmppActivity implements OnConversationUpdate, OnMucRosterUpdate { +public class ConferenceDetailsActivity extends XmppActivity implements OnConversationUpdate, OnMucRosterUpdate, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnRoleChanged, XmppConnectionService.OnConferenceOptionsPushed { public static final String ACTION_VIEW_MUC = "view_muc"; private Conversation mConversation; private OnClickListener inviteListener = new OnClickListener() { @@ -54,11 +58,14 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers private TextView mAccountJid; private LinearLayout membersView; private LinearLayout mMoreDetails; + private TextView mConferenceType; + private ImageButton mChangeConferenceSettingsButton; private Button mInviteButton; private String uuid = null; - private List<User> users = new ArrayList<>(); private User mSelectedUser = null; + private boolean mAdvancedMode = false; + private UiCallback<Conversation> renameCallback = new UiCallback<Conversation>() { @Override public void success(Conversation object) { @@ -66,7 +73,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers @Override public void run() { Toast.makeText(ConferenceDetailsActivity.this,getString(R.string.your_nick_has_been_changed),Toast.LENGTH_SHORT).show(); - populateView(); + updateView(); } }); @@ -87,6 +94,51 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } }; + private OnClickListener mChangeConferenceSettings = new OnClickListener() { + @Override + public void onClick(View v) { + final MucOptions mucOptions = mConversation.getMucOptions(); + AlertDialog.Builder builder = new AlertDialog.Builder(ConferenceDetailsActivity.this); + builder.setTitle(R.string.conference_options); + String[] options = {getString(R.string.members_only), + getString(R.string.non_anonymous)}; + final boolean[] values = new boolean[options.length]; + values[0] = mucOptions.membersOnly(); + values[1] = mucOptions.nonanonymous(); + builder.setMultiChoiceItems(options,values,new DialogInterface.OnMultiChoiceClickListener() { + @Override + public void onClick(DialogInterface dialog, int which, boolean isChecked) { + values[which] = isChecked; + } + }); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.confirm,new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (!mucOptions.membersOnly() && values[0]) { + xmppConnectionService.changeAffiliationsInConference(mConversation, + MucOptions.Affiliation.NONE, + MucOptions.Affiliation.MEMBER); + } + Bundle options = new Bundle(); + options.putString("muc#roomconfig_membersonly", values[0] ? "1" : "0"); + options.putString("muc#roomconfig_whois", values[1] ? "anyone" : "moderators"); + options.putString("muc#roomconfig_persistentroom", "1"); + xmppConnectionService.pushConferenceConfiguration(mConversation, + options, + ConferenceDetailsActivity.this); + } + }); + builder.create().show(); + } + }; + private OnValueEdited onSubjectEdited = new OnValueEdited() { + + @Override + public void onValueEdited(String value) { + xmppConnectionService.pushSubjectToConference(mConversation,value); + } + }; @Override public void onConversationUpdate() { @@ -94,7 +146,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers @Override public void run() { - populateView(); + updateView(); } }); } @@ -105,7 +157,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers @Override public void run() { - populateView(); + updateView(); } }); } @@ -122,8 +174,12 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers mAccountJid = (TextView) findViewById(R.id.details_account); mMoreDetails = (LinearLayout) findViewById(R.id.muc_more_details); mMoreDetails.setVisibility(View.GONE); + mChangeConferenceSettingsButton = (ImageButton) findViewById(R.id.change_conference_button); + mChangeConferenceSettingsButton.setOnClickListener(this.mChangeConferenceSettings); + mConferenceType = (TextView) findViewById(R.id.muc_conference_type); mInviteButton = (Button) findViewById(R.id.invite); mInviteButton.setOnClickListener(inviteListener); + mConferenceType = (TextView) findViewById(R.id.muc_conference_type); if (getActionBar() != null) { getActionBar().setHomeButtonEnabled(true); getActionBar().setDisplayHomeAsUpEnabled(true); @@ -152,17 +208,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers break; case R.id.action_edit_subject: if (mConversation != null) { - quickEdit(mConversation.getName(), new OnValueEdited() { - - @Override - public void onValueEdited(String value) { - MessagePacket packet = xmppConnectionService - .getMessageGenerator().conferenceSubject( - mConversation, value); - xmppConnectionService.sendMessagePacket( - mConversation.getAccount(), packet); - } - }); + quickEdit(mConversation.getName(),this.onSubjectEdited); } break; case R.id.action_save_as_bookmark: @@ -171,23 +217,16 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers case R.id.action_delete_bookmark: deleteBookmark(); break; + case R.id.action_advanced_mode: + this.mAdvancedMode = !menuItem.isChecked(); + menuItem.setChecked(this.mAdvancedMode); + invalidateOptionsMenu(); + updateView(); + break; } return super.onOptionsItemSelected(menuItem); } - public String getReadableRole(int role) { - switch (role) { - case User.ROLE_MODERATOR: - return getString(R.string.moderator); - case User.ROLE_PARTICIPANT: - return getString(R.string.participant); - case User.ROLE_VISITOR: - return getString(R.string.visitor); - default: - return ""; - } - } - @Override protected String getShareableUri() { if (mConversation != null) { @@ -201,6 +240,8 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers public boolean onPrepareOptionsMenu(Menu menu) { MenuItem menuItemSaveBookmark = menu.findItem(R.id.action_save_as_bookmark); MenuItem menuItemDeleteBookmark = menu.findItem(R.id.action_delete_bookmark); + MenuItem menuItemAdvancedMode = menu.findItem(R.id.action_advanced_mode); + menuItemAdvancedMode.setChecked(mAdvancedMode); Account account = mConversation.getAccount(); if (account.hasBookmarkFor(mConversation.getJid().toBareJid())) { menuItemSaveBookmark.setVisible(false); @@ -224,21 +265,45 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers if (tag instanceof User) { getMenuInflater().inflate(R.menu.muc_details_context,menu); final User user = (User) tag; + final User self = mConversation.getMucOptions().getSelf(); this.mSelectedUser = user; String name; - final Contact contact = user.getContact(); - if (contact != null) { - name = contact.getDisplayName(); - } else if (user.getJid() != null) { - name = user.getJid().toBareJid().toString(); - } else { - name = user.getName(); - } - menu.setHeaderTitle(name); - MenuItem startConversation = menu.findItem(R.id.start_conversation); - if (user.getJid() == null) { - startConversation.setVisible(false); + if (user.getJid() != null) { + final Contact contact = user.getContact(); + if (contact != null) { + name = contact.getDisplayName(); + } else { + name = user.getJid().toBareJid().toString(); + } + menu.setHeaderTitle(name); + MenuItem startConversation = menu.findItem(R.id.start_conversation); + MenuItem giveMembership = menu.findItem(R.id.give_membership); + MenuItem removeMembership = menu.findItem(R.id.remove_membership); + MenuItem giveAdminPrivileges = menu.findItem(R.id.give_admin_privileges); + MenuItem removeAdminPrivileges = menu.findItem(R.id.remove_admin_privileges); + MenuItem removeFromRoom = menu.findItem(R.id.remove_from_room); + MenuItem banFromConference = menu.findItem(R.id.ban_from_conference); + startConversation.setVisible(true); + if (self.getAffiliation().ranks(MucOptions.Affiliation.ADMIN) && + self.getAffiliation().outranks(user.getAffiliation())) { + if (mAdvancedMode) { + if (user.getAffiliation() == MucOptions.Affiliation.NONE) { + giveMembership.setVisible(true); + } else { + removeMembership.setVisible(true); + } + banFromConference.setVisible(true); + } else { + removeFromRoom.setVisible(true); + } + if (user.getAffiliation() != MucOptions.Affiliation.ADMIN) { + giveAdminPrivileges.setVisible(true); + } else { + removeAdminPrivileges.setVisible(true); + } + } } + } super.onCreateContextMenu(menu,v,menuInfo); } @@ -249,11 +314,50 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers case R.id.start_conversation: startConversation(mSelectedUser); return true; + case R.id.give_admin_privileges: + xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getJid(), MucOptions.Affiliation.ADMIN,this); + return true; + case R.id.give_membership: + xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getJid(), MucOptions.Affiliation.MEMBER,this); + return true; + case R.id.remove_membership: + xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getJid(), MucOptions.Affiliation.NONE,this); + return true; + case R.id.remove_admin_privileges: + xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getJid(), 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); + return true; default: return super.onContextItemSelected(item); } } + 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); + } else { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.ban_from_conference); + builder.setMessage(getString(R.string.removing_from_public_conference,user.getName())); + builder.setNegativeButton(R.string.cancel,null); + 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); + } + }); + builder.create().show(); + } + } + protected void startConversation(User user) { if (user.getJid() != null) { Conversation conversation = xmppConnectionService.findOrCreateConversation(this.mConversation.getAccount(),user.getJid().toBareJid(),false); @@ -290,39 +394,41 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers this.mConversation = xmppConnectionService .findConversationByUuid(uuid); if (this.mConversation != null) { - populateView(); + updateView(); } } } - private void populateView() { + private void updateView() { + final MucOptions mucOptions = mConversation.getMucOptions(); + final User self = mucOptions.getSelf(); mAccountJid.setText(getString(R.string.using_account, mConversation .getAccount().getJid().toBareJid())); - mYourPhoto.setImageBitmap(avatarService().get( - mConversation.getAccount(), getPixel(48))); + mYourPhoto.setImageBitmap(avatarService().get(mConversation.getAccount(), getPixel(48))); setTitle(mConversation.getName()); mFullJid.setText(mConversation.getJid().toBareJid().toString()); - mYourNick.setText(mConversation.getMucOptions().getActualNick()); + mYourNick.setText(mucOptions.getActualNick()); mRoleAffiliaton = (TextView) findViewById(R.id.muc_role); - if (mConversation.getMucOptions().online()) { + if (mucOptions.online()) { mMoreDetails.setVisibility(View.VISIBLE); - User self = mConversation.getMucOptions().getSelf(); - switch (self.getAffiliation()) { - case User.AFFILIATION_ADMIN: - mRoleAffiliaton.setText(getReadableRole(self.getRole()) + " (" - + getString(R.string.admin) + ")"); - break; - case User.AFFILIATION_OWNER: - mRoleAffiliaton.setText(getReadableRole(self.getRole()) + " (" - + getString(R.string.owner) + ")"); - break; - default: - mRoleAffiliaton.setText(getReadableRole(self.getRole())); - break; + final String status = getStatus(self); + if (status != null) { + mRoleAffiliaton.setVisibility(View.VISIBLE); + mRoleAffiliaton.setText(status); + } else { + mRoleAffiliaton.setVisibility(View.GONE); + } + if (mucOptions.membersOnly()) { + mConferenceType.setText(R.string.private_conference); + } else { + mConferenceType.setText(R.string.public_conference); + } + if (self.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) { + mChangeConferenceSettingsButton.setVisibility(View.VISIBLE); + } else { + mChangeConferenceSettingsButton.setVisibility(View.GONE); } } - this.users.clear(); - this.users.addAll(mConversation.getMucOptions().getUsers()); LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); membersView.removeAllViews(); for (final User user : mConversation.getMucOptions().getUsers()) { @@ -337,36 +443,53 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers }); registerForContextMenu(view); view.setTag(user); - TextView name = (TextView) view - .findViewById(R.id.contact_display_name); - TextView key = (TextView) view.findViewById(R.id.key); - TextView role = (TextView) view.findViewById(R.id.contact_jid); - if (user.getPgpKeyId() != 0) { - key.setVisibility(View.VISIBLE); - key.setOnClickListener(new OnClickListener() { + TextView tvDisplayName = (TextView) view.findViewById(R.id.contact_display_name); + TextView tvKey = (TextView) view.findViewById(R.id.key); + TextView tvStatus = (TextView) view.findViewById(R.id.contact_jid); + if (mAdvancedMode && user.getPgpKeyId() != 0) { + tvKey.setVisibility(View.VISIBLE); + tvKey.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { viewPgpKey(user); } }); - key.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId())); + tvKey.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId())); } Bitmap bm; Contact contact = user.getContact(); if (contact != null) { bm = avatarService().get(contact, getPixel(48)); - name.setText(contact.getDisplayName()); - role.setText(user.getName() + " \u2022 " - + getReadableRole(user.getRole())); + tvDisplayName.setText(contact.getDisplayName()); + tvStatus.setText(user.getName() + " \u2022 " + getStatus(user)); } else { bm = avatarService().get(user.getName(), getPixel(48)); - name.setText(user.getName()); - role.setText(getReadableRole(user.getRole())); + tvDisplayName.setText(user.getName()); + tvStatus.setText(getStatus(user)); + } ImageView iv = (ImageView) view.findViewById(R.id.contact_photo); iv.setImageBitmap(bm); membersView.addView(view); + if (mConversation.getMucOptions().canInvite()) { + mInviteButton.setVisibility(View.VISIBLE); + } else { + mInviteButton.setVisibility(View.GONE); + } + } + } + + private String getStatus(User user) { + if (mAdvancedMode) { + StringBuilder builder = new StringBuilder(); + builder.append(getString(user.getAffiliation().getResId())); + builder.append(" ("); + builder.append(getString(user.getRole().getResId())); + builder.append(')'); + return builder.toString(); + } else { + return getString(user.getAffiliation().getResId()); } } @@ -396,4 +519,43 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } } } + + @Override + public void onAffiliationChangedSuccessful(Jid jid) { + + } + + @Override + public void onAffiliationChangeFailed(Jid jid, int resId) { + displayToast(getString(resId,jid.toBareJid().toString())); + } + + @Override + public void onRoleChangedSuccessful(String nick) { + + } + + @Override + public void onRoleChangeFailed(String nick, int resId) { + displayToast(getString(resId,nick)); + } + + @Override + public void onPushSucceeded() { + displayToast(getString(R.string.modified_conference_options)); + } + + @Override + public void onPushFailed() { + displayToast(getString(R.string.could_not_modify_conference_options)); + } + + private void displayToast(final String msg) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(ConferenceDetailsActivity.this,msg,Toast.LENGTH_SHORT).show(); + } + }); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index 657ae75b..fda0c617 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -18,6 +18,7 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; +import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; @@ -40,6 +41,7 @@ import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; +import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; @@ -51,9 +53,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd @Override public void onClick(DialogInterface dialog, int which) { - ContactDetailsActivity.this.xmppConnectionService - .deleteContactOnServer(contact); - ContactDetailsActivity.this.finish(); + xmppConnectionService.deleteContactOnServer(contact); } }; private OnCheckedChangeListener mOnSendCheckedChange = new OnCheckedChangeListener() { @@ -102,6 +102,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd private TextView lastseen; private CheckBox send; private CheckBox receive; + private Button addContactButton; private QuickContactBadge badge; private LinearLayout keys; private LinearLayout tags; @@ -142,6 +143,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd @Override public void run() { + invalidateOptionsMenu(); populateView(); } }); @@ -153,6 +155,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd @Override public void run() { + invalidateOptionsMenu(); populateView(); } }); @@ -188,6 +191,13 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd send = (CheckBox) findViewById(R.id.details_send_presence); receive = (CheckBox) findViewById(R.id.details_receive_presence); badge = (QuickContactBadge) findViewById(R.id.details_contact_badge); + addContactButton = (Button) findViewById(R.id.add_contact_button); + addContactButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + showAddToRosterDialog(contact); + } + }); keys = (LinearLayout) findViewById(R.id.details_contact_keys); tags = (LinearLayout) findViewById(R.id.tags); if (getActionBar() != null) { @@ -201,7 +211,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd @Override public boolean onOptionsItemSelected(final MenuItem menuItem) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); + final AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setNegativeButton(getString(R.string.cancel), null); switch (menuItem.getItemId()) { case android.R.id.home: @@ -237,59 +247,96 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd startActivity(intent); } break; + case R.id.action_block: + BlockContactDialog.show(this, xmppConnectionService, contact); + break; + case R.id.action_unblock: + BlockContactDialog.show(this, xmppConnectionService, contact); + break; } return super.onOptionsItemSelected(menuItem); } @Override - public boolean onCreateOptionsMenu(Menu menu) { + public boolean onCreateOptionsMenu(final Menu menu) { getMenuInflater().inflate(R.menu.contact_details, menu); + MenuItem block = menu.findItem(R.id.action_block); + MenuItem unblock = menu.findItem(R.id.action_unblock); + MenuItem edit = menu.findItem(R.id.action_edit_contact); + MenuItem delete = menu.findItem(R.id.action_delete_contact); + final XmppConnection connection = contact.getAccount().getXmppConnection(); + if (connection != null && connection.getFeatures().blocking()) { + if (this.contact.isBlocked()) { + menu.findItem(R.id.action_block).setVisible(false); + } else { + menu.findItem(R.id.action_unblock).setVisible(false); + } + } else { + menu.findItem(R.id.action_unblock).setVisible(false); + menu.findItem(R.id.action_block).setVisible(false); + } + if (!contact.showInRoster()) { + edit.setVisible(false); + delete.setVisible(false); + } return true; } private void populateView() { - send.setOnCheckedChangeListener(null); - receive.setOnCheckedChangeListener(null); setTitle(contact.getDisplayName()); - if (contact.getOption(Contact.Options.FROM)) { - send.setText(R.string.send_presence_updates); - send.setChecked(true); - } else if (contact - .getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { - send.setChecked(false); - send.setText(R.string.send_presence_updates); - } else { - send.setText(R.string.preemptively_grant); - if (contact.getOption(Contact.Options.PREEMPTIVE_GRANT)) { + if (contact.showInRoster()) { + send.setVisibility(View.VISIBLE); + receive.setVisibility(View.VISIBLE); + addContactButton.setVisibility(View.GONE); + send.setOnCheckedChangeListener(null); + receive.setOnCheckedChangeListener(null); + + if (contact.getOption(Contact.Options.FROM)) { + send.setText(R.string.send_presence_updates); send.setChecked(true); - } else { + } else if (contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { send.setChecked(false); + send.setText(R.string.send_presence_updates); + } else { + send.setText(R.string.preemptively_grant); + if (contact.getOption(Contact.Options.PREEMPTIVE_GRANT)) { + send.setChecked(true); + } else { + send.setChecked(false); + } } - } - if (contact.getOption(Contact.Options.TO)) { - receive.setText(R.string.receive_presence_updates); - receive.setChecked(true); - } else { - receive.setText(R.string.ask_for_presence_updates); - if (contact.getOption(Contact.Options.ASKING)) { + if (contact.getOption(Contact.Options.TO)) { + receive.setText(R.string.receive_presence_updates); receive.setChecked(true); } else { - receive.setChecked(false); + receive.setText(R.string.ask_for_presence_updates); + if (contact.getOption(Contact.Options.ASKING)) { + receive.setChecked(true); + } else { + receive.setChecked(false); + } } - } - if (contact.getAccount().getStatus() == Account.State.ONLINE) { - receive.setEnabled(true); - send.setEnabled(true); + if (contact.getAccount().isOnlineAndConnected()) { + receive.setEnabled(true); + send.setEnabled(true); + } else { + receive.setEnabled(false); + send.setEnabled(false); + } + + send.setOnCheckedChangeListener(this.mOnSendCheckedChange); + receive.setOnCheckedChangeListener(this.mOnReceiveCheckedChange); } else { - receive.setEnabled(false); - send.setEnabled(false); + addContactButton.setVisibility(View.VISIBLE); + send.setVisibility(View.GONE); + receive.setVisibility(View.GONE); } - send.setOnCheckedChangeListener(this.mOnSendCheckedChange); - receive.setOnCheckedChangeListener(this.mOnReceiveCheckedChange); - - lastseen.setText(UIHelper.lastseen(getApplicationContext(), - contact.lastseen.time)); + if (contact.isBlocked() && !this.showDynamicTags) { + lastseen.setText(R.string.contact_blocked); + } else { + lastseen.setText(UIHelper.lastseen(getApplicationContext(), contact.lastseen.time)); + } if (contact.getPresences().size() > 1) { contactJidTv.setText(contact.getJid() + " (" @@ -424,6 +471,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd @Override public void run() { + invalidateOptionsMenu(); populateView(); } }); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 5cfc1116..0a55c6b5 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -9,8 +9,8 @@ import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.IntentSender.SendIntentException; -import android.media.MediaActionSound; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.SystemClock; import android.provider.MediaStore; @@ -49,8 +49,11 @@ import eu.siacs.conversations.xmpp.OnUpdateBlocklist; public class ConversationActivity extends XmppActivity implements OnAccountUpdate, OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist { + public static final String ACTION_DOWNLOAD = "eu.siacs.conversations.action.DOWNLOAD"; + public static final String VIEW_CONVERSATION = "viewConversation"; public static final String CONVERSATION = "conversationUuid"; + public static final String MESSAGE = "messageUuid"; public static final String TEXT = "text"; public static final String NICK = "nick"; @@ -83,11 +86,6 @@ public class ConversationActivity extends XmppActivity private boolean mActivityPaused = false; - - public List<Conversation> getConversationList() { - return this.conversationList; - } - public Conversation getSelectedConversation() { return this.mSelectedConversation; } @@ -139,7 +137,7 @@ public class ConversationActivity extends XmppActivity } @Override - protected void onCreate(Bundle savedInstanceState) { + protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) {mOpenConverstaion = savedInstanceState.getString( STATE_OPEN_CONVERSATION, null); @@ -176,6 +174,7 @@ public class ConversationActivity extends XmppActivity ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation()); } hideConversationsOverview(); + openConversation(); } }); mContentView = findViewById(R.id.content_view_spl); @@ -260,11 +259,16 @@ public class ConversationActivity extends XmppActivity xmppConnectionService.getNotificationService().setOpenConversation(conversation); sendReadMarkerIfNecessary(conversation); } + listAdapter.notifyDataSetChanged(); } public void sendReadMarkerIfNecessary(final Conversation conversation) { - if (!mActivityPaused && !conversation.isRead()) { - xmppConnectionService.sendReadMarker(conversation); + if (!mActivityPaused && conversation != null) { + if (!conversation.isRead()) { + xmppConnectionService.sendReadMarker(conversation); + } else { + xmppConnectionService.markRead(conversation); + } } } @@ -281,11 +285,8 @@ public class ConversationActivity extends XmppActivity final MenuItem menuInviteContact = menu.findItem(R.id.action_invite); final MenuItem menuMute = menu.findItem(R.id.action_mute); final MenuItem menuUnmute = menu.findItem(R.id.action_unmute); - final MenuItem menuBlock = menu.findItem(R.id.action_block); - final MenuItem menuUnblock = menu.findItem(R.id.action_unblock); - if (isConversationsOverviewVisable() - && isConversationsOverviewHideable()) { + if (isConversationsOverviewVisable() && isConversationsOverviewHideable()) { menuArchive.setVisible(false); menuMucDetails.setVisible(false); menuContactDetails.setVisible(false); @@ -295,33 +296,24 @@ public class ConversationActivity extends XmppActivity menuClearHistory.setVisible(false); menuMute.setVisible(false); menuUnmute.setVisible(false); - menuBlock.setVisible(false); - menuUnblock.setVisible(false); } else { menuAdd.setVisible(!isConversationsOverviewHideable()); if (this.getSelectedConversation() != null) { if (this.getSelectedConversation().getLatestMessage() .getEncryption() != Message.ENCRYPTION_NONE) { - menuSecure.setIcon(R.drawable.ic_action_secure); - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + menuSecure.setIcon(R.drawable.ic_lock_outline_white_48dp); + } else { + menuSecure.setIcon(R.drawable.ic_action_secure); + } + } if (this.getSelectedConversation().getMode() == Conversation.MODE_MULTI) { menuContactDetails.setVisible(false); menuAttach.setVisible(false); - menuBlock.setVisible(false); - menuUnblock.setVisible(false); + menuInviteContact.setVisible(getSelectedConversation().getMucOptions().canInvite()); } else { menuMucDetails.setVisible(false); - menuInviteContact.setTitle(R.string.conference_with); - if (this.getSelectedConversation().isBlocked()) { - menuBlock.setVisible(false); - } else { - menuUnblock.setVisible(false); - } final Account account = this.getSelectedConversation().getAccount(); - if (!(account.isOnlineAndConnected() && account.getXmppConnection().getFeatures().blocking())) { - menuBlock.setVisible(false); - menuUnblock.setVisible(false); - } } if (this.getSelectedConversation().isMuted()) { menuMute.setVisible(false); @@ -447,12 +439,7 @@ public class ConversationActivity extends XmppActivity this.endConversation(getSelectedConversation()); break; case R.id.action_contact_details: - Contact contact = this.getSelectedConversation().getContact(); - if (contact.showInRoster()) { - switchToContactDetails(contact); - } else { - showAddToRosterDialog(getSelectedConversation()); - } + switchToContactDetails(getSelectedConversation().getContact()); break; case R.id.action_muc_details: Intent intent = new Intent(this, @@ -492,7 +479,6 @@ public class ConversationActivity extends XmppActivity } public void endConversation(Conversation conversation) { - conversation.setStatus(Conversation.STATUS_ARCHIVED); showConversationsOverview(); xmppConnectionService.archiveConversation(conversation); if (conversationList.size() > 0) { @@ -542,25 +528,25 @@ public class ConversationActivity extends XmppActivity } attachFilePopup.setOnMenuItemClickListener(new OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - switch (item.getItemId()) { - case R.id.attach_choose_picture: - attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE); - break; - case R.id.attach_take_picture: - attachFile(ATTACHMENT_CHOICE_TAKE_PHOTO); - break; - case R.id.attach_choose_file: - attachFile(ATTACHMENT_CHOICE_CHOOSE_FILE); - break; - case R.id.attach_record_voice: - attachFile(ATTACHMENT_CHOICE_RECORD_VOICE); - break; - } - return false; + @Override + public boolean onMenuItemClick(MenuItem item) { + switch (item.getItemId()) { + case R.id.attach_choose_picture: + attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE); + break; + case R.id.attach_take_picture: + attachFile(ATTACHMENT_CHOICE_TAKE_PHOTO); + break; + case R.id.attach_choose_file: + attachFile(ATTACHMENT_CHOICE_CHOOSE_FILE); + break; + case R.id.attach_record_voice: + attachFile(ATTACHMENT_CHOICE_RECORD_VOICE); + break; } - }); + return false; + } + }); attachFilePopup.show(); } @@ -756,14 +742,20 @@ public class ConversationActivity extends XmppActivity @Override public void onResume() { super.onResume(); - int theme = findTheme(); - if (this.mTheme != theme) { + final int theme = findTheme(); + final boolean usingEnterKey = usingEnterKey(); + if (this.mTheme != theme || usingEnterKey != mUsingEnterKey) { recreate(); } this.mActivityPaused = false; if (this.xmppConnectionServiceBound) { this.xmppConnectionService.getNotificationService().setIsInForeground(true); } + + if (!isConversationsOverviewVisable() || !isConversationsOverviewHideable()) { + sendReadMarkerIfNecessary(getSelectedConversation()); + } + } @Override @@ -790,11 +782,9 @@ public class ConversationActivity extends XmppActivity } else if (conversationList.size() <= 0) { startActivity(new Intent(this, StartConversationActivity.class)); finish(); - } else if (getIntent() != null - && VIEW_CONVERSATION.equals(getIntent().getType())) { + } else if (getIntent() != null && VIEW_CONVERSATION.equals(getIntent().getType())) { handleViewConversationIntent(getIntent()); - } else if (mOpenConverstaion != null) { - selectConversationByUuid(mOpenConverstaion); + } else if (selectConversationByUuid(mOpenConverstaion)) { if (mPanelOpen) { showConversationsOverview(); } else { @@ -825,30 +815,43 @@ public class ConversationActivity extends XmppActivity setIntent(new Intent()); } - private void handleViewConversationIntent(Intent intent) { - String uuid = (String) intent.getExtras().get(CONVERSATION); - String text = intent.getExtras().getString(TEXT, ""); - String nick = intent.getExtras().getString(NICK,null); - selectConversationByUuid(uuid); - this.mConversationFragment.reInit(getSelectedConversation()); - if (nick!=null) { - this.mConversationFragment.highlightInConference(nick); - } else { - this.mConversationFragment.appendText(text); - } - hideConversationsOverview(); - openConversation(); - if (mContentView instanceof SlidingPaneLayout) { - updateActionBarTitle(true); //fixes bug where slp isn't properly closed yet + private void handleViewConversationIntent(final Intent intent) { + final String uuid = (String) intent.getExtras().get(CONVERSATION); + final String downloadUuid = (String) intent.getExtras().get(MESSAGE); + final String text = intent.getExtras().getString(TEXT, ""); + final String nick = intent.getExtras().getString(NICK, null); + if (selectConversationByUuid(uuid)) { + this.mConversationFragment.reInit(getSelectedConversation()); + if (nick != null) { + this.mConversationFragment.highlightInConference(nick); + } else { + this.mConversationFragment.appendText(text); + } + hideConversationsOverview(); + openConversation(); + if (mContentView instanceof SlidingPaneLayout) { + updateActionBarTitle(true); //fixes bug where slp isn't properly closed yet + } + if (downloadUuid != null) { + final Message message = mSelectedConversation.findMessageWithFileAndUuid(downloadUuid); + if (message != null) { + mConversationFragment.messageListAdapter.startDownloadable(message); + } + } } } - private void selectConversationByUuid(String uuid) { + private boolean selectConversationByUuid(String uuid) { + if (uuid == null) { + return false; + } for (Conversation aConversationList : conversationList) { if (aConversationList.getUuid().equals(uuid)) { setSelectedConversation(aConversationList); + return true; } } + return false; } @Override @@ -1050,10 +1053,10 @@ public class ConversationActivity extends XmppActivity @Override public void OnUpdateBlocklist(Status status) { - invalidateOptionsMenu(); runOnUiThread(new Runnable() { @Override public void run() { + invalidateOptionsMenu(); ConversationActivity.this.mConversationFragment.updateMessages(); } }); @@ -1063,7 +1066,7 @@ public class ConversationActivity extends XmppActivity xmppConnectionService.sendUnblockRequest(conversation); } - public void blockConversation(final Blockable conversation) { - xmppConnectionService.sendBlockRequest(conversation); + public boolean enterIsSend() { + return getPreferences().getBoolean("enter_is_send",false); } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 93a4cc7e..80ac9da1 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -9,6 +9,7 @@ import android.content.Intent; import android.content.IntentSender; import android.content.IntentSender.SendIntentException; import android.os.Bundle; +import android.text.InputType; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.Gravity; @@ -33,6 +34,7 @@ import android.widget.Toast; import net.java.otr4j.session.SessionStatus; +import java.net.URLConnection; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; @@ -296,6 +298,15 @@ public class ConversationFragment extends Fragment { default: break; } + getActivity().invalidateOptionsMenu(); + } + } + + private void setupIme() { + if (((ConversationActivity)getActivity()).usingEnterKey()) { + mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE)); + } else { + mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE); } } @@ -305,6 +316,7 @@ public class ConversationFragment extends Fragment { final View view = inflater.inflate(R.layout.fragment_conversation, container, false); mEditMessage = (EditMessage) view.findViewById(R.id.textinput); + setupIme(); mEditMessage.setOnClickListener(new OnClickListener() { @Override @@ -316,8 +328,13 @@ public class ConversationFragment extends Fragment { mEditMessage.setOnEnterPressedListener(new OnEnterPressed() { @Override - public void onEnterPressed() { - sendMessage(); + public boolean onEnterPressed() { + if (activity.enterIsSend()) { + sendMessage(); + return true; + } else { + return false; + } } }); @@ -346,14 +363,7 @@ public class ConversationFragment extends Fragment { } } } else { - Contact contact = message.getConversation() - .getContact(); - if (contact.showInRoster()) { - activity.switchToContactDetails(contact); - } else { - activity.showAddToRosterDialog(message - .getConversation()); - } + activity.switchToContactDetails(message.getContact()); } } else { Account account = message.getConversation().getAccount(); @@ -403,7 +413,7 @@ public class ConversationFragment extends Fragment { activity.getMenuInflater().inflate(R.menu.message_context, menu); menu.setHeaderTitle(R.string.message_options); MenuItem copyText = menu.findItem(R.id.copy_text); - MenuItem shareImage = menu.findItem(R.id.share_image); + MenuItem shareWith = menu.findItem(R.id.share_with); MenuItem sendAgain = menu.findItem(R.id.send_again); MenuItem copyUrl = menu.findItem(R.id.copy_url); MenuItem downloadImage = menu.findItem(R.id.download_image); @@ -411,33 +421,36 @@ public class ConversationFragment extends Fragment { if (m.getType() != Message.TYPE_TEXT || m.getDownloadable() != null) { copyText.setVisible(false); } - if (m.getType() != Message.TYPE_IMAGE || m.getDownloadable() != null) { - shareImage.setVisible(false); - } + if (m.getType() == Message.TYPE_TEXT + || m.getType() == Message.TYPE_PRIVATE + || m.getDownloadable() != null) { + shareWith.setVisible(false); + } if (m.getStatus() != Message.STATUS_SEND_FAILED) { sendAgain.setVisible(false); } if ((m.getType() != Message.TYPE_IMAGE && m.getDownloadable() == null) || m.getImageParams().url == null) { copyUrl.setVisible(false); - } + } if (m.getType() != Message.TYPE_TEXT || m.getDownloadable() != null || !m.bodyContainsDownloadable()) { downloadImage.setVisible(false); - } + } if (!((m.getDownloadable() != null && !(m.getDownloadable() instanceof DownloadablePlaceholder)) - || (m.isFileOrImage() && m.getStatus() == Message.STATUS_WAITING))) { + || (m.isFileOrImage() && (m.getStatus() == Message.STATUS_WAITING + || m.getStatus() == Message.STATUS_OFFERED)))) { cancelTransmission.setVisible(false); - } + } } } @Override public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { - case R.id.share_image: - shareImage(selectedMessage); + case R.id.share_with: + shareWith(selectedMessage); return true; case R.id.copy_text: copyText(selectedMessage); @@ -459,16 +472,20 @@ public class ConversationFragment extends Fragment { } } - private void shareImage(Message message) { + private void shareWith(Message message) { Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND); shareIntent.putExtra(Intent.EXTRA_STREAM, activity.xmppConnectionService.getFileBackend() .getJingleFileUri(message)); shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - shareIntent.setType("image/webp"); - activity.startActivity(Intent.createChooser(shareIntent, - getText(R.string.share_with))); + String path = message.getRelativeFilePath(); + String mime = path == null ? null :URLConnection.guessContentTypeFromName(path); + if (mime == null) { + mime = "image/webp"; + } + shareIntent.setType(mime); + activity.startActivity(Intent.createChooser(shareIntent,getText(R.string.share_with))); } private void copyText(Message message) { @@ -595,15 +612,6 @@ public class ConversationFragment extends Fragment { } } }); - } else if (this.conversation.isMuted()) { - showSnackbar(R.string.notifications_disabled, R.string.enable, - new OnClickListener() { - - @Override - public void onClick(final View v) { - activity.unmuteConversation(conversation); - } - }); } else if (!contact.showInRoster() && contact .getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { @@ -650,9 +658,18 @@ public class ConversationFragment extends Fragment { default: break; } - } + } else if (this.conversation.isMuted()) { + showSnackbar(R.string.notifications_disabled, R.string.enable, + new OnClickListener() { + + @Override + public void onClick(final View v) { + activity.unmuteConversation(conversation); + } + }); + } conversation.populateWithMessages(ConversationFragment.this.messageList); - for (Message message : this.messageList) { + for (final Message message : this.messageList) { if (message.getEncryption() == Message.ENCRYPTION_PGP && (message.getStatus() == Message.STATUS_RECEIVED || message .getStatus() >= Message.STATUS_SEND) @@ -951,7 +968,4 @@ public class ConversationFragment extends Fragment { this.mEditMessage.append(text); } - public void clearInputField() { - this.mEditMessage.setText(""); - } } diff --git a/src/main/java/eu/siacs/conversations/ui/EditMessage.java b/src/main/java/eu/siacs/conversations/ui/EditMessage.java index f8302050..5090bbf5 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditMessage.java +++ b/src/main/java/eu/siacs/conversations/ui/EditMessage.java @@ -21,9 +21,12 @@ public class EditMessage extends EditText { public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_ENTER) { if (mOnEnterPressed != null) { - mOnEnterPressed.onEnterPressed(); + if (mOnEnterPressed.onEnterPressed()) { + return true; + } else { + return super.onKeyDown(keyCode, event); + } } - return true; } return super.onKeyDown(keyCode, event); } @@ -33,7 +36,7 @@ public class EditMessage extends EditText { } public interface OnEnterPressed { - public void onEnterPressed(); + public boolean onEnterPressed(); } } diff --git a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java index 10ee0cd5..2ba0b090 100644 --- a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java @@ -11,6 +11,8 @@ import android.view.View.OnLongClickListener; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; +import android.widget.Toast; + import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.utils.PhoneHelper; @@ -31,13 +33,18 @@ public class PublishProfilePictureActivity extends XmppActivity { private Uri avatarUri; private Uri defaultUri; + private OnLongClickListener backToDefaultListener = new OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + avatarUri = defaultUri; + loadImageIntoPreview(defaultUri); + return true; + } + }; private Account account; - private boolean support = false; - private boolean mInitialAccountSetup; - private UiCallback<Avatar> avatarPublication = new UiCallback<Avatar>() { @Override @@ -50,6 +57,9 @@ public class PublishProfilePictureActivity extends XmppActivity { startActivity(new Intent(getApplicationContext(), StartConversationActivity.class)); } + Toast.makeText(PublishProfilePictureActivity.this, + R.string.avatar_has_been_published, + Toast.LENGTH_SHORT).show(); finish(); } }); @@ -75,16 +85,6 @@ public class PublishProfilePictureActivity extends XmppActivity { } }; - private OnLongClickListener backToDefaultListener = new OnLongClickListener() { - - @Override - public boolean onLongClick(View v) { - avatarUri = defaultUri; - loadImageIntoPreview(defaultUri); - return true; - } - }; - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java index 352652de..136108ef 100644 --- a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java @@ -75,6 +75,15 @@ public class SettingsActivity extends XmppActivity implements case "keep_foreground_service": xmppConnectionService.toggleForegroundService(); break; + case "confirm_messages": + if (xmppConnectionServiceBound) { + for (Account account : xmppConnectionService.getAccounts()) { + if (!account.isOptionSet(Account.OPTION_DISABLED)) { + xmppConnectionService.sendPresence(account); + } + } + } + break; } Settings.synchronizeSettingsClassWithPreferences(getPreferences(), name); } diff --git a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java index 5e770376..6be238dc 100644 --- a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java @@ -134,14 +134,10 @@ public class ShareWithActivity extends XmppActivity { @Override public void onStart() { final String type = getIntent().getType(); - if (type != null && !type.startsWith("text/")) { - this.share.uri = (Uri) getIntent().getParcelableExtra(Intent.EXTRA_STREAM); - try { - this.share.image = type.startsWith("image/") - || URLConnection.guessContentTypeFromName(this.share.uri.toString()).startsWith("image/"); - } catch (final StringIndexOutOfBoundsException ignored) { - this.share.image = false; - } + final Uri uri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM); + if (type != null && uri != null && !type.equalsIgnoreCase("text/plain")) { + this.share.uri = uri; + this.share.image = type.startsWith("image/") || isImage(uri); } else { this.share.text = getIntent().getStringExtra(Intent.EXTRA_TEXT); } @@ -151,6 +147,15 @@ public class ShareWithActivity extends XmppActivity { super.onStart(); } + protected boolean isImage(Uri uri) { + try { + String guess = URLConnection.guessContentTypeFromName(uri.toString()); + return (guess != null && guess.startsWith("image/")); + } catch (final StringIndexOutOfBoundsException ignored) { + return false; + } + } + @Override void onBackendConnected() { if (xmppConnectionServiceBound && share != null diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index 209c0a7b..ff46ffd8 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -59,6 +59,7 @@ import eu.siacs.conversations.entities.Bookmark; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.ListItem; +import eu.siacs.conversations.entities.Presences; import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate; import eu.siacs.conversations.ui.adapter.KnownHostsAdapter; import eu.siacs.conversations.ui.adapter.ListItemAdapter; @@ -114,6 +115,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU return true; } }; + private boolean mHideOfflineContacts = false; private TabListener mTabListener = new TabListener() { @Override @@ -159,7 +161,6 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU } }; private MenuItem mMenuSearchView; - private String mInitialJid; private ListItemAdapter.OnTagClickedListener mOnTagClickedListener = new ListItemAdapter.OnTagClickedListener() { @Override public void onTagClicked(String tag) { @@ -171,6 +172,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU } } }; + private String mInitialJid; @Override public void onRosterUpdate() { @@ -245,6 +247,8 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU } }); + this.mHideOfflineContacts = getPreferences().getBoolean("hide_offline", false); + } protected void openConversationForContact(int position) { @@ -448,8 +452,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU if (account.hasBookmarkFor(conferenceJid)) { jid.setError(getString(R.string.bookmark_already_exists)); } else { - final Bookmark bookmark = new Bookmark(account, - conferenceJid); + final Bookmark bookmark = new Bookmark(account,conferenceJid.toBareJid()); bookmark.setAutojoin(true); account.getBookmarks().add(bookmark); xmppConnectionService @@ -497,10 +500,10 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU public boolean onCreateOptionsMenu(Menu menu) { this.mOptionsMenu = 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 menuCreateContact = menu.findItem(R.id.action_create_contact); + MenuItem menuCreateConference = menu.findItem(R.id.action_join_conference); + MenuItem menuHideOffline = menu.findItem(R.id.action_hide_offline); + menuHideOffline.setChecked(this.mHideOfflineContacts); mMenuSearchView = menu.findItem(R.id.action_search); mMenuSearchView.setOnActionExpandListener(mOnActionExpandListener); View mSearchView = mMenuSearchView.getActionView(); @@ -532,6 +535,13 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU case R.id.action_scan_qr_code: new IntentIntegrator(this).initiateScan(); return true; + case R.id.action_hide_offline: + mHideOfflineContacts = !item.isChecked(); + getPreferences().edit().putBoolean("hide_offline", mHideOfflineContacts).commit(); + if (mSearchEditText != null) { + filter(mSearchEditText.getText().toString()); + } + invalidateOptionsMenu(); } return super.onOptionsItemSelected(item); } @@ -668,7 +678,9 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU for (Account account : xmppConnectionService.getAccounts()) { if (account.getStatus() != Account.State.DISABLED) { for (Contact contact : account.getRoster().getContacts()) { - if (contact.showInRoster() && contact.match(needle)) { + if (contact.showInRoster() && contact.match(needle) + && (!this.mHideOfflineContacts + || contact.getPresences().getMostAvailableStatus() < Presences.OFFLINE)) { this.contacts.add(contact); } } diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 535aa6a5..1f1af09c 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -2,6 +2,7 @@ package eu.siacs.conversations.ui; import android.annotation.SuppressLint; import android.annotation.TargetApi; +import android.app.ActionBar; import android.app.Activity; import android.app.AlertDialog; import android.app.AlertDialog.Builder; @@ -97,6 +98,7 @@ public abstract class XmppActivity extends Activity { private DisplayMetrics metrics; protected int mTheme; + protected boolean mUsingEnterKey = false; protected interface OnValueEdited { public void onValueEdited(String value); @@ -300,13 +302,23 @@ public abstract class XmppActivity extends Activity { mColorOrange = getResources().getColor(R.color.orange); mColorGreen = getResources().getColor(R.color.green); mPrimaryColor = getResources().getColor(R.color.primary); - mSecondaryBackgroundColor = getResources().getColor( - R.color.secondarybackground); + mSecondaryBackgroundColor = getResources().getColor(R.color.secondarybackground); this.mTheme = findTheme(); setTheme(this.mTheme); + this.mUsingEnterKey = usingEnterKey(); mUseSubject = getPreferences().getBoolean("use_subject", true); + Settings.initSettingsClassWithPreferences(getPreferences()); - } + + final ActionBar ab = getActionBar(); + if (ab!=null) { + ab.setDisplayHomeAsUpEnabled(true); + } + } + + protected boolean usingEnterKey() { + return getPreferences().getBoolean("display_enter_key", false); + } protected SharedPreferences getPreferences() { return PreferenceManager @@ -394,9 +406,7 @@ public abstract class XmppActivity extends Activity { public void success(Account account) { xmppConnectionService.databaseBackend .updateAccount(account); - xmppConnectionService.sendPresencePacket(account, - xmppConnectionService.getPresenceGenerator() - .sendPresence(account)); + xmppConnectionService.sendPresence(account); if (conversation != null) { conversation .setNextEncryption(Message.ENCRYPTION_PGP); @@ -430,9 +440,12 @@ public abstract class XmppActivity extends Activity { } protected void showAddToRosterDialog(final Conversation conversation) { - final Jid jid = conversation.getJid(); + showAddToRosterDialog(conversation.getContact()); + } + + protected void showAddToRosterDialog(final Contact contact) { AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(jid.toString()); + builder.setTitle(contact.getJid().toString()); builder.setMessage(getString(R.string.not_in_roster)); builder.setNegativeButton(getString(R.string.cancel), null); builder.setPositiveButton(getString(R.string.add_contact), @@ -440,11 +453,10 @@ public abstract class XmppActivity extends Activity { @Override public void onClick(DialogInterface dialog, int which) { - final Jid jid = conversation.getJid(); - Account account = conversation.getAccount(); + final Jid jid = contact.getJid(); + Account account = contact.getAccount(); Contact contact = account.getRoster().getContact(jid); xmppConnectionService.createContact(contact); - switchToContactDetails(contact); } }); builder.create().show(); @@ -677,6 +689,10 @@ public abstract class XmppActivity extends Activity { return this.mPrimaryColor; } + public int getOnlineColor() { + return this.mColorGreen; + } + public int getSecondaryBackgroundColor() { return this.mSecondaryBackgroundColor; } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java index 139f3657..29730914 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java @@ -39,7 +39,7 @@ public class AccountAdapter extends ArrayAdapter<Account> { statusView.setText(getContext().getString(account.getStatus().getReadableId())); switch (account.getStatus()) { case ONLINE: - statusView.setTextColor(activity.getPrimaryColor()); + statusView.setTextColor(activity.getOnlineColor()); break; case DISABLED: case CONNECTING: diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java index 22f6b662..e62aaf96 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java @@ -1,5 +1,16 @@ package eu.siacs.conversations.ui.adapter; +import android.content.Context; +import android.graphics.Color; +import android.graphics.Typeface; +import android.util.Pair; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + import java.util.List; import de.tzur.conversations.Settings; @@ -38,10 +49,8 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { @Override public View getView(int position, View view, ViewGroup parent) { if (view == null) { - LayoutInflater inflater = (LayoutInflater) activity - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - view = inflater.inflate(R.layout.conversation_list_row, - parent, false); + LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + view = inflater.inflate(R.layout.conversation_list_row,parent, false); } Conversation conversation = getItem(position); if (this.activity instanceof ConversationActivity) { @@ -57,20 +66,15 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { view.setBackgroundColor(Color.TRANSPARENT); } } - TextView convName = (TextView) view - .findViewById(R.id.conversation_name); - if (conversation.getMode() == Conversation.MODE_SINGLE - || activity.useSubjectToIdentifyConference()) { + TextView convName = (TextView) view.findViewById(R.id.conversation_name); + if (conversation.getMode() == Conversation.MODE_SINGLE || activity.useSubjectToIdentifyConference()) { convName.setText(conversation.getName()); } else { convName.setText(conversation.getJid().toBareJid().toString()); } - TextView mLastMessage = (TextView) view - .findViewById(R.id.conversation_lastmsg); - TextView mTimestamp = (TextView) view - .findViewById(R.id.conversation_lastupdate); - ImageView imagePreview = (ImageView) view - .findViewById(R.id.conversation_lastimage); + TextView mLastMessage = (TextView) view.findViewById(R.id.conversation_lastmsg); + TextView mTimestamp = (TextView) view.findViewById(R.id.conversation_lastupdate); + ImageView imagePreview = (ImageView) view.findViewById(R.id.conversation_lastimage); if (Settings.SHOW_ONLINE_STATUS) { TextView status = (TextView) view.findViewById(R.id.status); @@ -100,91 +104,35 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { convName.setTypeface(null, Typeface.NORMAL); } - if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE - || message.getDownloadable() != null) { - Downloadable d = message.getDownloadable(); - if (conversation.isRead()) { - mLastMessage.setTypeface(null, Typeface.ITALIC); - } else { - mLastMessage.setTypeface(null, Typeface.BOLD_ITALIC); - } - if (d != null) { - mLastMessage.setVisibility(View.VISIBLE); - imagePreview.setVisibility(View.GONE); - if (d.getStatus() == Downloadable.STATUS_CHECKING) { - mLastMessage.setText(R.string.checking_image); - } else if (d.getStatus() == Downloadable.STATUS_DOWNLOADING) { - if (message.getType() == Message.TYPE_FILE) { - mLastMessage.setText(getContext().getString(R.string.receiving_file,d.getMimeType(), d.getProgress())); - } else { - mLastMessage.setText(getContext().getString(R.string.receiving_image, d.getProgress())); - } - } else if (d.getStatus() == Downloadable.STATUS_OFFER) { - if (message.getType() == Message.TYPE_FILE) { - mLastMessage.setText(R.string.file_offered_for_download); - } else { - mLastMessage.setText(R.string.image_offered_for_download); - } - } else if (d.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) { - mLastMessage.setText(R.string.image_offered_for_download); - } else if (d.getStatus() == Downloadable.STATUS_DELETED) { - if (message.getType() == Message.TYPE_FILE) { - mLastMessage.setText(R.string.file_deleted); - } else { - mLastMessage.setText(R.string.image_file_deleted); - } - } else if (d.getStatus() == Downloadable.STATUS_FAILED) { - if (message.getType() == Message.TYPE_FILE) { - mLastMessage.setText(R.string.file_transmission_failed); - } else { - mLastMessage.setText(R.string.image_transmission_failed); - } - } else if (message.getImageParams().width > 0) { - mLastMessage.setVisibility(View.GONE); - imagePreview.setVisibility(View.VISIBLE); - activity.loadBitmap(message, imagePreview); + if (message.getImageParams().width > 0 + && (message.getDownloadable() == null + || message.getDownloadable().getStatus() != Downloadable.STATUS_DELETED)) { + mLastMessage.setVisibility(View.GONE); + imagePreview.setVisibility(View.VISIBLE); + activity.loadBitmap(message, imagePreview); + } else { + Pair<String,Boolean> preview = UIHelper.getMessagePreview(activity,message); + mLastMessage.setVisibility(View.VISIBLE); + imagePreview.setVisibility(View.GONE); + mLastMessage.setText(preview.first); + if (preview.second) { + if (conversation.isRead()) { + mLastMessage.setTypeface(null, Typeface.ITALIC); } else { - mLastMessage.setText(""); + mLastMessage.setTypeface(null,Typeface.BOLD_ITALIC); } - } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { - imagePreview.setVisibility(View.GONE); - mLastMessage.setVisibility(View.VISIBLE); - mLastMessage.setText(R.string.encrypted_message_received); - } else if (message.getType() == Message.TYPE_FILE && message.getImageParams().width <= 0) { - DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message); - mLastMessage.setVisibility(View.VISIBLE); - imagePreview.setVisibility(View.GONE); - mLastMessage.setText(getContext().getString(R.string.file,file.getMimeType())); - } else { - mLastMessage.setVisibility(View.GONE); - imagePreview.setVisibility(View.VISIBLE); - activity.loadBitmap(message, imagePreview); - } - } else { - if ((message.getEncryption() != Message.ENCRYPTION_PGP) - && (message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED)) { - boolean parseEmoticons = Settings.PARSE_EMOTICONS; - mLastMessage.setText(parseEmoticons ? UIHelper - .transformAsciiEmoticons(getContext(), message.getBody()) : message - .getBody()); } else { - mLastMessage.setText(R.string.encrypted_message_received); - } - if (!conversation.isRead()) { - mLastMessage.setTypeface(null, Typeface.BOLD); - } else { - mLastMessage.setTypeface(null, Typeface.NORMAL); + if (conversation.isRead()) { + mLastMessage.setTypeface(null,Typeface.NORMAL); + } else { + mLastMessage.setTypeface(null,Typeface.BOLD); + } } - mLastMessage.setVisibility(View.VISIBLE); - imagePreview.setVisibility(View.GONE); } - mTimestamp.setText(UIHelper.readableTimeDifference(getContext(), - conversation.getLatestMessage().getTimeSent())); - ImageView profilePicture = (ImageView) view - .findViewById(R.id.conversation_image); - profilePicture.setImageBitmap(activity.avatarService().get( - conversation, activity.getPixel(56))); + mTimestamp.setText(UIHelper.readableTimeDifference(activity,conversation.getLatestMessage().getTimeSent())); + ImageView profilePicture = (ImageView) view.findViewById(R.id.conversation_image); + profilePicture.setImageBitmap(activity.avatarService().get(conversation, activity.getPixel(56))); return view; } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 2771c4de..7fa05050 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -11,6 +11,7 @@ import android.text.SpannableString; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import android.util.DisplayMetrics; +import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; @@ -36,7 +37,6 @@ import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Message.ImageParams; import eu.siacs.conversations.ui.ConversationActivity; import eu.siacs.conversations.utils.UIHelper; -import eu.siacs.conversations.xmpp.jid.Jid; public class MessageAdapter extends ArrayAdapter<Message> { @@ -53,14 +53,14 @@ public class MessageAdapter extends ArrayAdapter<Message> { private OnContactPictureLongClicked mOnContactPictureLongClickedListener; private OnLongClickListener openContextMenu = new OnLongClickListener() { - + @Override public boolean onLongClick(View v) { v.showContextMenu(); return true; } }; - + public MessageAdapter(ConversationActivity activity, List<Message> messages) { super(activity, 0, messages); this.activity = activity; @@ -74,7 +74,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { public void setOnContactPictureLongClicked( OnContactPictureLongClicked listener) { this.mOnContactPictureLongClickedListener = listener; - } + } @Override public int getViewTypeCount() { @@ -102,57 +102,52 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.indicatorReceived.setVisibility(View.GONE); } boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI - && message.getMergedStatus() <= Message.STATUS_RECEIVED; + && message.getMergedStatus() <= Message.STATUS_RECEIVED; if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getDownloadable() != null) { ImageParams params = message.getImageParams(); if (params.size > (1.5 * 1024 * 1024)) { - filesize = params.size / (1024 * 1024)+ " MB"; + filesize = params.size / (1024 * 1024)+ " MiB"; } else if (params.size > 0) { - filesize = params.size / 1024 + " KB"; + filesize = params.size / 1024 + " KiB"; } if (message.getDownloadable() != null && message.getDownloadable().getStatus() == Downloadable.STATUS_FAILED) { error = true; } } switch (message.getMergedStatus()) { - case Message.STATUS_WAITING: - info = getContext().getString(R.string.waiting); - break; - case Message.STATUS_UNSEND: - Downloadable d = message.getDownloadable(); - if (d!=null) { - info = getContext().getString(R.string.sending_file,d.getProgress()); - } else { - info = getContext().getString(R.string.sending); - } - break; - case Message.STATUS_OFFERED: - info = getContext().getString(R.string.offering); - break; - case Message.STATUS_SEND_RECEIVED: - if (activity.indicateReceived()) { - viewHolder.indicatorReceived.setVisibility(View.VISIBLE); - } - break; - case Message.STATUS_SEND_DISPLAYED: - if (activity.indicateReceived()) { - viewHolder.indicatorReceived.setVisibility(View.VISIBLE); - } - break; - case Message.STATUS_SEND_FAILED: - info = getContext().getString(R.string.send_failed); - error = true; - break; - default: - if (multiReceived) { - Contact contact = message.getContact(); - if (contact != null) { - info = contact.getDisplayName(); + case Message.STATUS_WAITING: + info = getContext().getString(R.string.waiting); + break; + case Message.STATUS_UNSEND: + Downloadable d = message.getDownloadable(); + if (d!=null) { + info = getContext().getString(R.string.sending_file,d.getProgress()); } else { - info = getDisplayedMucCounterpart(message.getCounterpart()); + info = getContext().getString(R.string.sending); } - } - break; + break; + case Message.STATUS_OFFERED: + info = getContext().getString(R.string.offering); + break; + case Message.STATUS_SEND_RECEIVED: + if (activity.indicateReceived()) { + viewHolder.indicatorReceived.setVisibility(View.VISIBLE); + } + break; + case Message.STATUS_SEND_DISPLAYED: + if (activity.indicateReceived()) { + viewHolder.indicatorReceived.setVisibility(View.VISIBLE); + } + break; + case Message.STATUS_SEND_FAILED: + info = getContext().getString(R.string.send_failed); + error = true; + break; + default: + if (multiReceived) { + info = UIHelper.getMessageDisplayName(message); + } + break; } if (error) { viewHolder.time.setTextColor(activity.getWarningTextColor()); @@ -213,29 +208,40 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.VISIBLE); viewHolder.messageBody.setText(getContext().getString( - R.string.decryption_failed)); + R.string.decryption_failed)); viewHolder.messageBody.setTextColor(activity.getWarningTextColor()); viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); viewHolder.messageBody.setTextIsSelectable(false); } - private void displayTextMessage(ViewHolder viewHolder, Message message) { + private void displayTextMessage(final ViewHolder viewHolder, final Message message) { if (viewHolder.download_button != null) { viewHolder.download_button.setVisibility(View.GONE); } viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.VISIBLE); if (message.getBody() != null) { + final String nick = UIHelper.getMessageDisplayName(message); + final String formattedBody = message.getMergedBody().replaceAll("^" + Message.ME_COMMAND, + nick + " "); if (message.getType() != Message.TYPE_PRIVATE) { boolean parseEmoticons = Settings.PARSE_EMOTICONS; viewHolder.messageBody.setText(parseEmoticons ? UIHelper .transformAsciiEmoticons(getContext(), message.getMergedBody()) : message.getMergedBody()); + if (message.hasMeCommand()) { + final Spannable span = new SpannableString(formattedBody); + span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + viewHolder.messageBody.setText(span); + } else { + viewHolder.messageBody.setText(message.getMergedBody()); + } } else { String privateMarker; if (message.getStatus() <= Message.STATUS_RECEIVED) { privateMarker = activity - .getString(R.string.private_message); + .getString(R.string.private_message); } else { final String to; if (message.getCounterpart() != null) { @@ -245,15 +251,19 @@ public class MessageAdapter extends ArrayAdapter<Message> { } privateMarker = activity.getString(R.string.private_message_to, to); } - SpannableString span = new SpannableString(privateMarker + " " - + message.getBody()); - span.setSpan( - new ForegroundColorSpan(activity - .getSecondaryTextColor()), 0, privateMarker - .length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - span.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, + final Spannable span = new SpannableString(privateMarker + " " + + formattedBody); + span.setSpan(new ForegroundColorSpan(activity + .getSecondaryTextColor()), 0, privateMarker + .length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + span.setSpan(new StyleSpan(Typeface.BOLD), 0, privateMarker.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + if (message.hasMeCommand()) { + span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), privateMarker.length() + 1, + privateMarker.length() + 1 + nick.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } viewHolder.messageBody.setText(span); } } else { @@ -281,16 +291,15 @@ public class MessageAdapter extends ArrayAdapter<Message> { } private void displayOpenableMessage(ViewHolder viewHolder,final Message message) { - final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message); viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.GONE); viewHolder.download_button.setVisibility(View.VISIBLE); - viewHolder.download_button.setText(activity.getString(R.string.open_file,file.getMimeType())); + viewHolder.download_button.setText(activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity,message))); viewHolder.download_button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - openDownloadable(file); + openDownloadable(message); } }); viewHolder.download_button.setOnLongClickListener(openContextMenu); @@ -315,7 +324,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { scalledH = (int) (params.height / ((double) params.width / target)); } viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams( - scalledW, scalledH)); + scalledW, scalledH)); activity.loadBitmap(message, viewHolder.image); viewHolder.image.setOnClickListener(new OnClickListener() { @@ -330,16 +339,6 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.image.setOnLongClickListener(openContextMenu); } - private String getDisplayedMucCounterpart(final Jid counterpart) { - if (counterpart==null) { - return ""; - } else if (!counterpart.isBareJid()) { - return counterpart.getResourcepart(); - } else { - return counterpart.toString(); - } - } - @Override public View getView(int position, View view, ViewGroup parent) { final Message message = getItem(position); @@ -350,59 +349,58 @@ public class MessageAdapter extends ArrayAdapter<Message> { if (view == null) { viewHolder = new ViewHolder(); switch (type) { - case NULL: - view = activity.getLayoutInflater().inflate( - R.layout.message_null, parent, false); - break; - case SENT: - view = activity.getLayoutInflater().inflate( - R.layout.message_sent, parent, false); - viewHolder.message_box = (LinearLayout) view + case NULL: + view = activity.getLayoutInflater().inflate( + R.layout.message_null, parent, false); + break; + case SENT: + view = activity.getLayoutInflater().inflate( + R.layout.message_sent, parent, false); + viewHolder.message_box = (LinearLayout) view .findViewById(R.id.message_box); - viewHolder.contact_picture = (ImageView) view + viewHolder.contact_picture = (ImageView) view .findViewById(R.id.message_photo); - viewHolder.download_button = (Button) view + viewHolder.download_button = (Button) view .findViewById(R.id.download_button); - viewHolder.indicator = (ImageView) view + viewHolder.indicator = (ImageView) view .findViewById(R.id.security_indicator); - viewHolder.image = (ImageView) view + viewHolder.image = (ImageView) view .findViewById(R.id.message_image); - viewHolder.messageBody = (TextView) view + viewHolder.messageBody = (TextView) view .findViewById(R.id.message_body); - viewHolder.time = (TextView) view + viewHolder.time = (TextView) view .findViewById(R.id.message_time); - viewHolder.indicatorReceived = (ImageView) view + viewHolder.indicatorReceived = (ImageView) view .findViewById(R.id.indicator_received); - break; - case RECEIVED: - view = activity.getLayoutInflater().inflate( - R.layout.message_received, parent, false); - viewHolder.message_box = (LinearLayout) view + break; + case RECEIVED: + view = activity.getLayoutInflater().inflate( + R.layout.message_received, parent, false); + viewHolder.message_box = (LinearLayout) view .findViewById(R.id.message_box); - viewHolder.contact_picture = (ImageView) view + viewHolder.contact_picture = (ImageView) view .findViewById(R.id.message_photo); - viewHolder.download_button = (Button) view + viewHolder.download_button = (Button) view .findViewById(R.id.download_button); - viewHolder.indicator = (ImageView) view + viewHolder.indicator = (ImageView) view .findViewById(R.id.security_indicator); - viewHolder.image = (ImageView) view + viewHolder.image = (ImageView) view .findViewById(R.id.message_image); - viewHolder.messageBody = (TextView) view + viewHolder.messageBody = (TextView) view .findViewById(R.id.message_body); - viewHolder.time = (TextView) view + viewHolder.time = (TextView) view .findViewById(R.id.message_time); - viewHolder.indicatorReceived = (ImageView) view + viewHolder.indicatorReceived = (ImageView) view .findViewById(R.id.indicator_received); - break; - case STATUS: - view = activity.getLayoutInflater().inflate( - R.layout.message_status, parent, false); - viewHolder.contact_picture = (ImageView) view - .findViewById(R.id.message_photo); - break; - default: - viewHolder = null; - break; + break; + case STATUS: + view = activity.getLayoutInflater().inflate(R.layout.message_status, parent, false); + viewHolder.contact_picture = (ImageView) view.findViewById(R.id.message_photo); + viewHolder.status_message = (TextView) view.findViewById(R.id.status_message); + break; + default: + viewHolder = null; + break; } view.setTag(viewHolder); } else { @@ -416,26 +414,19 @@ public class MessageAdapter extends ArrayAdapter<Message> { if (conversation.getMode() == Conversation.MODE_SINGLE) { viewHolder.contact_picture.setImageBitmap(activity .avatarService().get(conversation.getContact(), - activity.getPixel(32))); + activity.getPixel(32))); viewHolder.contact_picture.setAlpha(0.5f); - viewHolder.contact_picture - .setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - String name = conversation.getName(); - String read = getContext() - .getString( - R.string.contact_has_read_up_to_this_point, - name); - Toast.makeText(getContext(), read, - Toast.LENGTH_SHORT).show(); - } - }); + viewHolder.status_message.setText( + activity.getString(R.string.contact_has_read_up_to_this_point, conversation.getName())); } return view; } else if (type == NULL) { + if (viewHolder.message_box != null) { + Log.e(Config.LOGTAG, "detected type=NULL but with wrong cached view"); + view = activity.getLayoutInflater().inflate(R.layout.message_null, parent, false); + view.setTag(new ViewHolder()); + } if (position == getCount() - 1) { view.getLayoutParams().height = 1; } else { @@ -444,6 +435,9 @@ public class MessageAdapter extends ArrayAdapter<Message> { } view.setLayoutParams(view.getLayoutParams()); return view; + } else if (message.wasMergedIntoPrevious()) { + Log.e(Config.LOGTAG,"detected wasMergedIntoPrevious with wrong type"); + return view; } else if (viewHolder.messageBody == null || viewHolder.image == null) { return view; //avoiding weird platform bugs } else if (type == RECEIVED) { @@ -451,70 +445,49 @@ public class MessageAdapter extends ArrayAdapter<Message> { if (contact != null) { viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(contact, activity.getPixel(48))); } else if (conversation.getMode() == Conversation.MODE_MULTI) { - viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(getDisplayedMucCounterpart(message.getCounterpart()), - activity.getPixel(48))); + viewHolder.contact_picture.setImageBitmap(activity.avatarService().get( + UIHelper.getMessageDisplayName(message), + activity.getPixel(48))); } } else if (type == SENT) { viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(account, activity.getPixel(48))); } viewHolder.contact_picture - .setOnClickListener(new OnClickListener() { + .setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (MessageAdapter.this.mOnContactPictureClickedListener != null) { - MessageAdapter.this.mOnContactPictureClickedListener - .onContactPictureClicked(message); - } - - } - }); - viewHolder.contact_picture - .setOnLongClickListener(new OnLongClickListener() { - - @Override - public boolean onLongClick(View v) { - if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) { - MessageAdapter.this.mOnContactPictureLongClickedListener - .onContactPictureLongClicked(message); - return true; - } else { - return false; - } + @Override + public void onClick(View v) { + if (MessageAdapter.this.mOnContactPictureClickedListener != null) { + MessageAdapter.this.mOnContactPictureClickedListener + .onContactPictureClicked(message); } - }); - if (message.getDownloadable() != null && message.getDownloadable().getStatus() != Downloadable.STATUS_UPLOADING) { - Downloadable d = message.getDownloadable(); - if (d.getStatus() == Downloadable.STATUS_DOWNLOADING) { - if (message.getType() == Message.TYPE_FILE) { - displayInfoMessage(viewHolder,activity.getString(R.string.receiving_file,d.getMimeType(),d.getProgress())); - } else { - displayInfoMessage(viewHolder,activity.getString(R.string.receiving_image,d.getProgress())); } - } else if (d.getStatus() == Downloadable.STATUS_CHECKING) { - displayInfoMessage(viewHolder,activity.getString(R.string.checking_image)); - } else if (d.getStatus() == Downloadable.STATUS_DELETED) { - if (message.getType() == Message.TYPE_FILE) { - displayInfoMessage(viewHolder, activity.getString(R.string.file_deleted)); - } else { - displayInfoMessage(viewHolder, activity.getString(R.string.image_file_deleted)); - } - } else if (d.getStatus() == Downloadable.STATUS_OFFER) { - if (message.getType() == Message.TYPE_FILE) { - displayDownloadableMessage(viewHolder,message,activity.getString(R.string.download_file,d.getMimeType())); - } else { - displayDownloadableMessage(viewHolder, message,activity.getString(R.string.download_image)); - } - } else if (d.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) { - displayDownloadableMessage(viewHolder, message,activity.getString(R.string.check_image_filesize)); - } else if (d.getStatus() == Downloadable.STATUS_FAILED) { - if (message.getType() == Message.TYPE_FILE) { - displayInfoMessage(viewHolder, activity.getString(R.string.file_transmission_failed)); - } else { - displayInfoMessage(viewHolder, activity.getString(R.string.image_transmission_failed)); + }); + viewHolder.contact_picture + .setOnLongClickListener(new OnLongClickListener() { + + @Override + public boolean onLongClick(View v) { + if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) { + MessageAdapter.this.mOnContactPictureLongClickedListener + .onContactPictureLongClicked(message); + return true; + } else { + return false; + } } + }); + + final Downloadable downloadable = message.getDownloadable(); + if (downloadable != null && downloadable.getStatus() != Downloadable.STATUS_UPLOADING) { + if (downloadable.getStatus() == Downloadable.STATUS_OFFER) { + displayDownloadableMessage(viewHolder,message,activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, message))); + } else if (downloadable.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) { + displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_image_filesize)); + } else { + displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first); } } else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) { displayImageMessage(viewHolder, message); @@ -532,13 +505,13 @@ public class MessageAdapter extends ArrayAdapter<Message> { activity.getString(R.string.install_openkeychain)); if (viewHolder != null) { viewHolder.message_box - .setOnClickListener(new OnClickListener() { + .setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - activity.showInstallPgpDialog(); - } - }); + @Override + public void onClick(View v) { + activity.showInstallPgpDialog(); + } + }); } } } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { @@ -562,7 +535,8 @@ public class MessageAdapter extends ArrayAdapter<Message> { } } - public void openDownloadable(DownloadableFile file) { + public void openDownloadable(Message message) { + DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message); if (!file.exists()) { Toast.makeText(activity,R.string.file_deleted,Toast.LENGTH_SHORT).show(); return; @@ -596,6 +570,6 @@ public class MessageAdapter extends ArrayAdapter<Message> { protected TextView time; protected TextView messageBody; protected ImageView contact_picture; - + protected TextView status_message; } } diff --git a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java index 7a36e2ba..fc21acbc 100644 --- a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java @@ -2,12 +2,17 @@ package eu.siacs.conversations.utils; import java.security.SecureRandom; import java.text.Normalizer; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashSet; -public class CryptoHelper { +import eu.siacs.conversations.Config; + +public final class CryptoHelper { public static final String FILETRANSFER = "?FILETRANSFERv1:"; - final protected static char[] hexArray = "0123456789abcdef".toCharArray(); - final protected static char[] vowels = "aeiou".toCharArray(); - final protected static char[] consonants = "bcdfghjklmnpqrstvwxyz".toCharArray(); + private final static char[] hexArray = "0123456789abcdef".toCharArray(); + private final static char[] vowels = "aeiou".toCharArray(); + private final static char[] consonants = "bcdfghjklmnpqrstvwxyz".toCharArray(); final public static byte[] ONE = new byte[] { 0, 0, 0, 1 }; public static String bytesToHex(byte[] bytes) { @@ -45,7 +50,7 @@ public class CryptoHelper { return randomWord(3, random) + "." + randomWord(7, random); } - protected static String randomWord(int lenght, SecureRandom random) { + private static String randomWord(int lenght, SecureRandom random) { StringBuilder builder = new StringBuilder(lenght); for (int i = 0; i < lenght; ++i) { if (i % 2 == 0) { @@ -91,4 +96,11 @@ public class CryptoHelper { builder.insert(35, " "); return builder.toString(); } + + public static String[] getSupportedCipherSuites(final String[] platformSupportedCipherSuites) { + //final Collection<String> cipherSuites = new LinkedHashSet<>(Arrays.asList(Config.ENABLED_CIPHERS)); + //cipherSuites.retainAll(Arrays.asList(platformSupportedCipherSuites)); + //return cipherSuites.toArray(new String[cipherSuites.size()]); + return platformSupportedCipherSuites; + } } diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index b4f28c45..333f6e27 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -1,6 +1,7 @@ package eu.siacs.conversations.utils; import java.util.ArrayList; +import java.net.URLConnection; import java.util.Calendar; import java.util.Date; import java.util.HashMap; @@ -13,18 +14,25 @@ import java.util.regex.Matcher; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Downloadable; +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.xmpp.jid.Jid; + import android.content.Context; import android.text.format.DateFormat; import android.text.format.DateUtils; import android.text.Spannable.Factory; import android.text.style.ImageSpan; import android.text.Spannable; +import android.util.Pair; public class UIHelper { private static final int SHORT_DATE_FLAGS = DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_NO_YEAR | DateUtils.FORMAT_ABBREV_ALL; + | DateUtils.FORMAT_NO_YEAR | DateUtils.FORMAT_ABBREV_ALL; private static final int FULL_DATE_FLAGS = DateUtils.FORMAT_SHOW_TIME - | DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE; + | DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE; public static String readableTimeDifference(Context context, long time) { return readableTimeDifference(context, time, false); @@ -63,13 +71,21 @@ public class UIHelper { } private static boolean today(Date date) { + return sameDay(date,new Date(System.currentTimeMillis())); + } + + public static boolean sameDay(long timestamp1, long timestamp2) { + return sameDay(new Date(timestamp1),new Date(timestamp2)); + } + + private static boolean sameDay(Date a, Date b) { Calendar cal1 = Calendar.getInstance(); Calendar cal2 = Calendar.getInstance(); - cal1.setTime(date); - cal2.setTimeInMillis(System.currentTimeMillis()); + cal1.setTime(a); + cal2.setTime(b); return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) - && cal1.get(Calendar.DAY_OF_YEAR) == cal2 - .get(Calendar.DAY_OF_YEAR); + && cal1.get(Calendar.DAY_OF_YEAR) == cal2 + .get(Calendar.DAY_OF_YEAR); } public static String lastseen(Context context, long time) { @@ -232,8 +248,117 @@ public class UIHelper { return 0xFF202020; } int colors[] = {0xFFe91e63, 0xFF9c27b0, 0xFF673ab7, 0xFF3f51b5, - 0xFF5677fc, 0xFF03a9f4, 0xFF00bcd4, 0xFF009688, 0xFFff5722, - 0xFF795548, 0xFF607d8b}; + 0xFF5677fc, 0xFF03a9f4, 0xFF00bcd4, 0xFF009688, 0xFFff5722, + 0xFF795548, 0xFF607d8b}; return colors[(int) ((name.hashCode() & 0xffffffffl) % colors.length)]; } + + public static Pair<String,Boolean> getMessagePreview(final Context context, final Message message) { + final Downloadable d = message.getDownloadable(); + if (d != null ) { + switch (d.getStatus()) { + case Downloadable.STATUS_CHECKING: + return new Pair<>(context.getString(R.string.checking_image),true); + case Downloadable.STATUS_DOWNLOADING: + return new Pair<>(context.getString(R.string.receiving_x_file, + getFileDescriptionString(context,message), + d.getProgress()),true); + case Downloadable.STATUS_OFFER: + case Downloadable.STATUS_OFFER_CHECK_FILESIZE: + return new Pair<>(context.getString(R.string.x_file_offered_for_download, + getFileDescriptionString(context,message)),true); + case Downloadable.STATUS_DELETED: + return new Pair<>(context.getString(R.string.file_deleted),true); + case Downloadable.STATUS_FAILED: + return new Pair<>(context.getString(R.string.file_transmission_failed),true); + case Downloadable.STATUS_UPLOADING: + if (message.getStatus() == Message.STATUS_OFFERED) { + return new Pair<>(context.getString(R.string.offering_x_file, + getFileDescriptionString(context, message)), true); + } else { + return new Pair<>(context.getString(R.string.sending_x_file, + getFileDescriptionString(context, message)), true); + } + default: + return new Pair<>("",false); + } + } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { + return new Pair<>(context.getString(R.string.encrypted_message_received),true); + } else if (message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) { + if (message.getStatus() == Message.STATUS_RECEIVED) { + return new Pair<>(context.getString(R.string.received_x_file, + getFileDescriptionString(context, message)), true); + } else { + return new Pair<>(getFileDescriptionString(context,message),true); + } + } else { + if (message.getBody().startsWith(Message.ME_COMMAND)) { + return new Pair<>(message.getBody().replaceAll("^" + Message.ME_COMMAND, + UIHelper.getMessageDisplayName(message) + " "), false); + } else { + return new Pair<>(message.getBody(), false); + } + } + } + + public static String getFileDescriptionString(final Context context, final Message message) { + if (message.getType() == Message.TYPE_IMAGE) { + return context.getString(R.string.image); + } + final String path = message.getRelativeFilePath(); + if (path == null) { + return ""; + } + final String mime; + try { + mime = URLConnection.guessContentTypeFromName(path.replace("#","")); + } catch (final StringIndexOutOfBoundsException ignored) { + return context.getString(R.string.file); + } + if (mime == null) { + return context.getString(R.string.file); + } else if (mime.startsWith("audio/")) { + return context.getString(R.string.audio); + } else if(mime.startsWith("video/")) { + return context.getString(R.string.video); + } else if (mime.startsWith("image/")) { + return context.getString(R.string.image); + } else if (mime.contains("pdf")) { + return context.getString(R.string.pdf_document) ; + } else if (mime.contains("application/vnd.android.package-archive")) { + return context.getString(R.string.apk) ; + } else if (mime.contains("vcard")) { + return context.getString(R.string.vcard) ; + } else { + return mime; + } + } + + public static String getMessageDisplayName(final Message message) { + if (message.getStatus() == Message.STATUS_RECEIVED) { + if (message.getConversation().getMode() == Conversation.MODE_MULTI) { + return getDisplayedMucCounterpart(message.getCounterpart()); + } else { + final Contact contact = message.getContact(); + return contact != null ? contact.getDisplayName() : ""; + } + } else { + if (message.getConversation().getMode() == Conversation.MODE_MULTI) { + return getDisplayedMucCounterpart(message.getConversation().getJid()); + } else { + final Jid jid = message.getConversation().getAccount().getJid(); + return jid.hasLocalpart() ? jid.getLocalpart() : jid.toDomainJid().toString(); + } + } + } + + private static String getDisplayedMucCounterpart(final Jid counterpart) { + if (counterpart==null) { + return ""; + } else if (!counterpart.isBareJid()) { + return counterpart.getResourcepart().trim(); + } else { + return counterpart.toString().trim(); + } + } } diff --git a/src/main/java/eu/siacs/conversations/utils/Xmlns.java b/src/main/java/eu/siacs/conversations/utils/Xmlns.java index 67de7c79..17fd2d26 100644 --- a/src/main/java/eu/siacs/conversations/utils/Xmlns.java +++ b/src/main/java/eu/siacs/conversations/utils/Xmlns.java @@ -4,4 +4,5 @@ public final class Xmlns { public static final String BLOCKING = "urn:xmpp:blocking"; public static final String ROSTER = "jabber:iq:roster"; public static final String REGISTER = "jabber:iq:register"; + public static final String BYTE_STREAMS = "http://jabber.org/protocol/bytestreams"; } diff --git a/src/main/java/eu/siacs/conversations/utils/XmppUri.java b/src/main/java/eu/siacs/conversations/utils/XmppUri.java index aacb6362..0f1b18c3 100644 --- a/src/main/java/eu/siacs/conversations/utils/XmppUri.java +++ b/src/main/java/eu/siacs/conversations/utils/XmppUri.java @@ -18,7 +18,11 @@ public class XmppUri { try { parse(Uri.parse(uri)); } catch (IllegalArgumentException e) { - jid = null; + try { + jid = Jid.fromString(uri).toBareJid().toString(); + } catch (InvalidJidException e2) { + jid = null; + } } } @@ -42,6 +46,13 @@ public class XmppUri { try { jid = URLDecoder.decode(uri.getEncodedPath(), "UTF-8").split("/")[1]; } catch (final UnsupportedEncodingException ignored) { + jid = null; + } + } else { + try { + jid = Jid.fromString(uri.toString()).toBareJid().toString(); + } catch (final InvalidJidException ignored) { + jid = null; } } } @@ -62,7 +73,7 @@ public class XmppUri { public Jid getJid() { try { - return Jid.fromString(this.jid); + return this.jid == null ? null :Jid.fromString(this.jid); } catch (InvalidJidException e) { return null; } @@ -71,8 +82,4 @@ public class XmppUri { public String getFingerprint() { return this.fingerprint; } - - public boolean isMuc() { - return this.muc; - } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index f7f0c346..19e271b2 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -9,6 +9,7 @@ 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; import org.apache.http.conn.ssl.StrictHostnameVerifier; @@ -52,6 +53,7 @@ import eu.siacs.conversations.crypto.sasl.ScramSha1; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.generator.IqGenerator; import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.DNSHelper; import eu.siacs.conversations.utils.Xmlns; import eu.siacs.conversations.xml.Element; @@ -94,17 +96,14 @@ public class XmppConnection implements Runnable { private int smVersion = 3; private final SparseArray<String> messageReceipts = new SparseArray<>(); - private boolean enabledEncryption = false; - private boolean enabledCarbons = false; - private int stanzasReceived = 0; private int stanzasSent = 0; - private long lastPaketReceived = 0; + private long lastPacketReceived = 0; private long lastPingSent = 0; private long lastConnect = 0; private long lastSessionStarted = 0; private int attempt = 0; - private final Map<String, PacketReceived> packetCallbacks = new Hashtable<>(); + private final Map<String, Pair<IqPacket, OnIqPacketReceived>> packetCallbacks = new Hashtable<>(); private OnPresencePacketReceived presenceListener = null; private OnJinglePacketReceived jingleListener = null; private OnIqPacketReceived unregisteredIqListener = null; @@ -146,13 +145,12 @@ public class XmppConnection implements Runnable { protected void connect() { Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": connecting"); - enabledEncryption = false; + features.encryptionEnabled = false; lastConnect = SystemClock.elapsedRealtime(); lastPingSent = SystemClock.elapsedRealtime(); this.attempt++; try { - shouldAuthenticate = shouldBind = !account - .isOptionSet(Account.OPTION_REGISTER); + shouldAuthenticate = shouldBind = !account.isOptionSet(Account.OPTION_REGISTER); tagReader = new XmlReader(wakeLock); tagWriter = new TagWriter(); packetCallbacks.clear(); @@ -231,6 +229,7 @@ public class XmppConnection implements Runnable { } catch (final IOException | XmlPullParserException | NoSuchAlgorithmException e) { Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage()); this.changeStatus(Account.State.OFFLINE); + this.attempt--; //don't count attempt when reconnecting instantly anyway } finally { if (wakeLock.isHeld()) { try { @@ -303,7 +302,7 @@ public class XmppConnection implements Runnable { final RequestPacket r = new RequestPacket(smVersion); tagWriter.writeStanzaAsync(r); } else if (nextTag.isStart("resumed")) { - lastPaketReceived = SystemClock.elapsedRealtime(); + lastPacketReceived = SystemClock.elapsedRealtime(); final Element resumed = tagReader.readElement(nextTag); final String h = resumed.getAttribute("h"); try { @@ -336,7 +335,7 @@ public class XmppConnection implements Runnable { tagWriter.writeStanzaAsync(ack); } else if (nextTag.isStart("a")) { final Element ack = tagReader.readElement(nextTag); - lastPaketReceived = SystemClock.elapsedRealtime(); + lastPacketReceived = SystemClock.elapsedRealtime(); final int serverSequence = Integer.parseInt(ack.getAttribute("h")); final String msgId = this.messageReceipts.get(serverSequence); if (msgId != null) { @@ -372,7 +371,7 @@ public class XmppConnection implements Runnable { private void sendInitialPing() { Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": sending intial ping"); - final IqPacket iq = new IqPacket(IqPacket.TYPE_GET); + final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); iq.setFrom(account.getJid()); iq.addChild("ping", "urn:xmpp:ping"); this.sendIqPacket(iq, new OnIqPacketReceived() { @@ -425,69 +424,54 @@ public class XmppConnection implements Runnable { } } ++stanzasReceived; - lastPaketReceived = SystemClock.elapsedRealtime(); + lastPacketReceived = SystemClock.elapsedRealtime(); return element; } - private void processIq(final Tag currentTag) throws XmlPullParserException, - IOException { - final IqPacket packet = (IqPacket) processPacket(currentTag, PACKET_IQ); - - if (packet.getId() == null) { - return; // an iq packet without id is definitely invalid - } + private void processIq(final Tag currentTag) throws XmlPullParserException, IOException { + final IqPacket packet = (IqPacket) processPacket(currentTag, PACKET_IQ); - if (packet instanceof JinglePacket) { - if (this.jingleListener != null) { - this.jingleListener.onJinglePacketReceived(account, - (JinglePacket) packet); - } - } else { - if (packetCallbacks.containsKey(packet.getId())) { - if (packetCallbacks.get(packet.getId()) instanceof OnIqPacketReceived) { - ((OnIqPacketReceived) packetCallbacks.get(packet.getId())) - .onIqPacketReceived(account, packet); - } + if (packet.getId() == null) { + return; // an iq packet without id is definitely invalid + } - packetCallbacks.remove(packet.getId()); - } else if ((packet.getType() == IqPacket.TYPE_GET || packet - .getType() == IqPacket.TYPE_SET) - && this.unregisteredIqListener != null) { - this.unregisteredIqListener.onIqPacketReceived(account, packet); - } - } + if (packet instanceof JinglePacket) { + if (this.jingleListener != null) { + this.jingleListener.onJinglePacketReceived(account,(JinglePacket) packet); + } + } else { + if (packetCallbacks.containsKey(packet.getId())) { + final Pair<IqPacket, OnIqPacketReceived> packetCallbackDuple = packetCallbacks.get(packet.getId()); + // Packets to the server should have responses from the server + if (packetCallbackDuple.first.toServer(account)) { + if (packet.fromServer(account)) { + packetCallbackDuple.second.onIqPacketReceived(account, packet); + packetCallbacks.remove(packet.getId()); + } else { + Log.e(Config.LOGTAG,account.getJid().toBareJid().toString()+": ignoring spoofed iq packet"); + } + } else { + if (packet.getFrom().equals(packetCallbackDuple.first.getTo())) { + packetCallbackDuple.second.onIqPacketReceived(account, packet); + packetCallbacks.remove(packet.getId()); + } else { + Log.e(Config.LOGTAG,account.getJid().toBareJid().toString()+": ignoring spoofed iq packet"); + } + } + } else if (packet.getType() == IqPacket.TYPE.GET|| packet.getType() == IqPacket.TYPE.SET) { + this.unregisteredIqListener.onIqPacketReceived(account, packet); + } + } } - private void processMessage(final Tag currentTag) throws XmlPullParserException, - IOException { - final MessagePacket packet = (MessagePacket) processPacket(currentTag, - PACKET_MESSAGE); - final String id = packet.getAttribute("id"); - if ((id != null) && (packetCallbacks.containsKey(id))) { - if (packetCallbacks.get(id) instanceof OnMessagePacketReceived) { - ((OnMessagePacketReceived) packetCallbacks.get(id)) - .onMessagePacketReceived(account, packet); - } - packetCallbacks.remove(id); - } else if (this.messageListener != null) { - this.messageListener.onMessagePacketReceived(account, packet); - } + private void processMessage(final Tag currentTag) throws XmlPullParserException, IOException { + final MessagePacket packet = (MessagePacket) processPacket(currentTag,PACKET_MESSAGE); + this.messageListener.onMessagePacketReceived(account, packet); } - private void processPresence(final Tag currentTag) throws XmlPullParserException, - IOException { - PresencePacket packet = (PresencePacket) processPacket(currentTag, - PACKET_PRESENCE); - final String id = packet.getAttribute("id"); - if ((id != null) && (packetCallbacks.containsKey(id))) { - if (packetCallbacks.get(id) instanceof OnPresencePacketReceived) { - ((OnPresencePacketReceived) packetCallbacks.get(id)) - .onPresencePacketReceived(account, packet); - } - packetCallbacks.remove(id); - } else if (this.presenceListener != null) { - this.presenceListener.onPresencePacketReceived(account, packet); - } + private void processPresence(final Tag currentTag) throws XmlPullParserException, IOException { + PresencePacket packet = (PresencePacket) processPacket(currentTag, PACKET_PRESENCE); + this.presenceListener.onPresencePacketReceived(account, packet); } private void sendStartTLS() throws IOException { @@ -497,8 +481,7 @@ public class XmppConnection implements Runnable { } private SharedPreferences getPreferences() { - return PreferenceManager - .getDefaultSharedPreferences(applicationContext); + return PreferenceManager.getDefaultSharedPreferences(applicationContext); } private boolean enableLegacySSL() { @@ -506,70 +489,72 @@ public class XmppConnection implements Runnable { } private void switchOverToTls(final Tag currentTag) throws XmlPullParserException, IOException { - tagReader.readTag(); - try { + tagReader.readTag(); + try { final SSLContext sc = SSLContext.getInstance("TLS"); sc.init(null,new X509TrustManager[]{this.mXmppConnectionService.getMemorizingTrustManager()},mXmppConnectionService.getRNG()); final SSLSocketFactory factory = sc.getSocketFactory(); final HostnameVerifier verifier = this.mXmppConnectionService.getMemorizingTrustManager().wrapHostnameVerifier(new StrictHostnameVerifier()); - final InetAddress address = socket == null ? null : socket.getInetAddress(); + final InetAddress address = socket == null ? null : socket.getInetAddress(); if (factory == null || address == null || verifier == null) { throw new IOException("could not setup ssl"); } - final SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket,address.getHostAddress(), socket.getPort(),true); + final SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket,address.getHostAddress(), socket.getPort(),true); if (sslSocket == null) { throw new IOException("could not initialize ssl socket"); } final String[] supportProtocols; - if (enableLegacySSL()) { - supportProtocols = sslSocket.getSupportedProtocols(); - } else { - final Collection<String> supportedProtocols = new LinkedList<>( - Arrays.asList(sslSocket.getSupportedProtocols())); - supportedProtocols.remove("SSLv3"); - supportProtocols = new String[supportedProtocols.size()]; - supportedProtocols.toArray(supportProtocols); - } + final Collection<String> supportedProtocols = new LinkedList<>( + Arrays.asList(sslSocket.getSupportedProtocols())); + supportedProtocols.remove("SSLv3"); + supportProtocols = supportedProtocols.toArray(new String[supportedProtocols.size()]); + sslSocket.setEnabledProtocols(supportProtocols); - if (!verifier.verify(account.getServer().getDomainpart(),sslSocket.getSession())) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed"); - disconnect(true); - changeStatus(Account.State.SECURITY_ERROR); + final String[] cipherSuites = CryptoHelper.getSupportedCipherSuites( + sslSocket.getSupportedCipherSuites()); + if (cipherSuites.length > 0) { + sslSocket.setEnabledCipherSuites(cipherSuites); + } + + if (!verifier.verify(account.getServer().getDomainpart(),sslSocket.getSession())) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed"); + disconnect(true); + changeStatus(Account.State.SECURITY_ERROR); } tagReader.setInputStream(sslSocket.getInputStream()); tagWriter.setOutputStream(sslSocket.getOutputStream()); sendStartStream(); Log.d(Config.LOGTAG, account.getJid().toBareJid()+ ": TLS connection established"); - enabledEncryption = true; + features.encryptionEnabled = true; processStream(tagReader.readTag()); sslSocket.close(); - } catch (final NoSuchAlgorithmException | KeyManagementException e1) { + } catch (final NoSuchAlgorithmException | KeyManagementException e1) { Log.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed"); disconnect(true); changeStatus(Account.State.SECURITY_ERROR); - } + } } private void processStreamFeatures(final Tag currentTag) throws XmlPullParserException, IOException { this.streamFeatures = tagReader.readElement(currentTag); - if (this.streamFeatures.hasChild("starttls") && !enabledEncryption) { + if (this.streamFeatures.hasChild("starttls") && !features.encryptionEnabled) { sendStartTLS(); } else if (this.streamFeatures.hasChild("register") && account.isOptionSet(Account.OPTION_REGISTER) - && enabledEncryption) { + && features.encryptionEnabled) { sendRegistryRequest(); } else if (!this.streamFeatures.hasChild("register") && account.isOptionSet(Account.OPTION_REGISTER)) { changeStatus(Account.State.REGISTRATION_NOT_SUPPORTED); disconnect(true); } else if (this.streamFeatures.hasChild("mechanisms") - && shouldAuthenticate && enabledEncryption) { + && shouldAuthenticate && features.encryptionEnabled) { final List<String> mechanisms = extractMechanisms(streamFeatures .findChild("mechanisms")); final Element auth = new Element("auth"); @@ -625,7 +610,7 @@ public class XmppConnection implements Runnable { } private void sendRegistryRequest() { - final IqPacket register = new IqPacket(IqPacket.TYPE_GET); + final IqPacket register = new IqPacket(IqPacket.TYPE.GET); register.query("jabber:iq:register"); register.setTo(account.getServer()); sendIqPacket(register, new OnIqPacketReceived() { @@ -635,7 +620,7 @@ public class XmppConnection implements Runnable { final Element instructions = packet.query().findChild("instructions"); if (packet.query().hasChild("username") && (packet.query().hasChild("password"))) { - final IqPacket register = new IqPacket(IqPacket.TYPE_SET); + final IqPacket register = new IqPacket(IqPacket.TYPE.SET); final Element username = new Element("username") .setContent(account.getUsername()); final Element password = new Element("password") @@ -646,7 +631,7 @@ public class XmppConnection implements Runnable { @Override public void onIqPacketReceived(final Account account, final IqPacket packet) { - if (packet.getType() == IqPacket.TYPE_RESULT) { + if (packet.getType() == IqPacket.TYPE.RESULT) { account.setOption(Account.OPTION_REGISTER, false); changeStatus(Account.State.REGISTRATION_SUCCESSFUL); @@ -673,10 +658,10 @@ public class XmppConnection implements Runnable { } private void sendBindRequest() { - final IqPacket iq = new IqPacket(IqPacket.TYPE_SET); + final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); iq.addChild("bind", "urn:ietf:params:xml:ns:xmpp-bind") .addChild("resource").setContent(account.getResource()); - this.sendUnboundIqPacket(iq, new OnIqPacketReceived() { + this.sendUnmodifiedIqPacket(iq, new OnIqPacketReceived() { @Override public void onIqPacketReceived(final Account account, final IqPacket packet) { final Element bind = packet.findChild("bind"); @@ -694,15 +679,15 @@ public class XmppConnection implements Runnable { tagWriter.writeStanzaAsync(enable); stanzasSent = 0; messageReceipts.clear(); - } else if (streamFeatures.hasChild("sm", - "urn:xmpp:sm:2")) { + } else if (streamFeatures.hasChild("sm", "urn:xmpp:sm:2")) { smVersion = 2; final EnablePacket enable = new EnablePacket(smVersion); tagWriter.writeStanzaAsync(enable); stanzasSent = 0; messageReceipts.clear(); } - enabledCarbons = false; + features.carbonsEnabled = false; + features.blockListRequested = false; disco.clear(); sendServiceDiscoveryInfo(account.getServer()); sendServiceDiscoveryItems(account.getServer()); @@ -719,12 +704,10 @@ public class XmppConnection implements Runnable { } }); if (this.streamFeatures.hasChild("session")) { - Log.d(Config.LOGTAG, account.getJid().toBareJid() - + ": sending deprecated session"); - final IqPacket startSession = new IqPacket(IqPacket.TYPE_SET); - startSession.addChild("session", - "urn:ietf:params:xml:ns:xmpp-session"); - this.sendUnboundIqPacket(startSession, null); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": sending deprecated session"); + final IqPacket startSession = new IqPacket(IqPacket.TYPE.SET); + startSession.addChild("session","urn:ietf:params:xml:ns:xmpp-session"); + this.sendUnmodifiedIqPacket(startSession, null); } } @@ -734,7 +717,7 @@ public class XmppConnection implements Runnable { enableAdvancedStreamFeatures(); } } else { - final IqPacket iq = new IqPacket(IqPacket.TYPE_GET); + final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); iq.setTo(server.toDomainJid()); iq.query("http://jabber.org/protocol/disco#info"); this.sendIqPacket(iq, new OnIqPacketReceived() { @@ -767,19 +750,17 @@ public class XmppConnection implements Runnable { } private void enableAdvancedStreamFeatures() { - if (getFeatures().carbons()) { - if (!enabledCarbons) { - sendEnableCarbons(); - } + if (getFeatures().carbons() && !features.carbonsEnabled) { + sendEnableCarbons(); } - if (getFeatures().blocking()) { + if (getFeatures().blocking() && !features.blockListRequested) { Log.d(Config.LOGTAG, "Requesting block list"); this.sendIqPacket(getIqGenerator().generateGetBlockList(), mXmppConnectionService.getIqParser()); } } private void sendServiceDiscoveryItems(final Jid server) { - final IqPacket iq = new IqPacket(IqPacket.TYPE_GET); + final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); iq.setTo(server.toDomainJid()); iq.query("http://jabber.org/protocol/disco#items"); this.sendIqPacket(iq, new OnIqPacketReceived() { @@ -800,7 +781,7 @@ public class XmppConnection implements Runnable { } private void sendEnableCarbons() { - final IqPacket iq = new IqPacket(IqPacket.TYPE_SET); + final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); iq.addChild("enable", "urn:xmpp:carbons:2"); this.sendIqPacket(iq, new OnIqPacketReceived() { @@ -809,7 +790,7 @@ public class XmppConnection implements Runnable { if (!packet.hasChild("error")) { Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": successfully enabled carbons"); - enabledCarbons = true; + features.carbonsEnabled = true; } else { Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": error enableing carbons " + packet.toString()); @@ -845,49 +826,44 @@ public class XmppConnection implements Runnable { return new BigInteger(50, mXmppConnectionService.getRNG()).toString(32); } - public void sendIqPacket(final IqPacket packet, final PacketReceived callback) { - if (packet.getId() == null) { - final String id = nextRandomId(); - packet.setAttribute("id", id); - } + public void sendIqPacket(final IqPacket packet, final OnIqPacketReceived callback) { packet.setFrom(account.getJid()); - this.sendPacket(packet, callback); + this.sendUnmodifiedIqPacket(packet,callback); + } - public void sendUnboundIqPacket(final IqPacket packet, final PacketReceived callback) { + private synchronized void sendUnmodifiedIqPacket(final IqPacket packet, final OnIqPacketReceived callback) { if (packet.getId() == null) { final String id = nextRandomId(); packet.setAttribute("id", id); } - this.sendPacket(packet, callback); + if (callback != null) { + if (packet.getId() == null) { + packet.setId(nextRandomId()); + } + packetCallbacks.put(packet.getId(), new Pair<>(packet, callback)); + } + this.sendPacket(packet); } public void sendMessagePacket(final MessagePacket packet) { - this.sendPacket(packet, null); + this.sendPacket(packet); } public void sendPresencePacket(final PresencePacket packet) { - this.sendPacket(packet, null); + this.sendPacket(packet); } - private synchronized void sendPacket(final AbstractStanza packet, final PacketReceived callback) { - if (packet.getName().equals("iq") || packet.getName().equals("message") - || packet.getName().equals("presence")) { + private synchronized void sendPacket(final AbstractStanza packet) { + final String name = packet.getName(); + if (name.equals("iq") || name.equals("message") || name.equals("presence")) { ++stanzasSent; - } + } tagWriter.writeStanzaAsync(packet); - if (packet instanceof MessagePacket && packet.getId() != null - && this.streamId != null) { - Log.d(Config.LOGTAG, "request delivery report for stanza " - + stanzasSent); + if (packet instanceof MessagePacket && packet.getId() != null && this.streamId != null) { + Log.d(Config.LOGTAG, "request delivery report for stanza " + stanzasSent); this.messageReceipts.put(stanzasSent, packet.getId()); tagWriter.writeStanzaAsync(new RequestPacket(this.smVersion)); - } - if (callback != null) { - if (packet.getId() == null) { - packet.setId(nextRandomId()); - } - packetCallbacks.put(packet.getId(), callback); } } @@ -895,7 +871,7 @@ public class XmppConnection implements Runnable { if (streamFeatures.hasChild("sm")) { tagWriter.writeStanzaAsync(new RequestPacket(smVersion)); } else { - final IqPacket iq = new IqPacket(IqPacket.TYPE_GET); + final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); iq.setFrom(account.getJid()); iq.addChild("ping", "urn:xmpp:ping"); this.sendIqPacket(iq, null); @@ -1040,19 +1016,22 @@ public class XmppConnection implements Runnable { } public long getLastPacketReceived() { - return this.lastPaketReceived; + return this.lastPacketReceived; } public void sendActive() { - this.sendPacket(new ActivePacket(), null); + this.sendPacket(new ActivePacket()); } public void sendInactive() { - this.sendPacket(new InactivePacket(), null); + this.sendPacket(new InactivePacket()); } public class Features { XmppConnection connection; + private boolean carbonsEnabled = false; + private boolean encryptionEnabled = false; + private boolean blockListRequested = false; public Features(final XmppConnection connection) { this.connection = connection; @@ -1100,9 +1079,8 @@ public class XmppConnection implements Runnable { return connection.streamFeatures != null && connection.streamFeatures.hasChild("ver"); } - public boolean streamhost() { - return connection - .findDiscoItemByFeature("http://jabber.org/protocol/bytestreams") != null; + public void setBlockListRequested(boolean value) { + this.blockListRequested = value; } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleCandidate.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleCandidate.java index 281ea3ca..dcadb92f 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleCandidate.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleCandidate.java @@ -85,8 +85,7 @@ public class JingleCandidate { } public boolean equalValues(JingleCandidate other) { - return other.getHost().equals(this.getHost()) - && (other.getPort() == this.getPort()); + return other != null && other.getHost().equals(this.getHost()) && (other.getPort() == this.getPort()); } public boolean isOurs() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java index d578ca38..2d949e21 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java @@ -36,8 +36,6 @@ public class JingleConnection implements Downloadable { protected static final int JINGLE_STATUS_INITIATED = 0; protected static final int JINGLE_STATUS_ACCEPTED = 1; - protected static final int JINGLE_STATUS_TERMINATED = 2; - protected static final int JINGLE_STATUS_CANCELED = 3; protected static final int JINGLE_STATUS_FINISHED = 4; protected static final int JINGLE_STATUS_TRANSMITTING = 5; protected static final int JINGLE_STATUS_FAILED = 99; @@ -75,7 +73,7 @@ public class JingleConnection implements Downloadable { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE_ERROR) { + if (packet.getType() == IqPacket.TYPE.ERROR) { fail(); } } @@ -101,11 +99,9 @@ public class JingleConnection implements Downloadable { file.delete(); } } - Log.d(Config.LOGTAG, - "sucessfully transmitted file:" + file.getAbsolutePath()); + Log.d(Config.LOGTAG,"sucessfully transmitted file:" + file.getAbsolutePath()); if (message.getEncryption() != Message.ENCRYPTION_PGP) { - Intent intent = new Intent( - Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); intent.setData(Uri.fromFile(file)); mXmppConnectionService.sendBroadcast(intent); } @@ -191,10 +187,10 @@ public class JingleConnection implements Downloadable { } IqPacket response; if (returnResult) { - response = packet.generateResponse(IqPacket.TYPE_RESULT); + response = packet.generateResponse(IqPacket.TYPE.RESULT); } else { - response = packet.generateResponse(IqPacket.TYPE_ERROR); + response = packet.generateResponse(IqPacket.TYPE.ERROR); } account.getXmppConnection().sendIqPacket(response, null); } @@ -280,16 +276,18 @@ public class JingleConnection implements Downloadable { Element fileNameElement = fileOffer.findChild("name"); if (fileNameElement != null) { String[] filename = fileNameElement.getContent() - .toLowerCase(Locale.US).split("\\."); - if (Arrays.asList(VALID_IMAGE_EXTENSIONS).contains( - filename[filename.length - 1])) { + .toLowerCase(Locale.US).toLowerCase().split("\\."); + String extension = filename[filename.length - 1]; + if (Arrays.asList(VALID_IMAGE_EXTENSIONS).contains(extension)) { message.setType(Message.TYPE_IMAGE); + message.setRelativeFilePath(message.getUuid()+"."+extension); } else if (Arrays.asList(VALID_CRYPTO_EXTENSIONS).contains( filename[filename.length - 1])) { if (filename.length == 3) { - if (Arrays.asList(VALID_IMAGE_EXTENSIONS).contains( - filename[filename.length - 2])) { + extension = filename[filename.length - 2]; + if (Arrays.asList(VALID_IMAGE_EXTENSIONS).contains(extension)) { message.setType(Message.TYPE_IMAGE); + message.setRelativeFilePath(message.getUuid()+"."+extension); } else { message.setType(Message.TYPE_FILE); } @@ -360,7 +358,6 @@ public class JingleConnection implements Downloadable { } private void sendInitRequest() { - this.mXmppConnectionService.markMessage(this.message, Message.STATUS_OFFERED); JinglePacket packet = this.bootstrapPacket("session-initiate"); Content content = new Content(this.contentCreator, this.contentName); if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) { @@ -379,8 +376,19 @@ public class JingleConnection implements Downloadable { content.setTransportId(this.transportId); content.socks5transport().setChildren(getCandidatesAsElements()); packet.setContent(content); - this.sendJinglePacket(packet); - this.mJingleStatus = JINGLE_STATUS_INITIATED; + this.sendJinglePacket(packet,new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() != IqPacket.TYPE.ERROR) { + mJingleStatus = JINGLE_STATUS_INITIATED; + mXmppConnectionService.markMessage(message, Message.STATUS_OFFERED); + } else { + fail(); + } + } + }); + } } @@ -396,58 +404,48 @@ public class JingleConnection implements Downloadable { mJingleStatus = JINGLE_STATUS_ACCEPTED; this.mStatus = Downloadable.STATUS_DOWNLOADING; mXmppConnectionService.updateConversationUi(); - this.mJingleConnectionManager.getPrimaryCandidate(this.account, - new OnPrimaryCandidateFound() { - - @Override - public void onPrimaryCandidateFound(boolean success, - final JingleCandidate candidate) { - final JinglePacket packet = bootstrapPacket("session-accept"); - final Content content = new Content(contentCreator, - contentName); - content.setFileOffer(fileOffer); - content.setTransportId(transportId); - if ((success) && (!equalCandidateExists(candidate))) { - final JingleSocks5Transport socksConnection = new JingleSocks5Transport( - JingleConnection.this, candidate); - connections.put(candidate.getCid(), socksConnection); - socksConnection.connect(new OnTransportConnected() { - - @Override - public void failed() { - Log.d(Config.LOGTAG, - "connection to our own primary candidate failed"); - content.socks5transport().setChildren( - getCandidatesAsElements()); - packet.setContent(content); - sendJinglePacket(packet); - connectNextCandidate(); - } + this.mJingleConnectionManager.getPrimaryCandidate(this.account, new OnPrimaryCandidateFound() { + @Override + public void onPrimaryCandidateFound(boolean success, final JingleCandidate candidate) { + final JinglePacket packet = bootstrapPacket("session-accept"); + final Content content = new Content(contentCreator,contentName); + content.setFileOffer(fileOffer); + content.setTransportId(transportId); + if (success && candidate != null && !equalCandidateExists(candidate)) { + final JingleSocks5Transport socksConnection = new JingleSocks5Transport( + JingleConnection.this, + candidate); + connections.put(candidate.getCid(), socksConnection); + socksConnection.connect(new OnTransportConnected() { - @Override - public void established() { - Log.d(Config.LOGTAG, - "connected to primary candidate"); - mergeCandidate(candidate); - content.socks5transport().setChildren( - getCandidatesAsElements()); - packet.setContent(content); - sendJinglePacket(packet); - connectNextCandidate(); - } - }); - } else { - Log.d(Config.LOGTAG, - "did not find a primary candidate for ourself"); - content.socks5transport().setChildren( - getCandidatesAsElements()); + @Override + public void failed() { + Log.d(Config.LOGTAG,"connection to our own primary candidate failed"); + content.socks5transport().setChildren(getCandidatesAsElements()); packet.setContent(content); sendJinglePacket(packet); connectNextCandidate(); } - } - }); + @Override + public void established() { + Log.d(Config.LOGTAG, "connected to primary candidate"); + mergeCandidate(candidate); + content.socks5transport().setChildren(getCandidatesAsElements()); + packet.setContent(content); + sendJinglePacket(packet); + connectNextCandidate(); + } + }); + } else { + Log.d(Config.LOGTAG,"did not find a primary candidate for ourself"); + content.socks5transport().setChildren(getCandidatesAsElements()); + packet.setContent(content); + sendJinglePacket(packet); + connectNextCandidate(); + } + } + }); } private JinglePacket bootstrapPacket(String action) { @@ -461,10 +459,13 @@ public class JingleConnection implements Downloadable { } private void sendJinglePacket(JinglePacket packet) { - // Log.d(Config.LOGTAG,packet.toString()); account.getXmppConnection().sendIqPacket(packet, responseListener); } + private void sendJinglePacket(JinglePacket packet, OnIqPacketReceived callback) { + account.getXmppConnection().sendIqPacket(packet,callback); + } + private boolean receiveAccept(JinglePacket packet) { Content content = packet.getJingleContent(); mergeCandidates(JingleCandidate.parse(content.socks5transport() @@ -479,16 +480,13 @@ public class JingleConnection implements Downloadable { Content content = packet.getJingleContent(); if (content.hasSocks5Transport()) { if (content.socks5transport().hasChild("activated")) { - if ((this.transport != null) - && (this.transport instanceof JingleSocks5Transport)) { + if ((this.transport != null) && (this.transport instanceof JingleSocks5Transport)) { onProxyActivated.success(); } else { - String cid = content.socks5transport() - .findChild("activated").getAttribute("cid"); + String cid = content.socks5transport().findChild("activated").getAttribute("cid"); Log.d(Config.LOGTAG, "received proxy activated (" + cid + ")prior to choosing our own transport"); - JingleSocks5Transport connection = this.connections - .get(cid); + JingleSocks5Transport connection = this.connections.get(cid); if (connection != null) { connection.setActivated(true); } else { @@ -552,7 +550,7 @@ public class JingleConnection implements Downloadable { Log.d(Config.LOGTAG, "candidate " + connection.getCandidate().getCid() + " was our proxy. going to activate"); - IqPacket activation = new IqPacket(IqPacket.TYPE_SET); + IqPacket activation = new IqPacket(IqPacket.TYPE.SET); activation.setTo(connection.getCandidate().getJid()); activation.query("http://jabber.org/protocol/bytestreams") .setAttribute("sid", this.getSessionId()); @@ -564,7 +562,7 @@ public class JingleConnection implements Downloadable { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE_ERROR) { + if (packet.getType() == IqPacket.TYPE.ERROR) { onProxyActivated.failed(); } else { onProxyActivated.success(); @@ -707,8 +705,7 @@ public class JingleConnection implements Downloadable { private void receiveSuccess() { this.mJingleStatus = JINGLE_STATUS_FINISHED; - this.mXmppConnectionService.markMessage(this.message, - Message.STATUS_SEND); + this.mXmppConnectionService.markMessage(this.message,Message.STATUS_SEND_RECEIVED); this.disconnectSocks5Connections(); if (this.transport != null && this.transport instanceof JingleInbandTransport) { this.transport.disconnect(); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index b0a730b1..5dfa3ff4 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -12,6 +12,7 @@ import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.utils.Xmlns; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.jid.InvalidJidException; @@ -46,7 +47,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { return; } } - IqPacket response = packet.generateResponse(IqPacket.TYPE_ERROR); + IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR); Element error = response.addChild("error"); error.setAttribute("type", "cancel"); error.addChild("item-not-found", @@ -80,49 +81,37 @@ public class JingleConnectionManager extends AbstractConnectionManager { return; } if (!this.primaryCandidates.containsKey(account.getJid().toBareJid())) { - String xmlns = "http://jabber.org/protocol/bytestreams"; - final String proxy = account.getXmppConnection() - .findDiscoItemByFeature(xmlns); + final String proxy = account.getXmppConnection().findDiscoItemByFeature(Xmlns.BYTE_STREAMS); if (proxy != null) { - IqPacket iq = new IqPacket(IqPacket.TYPE_GET); + IqPacket iq = new IqPacket(IqPacket.TYPE.GET); iq.setAttribute("to", proxy); - iq.query(xmlns); - account.getXmppConnection().sendIqPacket(iq, - new OnIqPacketReceived() { + iq.query(Xmlns.BYTE_STREAMS); + account.getXmppConnection().sendIqPacket(iq,new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, - IqPacket packet) { - Element streamhost = packet - .query() - .findChild("streamhost", - "http://jabber.org/protocol/bytestreams"); - if (streamhost != null) { - JingleCandidate candidate = new JingleCandidate( - nextRandomId(), true); - candidate.setHost(streamhost - .getAttribute("host")); - candidate.setPort(Integer - .parseInt(streamhost - .getAttribute("port"))); - candidate - .setType(JingleCandidate.TYPE_PROXY); - try { - candidate.setJid(Jid.fromString(proxy)); - } catch (final InvalidJidException e) { - candidate.setJid(null); - } - candidate.setPriority(655360 + 65535); - primaryCandidates.put(account.getJid().toBareJid(), - candidate); - listener.onPrimaryCandidateFound(true, - candidate); - } else { - listener.onPrimaryCandidateFound(false, - null); - } + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + Element streamhost = packet.query().findChild("streamhost",Xmlns.BYTE_STREAMS); + final String host = streamhost == null ? null : streamhost.getAttribute("host"); + final String port = streamhost == null ? null : streamhost.getAttribute("port"); + if (host != null && port != null) { + try { + JingleCandidate candidate = new JingleCandidate(nextRandomId(), true); + candidate.setHost(host); + candidate.setPort(Integer.parseInt(port)); + candidate.setType(JingleCandidate.TYPE_PROXY); + candidate.setJid(Jid.fromString(proxy)); + candidate.setPriority(655360 + 65535); + primaryCandidates.put(account.getJid().toBareJid(),candidate); + listener.onPrimaryCandidateFound(true,candidate); + } catch (final NumberFormatException | InvalidJidException e) { + listener.onPrimaryCandidateFound(false,null); + return; } - }); + } else { + listener.onPrimaryCandidateFound(false,null); + } + } + }); } else { listener.onPrimaryCandidateFound(false, null); } @@ -141,12 +130,10 @@ public class JingleConnectionManager extends AbstractConnectionManager { String sid = null; Element payload = null; if (packet.hasChild("open", "http://jabber.org/protocol/ibb")) { - payload = packet - .findChild("open", "http://jabber.org/protocol/ibb"); + payload = packet.findChild("open", "http://jabber.org/protocol/ibb"); sid = payload.getAttribute("sid"); } else if (packet.hasChild("data", "http://jabber.org/protocol/ibb")) { - payload = packet - .findChild("data", "http://jabber.org/protocol/ibb"); + payload = packet.findChild("data", "http://jabber.org/protocol/ibb"); sid = payload.getAttribute("sid"); } if (sid != null) { @@ -161,10 +148,9 @@ public class JingleConnectionManager extends AbstractConnectionManager { } } } - Log.d(Config.LOGTAG, - "couldnt deliver payload: " + payload.toString()); + Log.d(Config.LOGTAG,"couldn't deliver payload: " + payload.toString()); } else { - Log.d(Config.LOGTAG, "no sid found in incomming ibb packet"); + Log.d(Config.LOGTAG, "no sid found in incoming ibb packet"); } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java index e25f7e65..174f70fa 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java @@ -44,7 +44,7 @@ public class JingleInbandTransport extends JingleTransport { private OnIqPacketReceived onAckReceived = new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (connected && packet.getType() == IqPacket.TYPE_RESULT) { + if (connected && packet.getType() == IqPacket.TYPE.RESULT) { sendNextBlock(); } } @@ -60,7 +60,7 @@ public class JingleInbandTransport extends JingleTransport { } public void connect(final OnTransportConnected callback) { - IqPacket iq = new IqPacket(IqPacket.TYPE_SET); + IqPacket iq = new IqPacket(IqPacket.TYPE.SET); iq.setTo(this.counterpart); Element open = iq.addChild("open", "http://jabber.org/protocol/ibb"); open.setAttribute("sid", this.sessionId); @@ -73,7 +73,7 @@ public class JingleInbandTransport extends JingleTransport { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE_ERROR) { + if (packet.getType() == IqPacket.TYPE.ERROR) { callback.failed(); } else { callback.established(); @@ -157,7 +157,7 @@ public class JingleInbandTransport extends JingleTransport { this.remainingSize -= count; this.digest.update(buffer); String base64 = Base64.encodeToString(buffer, Base64.NO_WRAP); - IqPacket iq = new IqPacket(IqPacket.TYPE_SET); + IqPacket iq = new IqPacket(IqPacket.TYPE.SET); iq.setTo(this.counterpart); Element data = iq.addChild("data", "http://jabber.org/protocol/ibb"); @@ -208,15 +208,15 @@ public class JingleInbandTransport extends JingleTransport { established = true; connected = true; this.account.getXmppConnection().sendIqPacket( - packet.generateResponse(IqPacket.TYPE_RESULT), null); + packet.generateResponse(IqPacket.TYPE.RESULT), null); } else { this.account.getXmppConnection().sendIqPacket( - packet.generateResponse(IqPacket.TYPE_ERROR), null); + packet.generateResponse(IqPacket.TYPE.ERROR), null); } } else if (connected && payload.getName().equals("data")) { this.receiveNextBlock(payload.getContent()); this.account.getXmppConnection().sendIqPacket( - packet.generateResponse(IqPacket.TYPE_RESULT), null); + packet.generateResponse(IqPacket.TYPE.RESULT), null); } else { // TODO some sort of exception } diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java index 1a49b45e..55256ece 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java +++ b/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java @@ -1,11 +1,12 @@ package eu.siacs.conversations.xmpp.stanzas; +import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.jid.Jid; public class AbstractStanza extends Element { - protected AbstractStanza(String name) { + protected AbstractStanza(final String name) { super(name); } @@ -36,4 +37,18 @@ public class AbstractStanza extends Element { public void setId(final String id) { setAttribute("id", id); } + + public boolean fromServer(final Account account) { + return getFrom() == null + || getFrom().equals(account.getServer()) + || getFrom().equals(account.getJid().toBareJid()) + || getFrom().equals(account.getJid()); + } + + public boolean toServer(final Account account) { + return getTo() == null + || getTo().equals(account.getServer()) + || getTo().equals(account.getJid().toBareJid()) + || getTo().equals(account.getJid()); + } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java index 2481112b..7b36fc49 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java +++ b/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java @@ -4,32 +4,18 @@ import eu.siacs.conversations.xml.Element; public class IqPacket extends AbstractStanza { - public static final int TYPE_ERROR = -1; - public static final int TYPE_SET = 0; - public static final int TYPE_RESULT = 1; - public static final int TYPE_GET = 2; - - private IqPacket(final String name) { - super(name); + public static enum TYPE { + ERROR, + SET, + RESULT, + GET, + INVALID } - public IqPacket(final int type) { + public IqPacket(final TYPE type) { super("iq"); - switch (type) { - case TYPE_SET: - this.setAttribute("type", "set"); - break; - case TYPE_GET: - this.setAttribute("type", "get"); - break; - case TYPE_RESULT: - this.setAttribute("type", "result"); - break; - case TYPE_ERROR: - this.setAttribute("type", "error"); - break; - default: - break; + if (type != TYPE.INVALID) { + this.setAttribute("type", type.toString().toLowerCase()); } } @@ -51,23 +37,23 @@ public class IqPacket extends AbstractStanza { return query(); } - public int getType() { + public TYPE getType() { final String type = getAttribute("type"); switch (type) { case "error": - return TYPE_ERROR; + return TYPE.ERROR; case "result": - return TYPE_RESULT; + return TYPE.RESULT; case "set": - return TYPE_SET; + return TYPE.SET; case "get": - return TYPE_GET; + return TYPE.GET; default: - return 1000; + return TYPE.INVALID; } } - public IqPacket generateResponse(final int type) { + public IqPacket generateResponse(final TYPE type) { final IqPacket packet = new IqPacket(type); packet.setTo(this.getFrom()); packet.setId(this.getId()); |