aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/eu/siacs/conversations/entities
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/eu/siacs/conversations/entities')
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Account.java3
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Contact.java32
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Conversation.java30
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Message.java8
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Presence.java76
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Presences.java46
-rw-r--r--src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java265
7 files changed, 410 insertions, 50 deletions
diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java
index 69d7e94a..1931988d 100644
--- a/src/main/java/eu/siacs/conversations/entities/Account.java
+++ b/src/main/java/eu/siacs/conversations/entities/Account.java
@@ -3,6 +3,7 @@ package eu.siacs.conversations.entities;
import android.content.ContentValues;
import android.database.Cursor;
import android.os.SystemClock;
+import android.util.Pair;
import net.java.otr4j.crypto.OtrCryptoEngineImpl;
import net.java.otr4j.crypto.OtrCryptoException;
@@ -13,6 +14,7 @@ import org.json.JSONObject;
import java.security.PublicKey;
import java.security.interfaces.DSAPublicKey;
import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
@@ -48,6 +50,7 @@ public class Account extends AbstractEntity {
public static final int OPTION_DISABLED = 1;
public static final int OPTION_REGISTER = 2;
public static final int OPTION_USECOMPRESSION = 3;
+ public final HashSet<Pair<String, String>> inProgressDiscoFetches = new HashSet<>();
public boolean httpUploadAvailable() {
return xmppConnection != null && xmppConnection.getFeatures().httpUpload();
diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java
index 6d252376..be6325e5 100644
--- a/src/main/java/eu/siacs/conversations/entities/Contact.java
+++ b/src/main/java/eu/siacs/conversations/entities/Contact.java
@@ -39,6 +39,7 @@ public class Contact implements ListItem, Blockable {
protected String systemName;
protected String serverName;
protected String presenceName;
+ protected String commonName;
protected Jid jid;
protected int subscription = 0;
protected String systemAccount;
@@ -106,8 +107,8 @@ public class Contact implements ListItem, Blockable {
}
public String getDisplayName() {
- if (this.presenceName != null && Config.X509_VERIFICATION) {
- return this.presenceName;
+ if (this.commonName != null && Config.X509_VERIFICATION) {
+ return this.commonName;
} else if (this.systemName != null) {
return this.systemName;
} else if (this.serverName != null) {
@@ -136,17 +137,17 @@ public class Contact implements ListItem, Blockable {
tags.add(new Tag(group, UIHelper.getColorForName(group)));
}
switch (getMostAvailableStatus()) {
- case Presences.CHAT:
- case Presences.ONLINE:
+ case CHAT:
+ case ONLINE:
tags.add(new Tag("online", 0xff259b24));
break;
- case Presences.AWAY:
+ case AWAY:
tags.add(new Tag("away", 0xffff9800));
break;
- case Presences.XA:
+ case XA:
tags.add(new Tag("not available", 0xfff44336));
break;
- case Presences.DND:
+ case DND:
tags.add(new Tag("dnd", 0xfff44336));
break;
}
@@ -231,8 +232,8 @@ public class Contact implements ListItem, Blockable {
this.presences = pres;
}
- public void updatePresence(final String resource, final int status) {
- this.presences.updatePresence(resource, status);
+ public void updatePresence(final String resource, final Presence presence) {
+ this.presences.updatePresence(resource, presence);
}
public void removePresence(final String resource) {
@@ -244,8 +245,13 @@ public class Contact implements ListItem, Blockable {
this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
}
- public int getMostAvailableStatus() {
- return this.presences.getMostAvailableStatus();
+ public Presence.Status getMostAvailableStatus() {
+ Presence p = this.presences.getMostAvailablePresence();
+ if (p == null) {
+ return Presence.Status.OFFLINE;
+ }
+
+ return p.getStatus();
}
public boolean setPhotoUri(String uri) {
@@ -516,6 +522,10 @@ public class Contact implements ListItem, Blockable {
return account.getJid().toBareJid().equals(getJid().toBareJid());
}
+ public void setCommonName(String cn) {
+ this.commonName = cn;
+ }
+
public static class Lastseen {
public long time;
public String presence;
diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java
index 704bfcb1..ba1244d1 100644
--- a/src/main/java/eu/siacs/conversations/entities/Conversation.java
+++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java
@@ -83,6 +83,7 @@ public class Conversation extends AbstractEntity implements Blockable {
private ChatState mOutgoingChatState = Config.DEFAULT_CHATSTATE;
private ChatState mIncomingChatState = Config.DEFAULT_CHATSTATE;
private String mLastReceivedOtrMessageId = null;
+ private String mFirstMamReference = null;
public boolean hasMessagesLeftOnServer() {
return messagesLeftOnServer;
@@ -279,6 +280,22 @@ public class Conversation extends AbstractEntity implements Blockable {
}
}
+ public void setFirstMamReference(String reference) {
+ this.mFirstMamReference = reference;
+ }
+
+ public String getFirstMamReference() {
+ return this.mFirstMamReference;
+ }
+
+ public void setLastClearHistory(long time) {
+ setAttribute("last_clear_history",String.valueOf(time));
+ }
+
+ public long getLastClearHistory() {
+ return getLongAttribute("last_clear_history", 0);
+ }
+
public interface OnMessageFound {
void onMessageFound(final Message message);
}
@@ -713,6 +730,10 @@ public class Conversation extends AbstractEntity implements Blockable {
}
public long getLastMessageTransmitted() {
+ long last_clear = getLastClearHistory();
+ if (last_clear != 0) {
+ return last_clear;
+ }
synchronized (this.messages) {
for(int i = this.messages.size() - 1; i >= 0; --i) {
Message message = this.messages.get(i);
@@ -733,7 +754,7 @@ public class Conversation extends AbstractEntity implements Blockable {
}
public boolean alwaysNotify() {
- return mode == MODE_SINGLE || getBooleanAttribute(ATTRIBUTE_ALWAYS_NOTIFY,Config.ALWAYS_NOTIFY_BY_DEFAULT || isPnNA());
+ return mode == MODE_SINGLE || getBooleanAttribute(ATTRIBUTE_ALWAYS_NOTIFY, Config.ALWAYS_NOTIFY_BY_DEFAULT || isPnNA());
}
public boolean setAttribute(String key, String value) {
@@ -795,6 +816,13 @@ public class Conversation extends AbstractEntity implements Blockable {
}
}
+ public void prepend(Message message) {
+ message.setConversation(this);
+ synchronized (this.messages) {
+ this.messages.add(0,message);
+ }
+ }
+
public void addAll(int index, List<Message> messages) {
synchronized (this.messages) {
this.messages.addAll(index, messages);
diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java
index 92eaa706..f800def1 100644
--- a/src/main/java/eu/siacs/conversations/entities/Message.java
+++ b/src/main/java/eu/siacs/conversations/entities/Message.java
@@ -177,6 +177,14 @@ public class Message extends AbstractEntity {
return message;
}
+ public static Message createLoadMoreMessage(Conversation conversation) {
+ final Message message = new Message();
+ message.setType(Message.TYPE_STATUS);
+ message.setConversation(conversation);
+ message.setBody("LOAD_MORE");
+ return message;
+ }
+
@Override
public ContentValues getContentValues() {
ContentValues values = new ContentValues();
diff --git a/src/main/java/eu/siacs/conversations/entities/Presence.java b/src/main/java/eu/siacs/conversations/entities/Presence.java
new file mode 100644
index 00000000..69cde832
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/entities/Presence.java
@@ -0,0 +1,76 @@
+package eu.siacs.conversations.entities;
+
+import java.lang.Comparable;
+
+import eu.siacs.conversations.xml.Element;
+
+public class Presence implements Comparable {
+
+ public enum Status {
+ CHAT, ONLINE, AWAY, XA, DND, OFFLINE;
+
+ public String toShowString() {
+ switch(this) {
+ case CHAT: return "chat";
+ case AWAY: return "away";
+ case XA: return "xa";
+ case DND: return "dnd";
+ }
+
+ return null;
+ }
+ }
+
+ protected final Status status;
+ protected ServiceDiscoveryResult disco;
+ protected final String ver;
+ protected final String hash;
+
+ private Presence(Status status, String ver, String hash) {
+ this.status = status;
+ this.ver = ver;
+ this.hash = hash;
+ }
+
+ public static Presence parse(Element show, Element caps) {
+ final String hash = caps == null ? null : caps.getAttribute("hash");
+ final String ver = caps == null ? null : caps.getAttribute("ver");
+ if ((show == null) || (show.getContent() == null)) {
+ return new Presence(Status.ONLINE, ver, hash);
+ } else if (show.getContent().equals("away")) {
+ return new Presence(Status.AWAY, ver, hash);
+ } else if (show.getContent().equals("xa")) {
+ return new Presence(Status.XA, ver, hash);
+ } else if (show.getContent().equals("chat")) {
+ return new Presence(Status.CHAT, ver, hash);
+ } else if (show.getContent().equals("dnd")) {
+ return new Presence(Status.DND, ver, hash);
+ } else {
+ return new Presence(Status.OFFLINE, ver, hash);
+ }
+ }
+
+ public int compareTo(Object other) {
+ return this.status.compareTo(((Presence)other).status);
+ }
+
+ public Status getStatus() {
+ return this.status;
+ }
+
+ public boolean hasCaps() {
+ return ver != null && hash != null;
+ }
+
+ public String getVer() {
+ return this.ver;
+ }
+
+ public String getHash() {
+ return this.hash;
+ }
+
+ public void setServiceDiscoveryResult(ServiceDiscoveryResult disco) {
+ this.disco = disco;
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/entities/Presences.java b/src/main/java/eu/siacs/conversations/entities/Presences.java
index 4729a11b..813eda7a 100644
--- a/src/main/java/eu/siacs/conversations/entities/Presences.java
+++ b/src/main/java/eu/siacs/conversations/entities/Presences.java
@@ -1,29 +1,21 @@
package eu.siacs.conversations.entities;
+import java.util.Collections;
import java.util.Hashtable;
import java.util.Iterator;
-import java.util.Map.Entry;
import eu.siacs.conversations.xml.Element;
public class Presences {
+ private final Hashtable<String, Presence> presences = new Hashtable<>();
- public static final int CHAT = -1;
- public static final int ONLINE = 0;
- public static final int AWAY = 1;
- public static final int XA = 2;
- public static final int DND = 3;
- public static final int OFFLINE = 4;
-
- private final Hashtable<String, Integer> presences = new Hashtable<>();
-
- public Hashtable<String, Integer> getPresences() {
+ public Hashtable<String, Presence> getPresences() {
return this.presences;
}
- public void updatePresence(String resource, int status) {
+ public void updatePresence(String resource, Presence presence) {
synchronized (this.presences) {
- this.presences.put(resource, status);
+ this.presences.put(resource, presence);
}
}
@@ -39,32 +31,10 @@ public class Presences {
}
}
- public int getMostAvailableStatus() {
- int status = OFFLINE;
+ public Presence getMostAvailablePresence() {
synchronized (this.presences) {
- Iterator<Entry<String, Integer>> it = presences.entrySet().iterator();
- while (it.hasNext()) {
- Entry<String, Integer> entry = it.next();
- if (entry.getValue() < status)
- status = entry.getValue();
- }
- }
- return status;
- }
-
- public static int parseShow(Element show) {
- if ((show == null) || (show.getContent() == null)) {
- return Presences.ONLINE;
- } else if (show.getContent().equals("away")) {
- return Presences.AWAY;
- } else if (show.getContent().equals("xa")) {
- return Presences.XA;
- } else if (show.getContent().equals("chat")) {
- return Presences.CHAT;
- } else if (show.getContent().equals("dnd")) {
- return Presences.DND;
- } else {
- return Presences.OFFLINE;
+ if (presences.size() < 1) { return null; }
+ return Collections.min(presences.values());
}
}
diff --git a/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java b/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java
new file mode 100644
index 00000000..42f2d840
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java
@@ -0,0 +1,265 @@
+package eu.siacs.conversations.entities;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.util.Base64;
+import java.io.UnsupportedEncodingException;
+import java.lang.Comparable;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.forms.Data;
+import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+
+public class ServiceDiscoveryResult {
+ public static final String TABLENAME = "discovery_results";
+ public static final String HASH = "hash";
+ public static final String VER = "ver";
+ public static final String RESULT = "result";
+
+ protected static String blankNull(String s) {
+ return s == null ? "" : s;
+ }
+
+ public static class Identity implements Comparable {
+ protected final String category;
+ protected final String type;
+ protected final String lang;
+ protected final String name;
+
+ public Identity(final String category, final String type, final String lang, final String name) {
+ this.category = category;
+ this.type = type;
+ this.lang = lang;
+ this.name = name;
+ }
+
+ public Identity(final Element el) {
+ this(
+ el.getAttribute("category"),
+ el.getAttribute("type"),
+ el.getAttribute("xml:lang"),
+ el.getAttribute("name")
+ );
+ }
+
+ public Identity(final JSONObject o) {
+ this(
+ o.optString("category", null),
+ o.optString("type", null),
+ o.optString("lang", null),
+ o.optString("name", null)
+ );
+ }
+
+ public String getCategory() {
+ return this.category;
+ }
+
+ public String getType() {
+ return this.type;
+ }
+
+ public String getLang() {
+ return this.lang;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public int compareTo(Object other) {
+ Identity o = (Identity)other;
+ int r = blankNull(this.getCategory()).compareTo(blankNull(o.getCategory()));
+ if(r == 0) {
+ r = blankNull(this.getType()).compareTo(blankNull(o.getType()));
+ }
+ if(r == 0) {
+ r = blankNull(this.getLang()).compareTo(blankNull(o.getLang()));
+ }
+ if(r == 0) {
+ r = blankNull(this.getName()).compareTo(blankNull(o.getName()));
+ }
+
+ return r;
+ }
+
+ public JSONObject toJSON() {
+ try {
+ JSONObject o = new JSONObject();
+ o.put("category", this.getCategory());
+ o.put("type", this.getType());
+ o.put("lang", this.getLang());
+ o.put("name", this.getName());
+ return o;
+ } catch(JSONException e) {
+ return null;
+ }
+ }
+ }
+
+ protected final String hash;
+ protected final byte[] ver;
+ protected final List<Identity> identities;
+ protected final List<String> features;
+ protected final List<Data> forms;
+
+ public ServiceDiscoveryResult(final IqPacket packet) {
+ this.identities = new ArrayList<>();
+ this.features = new ArrayList<>();
+ this.forms = new ArrayList<>();
+ this.hash = "sha-1"; // We only support sha-1 for now
+
+ final List<Element> elements = packet.query().getChildren();
+
+ for (final Element element : elements) {
+ if (element.getName().equals("identity")) {
+ Identity id = new Identity(element);
+ if (id.getType() != null && id.getCategory() != null) {
+ identities.add(id);
+ }
+ } else if (element.getName().equals("feature")) {
+ if (element.getAttribute("var") != null) {
+ features.add(element.getAttribute("var"));
+ }
+ } else if (element.getName().equals("x") && "jabber:x:data".equals(element.getAttribute("xmlns"))) {
+ forms.add(Data.parse(element));
+ }
+ }
+ this.ver = this.mkCapHash();
+ }
+
+ public ServiceDiscoveryResult(String hash, byte[] ver, JSONObject o) throws JSONException {
+ this.identities = new ArrayList<>();
+ this.features = new ArrayList<>();
+ this.forms = new ArrayList<>();
+ this.hash = hash;
+ this.ver = ver;
+
+ JSONArray identities = o.optJSONArray("identities");
+ if (identities != null) {
+ for (int i = 0; i < identities.length(); i++) {
+ this.identities.add(new Identity(identities.getJSONObject(i)));
+ }
+ }
+ JSONArray features = o.optJSONArray("features");
+ if (features != null) {
+ for (int i = 0; i < features.length(); i++) {
+ this.features.add(features.getString(i));
+ }
+ }
+ }
+
+ public String getVer() {
+ return new String(Base64.encode(this.ver, Base64.DEFAULT)).trim();
+ }
+
+ public ServiceDiscoveryResult(Cursor cursor) throws JSONException {
+ this(
+ cursor.getString(cursor.getColumnIndex(HASH)),
+ Base64.decode(cursor.getString(cursor.getColumnIndex(VER)), Base64.DEFAULT),
+ new JSONObject(cursor.getString(cursor.getColumnIndex(RESULT)))
+ );
+ }
+
+ public List<Identity> getIdentities() {
+ return this.identities;
+ }
+
+ public List<String> getFeatures() {
+ return this.features;
+ }
+
+ public boolean hasIdentity(String category, String type) {
+ for(Identity id : this.getIdentities()) {
+ if((category == null || id.getCategory().equals(category)) &&
+ (type == null || id.getType().equals(type))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected byte[] mkCapHash() {
+ StringBuilder s = new StringBuilder();
+
+ List<Identity> identities = this.getIdentities();
+ Collections.sort(identities);
+
+ for(Identity id : identities) {
+ s.append(
+ blankNull(id.getCategory()) + "/" +
+ blankNull(id.getType()) + "/" +
+ blankNull(id.getLang()) + "/" +
+ blankNull(id.getName()) + "<"
+ );
+ }
+
+ List<String> features = this.getFeatures();
+ Collections.sort(features);
+
+ for (String feature : features) {
+ s.append(feature + "<");
+ }
+
+ Collections.sort(forms, new Comparator<Data>() {
+ @Override
+ public int compare(Data lhs, Data rhs) {
+ return lhs.getFormType().compareTo(rhs.getFormType());
+ }
+ });
+
+ for(Data form : forms) {
+ s.append(form.getFormType()+"<");
+ //TODO append fields and values
+ }
+
+ MessageDigest md;
+ try {
+ md = MessageDigest.getInstance("SHA-1");
+ } catch (NoSuchAlgorithmException e) {
+ return null;
+ }
+
+ try {
+ return md.digest(s.toString().getBytes("UTF-8"));
+ } catch(UnsupportedEncodingException e) {
+ return null;
+ }
+ }
+
+ public JSONObject toJSON() {
+ try {
+ JSONObject o = new JSONObject();
+
+ JSONArray ids = new JSONArray();
+ for(Identity id : this.getIdentities()) {
+ ids.put(id.toJSON());
+ }
+ o.put("identites", ids);
+
+ o.put("features", new JSONArray(this.getFeatures()));
+
+ return o;
+ } catch(JSONException e) {
+ return null;
+ }
+ }
+
+ public ContentValues getContentValues() {
+ final ContentValues values = new ContentValues();
+ values.put(HASH, this.hash);
+ values.put(VER, getVer());
+ values.put(RESULT, this.toJSON().toString());
+ return values;
+ }
+}