From 281ce3105fad5f012471cf3e5062a4032dfbc7a6 Mon Sep 17 00:00:00 2001 From: Sam Whited Date: Wed, 22 Oct 2014 15:44:55 -0400 Subject: Make conversations the root project --- .../conversations/entities/AbstractEntity.java | 21 + .../eu/siacs/conversations/entities/Account.java | 399 ++++++++++++++++ .../eu/siacs/conversations/entities/Bookmark.java | 137 ++++++ .../eu/siacs/conversations/entities/Contact.java | 367 +++++++++++++++ .../siacs/conversations/entities/Conversation.java | 500 +++++++++++++++++++++ .../siacs/conversations/entities/Downloadable.java | 21 + .../conversations/entities/DownloadableFile.java | 154 +++++++ .../eu/siacs/conversations/entities/ListItem.java | 7 + .../eu/siacs/conversations/entities/Message.java | 478 ++++++++++++++++++++ .../siacs/conversations/entities/MucOptions.java | 369 +++++++++++++++ .../eu/siacs/conversations/entities/Presences.java | 76 ++++ .../eu/siacs/conversations/entities/Roster.java | 83 ++++ 12 files changed, 2612 insertions(+) create mode 100644 src/main/java/eu/siacs/conversations/entities/AbstractEntity.java create mode 100644 src/main/java/eu/siacs/conversations/entities/Account.java create mode 100644 src/main/java/eu/siacs/conversations/entities/Bookmark.java create mode 100644 src/main/java/eu/siacs/conversations/entities/Contact.java create mode 100644 src/main/java/eu/siacs/conversations/entities/Conversation.java create mode 100644 src/main/java/eu/siacs/conversations/entities/Downloadable.java create mode 100644 src/main/java/eu/siacs/conversations/entities/DownloadableFile.java create mode 100644 src/main/java/eu/siacs/conversations/entities/ListItem.java create mode 100644 src/main/java/eu/siacs/conversations/entities/Message.java create mode 100644 src/main/java/eu/siacs/conversations/entities/MucOptions.java create mode 100644 src/main/java/eu/siacs/conversations/entities/Presences.java create mode 100644 src/main/java/eu/siacs/conversations/entities/Roster.java (limited to 'src/main/java/eu/siacs/conversations/entities') diff --git a/src/main/java/eu/siacs/conversations/entities/AbstractEntity.java b/src/main/java/eu/siacs/conversations/entities/AbstractEntity.java new file mode 100644 index 00000000..92b8a729 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/entities/AbstractEntity.java @@ -0,0 +1,21 @@ +package eu.siacs.conversations.entities; + +import android.content.ContentValues; + +public abstract class AbstractEntity { + + public static final String UUID = "uuid"; + + protected String uuid; + + public String getUuid() { + return this.uuid; + } + + public abstract ContentValues getContentValues(); + + public boolean equals(AbstractEntity entity) { + return this.getUuid().equals(entity.getUuid()); + } + +} diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java new file mode 100644 index 00000000..80a9d62f --- /dev/null +++ b/src/main/java/eu/siacs/conversations/entities/Account.java @@ -0,0 +1,399 @@ +package eu.siacs.conversations.entities; + +import java.security.interfaces.DSAPublicKey; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.CopyOnWriteArrayList; + +import net.java.otr4j.crypto.OtrCryptoEngineImpl; +import net.java.otr4j.crypto.OtrCryptoException; + +import org.json.JSONException; +import org.json.JSONObject; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.crypto.OtrEngine; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.xmpp.XmppConnection; +import android.content.ContentValues; +import android.database.Cursor; +import android.os.SystemClock; + +public class Account extends AbstractEntity { + + public static final String TABLENAME = "accounts"; + + public static final String USERNAME = "username"; + public static final String SERVER = "server"; + public static final String PASSWORD = "password"; + public static final String OPTIONS = "options"; + public static final String ROSTERVERSION = "rosterversion"; + public static final String KEYS = "keys"; + public static final String AVATAR = "avatar"; + + public static final int OPTION_USETLS = 0; + public static final int OPTION_DISABLED = 1; + public static final int OPTION_REGISTER = 2; + public static final int OPTION_USECOMPRESSION = 3; + + public static final int STATUS_CONNECTING = 0; + public static final int STATUS_DISABLED = -2; + public static final int STATUS_OFFLINE = -1; + public static final int STATUS_ONLINE = 1; + public static final int STATUS_NO_INTERNET = 2; + public static final int STATUS_UNAUTHORIZED = 3; + public static final int STATUS_SERVER_NOT_FOUND = 5; + + public static final int STATUS_REGISTRATION_FAILED = 7; + public static final int STATUS_REGISTRATION_CONFLICT = 8; + public static final int STATUS_REGISTRATION_SUCCESSFULL = 9; + public static final int STATUS_REGISTRATION_NOT_SUPPORTED = 10; + + protected String username; + protected String server; + protected String password; + protected int options = 0; + protected String rosterVersion; + protected String resource = "mobile"; + protected int status = -1; + protected JSONObject keys = new JSONObject(); + protected String avatar; + + protected boolean online = false; + + private OtrEngine otrEngine = null; + private XmppConnection xmppConnection = null; + private Presences presences = new Presences(); + private long mEndGracePeriod = 0L; + private String otrFingerprint; + private Roster roster = null; + + private List bookmarks = new CopyOnWriteArrayList(); + public List pendingConferenceJoins = new CopyOnWriteArrayList(); + public List pendingConferenceLeaves = new CopyOnWriteArrayList(); + + public Account() { + this.uuid = "0"; + } + + public Account(String username, String server, String password) { + this(java.util.UUID.randomUUID().toString(), username, server, + password, 0, null, "", null); + } + + public Account(String uuid, String username, String server, + String password, int options, String rosterVersion, String keys, + String avatar) { + this.uuid = uuid; + this.username = username; + this.server = server; + this.password = password; + this.options = options; + this.rosterVersion = rosterVersion; + try { + this.keys = new JSONObject(keys); + } catch (JSONException e) { + + } + this.avatar = avatar; + } + + public boolean isOptionSet(int option) { + return ((options & (1 << option)) != 0); + } + + public void setOption(int option, boolean value) { + if (value) { + this.options |= 1 << option; + } else { + this.options &= ~(1 << option); + } + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getServer() { + return server; + } + + public void setServer(String server) { + this.server = server; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setStatus(int status) { + this.status = status; + } + + public int getStatus() { + if (isOptionSet(OPTION_DISABLED)) { + return STATUS_DISABLED; + } else { + return this.status; + } + } + + public boolean errorStatus() { + int s = getStatus(); + return (s == STATUS_REGISTRATION_FAILED + || s == STATUS_REGISTRATION_CONFLICT + || s == STATUS_REGISTRATION_NOT_SUPPORTED + || s == STATUS_SERVER_NOT_FOUND || s == STATUS_UNAUTHORIZED); + } + + public boolean hasErrorStatus() { + if (getXmppConnection() == null) { + return false; + } else { + return getStatus() > STATUS_NO_INTERNET + && (getXmppConnection().getAttempt() >= 2); + } + } + + public void setResource(String resource) { + this.resource = resource; + } + + public String getResource() { + return this.resource; + } + + public String getJid() { + return username.toLowerCase(Locale.getDefault()) + "@" + + server.toLowerCase(Locale.getDefault()); + } + + public JSONObject getKeys() { + return keys; + } + + public String getSSLFingerprint() { + if (keys.has("ssl_cert")) { + try { + return keys.getString("ssl_cert"); + } catch (JSONException e) { + return null; + } + } else { + return null; + } + } + + public void setSSLCertFingerprint(String fingerprint) { + this.setKey("ssl_cert", fingerprint); + } + + public boolean setKey(String keyName, String keyValue) { + try { + this.keys.put(keyName, keyValue); + return true; + } catch (JSONException e) { + return false; + } + } + + @Override + public ContentValues getContentValues() { + ContentValues values = new ContentValues(); + values.put(UUID, uuid); + values.put(USERNAME, username); + values.put(SERVER, server); + values.put(PASSWORD, password); + values.put(OPTIONS, options); + values.put(KEYS, this.keys.toString()); + values.put(ROSTERVERSION, rosterVersion); + values.put(AVATAR, avatar); + return values; + } + + public static Account fromCursor(Cursor cursor) { + return new Account(cursor.getString(cursor.getColumnIndex(UUID)), + cursor.getString(cursor.getColumnIndex(USERNAME)), + cursor.getString(cursor.getColumnIndex(SERVER)), + cursor.getString(cursor.getColumnIndex(PASSWORD)), + cursor.getInt(cursor.getColumnIndex(OPTIONS)), + cursor.getString(cursor.getColumnIndex(ROSTERVERSION)), + cursor.getString(cursor.getColumnIndex(KEYS)), + cursor.getString(cursor.getColumnIndex(AVATAR))); + } + + public OtrEngine getOtrEngine(XmppConnectionService context) { + if (otrEngine == null) { + otrEngine = new OtrEngine(context, this); + } + return this.otrEngine; + } + + public XmppConnection getXmppConnection() { + return this.xmppConnection; + } + + public void setXmppConnection(XmppConnection connection) { + this.xmppConnection = connection; + } + + public String getFullJid() { + return this.getJid() + "/" + this.resource; + } + + public String getOtrFingerprint() { + if (this.otrFingerprint == null) { + try { + DSAPublicKey pubkey = (DSAPublicKey) this.otrEngine + .getPublicKey(); + if (pubkey == null) { + return null; + } + StringBuilder builder = new StringBuilder( + new OtrCryptoEngineImpl().getFingerprint(pubkey)); + builder.insert(8, " "); + builder.insert(17, " "); + builder.insert(26, " "); + builder.insert(35, " "); + this.otrFingerprint = builder.toString(); + } catch (OtrCryptoException e) { + + } + } + return this.otrFingerprint; + } + + public String getRosterVersion() { + if (this.rosterVersion == null) { + return ""; + } else { + return this.rosterVersion; + } + } + + public void setRosterVersion(String version) { + this.rosterVersion = version; + } + + public String getOtrFingerprint(XmppConnectionService service) { + this.getOtrEngine(service); + return this.getOtrFingerprint(); + } + + public void updatePresence(String resource, int status) { + this.presences.updatePresence(resource, status); + } + + public void removePresence(String resource) { + this.presences.removePresence(resource); + } + + public void clearPresences() { + this.presences = new Presences(); + } + + public int countPresences() { + return this.presences.size(); + } + + public String getPgpSignature() { + if (keys.has("pgp_signature")) { + try { + return keys.getString("pgp_signature"); + } catch (JSONException e) { + return null; + } + } else { + return null; + } + } + + public Roster getRoster() { + if (this.roster == null) { + this.roster = new Roster(this); + } + return this.roster; + } + + public void setBookmarks(List bookmarks) { + this.bookmarks = bookmarks; + } + + public List getBookmarks() { + return this.bookmarks; + } + + public boolean hasBookmarkFor(String conferenceJid) { + for (Bookmark bmark : this.bookmarks) { + if (bmark.getJid().equals(conferenceJid)) { + return true; + } + } + return false; + } + + public boolean setAvatar(String filename) { + if (this.avatar != null && this.avatar.equals(filename)) { + return false; + } else { + this.avatar = filename; + return true; + } + } + + public String getAvatar() { + return this.avatar; + } + + public int getReadableStatusId() { + switch (getStatus()) { + + case Account.STATUS_DISABLED: + return R.string.account_status_disabled; + case Account.STATUS_ONLINE: + return R.string.account_status_online; + case Account.STATUS_CONNECTING: + return R.string.account_status_connecting; + case Account.STATUS_OFFLINE: + return R.string.account_status_offline; + case Account.STATUS_UNAUTHORIZED: + return R.string.account_status_unauthorized; + case Account.STATUS_SERVER_NOT_FOUND: + return R.string.account_status_not_found; + case Account.STATUS_NO_INTERNET: + return R.string.account_status_no_internet; + case Account.STATUS_REGISTRATION_FAILED: + return R.string.account_status_regis_fail; + case Account.STATUS_REGISTRATION_CONFLICT: + return R.string.account_status_regis_conflict; + case Account.STATUS_REGISTRATION_SUCCESSFULL: + return R.string.account_status_regis_success; + case Account.STATUS_REGISTRATION_NOT_SUPPORTED: + return R.string.account_status_regis_not_sup; + default: + return R.string.account_status_unknown; + } + } + + public void activateGracePeriod() { + this.mEndGracePeriod = SystemClock.elapsedRealtime() + + (Config.CARBON_GRACE_PERIOD * 1000); + } + + public void deactivateGracePeriod() { + this.mEndGracePeriod = 0L; + } + + public boolean inGracePeriod() { + return SystemClock.elapsedRealtime() < this.mEndGracePeriod; + } +} diff --git a/src/main/java/eu/siacs/conversations/entities/Bookmark.java b/src/main/java/eu/siacs/conversations/entities/Bookmark.java new file mode 100644 index 00000000..dd9e805c --- /dev/null +++ b/src/main/java/eu/siacs/conversations/entities/Bookmark.java @@ -0,0 +1,137 @@ +package eu.siacs.conversations.entities; + +import java.util.Locale; + +import eu.siacs.conversations.xml.Element; + +public class Bookmark extends Element implements ListItem { + + private Account account; + private Conversation mJoinedConversation; + + public Bookmark(Account account, String jid) { + super("conference"); + this.setAttribute("jid", jid); + this.account = account; + } + + private Bookmark(Account account) { + super("conference"); + this.account = account; + } + + public static Bookmark parse(Element element, Account account) { + Bookmark bookmark = new Bookmark(account); + bookmark.setAttributes(element.getAttributes()); + bookmark.setChildren(element.getChildren()); + return bookmark; + } + + public void setAutojoin(boolean autojoin) { + if (autojoin) { + this.setAttribute("autojoin", "true"); + } else { + this.setAttribute("autojoin", "false"); + } + } + + public void setName(String name) { + this.name = name; + } + + public void setNick(String nick) { + Element element = this.findChild("nick"); + if (element == null) { + element = this.addChild("nick"); + } + element.setContent(nick); + } + + public void setPassword(String password) { + Element element = this.findChild("password"); + if (element != null) { + element.setContent(password); + } + } + + @Override + public int compareTo(ListItem another) { + return this.getDisplayName().compareToIgnoreCase( + another.getDisplayName()); + } + + @Override + public String getDisplayName() { + if (this.mJoinedConversation != null + && (this.mJoinedConversation.getMucOptions().getSubject() != null)) { + return this.mJoinedConversation.getMucOptions().getSubject(); + } else if (getName() != null) { + return getName(); + } else { + return this.getJid().split("@")[0]; + } + } + + @Override + public String getJid() { + String jid = this.getAttribute("jid"); + if (jid != null) { + return jid.toLowerCase(Locale.US); + } else { + return null; + } + } + + public String getNick() { + Element nick = this.findChild("nick"); + if (nick != null) { + return nick.getContent(); + } else { + return null; + } + } + + public boolean autojoin() { + String autojoin = this.getAttribute("autojoin"); + return (autojoin != null && (autojoin.equalsIgnoreCase("true") || autojoin + .equalsIgnoreCase("1"))); + } + + public String getPassword() { + Element password = this.findChild("password"); + if (password != null) { + return password.getContent(); + } else { + return null; + } + } + + public boolean match(String needle) { + return needle == null + || getJid().contains(needle.toLowerCase(Locale.US)) + || getDisplayName().toLowerCase(Locale.US).contains( + needle.toLowerCase(Locale.US)); + } + + public Account getAccount() { + return this.account; + } + + public void setConversation(Conversation conversation) { + this.mJoinedConversation = conversation; + } + + public Conversation getConversation() { + return this.mJoinedConversation; + } + + public String getName() { + return this.getAttribute("name"); + } + + public void unregisterConversation() { + if (this.mJoinedConversation != null) { + this.mJoinedConversation.deregisterWithBookmark(); + } + } +} diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java new file mode 100644 index 00000000..60c31a42 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/entities/Contact.java @@ -0,0 +1,367 @@ +package eu.siacs.conversations.entities; + +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import eu.siacs.conversations.xml.Element; +import android.content.ContentValues; +import android.database.Cursor; + +public class Contact implements ListItem { + public static final String TABLENAME = "contacts"; + + public static final String SYSTEMNAME = "systemname"; + public static final String SERVERNAME = "servername"; + public static final String JID = "jid"; + public static final String OPTIONS = "options"; + public static final String SYSTEMACCOUNT = "systemaccount"; + public static final String PHOTOURI = "photouri"; + public static final String KEYS = "pgpkey"; + public static final String ACCOUNT = "accountUuid"; + public static final String AVATAR = "avatar"; + + protected String accountUuid; + protected String systemName; + protected String serverName; + protected String presenceName; + protected String jid; + protected int subscription = 0; + protected String systemAccount; + protected String photoUri; + protected String avatar; + protected JSONObject keys = new JSONObject(); + protected Presences presences = new Presences(); + + protected Account account; + + protected boolean inRoster = true; + + public Lastseen lastseen = new Lastseen(); + + public Contact(String account, String systemName, String serverName, + String jid, int subscription, String photoUri, + String systemAccount, String keys, String avatar) { + this.accountUuid = account; + this.systemName = systemName; + this.serverName = serverName; + this.jid = jid; + this.subscription = subscription; + this.photoUri = photoUri; + this.systemAccount = systemAccount; + if (keys == null) { + keys = ""; + } + try { + this.keys = new JSONObject(keys); + } catch (JSONException e) { + this.keys = new JSONObject(); + } + this.avatar = avatar; + } + + public Contact(String jid) { + this.jid = jid; + } + + public String getDisplayName() { + if (this.systemName != null) { + return this.systemName; + } else if (this.serverName != null) { + return this.serverName; + } else if (this.presenceName != null) { + return this.presenceName; + } else { + return this.jid.split("@")[0]; + } + } + + public String getProfilePhoto() { + return this.photoUri; + } + + public String getJid() { + return this.jid.toLowerCase(Locale.getDefault()); + } + + public boolean match(String needle) { + return needle == null + || jid.contains(needle.toLowerCase()) + || getDisplayName().toLowerCase() + .contains(needle.toLowerCase()); + } + + public ContentValues getContentValues() { + ContentValues values = new ContentValues(); + values.put(ACCOUNT, accountUuid); + values.put(SYSTEMNAME, systemName); + values.put(SERVERNAME, serverName); + values.put(JID, jid); + values.put(OPTIONS, subscription); + values.put(SYSTEMACCOUNT, systemAccount); + values.put(PHOTOURI, photoUri); + values.put(KEYS, keys.toString()); + values.put(AVATAR, avatar); + return values; + } + + public static Contact fromCursor(Cursor cursor) { + return new Contact(cursor.getString(cursor.getColumnIndex(ACCOUNT)), + cursor.getString(cursor.getColumnIndex(SYSTEMNAME)), + cursor.getString(cursor.getColumnIndex(SERVERNAME)), + cursor.getString(cursor.getColumnIndex(JID)), + cursor.getInt(cursor.getColumnIndex(OPTIONS)), + cursor.getString(cursor.getColumnIndex(PHOTOURI)), + cursor.getString(cursor.getColumnIndex(SYSTEMACCOUNT)), + cursor.getString(cursor.getColumnIndex(KEYS)), + cursor.getString(cursor.getColumnIndex(AVATAR))); + } + + public int getSubscription() { + return this.subscription; + } + + public void setSystemAccount(String account) { + this.systemAccount = account; + } + + public void setAccount(Account account) { + this.account = account; + this.accountUuid = account.getUuid(); + } + + public Account getAccount() { + return this.account; + } + + public Presences getPresences() { + return this.presences; + } + + public void updatePresence(String resource, int status) { + this.presences.updatePresence(resource, status); + } + + public void removePresence(String resource) { + this.presences.removePresence(resource); + } + + public void clearPresences() { + this.presences.clearPresences(); + this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST); + } + + public int getMostAvailableStatus() { + return this.presences.getMostAvailableStatus(); + } + + public void setPresences(Presences pres) { + this.presences = pres; + } + + public void setPhotoUri(String uri) { + this.photoUri = uri; + } + + public void setServerName(String serverName) { + this.serverName = serverName; + } + + public void setSystemName(String systemName) { + this.systemName = systemName; + } + + public void setPresenceName(String presenceName) { + this.presenceName = presenceName; + } + + public String getSystemAccount() { + return systemAccount; + } + + public Set getOtrFingerprints() { + Set set = new HashSet(); + try { + if (this.keys.has("otr_fingerprints")) { + JSONArray fingerprints = this.keys + .getJSONArray("otr_fingerprints"); + for (int i = 0; i < fingerprints.length(); ++i) { + set.add(fingerprints.getString(i)); + } + } + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return set; + } + + public void addOtrFingerprint(String print) { + try { + JSONArray fingerprints; + if (!this.keys.has("otr_fingerprints")) { + fingerprints = new JSONArray(); + + } else { + fingerprints = this.keys.getJSONArray("otr_fingerprints"); + } + fingerprints.put(print); + this.keys.put("otr_fingerprints", fingerprints); + } catch (JSONException e) { + + } + } + + public void setPgpKeyId(long keyId) { + try { + this.keys.put("pgp_keyid", keyId); + } catch (JSONException e) { + + } + } + + public long getPgpKeyId() { + if (this.keys.has("pgp_keyid")) { + try { + return this.keys.getLong("pgp_keyid"); + } catch (JSONException e) { + return 0; + } + } else { + return 0; + } + } + + public void setOption(int option) { + this.subscription |= 1 << option; + } + + public void resetOption(int option) { + this.subscription &= ~(1 << option); + } + + public boolean getOption(int option) { + return ((this.subscription & (1 << option)) != 0); + } + + public boolean showInRoster() { + return (this.getOption(Contact.Options.IN_ROSTER) && (!this + .getOption(Contact.Options.DIRTY_DELETE))) + || (this.getOption(Contact.Options.DIRTY_PUSH)); + } + + public void parseSubscriptionFromElement(Element item) { + String ask = item.getAttribute("ask"); + String subscription = item.getAttribute("subscription"); + + if (subscription != null) { + if (subscription.equals("to")) { + this.resetOption(Contact.Options.FROM); + this.setOption(Contact.Options.TO); + } else if (subscription.equals("from")) { + this.resetOption(Contact.Options.TO); + this.setOption(Contact.Options.FROM); + this.resetOption(Contact.Options.PREEMPTIVE_GRANT); + } else if (subscription.equals("both")) { + this.setOption(Contact.Options.TO); + this.setOption(Contact.Options.FROM); + this.resetOption(Contact.Options.PREEMPTIVE_GRANT); + } else if (subscription.equals("none")) { + this.resetOption(Contact.Options.FROM); + this.resetOption(Contact.Options.TO); + } + } + + // do NOT override asking if pending push request + if (!this.getOption(Contact.Options.DIRTY_PUSH)) { + if ((ask != null) && (ask.equals("subscribe"))) { + this.setOption(Contact.Options.ASKING); + } else { + this.resetOption(Contact.Options.ASKING); + } + } + } + + public Element asElement() { + Element item = new Element("item"); + item.setAttribute("jid", this.jid); + if (this.serverName != null) { + item.setAttribute("name", this.serverName); + } + return item; + } + + public class Options { + public static final int TO = 0; + public static final int FROM = 1; + public static final int ASKING = 2; + public static final int PREEMPTIVE_GRANT = 3; + public static final int IN_ROSTER = 4; + public static final int PENDING_SUBSCRIPTION_REQUEST = 5; + public static final int DIRTY_PUSH = 6; + public static final int DIRTY_DELETE = 7; + } + + public class Lastseen { + public long time = 0; + public String presence = null; + } + + @Override + public int compareTo(ListItem another) { + return this.getDisplayName().compareToIgnoreCase( + another.getDisplayName()); + } + + public String getServer() { + String[] split = getJid().split("@"); + if (split.length >= 2) { + return split[1]; + } else { + return null; + } + } + + public boolean setAvatar(String filename) { + if (this.avatar != null && this.avatar.equals(filename)) { + return false; + } else { + this.avatar = filename; + return true; + } + } + + public String getAvatar() { + return this.avatar; + } + + public boolean deleteOtrFingerprint(String fingerprint) { + boolean success = false; + try { + if (this.keys.has("otr_fingerprints")) { + JSONArray newPrints = new JSONArray(); + JSONArray oldPrints = this.keys + .getJSONArray("otr_fingerprints"); + for (int i = 0; i < oldPrints.length(); ++i) { + if (!oldPrints.getString(i).equals(fingerprint)) { + newPrints.put(oldPrints.getString(i)); + } else { + success = true; + } + } + this.keys.put("otr_fingerprints", newPrints); + } + return success; + } catch (JSONException e) { + return false; + } + } + + public boolean trusted() { + return getOption(Options.FROM) && getOption(Options.TO); + } +} diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java new file mode 100644 index 00000000..9d4c36db --- /dev/null +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -0,0 +1,500 @@ +package eu.siacs.conversations.entities; + +import java.security.interfaces.DSAPublicKey; +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONException; +import org.json.JSONObject; + +import eu.siacs.conversations.services.XmppConnectionService; + +import net.java.otr4j.OtrException; +import net.java.otr4j.crypto.OtrCryptoEngineImpl; +import net.java.otr4j.crypto.OtrCryptoException; +import net.java.otr4j.session.SessionID; +import net.java.otr4j.session.SessionImpl; +import net.java.otr4j.session.SessionStatus; +import android.content.ContentValues; +import android.database.Cursor; +import android.os.SystemClock; + +public class Conversation extends AbstractEntity { + public static final String TABLENAME = "conversations"; + + public static final int STATUS_AVAILABLE = 0; + public static final int STATUS_ARCHIVED = 1; + public static final int STATUS_DELETED = 2; + + public static final int MODE_MULTI = 1; + public static final int MODE_SINGLE = 0; + + public static final String NAME = "name"; + public static final String ACCOUNT = "accountUuid"; + public static final String CONTACT = "contactUuid"; + public static final String CONTACTJID = "contactJid"; + public static final String STATUS = "status"; + public static final String CREATED = "created"; + public static final String MODE = "mode"; + public static final String ATTRIBUTES = "attributes"; + + public static final String ATTRIBUTE_NEXT_ENCRYPTION = "next_encryption"; + public static final String ATTRIBUTE_MUC_PASSWORD = "muc_password"; + public static final String ATTRIBUTE_MUTED_TILL = "muted_till"; + + private String name; + private String contactUuid; + private String accountUuid; + private String contactJid; + private int status; + private long created; + private int mode; + + private JSONObject attributes = new JSONObject(); + + private String nextPresence; + + protected ArrayList messages = new ArrayList(); + protected Account account = null; + + private transient SessionImpl otrSession; + + private transient String otrFingerprint = null; + + private String nextMessage; + + private transient MucOptions mucOptions = null; + + // private transient String latestMarkableMessageId; + + private byte[] symmetricKey; + + private Bookmark bookmark; + + public Conversation(String name, Account account, String contactJid, + int mode) { + this(java.util.UUID.randomUUID().toString(), name, null, account + .getUuid(), contactJid, System.currentTimeMillis(), + STATUS_AVAILABLE, mode, ""); + this.account = account; + } + + public Conversation(String uuid, String name, String contactUuid, + String accountUuid, String contactJid, long created, int status, + int mode, String attributes) { + this.uuid = uuid; + this.name = name; + this.contactUuid = contactUuid; + this.accountUuid = accountUuid; + this.contactJid = contactJid; + this.created = created; + this.status = status; + this.mode = mode; + try { + if (attributes == null) { + attributes = new String(); + } + this.attributes = new JSONObject(attributes); + } catch (JSONException e) { + this.attributes = new JSONObject(); + } + } + + public List getMessages() { + return messages; + } + + public boolean isRead() { + if ((this.messages == null) || (this.messages.size() == 0)) + return true; + return this.messages.get(this.messages.size() - 1).isRead(); + } + + public void markRead() { + if (this.messages == null) { + return; + } + for (int i = this.messages.size() - 1; i >= 0; --i) { + if (messages.get(i).isRead()) { + break; + } + this.messages.get(i).markRead(); + } + } + + public String getLatestMarkableMessageId() { + if (this.messages == null) { + return null; + } + for (int i = this.messages.size() - 1; i >= 0; --i) { + if (this.messages.get(i).getStatus() <= Message.STATUS_RECEIVED + && this.messages.get(i).markable) { + if (this.messages.get(i).isRead()) { + return null; + } else { + return this.messages.get(i).getRemoteMsgId(); + } + } + } + return null; + } + + public Message getLatestMessage() { + if ((this.messages == null) || (this.messages.size() == 0)) { + Message message = new Message(this, "", Message.ENCRYPTION_NONE); + message.setTime(getCreated()); + return message; + } else { + Message message = this.messages.get(this.messages.size() - 1); + message.setConversation(this); + return message; + } + } + + public void setMessages(ArrayList msgs) { + this.messages = msgs; + } + + public String getName() { + if (getMode() == MODE_MULTI && getMucOptions().getSubject() != null) { + return getMucOptions().getSubject(); + } else if (getMode() == MODE_MULTI && bookmark != null + && bookmark.getName() != null) { + return bookmark.getName(); + } else { + return this.getContact().getDisplayName(); + } + } + + public String getProfilePhotoString() { + return this.getContact().getProfilePhoto(); + } + + public String getAccountUuid() { + return this.accountUuid; + } + + public Account getAccount() { + return this.account; + } + + public Contact getContact() { + return this.account.getRoster().getContact(this.contactJid); + } + + public void setAccount(Account account) { + this.account = account; + } + + public String getContactJid() { + return this.contactJid; + } + + public int getStatus() { + return this.status; + } + + public long getCreated() { + return this.created; + } + + public ContentValues getContentValues() { + ContentValues values = new ContentValues(); + values.put(UUID, uuid); + values.put(NAME, name); + values.put(CONTACT, contactUuid); + values.put(ACCOUNT, accountUuid); + values.put(CONTACTJID, contactJid); + values.put(CREATED, created); + values.put(STATUS, status); + values.put(MODE, mode); + values.put(ATTRIBUTES, attributes.toString()); + return values; + } + + public static Conversation fromCursor(Cursor cursor) { + return new Conversation(cursor.getString(cursor.getColumnIndex(UUID)), + cursor.getString(cursor.getColumnIndex(NAME)), + cursor.getString(cursor.getColumnIndex(CONTACT)), + cursor.getString(cursor.getColumnIndex(ACCOUNT)), + cursor.getString(cursor.getColumnIndex(CONTACTJID)), + cursor.getLong(cursor.getColumnIndex(CREATED)), + cursor.getInt(cursor.getColumnIndex(STATUS)), + cursor.getInt(cursor.getColumnIndex(MODE)), + cursor.getString(cursor.getColumnIndex(ATTRIBUTES))); + } + + public void setStatus(int status) { + this.status = status; + } + + public int getMode() { + return this.mode; + } + + public void setMode(int mode) { + this.mode = mode; + } + + public SessionImpl startOtrSession(XmppConnectionService service, + String presence, boolean sendStart) { + if (this.otrSession != null) { + return this.otrSession; + } else { + SessionID sessionId = new SessionID(this.getContactJid().split("/", + 2)[0], presence, "xmpp"); + this.otrSession = new SessionImpl(sessionId, getAccount() + .getOtrEngine(service)); + try { + if (sendStart) { + this.otrSession.startSession(); + return this.otrSession; + } + return this.otrSession; + } catch (OtrException e) { + return null; + } + } + + } + + public SessionImpl getOtrSession() { + return this.otrSession; + } + + public void resetOtrSession() { + this.otrFingerprint = null; + this.otrSession = null; + } + + public void startOtrIfNeeded() { + if (this.otrSession != null + && this.otrSession.getSessionStatus() != SessionStatus.ENCRYPTED) { + try { + this.otrSession.startSession(); + } catch (OtrException e) { + this.resetOtrSession(); + } + } + } + + public boolean endOtrIfNeeded() { + if (this.otrSession != null) { + if (this.otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) { + try { + this.otrSession.endSession(); + this.resetOtrSession(); + return true; + } catch (OtrException e) { + this.resetOtrSession(); + return false; + } + } else { + this.resetOtrSession(); + return false; + } + } else { + return false; + } + } + + public boolean hasValidOtrSession() { + return this.otrSession != null; + } + + public String getOtrFingerprint() { + if (this.otrFingerprint == null) { + try { + if (getOtrSession() == null) { + return ""; + } + DSAPublicKey remotePubKey = (DSAPublicKey) getOtrSession() + .getRemotePublicKey(); + StringBuilder builder = new StringBuilder( + new OtrCryptoEngineImpl().getFingerprint(remotePubKey)); + builder.insert(8, " "); + builder.insert(17, " "); + builder.insert(26, " "); + builder.insert(35, " "); + this.otrFingerprint = builder.toString(); + } catch (OtrCryptoException e) { + + } + } + return this.otrFingerprint; + } + + public synchronized MucOptions getMucOptions() { + if (this.mucOptions == null) { + this.mucOptions = new MucOptions(this); + } + return this.mucOptions; + } + + public void resetMucOptions() { + this.mucOptions = null; + } + + public void setContactJid(String jid) { + this.contactJid = jid; + } + + public void setNextPresence(String presence) { + this.nextPresence = presence; + } + + public String getNextPresence() { + return this.nextPresence; + } + + public int getLatestEncryption() { + int latestEncryption = this.getLatestMessage().getEncryption(); + if ((latestEncryption == Message.ENCRYPTION_DECRYPTED) + || (latestEncryption == Message.ENCRYPTION_DECRYPTION_FAILED)) { + return Message.ENCRYPTION_PGP; + } else { + return latestEncryption; + } + } + + public int getNextEncryption(boolean force) { + int next = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, -1); + if (next == -1) { + int latest = this.getLatestEncryption(); + if (latest == Message.ENCRYPTION_NONE) { + if (force && getMode() == MODE_SINGLE) { + return Message.ENCRYPTION_OTR; + } else if (getContact().getPresences().size() == 1) { + if (getContact().getOtrFingerprints().size() >= 1) { + return Message.ENCRYPTION_OTR; + } else { + return latest; + } + } else { + return latest; + } + } else { + return latest; + } + } + if (next == Message.ENCRYPTION_NONE && force + && getMode() == MODE_SINGLE) { + return Message.ENCRYPTION_OTR; + } else { + return next; + } + } + + public void setNextEncryption(int encryption) { + this.setAttribute(ATTRIBUTE_NEXT_ENCRYPTION, String.valueOf(encryption)); + } + + public String getNextMessage() { + if (this.nextMessage == null) { + return ""; + } else { + return this.nextMessage; + } + } + + public void setNextMessage(String message) { + this.nextMessage = message; + } + + public void setSymmetricKey(byte[] key) { + this.symmetricKey = key; + } + + public byte[] getSymmetricKey() { + return this.symmetricKey; + } + + public void setBookmark(Bookmark bookmark) { + this.bookmark = bookmark; + this.bookmark.setConversation(this); + } + + public void deregisterWithBookmark() { + if (this.bookmark != null) { + this.bookmark.setConversation(null); + } + } + + public Bookmark getBookmark() { + return this.bookmark; + } + + public boolean hasDuplicateMessage(Message message) { + for (int i = this.getMessages().size() - 1; i >= 0; --i) { + if (this.messages.get(i).equals(message)) { + return true; + } + } + return false; + } + + public void setMutedTill(long value) { + this.setAttribute(ATTRIBUTE_MUTED_TILL, String.valueOf(value)); + } + + public boolean isMuted() { + return SystemClock.elapsedRealtime() < this.getLongAttribute( + ATTRIBUTE_MUTED_TILL, 0); + } + + public boolean setAttribute(String key, String value) { + try { + this.attributes.put(key, value); + return true; + } catch (JSONException e) { + return false; + } + } + + public String getAttribute(String key) { + try { + return this.attributes.getString(key); + } catch (JSONException e) { + return null; + } + } + + public int getIntAttribute(String key, int defaultValue) { + String value = this.getAttribute(key); + if (value == null) { + return defaultValue; + } else { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + } + + public long getLongAttribute(String key, long defaultValue) { + String value = this.getAttribute(key); + if (value == null) { + return defaultValue; + } else { + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + } + + public void add(Message message) { + message.setConversation(this); + synchronized (this.messages) { + this.messages.add(message); + } + } + + public void addAll(int index, List messages) { + synchronized (this.messages) { + this.messages.addAll(index, messages); + } + } +} diff --git a/src/main/java/eu/siacs/conversations/entities/Downloadable.java b/src/main/java/eu/siacs/conversations/entities/Downloadable.java new file mode 100644 index 00000000..70516b20 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/entities/Downloadable.java @@ -0,0 +1,21 @@ +package eu.siacs.conversations.entities; + +public interface Downloadable { + + public final String[] VALID_EXTENSIONS = { "webp", "jpeg", "jpg", "png" }; + public final String[] VALID_CRYPTO_EXTENSIONS = { "pgp", "gpg", "otr" }; + + public static final int STATUS_UNKNOWN = 0x200; + public static final int STATUS_CHECKING = 0x201; + public static final int STATUS_FAILED = 0x202; + public static final int STATUS_OFFER = 0x203; + public static final int STATUS_DOWNLOADING = 0x204; + public static final int STATUS_DELETED = 0x205; + public static final int STATUS_OFFER_CHECK_FILESIZE = 0x206; + + public boolean start(); + + public int getStatus(); + + public long getFileSize(); +} diff --git a/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java b/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java new file mode 100644 index 00000000..1605c75b --- /dev/null +++ b/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java @@ -0,0 +1,154 @@ +package eu.siacs.conversations.entities; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import eu.siacs.conversations.Config; +import android.util.Log; + +public class DownloadableFile extends File { + + private static final long serialVersionUID = 2247012619505115863L; + + private long expectedSize = 0; + private String sha1sum; + private Key aeskey; + + private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf }; + + public DownloadableFile(String path) { + super(path); + } + + public long getSize() { + return super.length(); + } + + public long getExpectedSize() { + if (this.aeskey != null) { + if (this.expectedSize == 0) { + return 0; + } else { + return (this.expectedSize / 16 + 1) * 16; + } + } else { + return this.expectedSize; + } + } + + public void setExpectedSize(long size) { + this.expectedSize = size; + } + + public String getSha1Sum() { + return this.sha1sum; + } + + public void setSha1Sum(String sum) { + this.sha1sum = sum; + } + + public void setKey(byte[] key) { + if (key.length == 48) { + byte[] secretKey = new byte[32]; + byte[] iv = new byte[16]; + System.arraycopy(key, 0, iv, 0, 16); + System.arraycopy(key, 16, secretKey, 0, 32); + this.aeskey = new SecretKeySpec(secretKey, "AES"); + this.iv = iv; + } else if (key.length >= 32) { + byte[] secretKey = new byte[32]; + System.arraycopy(key, 0, secretKey, 0, 32); + this.aeskey = new SecretKeySpec(secretKey, "AES"); + } else if (key.length >= 16) { + byte[] secretKey = new byte[16]; + System.arraycopy(key, 0, secretKey, 0, 16); + this.aeskey = new SecretKeySpec(secretKey, "AES"); + } + } + + public Key getKey() { + return this.aeskey; + } + + public InputStream createInputStream() { + if (this.getKey() == null) { + try { + return new FileInputStream(this); + } catch (FileNotFoundException e) { + return null; + } + } else { + try { + IvParameterSpec ips = new IvParameterSpec(iv); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, this.getKey(), ips); + Log.d(Config.LOGTAG, "opening encrypted input stream"); + return new CipherInputStream(new FileInputStream(this), cipher); + } catch (NoSuchAlgorithmException e) { + Log.d(Config.LOGTAG, "no such algo: " + e.getMessage()); + return null; + } catch (NoSuchPaddingException e) { + Log.d(Config.LOGTAG, "no such padding: " + e.getMessage()); + return null; + } catch (InvalidKeyException e) { + Log.d(Config.LOGTAG, "invalid key: " + e.getMessage()); + return null; + } catch (InvalidAlgorithmParameterException e) { + Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage()); + return null; + } catch (FileNotFoundException e) { + return null; + } + } + } + + public OutputStream createOutputStream() { + if (this.getKey() == null) { + try { + return new FileOutputStream(this); + } catch (FileNotFoundException e) { + return null; + } + } else { + try { + IvParameterSpec ips = new IvParameterSpec(this.iv); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, this.getKey(), ips); + Log.d(Config.LOGTAG, "opening encrypted output stream"); + return new CipherOutputStream(new FileOutputStream(this), + cipher); + } catch (NoSuchAlgorithmException e) { + Log.d(Config.LOGTAG, "no such algo: " + e.getMessage()); + return null; + } catch (NoSuchPaddingException e) { + Log.d(Config.LOGTAG, "no such padding: " + e.getMessage()); + return null; + } catch (InvalidKeyException e) { + Log.d(Config.LOGTAG, "invalid key: " + e.getMessage()); + return null; + } catch (InvalidAlgorithmParameterException e) { + Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage()); + return null; + } catch (FileNotFoundException e) { + return null; + } + } + } +} diff --git a/src/main/java/eu/siacs/conversations/entities/ListItem.java b/src/main/java/eu/siacs/conversations/entities/ListItem.java new file mode 100644 index 00000000..a1872d2f --- /dev/null +++ b/src/main/java/eu/siacs/conversations/entities/ListItem.java @@ -0,0 +1,7 @@ +package eu.siacs.conversations.entities; + +public interface ListItem extends Comparable { + public String getDisplayName(); + + public String getJid(); +} diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java new file mode 100644 index 00000000..a390c7ca --- /dev/null +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -0,0 +1,478 @@ +package eu.siacs.conversations.entities; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; + +public class Message extends AbstractEntity { + + public static final String TABLENAME = "messages"; + + public static final int STATUS_RECEIVED = 0; + public static final int STATUS_UNSEND = 1; + public static final int STATUS_SEND = 2; + public static final int STATUS_SEND_FAILED = 3; + public static final int STATUS_SEND_REJECTED = 4; + public static final int STATUS_WAITING = 5; + public static final int STATUS_OFFERED = 6; + public static final int STATUS_SEND_RECEIVED = 7; + public static final int STATUS_SEND_DISPLAYED = 8; + + public static final int ENCRYPTION_NONE = 0; + public static final int ENCRYPTION_PGP = 1; + public static final int ENCRYPTION_OTR = 2; + public static final int ENCRYPTION_DECRYPTED = 3; + public static final int ENCRYPTION_DECRYPTION_FAILED = 4; + + public static final int TYPE_TEXT = 0; + public static final int TYPE_IMAGE = 1; + public static final int TYPE_AUDIO = 2; + 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"; + + protected String conversationUuid; + protected String counterpart; + protected String trueCounterpart; + protected String body; + protected String encryptedBody; + protected long timeSent; + protected int encryption; + protected int status; + protected int type; + protected boolean read = true; + protected String remoteMsgId = null; + + protected Conversation conversation = null; + protected Downloadable downloadable = null; + public boolean markable = false; + + private Message mNextMessage = null; + private Message mPreviousMessage = null; + + private Message() { + + } + + public Message(Conversation conversation, String body, int encryption) { + this(java.util.UUID.randomUUID().toString(), conversation.getUuid(), + conversation.getContactJid(), null, body, System + .currentTimeMillis(), encryption, + Message.STATUS_UNSEND, TYPE_TEXT, null); + this.conversation = conversation; + } + + public Message(Conversation conversation, String counterpart, String body, + int encryption, int status) { + this(java.util.UUID.randomUUID().toString(), conversation.getUuid(), + counterpart, null, body, System.currentTimeMillis(), + encryption, status, TYPE_TEXT, null); + this.conversation = conversation; + } + + public Message(String uuid, String conversationUUid, String counterpart, + String trueCounterpart, String body, long timeSent, int encryption, + int status, int type, String remoteMsgId) { + this.uuid = uuid; + this.conversationUuid = conversationUUid; + this.counterpart = counterpart; + this.trueCounterpart = trueCounterpart; + this.body = body; + this.timeSent = timeSent; + this.encryption = encryption; + this.status = status; + this.type = type; + this.remoteMsgId = remoteMsgId; + } + + @Override + public ContentValues getContentValues() { + ContentValues values = new ContentValues(); + values.put(UUID, uuid); + values.put(CONVERSATION, conversationUuid); + values.put(COUNTERPART, counterpart); + values.put(TRUE_COUNTERPART, trueCounterpart); + values.put(BODY, body); + values.put(TIME_SENT, timeSent); + values.put(ENCRYPTION, encryption); + values.put(STATUS, status); + values.put(TYPE, type); + values.put(REMOTE_MSG_ID, remoteMsgId); + return values; + } + + public String getConversationUuid() { + return conversationUuid; + } + + public Conversation getConversation() { + return this.conversation; + } + + public String getCounterpart() { + return counterpart; + } + + public Contact getContact() { + if (this.conversation.getMode() == Conversation.MODE_SINGLE) { + return this.conversation.getContact(); + } else { + if (this.trueCounterpart == null) { + return null; + } else { + return this.conversation.getAccount().getRoster() + .getContactFromRoster(this.trueCounterpart); + } + } + } + + public String getBody() { + return body; + } + + public String getReadableBody(Context context) { + if (encryption == ENCRYPTION_PGP) { + return context.getText(R.string.encrypted_message_received) + .toString(); + } else if (encryption == ENCRYPTION_DECRYPTION_FAILED) { + return context.getText(R.string.decryption_failed).toString(); + } else if (type == TYPE_IMAGE) { + return context.getText(R.string.image_file).toString(); + } else { + return body.trim(); + } + } + + public long getTimeSent() { + return timeSent; + } + + public int getEncryption() { + return encryption; + } + + public int getStatus() { + return status; + } + + public String getRemoteMsgId() { + return this.remoteMsgId; + } + + public void setRemoteMsgId(String id) { + this.remoteMsgId = id; + } + + public static Message fromCursor(Cursor cursor) { + return new Message(cursor.getString(cursor.getColumnIndex(UUID)), + cursor.getString(cursor.getColumnIndex(CONVERSATION)), + cursor.getString(cursor.getColumnIndex(COUNTERPART)), + cursor.getString(cursor.getColumnIndex(TRUE_COUNTERPART)), + cursor.getString(cursor.getColumnIndex(BODY)), + cursor.getLong(cursor.getColumnIndex(TIME_SENT)), + cursor.getInt(cursor.getColumnIndex(ENCRYPTION)), + cursor.getInt(cursor.getColumnIndex(STATUS)), + cursor.getInt(cursor.getColumnIndex(TYPE)), + cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID))); + } + + public void setConversation(Conversation conv) { + this.conversation = conv; + } + + public void setStatus(int status) { + this.status = status; + } + + public boolean isRead() { + return this.read; + } + + public void markRead() { + this.read = true; + } + + public void markUnread() { + this.read = false; + } + + public void setTime(long time) { + this.timeSent = time; + } + + public void setEncryption(int encryption) { + this.encryption = encryption; + } + + public void setBody(String body) { + this.body = body; + } + + public String getEncryptedBody() { + return this.encryptedBody; + } + + public void setEncryptedBody(String body) { + this.encryptedBody = body; + } + + public void setType(int type) { + this.type = type; + } + + public int getType() { + return this.type; + } + + public void setPresence(String presence) { + if (presence == null) { + this.counterpart = this.counterpart.split("/", 2)[0]; + } else { + this.counterpart = this.counterpart.split("/", 2)[0] + "/" + + presence; + } + } + + public void setTrueCounterpart(String trueCounterpart) { + this.trueCounterpart = trueCounterpart; + } + + public String getPresence() { + String[] counterparts = this.counterpart.split("/", 2); + if (counterparts.length == 2) { + return counterparts[1]; + } else { + if (this.counterpart.contains("/")) { + return ""; + } else { + return null; + } + } + } + + public void setDownloadable(Downloadable downloadable) { + this.downloadable = downloadable; + } + + public Downloadable getDownloadable() { + return this.downloadable; + } + + public static Message createStatusMessage(Conversation conversation) { + Message message = new Message(); + message.setType(Message.TYPE_STATUS); + message.setConversation(conversation); + return message; + } + + public void setCounterpart(String counterpart) { + this.counterpart = counterpart; + } + + public boolean equals(Message message) { + if ((this.remoteMsgId != null) && (this.body != null) + && (this.counterpart != null)) { + return this.remoteMsgId.equals(message.getRemoteMsgId()) + && this.body.equals(message.getBody()) + && this.counterpart.equals(message.getCounterpart()); + } else { + return false; + } + } + + public Message next() { + if (this.mNextMessage == null) { + synchronized (this.conversation.messages) { + int index = this.conversation.messages.indexOf(this); + if (index < 0 + || index >= this.conversation.getMessages().size() - 1) { + this.mNextMessage = null; + } else { + this.mNextMessage = this.conversation.messages + .get(index + 1); + } + } + } + return this.mNextMessage; + } + + public Message prev() { + if (this.mPreviousMessage == null) { + synchronized (this.conversation.messages) { + int index = this.conversation.messages.indexOf(this); + if (index <= 0 || index > this.conversation.messages.size()) { + this.mPreviousMessage = null; + } else { + this.mPreviousMessage = this.conversation.messages + .get(index - 1); + } + } + } + return this.mPreviousMessage; + } + + public boolean mergable(Message message) { + if (message == null) { + return false; + } + return (message.getType() == Message.TYPE_TEXT + && this.getDownloadable() == null + && message.getDownloadable() == null + && message.getEncryption() != Message.ENCRYPTION_PGP + && this.getType() == message.getType() + && this.getEncryption() == message.getEncryption() + && this.getCounterpart().equals(message.getCounterpart()) + && (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) && ((this + .getStatus() == message.getStatus() || ((this.getStatus() == Message.STATUS_SEND || this + .getStatus() == Message.STATUS_SEND_RECEIVED) && (message + .getStatus() == Message.STATUS_UNSEND + || message.getStatus() == Message.STATUS_SEND || message + .getStatus() == Message.STATUS_SEND_DISPLAYED))))); + } + + public String getMergedBody() { + Message next = this.next(); + if (this.mergable(next)) { + return body.trim() + '\n' + next.getMergedBody(); + } + return body.trim(); + } + + public int getMergedStatus() { + Message next = this.next(); + if (this.mergable(next)) { + return next.getMergedStatus(); + } else { + return getStatus(); + } + } + + public long getMergedTimeSent() { + Message next = this.next(); + if (this.mergable(next)) { + return next.getMergedTimeSent(); + } else { + return getTimeSent(); + } + } + + public boolean wasMergedIntoPrevious() { + Message prev = this.prev(); + if (prev == null) { + return false; + } else { + return prev.mergable(this); + } + } + + public boolean bodyContainsDownloadable() { + Contact contact = this.getContact(); + if (status <= STATUS_RECEIVED + && (contact == null || !contact.trusted())) { + return false; + } + try { + URL url = new URL(this.getBody()); + if (!url.getProtocol().equalsIgnoreCase("http") + && !url.getProtocol().equalsIgnoreCase("https")) { + return false; + } + if (url.getPath() == null) { + return false; + } + String[] pathParts = url.getPath().split("/"); + String filename = pathParts[pathParts.length - 1]; + String[] extensionParts = filename.split("\\."); + if (extensionParts.length == 2 + && Arrays.asList(Downloadable.VALID_EXTENSIONS).contains( + extensionParts[extensionParts.length - 1])) { + return true; + } else if (extensionParts.length == 3 + && Arrays + .asList(Downloadable.VALID_CRYPTO_EXTENSIONS) + .contains(extensionParts[extensionParts.length - 1]) + && Arrays.asList(Downloadable.VALID_EXTENSIONS).contains( + extensionParts[extensionParts.length - 2])) { + return true; + } else { + return false; + } + } catch (MalformedURLException e) { + return false; + } + } + + public ImageParams getImageParams() { + ImageParams params = new ImageParams(); + if (this.downloadable != null) { + params.size = this.downloadable.getFileSize(); + } + if (body == null) { + return params; + } + String parts[] = body.split(","); + if (parts.length == 1) { + try { + params.size = Long.parseLong(parts[0]); + } catch (NumberFormatException e) { + params.origin = parts[0]; + } + } else if (parts.length == 3) { + try { + params.size = Long.parseLong(parts[0]); + } catch (NumberFormatException e) { + params.size = 0; + } + try { + params.width = Integer.parseInt(parts[1]); + } catch (NumberFormatException e) { + params.width = 0; + } + try { + params.height = Integer.parseInt(parts[2]); + } catch (NumberFormatException e) { + params.height = 0; + } + } else if (parts.length == 4) { + params.origin = parts[0]; + try { + params.size = Long.parseLong(parts[1]); + } catch (NumberFormatException e) { + params.size = 0; + } + try { + params.width = Integer.parseInt(parts[2]); + } catch (NumberFormatException e) { + params.width = 0; + } + try { + params.height = Integer.parseInt(parts[3]); + } catch (NumberFormatException e) { + params.height = 0; + } + } + return params; + } + + public class ImageParams { + public long size = 0; + public int width = 0; + public int height = 0; + public String origin; + } +} diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java new file mode 100644 index 00000000..d7407cd5 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -0,0 +1,369 @@ +package eu.siacs.conversations.entities; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import eu.siacs.conversations.crypto.PgpEngine; +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.stanzas.PresencePacket; +import android.annotation.SuppressLint; + +@SuppressLint("DefaultLocale") +public class MucOptions { + public static final int ERROR_NO_ERROR = 0; + public static final int ERROR_NICK_IN_USE = 1; + public static final int ERROR_ROOM_NOT_FOUND = 2; + public static final int ERROR_PASSWORD_REQUIRED = 3; + public static final int ERROR_BANNED = 4; + public static final int ERROR_MEMBERS_ONLY = 5; + + public static final int KICKED_FROM_ROOM = 9; + + public static final String STATUS_CODE_BANNED = "301"; + public static final String STATUS_CODE_KICKED = "307"; + + public interface OnRenameListener { + public void onRename(boolean success); + } + + 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 String name; + private String jid; + private long pgpKeyId = 0; + + public String getName() { + return name; + } + + public void setName(String user) { + this.name = user; + } + + public void setJid(String jid) { + this.jid = jid; + } + + public String getJid() { + return this.jid; + } + + public int getRole() { + return this.role; + } + + public void setRole(String role) { + role = role.toLowerCase(); + if (role.equals("moderator")) { + this.role = ROLE_MODERATOR; + } else if (role.equals("participant")) { + this.role = ROLE_PARTICIPANT; + } else if (role.equals("visitor")) { + this.role = ROLE_VISITOR; + } else { + this.role = ROLE_NONE; + } + } + + public int 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; + } + } + + public void setPgpKeyId(long id) { + this.pgpKeyId = id; + } + + public long getPgpKeyId() { + return this.pgpKeyId; + } + + public Contact getContact() { + return account.getRoster().getContactFromRoster(getJid()); + } + } + + private Account account; + private List users = new CopyOnWriteArrayList(); + private Conversation conversation; + private boolean isOnline = false; + private int error = ERROR_ROOM_NOT_FOUND; + private OnRenameListener renameListener = null; + private boolean aboutToRename = false; + private User self = new User(); + private String subject = null; + private String joinnick; + private String password = null; + + public MucOptions(Conversation conversation) { + this.account = conversation.getAccount(); + this.conversation = conversation; + } + + public void deleteUser(String name) { + for (int i = 0; i < users.size(); ++i) { + if (users.get(i).getName().equals(name)) { + users.remove(i); + return; + } + } + } + + public void addUser(User user) { + for (int i = 0; i < users.size(); ++i) { + if (users.get(i).getName().equals(user.getName())) { + users.set(i, user); + return; + } + } + users.add(user); + } + + public void processPacket(PresencePacket packet, PgpEngine pgp) { + String[] fromParts = packet.getFrom().split("/", 2); + if (fromParts.length >= 2) { + String name = fromParts[1]; + String type = packet.getAttribute("type"); + if (type == null) { + User user = new User(); + Element item = packet.findChild("x", + "http://jabber.org/protocol/muc#user") + .findChild("item"); + user.setName(name); + user.setAffiliation(item.getAttribute("affiliation")); + user.setRole(item.getAttribute("role")); + user.setJid(item.getAttribute("jid")); + user.setName(name); + if (name.equals(this.joinnick)) { + this.isOnline = true; + this.error = ERROR_NO_ERROR; + self = user; + if (aboutToRename) { + if (renameListener != null) { + renameListener.onRename(true); + } + aboutToRename = false; + } + } else { + addUser(user); + } + if (pgp != null) { + Element x = packet.findChild("x", "jabber:x:signed"); + if (x != null) { + Element status = packet.findChild("status"); + String msg; + if (status != null) { + msg = status.getContent(); + } else { + msg = ""; + } + user.setPgpKeyId(pgp.fetchKeyId(account, msg, + x.getContent())); + } + } + } else if (type.equals("unavailable") && name.equals(this.joinnick)) { + Element x = packet.findChild("x", + "http://jabber.org/protocol/muc#user"); + if (x != null) { + Element status = x.findChild("status"); + if (status != null) { + String code = status.getAttribute("code"); + if (STATUS_CODE_KICKED.equals(code)) { + this.isOnline = false; + this.error = KICKED_FROM_ROOM; + } else if (STATUS_CODE_BANNED.equals(code)) { + this.isOnline = false; + this.error = ERROR_BANNED; + } + } + } + } else if (type.equals("unavailable")) { + deleteUser(packet.getAttribute("from").split("/", 2)[1]); + } else if (type.equals("error")) { + Element error = packet.findChild("error"); + if (error != null && error.hasChild("conflict")) { + if (aboutToRename) { + if (renameListener != null) { + renameListener.onRename(false); + } + aboutToRename = false; + this.setJoinNick(getActualNick()); + } else { + this.error = ERROR_NICK_IN_USE; + } + } else if (error != null && error.hasChild("not-authorized")) { + this.error = ERROR_PASSWORD_REQUIRED; + } else if (error != null && error.hasChild("forbidden")) { + this.error = ERROR_BANNED; + } else if (error != null + && error.hasChild("registration-required")) { + this.error = ERROR_MEMBERS_ONLY; + } + } + } + } + + public List getUsers() { + return this.users; + } + + public String getProposedNick() { + String[] mucParts = conversation.getContactJid().split("/", 2); + if (conversation.getBookmark() != null + && conversation.getBookmark().getNick() != null) { + return conversation.getBookmark().getNick(); + } else { + if (mucParts.length == 2) { + return mucParts[1]; + } else { + return account.getUsername(); + } + } + } + + public String getActualNick() { + if (this.self.getName() != null) { + return this.self.getName(); + } else { + return this.getProposedNick(); + } + } + + public void setJoinNick(String nick) { + this.joinnick = nick; + } + + public boolean online() { + return this.isOnline; + } + + public int getError() { + return this.error; + } + + public void setOnRenameListener(OnRenameListener listener) { + this.renameListener = listener; + } + + public OnRenameListener getOnRenameListener() { + return this.renameListener; + } + + public void setOffline() { + this.users.clear(); + this.error = 0; + this.isOnline = false; + } + + public User getSelf() { + return self; + } + + public void setSubject(String content) { + this.subject = content; + } + + public String getSubject() { + return this.subject; + } + + public void flagAboutToRename() { + this.aboutToRename = true; + } + + public long[] getPgpKeyIds() { + List ids = new ArrayList(); + for (User user : getUsers()) { + if (user.getPgpKeyId() != 0) { + ids.add(user.getPgpKeyId()); + } + } + long[] primitivLongArray = new long[ids.size()]; + for (int i = 0; i < ids.size(); ++i) { + primitivLongArray[i] = ids.get(i); + } + return primitivLongArray; + } + + public boolean pgpKeysInUse() { + for (User user : getUsers()) { + if (user.getPgpKeyId() != 0) { + return true; + } + } + return false; + } + + public boolean everybodyHasKeys() { + for (User user : getUsers()) { + if (user.getPgpKeyId() == 0) { + return false; + } + } + return true; + } + + public String getJoinJid() { + return this.conversation.getContactJid().split("/", 2)[0] + "/" + + this.joinnick; + } + + public String getTrueCounterpart(String counterpart) { + for (User user : this.getUsers()) { + if (user.getName().equals(counterpart)) { + return user.getJid(); + } + } + return null; + } + + public String getPassword() { + this.password = conversation + .getAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD); + if (this.password == null && conversation.getBookmark() != null + && conversation.getBookmark().getPassword() != null) { + return conversation.getBookmark().getPassword(); + } else { + return this.password; + } + } + + public void setPassword(String password) { + if (conversation.getBookmark() != null) { + conversation.getBookmark().setPassword(password); + } else { + this.password = password; + } + conversation + .setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password); + } + + public Conversation getConversation() { + return this.conversation; + } +} \ No newline at end of file diff --git a/src/main/java/eu/siacs/conversations/entities/Presences.java b/src/main/java/eu/siacs/conversations/entities/Presences.java new file mode 100644 index 00000000..b5899847 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/entities/Presences.java @@ -0,0 +1,76 @@ +package eu.siacs.conversations.entities; + +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Map.Entry; + +import eu.siacs.conversations.xml.Element; + +public class Presences { + + 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 Hashtable presences = new Hashtable(); + + public Hashtable getPresences() { + return this.presences; + } + + public void updatePresence(String resource, int status) { + this.presences.put(resource, status); + } + + public void removePresence(String resource) { + this.presences.remove(resource); + } + + public void clearPresences() { + this.presences.clear(); + } + + public int getMostAvailableStatus() { + int status = OFFLINE; + Iterator> it = presences.entrySet().iterator(); + while (it.hasNext()) { + Entry 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; + } + } + + public int size() { + return presences.size(); + } + + public String[] asStringArray() { + final String[] presencesArray = new String[presences.size()]; + presences.keySet().toArray(presencesArray); + return presencesArray; + } + + public boolean has(String presence) { + return presences.containsKey(presence); + } +} diff --git a/src/main/java/eu/siacs/conversations/entities/Roster.java b/src/main/java/eu/siacs/conversations/entities/Roster.java new file mode 100644 index 00000000..3267b15a --- /dev/null +++ b/src/main/java/eu/siacs/conversations/entities/Roster.java @@ -0,0 +1,83 @@ +package eu.siacs.conversations.entities; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.ConcurrentHashMap; + +public class Roster { + Account account; + ConcurrentHashMap contacts = new ConcurrentHashMap(); + private String version = null; + + public Roster(Account account) { + this.account = account; + } + + public Contact getContactFromRoster(String jid) { + if (jid == null) { + return null; + } + String cleanJid = jid.split("/", 2)[0]; + Contact contact = contacts.get(cleanJid); + if (contact != null && contact.showInRoster()) { + return contact; + } else { + return null; + } + } + + public Contact getContact(String jid) { + String cleanJid = jid.split("/", 2)[0].toLowerCase(Locale.getDefault()); + if (contacts.containsKey(cleanJid)) { + return contacts.get(cleanJid); + } else { + Contact contact = new Contact(cleanJid); + contact.setAccount(account); + contacts.put(cleanJid, contact); + return contact; + } + } + + public void clearPresences() { + for (Contact contact : getContacts()) { + contact.clearPresences(); + } + } + + public void markAllAsNotInRoster() { + for (Contact contact : getContacts()) { + contact.resetOption(Contact.Options.IN_ROSTER); + } + } + + public void clearSystemAccounts() { + for (Contact contact : getContacts()) { + contact.setPhotoUri(null); + contact.setSystemName(null); + contact.setSystemAccount(null); + } + } + + public List getContacts() { + return new ArrayList(this.contacts.values()); + } + + public void initContact(Contact contact) { + contact.setAccount(account); + contact.setOption(Contact.Options.IN_ROSTER); + contacts.put(contact.getJid(), contact); + } + + public void setVersion(String version) { + this.version = version; + } + + public String getVersion() { + return this.version; + } + + public Account getAccount() { + return this.account; + } +} -- cgit v1.2.3