diff options
Diffstat (limited to 'src/eu/siacs')
84 files changed, 2445 insertions, 1758 deletions
diff --git a/src/eu/siacs/conversations/Config.java b/src/eu/siacs/conversations/Config.java new file mode 100644 index 00000000..6219b6b0 --- /dev/null +++ b/src/eu/siacs/conversations/Config.java @@ -0,0 +1,25 @@ +package eu.siacs.conversations; + +import android.graphics.Bitmap; + +public final class Config { + + public static final String LOGTAG = "conversations"; + + public static final int PING_MAX_INTERVAL = 300; + public static final int PING_MIN_INTERVAL = 30; + public static final int PING_TIMEOUT = 10; + public static final int CONNECT_TIMEOUT = 90; + public static final int CARBON_GRACE_PERIOD = 60; + + public static final int AVATAR_SIZE = 192; + public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.WEBP; + + public static final int MESSAGE_MERGE_WINDOW = 20; + + public static final boolean PARSE_EMOTICONS = false; + + private Config() { + + } +} diff --git a/src/eu/siacs/conversations/crypto/OtrEngine.java b/src/eu/siacs/conversations/crypto/OtrEngine.java index 7960aa2b..0ef112c6 100644 --- a/src/eu/siacs/conversations/crypto/OtrEngine.java +++ b/src/eu/siacs/conversations/crypto/OtrEngine.java @@ -17,6 +17,7 @@ import org.json.JSONObject; import android.content.Context; import android.util.Log; +import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.xmpp.stanzas.MessagePacket; @@ -29,9 +30,7 @@ import net.java.otr4j.session.InstanceTag; import net.java.otr4j.session.SessionID; public class OtrEngine implements OtrEngineHost { - - private static final String LOGTAG = "xmppService"; - + private Account account; private OtrPolicy otrPolicy; private KeyPair keyPair; @@ -45,17 +44,17 @@ public class OtrEngine implements OtrEngineHost { this.otrPolicy.setAllowV3(true); this.keyPair = loadKey(account.getKeys()); } - + private KeyPair loadKey(JSONObject keys) { if (keys == null) { return null; } try { - BigInteger x = new BigInteger(keys.getString("otr_x"),16); - BigInteger y = new BigInteger(keys.getString("otr_y"),16); - BigInteger p = new BigInteger(keys.getString("otr_p"),16); - BigInteger q = new BigInteger(keys.getString("otr_q"),16); - BigInteger g = new BigInteger(keys.getString("otr_g"),16); + BigInteger x = new BigInteger(keys.getString("otr_x"), 16); + BigInteger y = new BigInteger(keys.getString("otr_y"), 16); + BigInteger p = new BigInteger(keys.getString("otr_p"), 16); + BigInteger q = new BigInteger(keys.getString("otr_q"), 16); + BigInteger g = new BigInteger(keys.getString("otr_g"), 16); KeyFactory keyFactory = KeyFactory.getInstance("DSA"); DSAPublicKeySpec pubKeySpec = new DSAPublicKeySpec(y, p, q, g); DSAPrivateKeySpec privateKeySpec = new DSAPrivateKeySpec(x, p, q, g); @@ -70,26 +69,28 @@ public class OtrEngine implements OtrEngineHost { return null; } } - + private void saveKey() { PublicKey publicKey = keyPair.getPublic(); PrivateKey privateKey = keyPair.getPrivate(); KeyFactory keyFactory; try { keyFactory = KeyFactory.getInstance("DSA"); - DSAPrivateKeySpec privateKeySpec = keyFactory.getKeySpec(privateKey, DSAPrivateKeySpec.class); - DSAPublicKeySpec publicKeySpec = keyFactory.getKeySpec(publicKey, DSAPublicKeySpec.class); - this.account.setKey("otr_x",privateKeySpec.getX().toString(16)); - this.account.setKey("otr_g",privateKeySpec.getG().toString(16)); - this.account.setKey("otr_p",privateKeySpec.getP().toString(16)); - this.account.setKey("otr_q",privateKeySpec.getQ().toString(16)); - this.account.setKey("otr_y",publicKeySpec.getY().toString(16)); + DSAPrivateKeySpec privateKeySpec = keyFactory.getKeySpec( + privateKey, DSAPrivateKeySpec.class); + DSAPublicKeySpec publicKeySpec = keyFactory.getKeySpec(publicKey, + DSAPublicKeySpec.class); + this.account.setKey("otr_x", privateKeySpec.getX().toString(16)); + this.account.setKey("otr_g", privateKeySpec.getG().toString(16)); + this.account.setKey("otr_p", privateKeySpec.getP().toString(16)); + this.account.setKey("otr_q", privateKeySpec.getQ().toString(16)); + this.account.setKey("otr_y", publicKeySpec.getY().toString(16)); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (InvalidKeySpecException e) { e.printStackTrace(); } - + } @Override @@ -123,18 +124,19 @@ public class OtrEngine implements OtrEngineHost { } return this.keyPair.getPublic(); } - + @Override public KeyPair getLocalKeyPair(SessionID arg0) throws OtrException { - if (this.keyPair==null) { + if (this.keyPair == null) { KeyPairGenerator kg; try { - kg = KeyPairGenerator.getInstance("DSA"); - this.keyPair = kg.genKeyPair(); - this.saveKey(); - DatabaseBackend.getInstance(context).updateAccount(account); + kg = KeyPairGenerator.getInstance("DSA"); + this.keyPair = kg.genKeyPair(); + this.saveKey(); + DatabaseBackend.getInstance(context).updateAccount(account); } catch (NoSuchAlgorithmException e) { - Log.d(LOGTAG,"error generating key pair "+e.getMessage()); + Log.d(Config.LOGTAG, + "error generating key pair " + e.getMessage()); } } return this.keyPair; @@ -152,17 +154,18 @@ public class OtrEngine implements OtrEngineHost { } @Override - public void injectMessage(SessionID session, String body) throws OtrException { + public void injectMessage(SessionID session, String body) + throws OtrException { MessagePacket packet = new MessagePacket(); packet.setFrom(account.getFullJid()); if (session.getUserID().isEmpty()) { packet.setTo(session.getAccountID()); } else { - packet.setTo(session.getAccountID()+"/"+session.getUserID()); + packet.setTo(session.getAccountID() + "/" + session.getUserID()); } packet.setBody(body); - packet.addChild("private","urn:xmpp:carbons:2"); - packet.addChild("no-copy","urn:xmpp:hints"); + packet.addChild("private", "urn:xmpp:carbons:2"); + packet.addChild("no-copy", "urn:xmpp:hints"); packet.setType(MessagePacket.TYPE_CHAT); account.getXmppConnection().sendMessagePacket(packet); } diff --git a/src/eu/siacs/conversations/crypto/PgpEngine.java b/src/eu/siacs/conversations/crypto/PgpEngine.java index 65b7ccc7..2f678279 100644 --- a/src/eu/siacs/conversations/crypto/PgpEngine.java +++ b/src/eu/siacs/conversations/crypto/PgpEngine.java @@ -14,6 +14,7 @@ import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; @@ -38,7 +39,7 @@ public class PgpEngine { public void decrypt(final Message message, final UiCallback<Message> callback) { - Log.d("xmppService", "decrypting message " + message.getUuid()); + Log.d(Config.LOGTAG, "decrypting message " + message.getUuid()); Intent params = new Intent(); params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY); params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message @@ -65,7 +66,7 @@ public class PgpEngine { callback.error(R.string.openpgp_error, message); return; } - + return; case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: callback.userInputRequried((PendingIntent) result @@ -73,8 +74,9 @@ public class PgpEngine { message); return; case OpenPgpApi.RESULT_CODE_ERROR: - OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR); - Log.d("xmppService",error.getMessage()); + OpenPgpError error = result + .getParcelableExtra(OpenPgpApi.RESULT_ERROR); + Log.d(Config.LOGTAG, error.getMessage()); callback.error(R.string.openpgp_error, message); return; default: @@ -104,12 +106,13 @@ public class PgpEngine { outputFile.getAbsolutePath(), options); int imageHeight = options.outHeight; int imageWidth = options.outWidth; - message.setBody("" + outputFile.getSize() + "," - + imageWidth + "," + imageHeight); + message.setBody(Long.toString(outputFile.getSize()) + ',' + + imageWidth + ',' + imageHeight); message.setEncryption(Message.ENCRYPTION_DECRYPTED); PgpEngine.this.mXmppConnectionService .updateMessage(message); - PgpEngine.this.mXmppConnectionService.updateConversationUi(); + PgpEngine.this.mXmppConnectionService + .updateConversationUi(); callback.success(message); return; case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: @@ -168,8 +171,10 @@ public class PgpEngine { os.flush(); StringBuilder encryptedMessageBody = new StringBuilder(); String[] lines = os.toString().split("\n"); - for (int i = 3; i < lines.length - 1; ++i) { - encryptedMessageBody.append(lines[i].trim()); + for (int i = 2; i < lines.length - 1; ++i) { + if (!lines[i].contains("Version")) { + encryptedMessageBody.append(lines[i].trim()); + } } message.setEncryptedBody(encryptedMessageBody .toString()); @@ -177,7 +182,7 @@ public class PgpEngine { } catch (IOException e) { callback.error(R.string.openpgp_error, message); } - + break; case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: callback.userInputRequried((PendingIntent) result @@ -221,9 +226,9 @@ public class PgpEngine { } }); } catch (FileNotFoundException e) { - Log.d("xmppService", "file not found: " + e.getMessage()); + Log.d(Config.LOGTAG, "file not found: " + e.getMessage()); } catch (IOException e) { - Log.d("xmppService", "io exception during file encrypt"); + Log.d(Config.LOGTAG, "io exception during file encrypt"); } } } @@ -267,7 +272,7 @@ public class PgpEngine { case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: return 0; case OpenPgpApi.RESULT_CODE_ERROR: - Log.d("xmppService", + Log.d(Config.LOGTAG, "openpgp error: " + ((OpenPgpError) result .getParcelableExtra(OpenPgpApi.RESULT_ERROR)) @@ -292,9 +297,27 @@ public class PgpEngine { switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { case OpenPgpApi.RESULT_CODE_SUCCESS: StringBuilder signatureBuilder = new StringBuilder(); - String[] lines = os.toString().split("\n"); - for (int i = 7; i < lines.length - 1; ++i) { - signatureBuilder.append(lines[i].trim()); + try { + os.flush(); + String[] lines = os.toString().split("\n"); + boolean sig = false; + for(String line : lines) { + if (sig) { + if (line.contains("END PGP SIGNATURE")) { + sig = false; + } else { + if (!line.contains("Version")) { + signatureBuilder.append(line.trim()); + } + } + } + if (line.contains("BEGIN PGP SIGNATURE")) { + sig = true; + } + } + } catch (IOException e) { + callback.error(R.string.openpgp_error, account); + return; } account.setKey("pgp_signature", signatureBuilder.toString()); callback.success(account); diff --git a/src/eu/siacs/conversations/entities/AbstractEntity.java b/src/eu/siacs/conversations/entities/AbstractEntity.java index 4891723e..92b8a729 100644 --- a/src/eu/siacs/conversations/entities/AbstractEntity.java +++ b/src/eu/siacs/conversations/entities/AbstractEntity.java @@ -4,19 +4,18 @@ 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/eu/siacs/conversations/entities/Account.java b/src/eu/siacs/conversations/entities/Account.java index d31d2324..272cf379 100644 --- a/src/eu/siacs/conversations/entities/Account.java +++ b/src/eu/siacs/conversations/entities/Account.java @@ -82,11 +82,12 @@ public class Account extends AbstractEntity { public Account(String username, String server, String password) { this(java.util.UUID.randomUUID().toString(), username, server, - password, 0, null, "",null); + password, 0, null, "", null); } public Account(String uuid, String username, String server, - String password, int options, String rosterVersion, String keys, String avatar) { + String password, int options, String rosterVersion, String keys, + String avatar) { this.uuid = uuid; this.username = username; this.server = server; @@ -151,7 +152,10 @@ public class Account extends AbstractEntity { 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); + 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() { diff --git a/src/eu/siacs/conversations/entities/Bookmark.java b/src/eu/siacs/conversations/entities/Bookmark.java index 38c03410..596cbe01 100644 --- a/src/eu/siacs/conversations/entities/Bookmark.java +++ b/src/eu/siacs/conversations/entities/Bookmark.java @@ -8,30 +8,31 @@ import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xml.Element; public class Bookmark implements ListItem { - + private Account account; private String jid; private String nick; private String name; private boolean autojoin; private Conversation mJoinedConversation; - + public Bookmark(Account account, String jid) { this.account = account; this.jid = jid; } public static Bookmark parse(Element element, Account account) { - Bookmark bookmark = new Bookmark(account,element.getAttribute("jid")); + Bookmark bookmark = new Bookmark(account, element.getAttribute("jid")); bookmark.setName(element.getAttribute("name")); String autojoin = element.getAttribute("autojoin"); - if (autojoin!=null && (autojoin.equals("true")||autojoin.equals("1"))) { + if (autojoin != null + && (autojoin.equals("true") || autojoin.equals("1"))) { bookmark.setAutojoin(true); } else { bookmark.setAutojoin(false); } Element nick = element.findChild("nick"); - if (nick!=null) { + if (nick != null) { bookmark.setNick(nick.getContent()); } return bookmark; @@ -40,25 +41,27 @@ public class Bookmark implements ListItem { public void setAutojoin(boolean autojoin) { this.autojoin = autojoin; } - + public void setName(String name) { this.name = name; } - + public void setNick(String nick) { this.nick = nick; } @Override public int compareTo(ListItem another) { - return this.getDisplayName().compareToIgnoreCase(another.getDisplayName()); + return this.getDisplayName().compareToIgnoreCase( + another.getDisplayName()); } @Override public String getDisplayName() { - if (this.mJoinedConversation!=null && (this.mJoinedConversation.getMucOptions().getSubject() != null)) { + if (this.mJoinedConversation != null + && (this.mJoinedConversation.getMucOptions().getSubject() != null)) { return this.mJoinedConversation.getMucOptions().getSubject(); - } else if (name!=null) { + } else if (name != null) { return name; } else { return this.jid.split("@")[0]; @@ -69,11 +72,11 @@ public class Bookmark implements ListItem { public String getJid() { return this.jid.toLowerCase(Locale.US); } - + public String getNick() { return this.nick; } - + public boolean autojoin() { return autojoin; } @@ -81,8 +84,8 @@ public class Bookmark implements ListItem { public boolean match(String needle) { return needle == null || getJid().contains(needle.toLowerCase(Locale.US)) - || getDisplayName().toLowerCase(Locale.US) - .contains(needle.toLowerCase(Locale.US)); + || getDisplayName().toLowerCase(Locale.US).contains( + needle.toLowerCase(Locale.US)); } public Account getAccount() { @@ -91,10 +94,12 @@ public class Bookmark implements ListItem { @Override public Bitmap getImage(int dpSize, Context context) { - if (this.mJoinedConversation==null) { - return UIHelper.getContactPicture(getDisplayName(), dpSize, context, false); + if (this.mJoinedConversation == null) { + return UIHelper.getContactPicture(getDisplayName(), dpSize, + context, false); } else { - return UIHelper.getContactPicture(this.mJoinedConversation, dpSize, context, false); + return UIHelper.getContactPicture(this.mJoinedConversation, dpSize, + context, false); } } @@ -105,7 +110,7 @@ public class Bookmark implements ListItem { public String getName() { return name; } - + public Element toElement() { Element element = new Element("conference"); element.setAttribute("jid", this.getJid()); diff --git a/src/eu/siacs/conversations/entities/Contact.java b/src/eu/siacs/conversations/entities/Contact.java index 5892563d..8a6fd99c 100644 --- a/src/eu/siacs/conversations/entities/Contact.java +++ b/src/eu/siacs/conversations/entities/Contact.java @@ -109,7 +109,7 @@ public class Contact implements ListItem { values.put(SYSTEMACCOUNT, systemAccount); values.put(PHOTOURI, photoUri); values.put(KEYS, keys.toString()); - values.put(AVATAR,avatar); + values.put(AVATAR, avatar); return values; } @@ -141,7 +141,7 @@ public class Contact implements ListItem { public Account getAccount() { return this.account; } - + public Presences getPresences() { return this.presences; } @@ -316,7 +316,8 @@ public class Contact implements ListItem { @Override public int compareTo(ListItem another) { - return this.getDisplayName().compareToIgnoreCase(another.getDisplayName()); + return this.getDisplayName().compareToIgnoreCase( + another.getDisplayName()); } public String getServer() { @@ -330,9 +331,9 @@ public class Contact implements ListItem { @Override public Bitmap getImage(int size, Context context) { - if (this.avatar!=null) { + if (this.avatar != null) { Bitmap bm = FileBackend.getAvatar(avatar, size, context); - if (bm==null) { + if (bm == null) { return UIHelper.getContactPicture(this, size, context, false); } else { return bm; diff --git a/src/eu/siacs/conversations/entities/Conversation.java b/src/eu/siacs/conversations/entities/Conversation.java index 439f9f22..2097b2a4 100644 --- a/src/eu/siacs/conversations/entities/Conversation.java +++ b/src/eu/siacs/conversations/entities/Conversation.java @@ -16,6 +16,7 @@ import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; +import android.os.SystemClock; public class Conversation extends AbstractEntity { public static final String TABLENAME = "conversations"; @@ -42,6 +43,8 @@ public class Conversation extends AbstractEntity { private int status; private long created; private int mode; + + private long mutedTill = 0; private String nextPresence; @@ -88,7 +91,8 @@ public class Conversation extends AbstractEntity { public List<Message> getMessages() { if (messages == null) { - this.messages = new CopyOnWriteArrayList<Message>(); // prevent null pointer + this.messages = new CopyOnWriteArrayList<Message>(); // prevent null + // pointer } // populate with Conversation (this) @@ -140,9 +144,8 @@ public class Conversation extends AbstractEntity { this.messages = msgs; } - public String getName(boolean useSubject) { - if ((getMode() == MODE_MULTI) && (getMucOptions().getSubject() != null) - && useSubject) { + public String getName() { + if (getMode() == MODE_MULTI && getMucOptions().getSubject() != null) { return getMucOptions().getSubject(); } else if (getMode() == MODE_MULTI && bookmark != null && bookmark.getName() != null) { @@ -287,7 +290,7 @@ public class Conversation extends AbstractEntity { public String getOtrFingerprint() { if (this.otrFingerprint == null) { try { - if (getOtrSession()== null) { + if (getOtrSession() == null) { return ""; } DSAPublicKey remotePubKey = (DSAPublicKey) getOtrSession() @@ -403,19 +406,27 @@ public class Conversation extends AbstractEntity { } public Bitmap getImage(Context context, int size) { - if (mode==MODE_SINGLE) { + if (mode == MODE_SINGLE) { return getContact().getImage(size, context); } else { return UIHelper.getContactPicture(this, size, context, false); } } - + public boolean hasDuplicateMessage(Message message) { - for(int i = this.getMessages().size() -1; i >= 0; --i) { + 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 mutedTill) { + this.mutedTill = mutedTill; + } + + public boolean isMuted() { + return SystemClock.elapsedRealtime() < this.mutedTill; + } } diff --git a/src/eu/siacs/conversations/entities/ListItem.java b/src/eu/siacs/conversations/entities/ListItem.java index c89c85d9..19089b28 100644 --- a/src/eu/siacs/conversations/entities/ListItem.java +++ b/src/eu/siacs/conversations/entities/ListItem.java @@ -5,6 +5,8 @@ import android.graphics.Bitmap; public interface ListItem extends Comparable<ListItem> { public String getDisplayName(); + public String getJid(); + public Bitmap getImage(int dpSize, Context context); } diff --git a/src/eu/siacs/conversations/entities/Message.java b/src/eu/siacs/conversations/entities/Message.java index 9dea2f8a..f36ffa90 100644 --- a/src/eu/siacs/conversations/entities/Message.java +++ b/src/eu/siacs/conversations/entities/Message.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.entities; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.xmpp.jingle.JingleConnection; import android.content.ContentValues; @@ -7,7 +8,7 @@ import android.content.Context; import android.database.Cursor; public class Message extends AbstractEntity { - + public static final String TABLENAME = "messages"; public static final int STATUS_RECEPTION_FAILED = -3; @@ -28,7 +29,7 @@ public class Message extends AbstractEntity { 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; @@ -58,27 +59,32 @@ public class Message extends AbstractEntity { protected String remoteMsgId = null; protected transient Conversation conversation = null; - + protected transient JingleConnection jingleConnection = 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); + 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); + + 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) { + + 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; @@ -97,20 +103,20 @@ public class Message extends AbstractEntity { values.put(UUID, uuid); values.put(CONVERSATION, conversationUuid); values.put(COUNTERPART, counterpart); - values.put(TRUE_COUNTERPART,trueCounterpart); + 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); + values.put(REMOTE_MSG_ID, remoteMsgId); return values; } public String getConversationUuid() { return conversationUuid; } - + public Conversation getConversation() { return this.conversation; } @@ -118,7 +124,7 @@ public class Message extends AbstractEntity { public String getCounterpart() { return counterpart; } - + public Contact getContact() { if (this.conversation.getMode() == Conversation.MODE_SINGLE) { return this.conversation.getContact(); @@ -127,7 +133,8 @@ public class Message extends AbstractEntity { return null; } else { Account account = this.conversation.getAccount(); - Contact contact = account.getRoster().getContact(this.trueCounterpart); + Contact contact = account.getRoster().getContact( + this.trueCounterpart); if (contact.showInRoster()) { return contact; } else { @@ -140,16 +147,16 @@ public class Message extends AbstractEntity { public String getBody() { return body; } - + public String getReadableBody(Context context) { - if ((encryption == ENCRYPTION_PGP)&&(type == TYPE_TEXT)) { - return ""+context.getText(R.string.encrypted_message_received); - } else if ((encryption == ENCRYPTION_OTR)&&(type == TYPE_IMAGE)) { - return ""+context.getText(R.string.encrypted_image_received); + if ((encryption == ENCRYPTION_PGP) && (type == TYPE_TEXT)) { + return context.getText(R.string.encrypted_message_received).toString(); + } else if ((encryption == ENCRYPTION_OTR) && (type == TYPE_IMAGE)) { + return context.getText(R.string.encrypted_image_received).toString(); } else if (encryption == ENCRYPTION_DECRYPTION_FAILED) { - return ""+context.getText(R.string.decryption_failed); + return context.getText(R.string.decryption_failed).toString(); } else if (type == TYPE_IMAGE) { - return ""+context.getText(R.string.image_file); + return context.getText(R.string.image_file).toString(); } else { return body.trim(); } @@ -166,11 +173,11 @@ public class Message extends AbstractEntity { public int getStatus() { return status; } - + public String getRemoteMsgId() { return this.remoteMsgId; } - + public void setRemoteMsgId(String id) { this.remoteMsgId = id; } @@ -199,11 +206,11 @@ public class Message extends AbstractEntity { public boolean isRead() { return this.read; } - + public void markRead() { this.read = true; } - + public void markUnread() { this.read = false; } @@ -223,7 +230,7 @@ public class Message extends AbstractEntity { public String getEncryptedBody() { return this.encryptedBody; } - + public void setEncryptedBody(String body) { this.encryptedBody = body; } @@ -231,7 +238,7 @@ public class Message extends AbstractEntity { public void setType(int type) { this.type = type; } - + public int getType() { return this.type; } @@ -243,11 +250,11 @@ public class Message extends AbstractEntity { this.counterpart = this.counterpart.split("/")[0] + "/" + presence; } } - + public void setTrueCounterpart(String trueCounterpart) { this.trueCounterpart = trueCounterpart; } - + public String getPresence() { String[] counterparts = this.counterpart.split("/"); if (counterparts.length == 2) { @@ -256,15 +263,15 @@ public class Message extends AbstractEntity { return null; } } - + public void setJingleConnection(JingleConnection connection) { this.jingleConnection = connection; } - + public JingleConnection getJingleConnection() { return this.jingleConnection; } - + public static Message createStatusMessage(Conversation conversation) { Message message = new Message(); message.setType(Message.TYPE_STATUS); @@ -275,12 +282,85 @@ public class Message extends AbstractEntity { 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()); + 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() { + int index = this.conversation.getMessages().indexOf(this); + if (index < 0 || index >= this.conversation.getMessages().size() - 1) { + return null; + } else { + return this.conversation.getMessages().get(index + 1); + } + } + + public Message prev() { + int index = this.conversation.getMessages().indexOf(this); + if (index <= 0 || index > this.conversation.getMessages().size()) { + return null; + } else { + return this.conversation.getMessages().get(index - 1); + } + } + + public boolean mergable(Message message) { + if (message == null) { + return false; + } + return (message.getType() == Message.TYPE_TEXT + && 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); + } + } } diff --git a/src/eu/siacs/conversations/entities/MucOptions.java b/src/eu/siacs/conversations/entities/MucOptions.java index 61b2732d..72fbeeab 100644 --- a/src/eu/siacs/conversations/entities/MucOptions.java +++ b/src/eu/siacs/conversations/entities/MucOptions.java @@ -14,6 +14,7 @@ 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 interface OnRenameListener { public void onRename(boolean success); @@ -106,6 +107,7 @@ public class MucOptions { private User self = new User(); private String subject = null; private String joinnick; + private String password = null; public MucOptions(Account account) { this.account = account; @@ -186,6 +188,8 @@ public class MucOptions { } else { this.error = ERROR_NICK_IN_USE; } + } else if (error.hasChild("not-authorized")) { + this.error = ERROR_PASSWORD_REQUIRED; } } } @@ -299,13 +303,21 @@ public class MucOptions { return this.conversation.getContactJid().split("/")[0] + "/" + this.joinnick; } - + public String getTrueCounterpart(String counterpart) { - for(User user : this.getUsers()) { + for (User user : this.getUsers()) { if (user.getName().equals(counterpart)) { return user.getJid(); } } return null; } + + public String getPassword() { + return this.password; + } + + public void setPassword(String password) { + this.password = password; + } }
\ No newline at end of file diff --git a/src/eu/siacs/conversations/entities/Presences.java b/src/eu/siacs/conversations/entities/Presences.java index acd80735..b5899847 100644 --- a/src/eu/siacs/conversations/entities/Presences.java +++ b/src/eu/siacs/conversations/entities/Presences.java @@ -14,7 +14,7 @@ public class Presences { public static final int XA = 2; public static final int DND = 3; public static final int OFFLINE = 4; - + private Hashtable<String, Integer> presences = new Hashtable<String, Integer>(); public Hashtable<String, Integer> getPresences() { @@ -28,23 +28,24 @@ public class Presences { public void removePresence(String resource) { this.presences.remove(resource); } - + public void clearPresences() { this.presences.clear(); } - + public int getMostAvailableStatus() { int status = OFFLINE; Iterator<Entry<String, Integer>> it = presences.entrySet().iterator(); while (it.hasNext()) { Entry<String, Integer> entry = it.next(); - if (entry.getValue()<status) status = entry.getValue(); + if (entry.getValue() < status) + status = entry.getValue(); } return status; } public static int parseShow(Element show) { - if ((show == null)||(show.getContent() == null)) { + if ((show == null) || (show.getContent() == null)) { return Presences.ONLINE; } else if (show.getContent().equals("away")) { return Presences.AWAY; @@ -53,16 +54,16 @@ public class Presences { } else if (show.getContent().equals("chat")) { return Presences.CHAT; } else if (show.getContent().equals("dnd")) { - return Presences.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); diff --git a/src/eu/siacs/conversations/entities/Roster.java b/src/eu/siacs/conversations/entities/Roster.java index aa328664..c6212f77 100644 --- a/src/eu/siacs/conversations/entities/Roster.java +++ b/src/eu/siacs/conversations/entities/Roster.java @@ -9,16 +9,16 @@ public class Roster { Account account; ConcurrentHashMap<String, Contact> contacts = new ConcurrentHashMap<String, Contact>(); private String version = null; - + public Roster(Account account) { this.account = account; } - + public boolean hasContact(String jid) { String cleanJid = jid.split("/")[0]; return contacts.containsKey(cleanJid); } - + public Contact getContact(String jid) { String cleanJid = jid.split("/")[0].toLowerCase(Locale.getDefault()); if (contacts.containsKey(cleanJid)) { @@ -32,19 +32,19 @@ public class Roster { } public void clearPresences() { - for(Contact contact : getContacts()) { + for (Contact contact : getContacts()) { contact.clearPresences(); } } - + public void markAllAsNotInRoster() { - for(Contact contact : getContacts()) { + for (Contact contact : getContacts()) { contact.resetOption(Contact.Options.IN_ROSTER); } } - + public void clearSystemAccounts() { - for(Contact contact : getContacts()) { + for (Contact contact : getContacts()) { contact.setPhotoUri(null); contact.setSystemName(null); contact.setSystemAccount(null); @@ -58,13 +58,13 @@ public class Roster { public void initContact(Contact contact) { contact.setAccount(account); contact.setOption(Contact.Options.IN_ROSTER); - contacts.put(contact.getJid(),contact); + contacts.put(contact.getJid(), contact); } public void setVersion(String version) { this.version = version; } - + public String getVersion() { return this.version; } diff --git a/src/eu/siacs/conversations/generator/AbstractGenerator.java b/src/eu/siacs/conversations/generator/AbstractGenerator.java index d9839572..ecee8253 100644 --- a/src/eu/siacs/conversations/generator/AbstractGenerator.java +++ b/src/eu/siacs/conversations/generator/AbstractGenerator.java @@ -12,40 +12,29 @@ public abstract class AbstractGenerator { public 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", "urn:xmpp:receipts", + "urn:xmpp:chat-markers:0", "http://jabber.org/protocol/muc", + "jabber:x:conference", "http://jabber.org/protocol/caps", "http://jabber.org/protocol/disco#info", - "urn:xmpp:avatar:metadata+notify"}; - public final String IDENTITY_NAME = "Conversations 0.5"; + "urn:xmpp:avatar:metadata+notify" }; + public final String IDENTITY_NAME = "Conversations 0.6"; public final String IDENTITY_TYPE = "phone"; - /*public final String[] FEATURES = { "http://jabber.org/protocol/muc","http://jabber.org/protocol/disco#info", "http://jabber.org/protocol/disco#items", "http://jabber.org/protocol/caps" }; - - public final String IDENTITY_NAME = "Exodus 0.9.1"; - public final String IDENTITY_TYPE = "pc";*/ - - - - + public String getCapHash() { StringBuilder s = new StringBuilder(); - s.append("client/"+IDENTITY_TYPE+"//"+IDENTITY_NAME+"<"); + s.append("client/" + IDENTITY_TYPE + "//" + IDENTITY_NAME + "<"); MessageDigest md = null; - try { - md = MessageDigest.getInstance("SHA-1"); - } - catch(NoSuchAlgorithmException e) { - return null; - } - List<String> features = Arrays.asList(FEATURES); + try { + md = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + return null; + } + List<String> features = Arrays.asList(FEATURES); Collections.sort(features); - for(String feature : features) { - s.append(feature+"<"); - } + for (String feature : features) { + s.append(feature + "<"); + } byte[] sha1 = md.digest(s.toString().getBytes()); - return new String(Base64.encode(sha1, Base64.DEFAULT)).trim(); + return new String(Base64.encode(sha1, Base64.DEFAULT)).trim(); } } diff --git a/src/eu/siacs/conversations/generator/IqGenerator.java b/src/eu/siacs/conversations/generator/IqGenerator.java index 259538c2..b5ecafb5 100644 --- a/src/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/eu/siacs/conversations/generator/IqGenerator.java @@ -10,67 +10,69 @@ import eu.siacs.conversations.xmpp.stanzas.IqPacket; public class IqGenerator extends AbstractGenerator { - - public IqPacket discoResponse(IqPacket request) { IqPacket packet = new IqPacket(IqPacket.TYPE_RESULT); packet.setId(request.getId()); packet.setTo(request.getFrom()); - Element query = packet.addChild("query","http://jabber.org/protocol/disco#info"); + Element query = packet.addChild("query", + "http://jabber.org/protocol/disco#info"); query.setAttribute("node", request.query().getAttribute("node")); Element identity = query.addChild("identity"); - identity.setAttribute("category","client"); + identity.setAttribute("category", "client"); identity.setAttribute("type", this.IDENTITY_TYPE); identity.setAttribute("name", IDENTITY_NAME); List<String> features = Arrays.asList(FEATURES); Collections.sort(features); - for(String feature : features) { - query.addChild("feature").setAttribute("var",feature); + for (String feature : features) { + query.addChild("feature").setAttribute("var", feature); } return packet; } - + protected IqPacket publish(String node, Element item) { IqPacket packet = new IqPacket(IqPacket.TYPE_SET); - Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub"); + Element pubsub = packet.addChild("pubsub", + "http://jabber.org/protocol/pubsub"); Element publish = pubsub.addChild("publish"); publish.setAttribute("node", node); publish.addChild(item); return packet; } - + protected IqPacket retrieve(String node, Element item) { IqPacket packet = new IqPacket(IqPacket.TYPE_GET); - Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub"); - Element items = pubsub.addChild("items"); - items.setAttribute("node", node); - if (item!=null) { + Element pubsub = packet.addChild("pubsub", + "http://jabber.org/protocol/pubsub"); + Element items = pubsub.addChild("items"); + items.setAttribute("node", node); + if (item != null) { items.addChild(item); } return packet; } - + public IqPacket publishAvatar(Avatar avatar) { Element item = new Element("item"); item.setAttribute("id", avatar.sha1sum); - Element data = item.addChild("data","urn:xmpp:avatar:data"); + Element data = item.addChild("data", "urn:xmpp:avatar:data"); data.setContent(avatar.image); return publish("urn:xmpp:avatar:data", item); } - + public IqPacket publishAvatarMetadata(Avatar avatar) { Element item = new Element("item"); item.setAttribute("id", avatar.sha1sum); - Element metadata = item.addChild("metadata","urn:xmpp:avatar:metadata"); + Element metadata = item + .addChild("metadata", "urn:xmpp:avatar:metadata"); Element info = metadata.addChild("info"); - info.setAttribute("bytes",avatar.size); - info.setAttribute("id",avatar.sha1sum); - info.setAttribute("height",avatar.height); - info.setAttribute("width",avatar.height); + info.setAttribute("bytes", avatar.size); + info.setAttribute("id", avatar.sha1sum); + info.setAttribute("height", avatar.height); + info.setAttribute("width", avatar.height); info.setAttribute("type", avatar.type); - return publish("urn:xmpp:avatar:metadata",item); + return publish("urn:xmpp:avatar:metadata", item); } - + public IqPacket retrieveAvatar(Avatar avatar) { Element item = new Element("item"); item.setAttribute("id", avatar.sha1sum); @@ -81,7 +83,7 @@ public class IqGenerator extends AbstractGenerator { public IqPacket retrieveAvatarMetaData(String to) { IqPacket packet = retrieve("urn:xmpp:avatar:metadata", null); - if (to!=null) { + if (to != null) { packet.setTo(to); } return packet; diff --git a/src/eu/siacs/conversations/generator/MessageGenerator.java b/src/eu/siacs/conversations/generator/MessageGenerator.java index 26182aad..ea53432c 100644 --- a/src/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/eu/siacs/conversations/generator/MessageGenerator.java @@ -32,56 +32,56 @@ public class MessageGenerator { packet.setFrom(account.getFullJid()); packet.setId(message.getUuid()); if (addDelay) { - addDelay(packet,message.getTimeSent()); + addDelay(packet, message.getTimeSent()); } return packet; } - + private void addDelay(MessagePacket packet, long timestamp) { - final SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",Locale.US); + final SimpleDateFormat mDateFormat = new SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); Element delay = packet.addChild("delay", "urn:xmpp:delay"); Date date = new Date(timestamp); delay.setAttribute("stamp", mDateFormat.format(date)); } - + public MessagePacket generateOtrChat(Message message) { return generateOtrChat(message, false); } - + public MessagePacket generateOtrChat(Message message, boolean addDelay) { Session otrSession = message.getConversation().getOtrSession(); - if (otrSession==null) { + if (otrSession == null) { return null; } - MessagePacket packet = preparePacket(message,addDelay); + MessagePacket packet = preparePacket(message, addDelay); 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())); return packet; } catch (OtrException e) { return null; } } - + public MessagePacket generateChat(Message message) { return generateChat(message, false); } - + public MessagePacket generateChat(Message message, boolean addDelay) { - MessagePacket packet = preparePacket(message,addDelay); + MessagePacket packet = preparePacket(message, addDelay); packet.setBody(message.getBody()); return packet; } - + public MessagePacket generatePgpChat(Message message) { return generatePgpChat(message, false); } - + public MessagePacket generatePgpChat(Message message, boolean addDelay) { - MessagePacket packet = preparePacket(message,addDelay); + MessagePacket packet = preparePacket(message, addDelay); packet.setBody("This is an XEP-0027 encryted message"); if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { packet.addChild("x", "jabber:x:encrypted").setContent( @@ -100,7 +100,7 @@ public class MessageGenerator { error.addChild("not-acceptable"); return packet; } - + private MessagePacket generateError(MessagePacket origin) { MessagePacket packet = new MessagePacket(); packet.setId(origin.getId()); @@ -109,7 +109,7 @@ public class MessageGenerator { packet.setType(MessagePacket.TYPE_ERROR); return packet; } - + public MessagePacket confirm(Account account, String to, String id) { MessagePacket packet = new MessagePacket(); packet.setType(MessagePacket.TYPE_NORMAL); @@ -120,8 +120,9 @@ public class MessageGenerator { received.setAttribute("id", id); return packet; } - - public MessagePacket conferenceSubject(Conversation conversation,String subject) { + + public MessagePacket conferenceSubject(Conversation conversation, + String subject) { MessagePacket packet = new MessagePacket(); packet.setType(MessagePacket.TYPE_GROUPCHAT); packet.setTo(conversation.getContactJid().split("/")[0]); @@ -131,7 +132,7 @@ public class MessageGenerator { packet.setFrom(conversation.getAccount().getJid()); return packet; } - + public MessagePacket directInvite(Conversation conversation, String contact) { MessagePacket packet = new MessagePacket(); packet.setType(MessagePacket.TYPE_NORMAL); @@ -141,7 +142,7 @@ public class MessageGenerator { x.setAttribute("jid", conversation.getContactJid().split("/")[0]); return packet; } - + public MessagePacket invite(Conversation conversation, String contact) { MessagePacket packet = new MessagePacket(); packet.setTo(conversation.getContactJid().split("/")[0]); @@ -154,13 +155,14 @@ public class MessageGenerator { packet.addChild(x); return packet; } - - public MessagePacket received(Account account, MessagePacket originalMessage, String namespace) { + + public MessagePacket received(Account account, + MessagePacket originalMessage, String namespace) { MessagePacket receivedPacket = new MessagePacket(); receivedPacket.setType(MessagePacket.TYPE_NORMAL); receivedPacket.setTo(originalMessage.getFrom()); receivedPacket.setFrom(account.getFullJid()); - Element received = receivedPacket.addChild("received",namespace); + Element received = receivedPacket.addChild("received", namespace); received.setAttribute("id", originalMessage.getId()); return receivedPacket; } diff --git a/src/eu/siacs/conversations/generator/PresenceGenerator.java b/src/eu/siacs/conversations/generator/PresenceGenerator.java index b3431568..87e361f5 100644 --- a/src/eu/siacs/conversations/generator/PresenceGenerator.java +++ b/src/eu/siacs/conversations/generator/PresenceGenerator.java @@ -14,7 +14,7 @@ public class PresenceGenerator extends AbstractGenerator { packet.setAttribute("from", contact.getAccount().getJid()); return packet; } - + public PresencePacket requestPresenceUpdatesFrom(Contact contact) { return subscription("subscribe", contact); } @@ -41,9 +41,10 @@ public class PresenceGenerator extends AbstractGenerator { } String capHash = getCapHash(); if (capHash != null) { - Element cap = packet.addChild("c","http://jabber.org/protocol/caps"); + 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.siacs.eu"); cap.setAttribute("ver", capHash); } return packet; diff --git a/src/eu/siacs/conversations/parser/AbstractParser.java b/src/eu/siacs/conversations/parser/AbstractParser.java index 96d11508..25fcd921 100644 --- a/src/eu/siacs/conversations/parser/AbstractParser.java +++ b/src/eu/siacs/conversations/parser/AbstractParser.java @@ -13,17 +13,17 @@ import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; public abstract class AbstractParser { - + protected XmppConnectionService mXmppConnectionService; protected AbstractParser(XmppConnectionService service) { this.mXmppConnectionService = service; } - + protected long getTimestamp(Element packet) { long now = System.currentTimeMillis(); ArrayList<String> stamps = new ArrayList<String>(); - for(Element child : packet.getChildren()) { + for (Element child : packet.getChildren()) { if (child.getName().equals("delay")) { stamps.add(child.getAttribute("stamp").replace("Z", "+0000")); } @@ -33,17 +33,18 @@ public abstract class AbstractParser { try { String stamp = stamps.get(stamps.size() - 1); if (stamp.contains(".")) { - Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ",Locale.US) - .parse(stamp); - if (now<date.getTime()) { + Date date = new SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US) + .parse(stamp); + if (now < date.getTime()) { return now; } else { return date.getTime(); } } else { - Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ",Locale.US) - .parse(stamp); - if (now<date.getTime()) { + Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", + Locale.US).parse(stamp); + if (now < date.getTime()) { return now; } else { return date.getTime(); @@ -56,8 +57,9 @@ public abstract class AbstractParser { return now; } } - - protected void updateLastseen(Element packet, Account account, boolean presenceOverwrite) { + + protected void updateLastseen(Element packet, Account account, + boolean presenceOverwrite) { String[] fromParts = packet.getAttribute("from").split("/"); String from = fromParts[0]; String presence = null; @@ -70,19 +72,19 @@ public abstract class AbstractParser { long timestamp = getTimestamp(packet); if (timestamp >= contact.lastseen.time) { contact.lastseen.time = timestamp; - if ((presence!=null)&&(presenceOverwrite)) { + if ((presence != null) && (presenceOverwrite)) { contact.lastseen.presence = presence; } } } - + protected String avatarData(Element items) { Element item = items.findChild("item"); - if (item==null) { + if (item == null) { return null; } - Element data = item.findChild("data","urn:xmpp:avatar:data"); - if (data==null) { + Element data = item.findChild("data", "urn:xmpp:avatar:data"); + if (data == null) { return null; } return data.getContent(); diff --git a/src/eu/siacs/conversations/parser/IqParser.java b/src/eu/siacs/conversations/parser/IqParser.java index a22ff6a5..592b77a4 100644 --- a/src/eu/siacs/conversations/parser/IqParser.java +++ b/src/eu/siacs/conversations/parser/IqParser.java @@ -12,7 +12,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { public IqParser(XmppConnectionService service) { super(service); } - + public void rosterItems(Account account, Element query) { String version = query.getAttribute("ver"); if (version != null) { @@ -27,7 +27,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { if (!contact.getOption(Contact.Options.DIRTY_PUSH)) { contact.setServerName(name); } - if (subscription!=null) { + if (subscription != null) { if (subscription.equals("remove")) { contact.resetOption(Contact.Options.IN_ROSTER); contact.resetOption(Contact.Options.DIRTY_DELETE); @@ -42,14 +42,15 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { } mXmppConnectionService.updateRosterUi(); } - + public String avatarData(IqPacket packet) { - Element pubsub = packet.findChild("pubsub", "http://jabber.org/protocol/pubsub"); - if (pubsub==null) { + Element pubsub = packet.findChild("pubsub", + "http://jabber.org/protocol/pubsub"); + if (pubsub == null) { return null; } Element items = pubsub.findChild("items"); - if (items==null) { + if (items == null) { return null; } return super.avatarData(items); @@ -63,20 +64,19 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { Element query = packet.findChild("query"); this.rosterItems(account, query); } - } else if (packet - .hasChild("open", "http://jabber.org/protocol/ibb") - || packet - .hasChild("data", "http://jabber.org/protocol/ibb")) { - mXmppConnectionService.getJingleConnectionManager().deliverIbbPacket(account, packet); + } else if (packet.hasChild("open", "http://jabber.org/protocol/ibb") + || packet.hasChild("data", "http://jabber.org/protocol/ibb")) { + mXmppConnectionService.getJingleConnectionManager() + .deliverIbbPacket(account, packet); } else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) { - IqPacket response = mXmppConnectionService.getIqGenerator().discoResponse(packet); + IqPacket response = mXmppConnectionService.getIqGenerator() + .discoResponse(packet); account.getXmppConnection().sendIqPacket(response, null); } else { if ((packet.getType() == IqPacket.TYPE_GET) || (packet.getType() == IqPacket.TYPE_SET)) { - IqPacket response = packet - .generateRespone(IqPacket.TYPE_ERROR); + IqPacket response = packet.generateRespone(IqPacket.TYPE_ERROR); Element error = response.addChild("error"); error.setAttribute("type", "cancel"); error.addChild("feature-not-implemented", diff --git a/src/eu/siacs/conversations/parser/MessageParser.java b/src/eu/siacs/conversations/parser/MessageParser.java index 0d46074d..10859345 100644 --- a/src/eu/siacs/conversations/parser/MessageParser.java +++ b/src/eu/siacs/conversations/parser/MessageParser.java @@ -4,6 +4,7 @@ import android.os.SystemClock; 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; @@ -18,7 +19,7 @@ import eu.siacs.conversations.xmpp.stanzas.MessagePacket; public class MessageParser extends AbstractParser implements OnMessagePacketReceived { - private long lastCarbonMessageReceived = -XmppConnectionService.CARBON_GRACE_PERIOD; + private long lastCarbonMessageReceived = -(Config.CARBON_GRACE_PERIOD * 1000); public MessageParser(XmppConnectionService service) { super(service); @@ -173,7 +174,8 @@ public class MessageParser extends AbstractParser implements finishedMessage.setTrueCounterpart(conversation.getMucOptions() .getTrueCounterpart(counterPart)); } - if (packet.hasChild("delay") && conversation.hasDuplicateMessage(finishedMessage)) { + if (packet.hasChild("delay") + && conversation.hasDuplicateMessage(finishedMessage)) { return null; } finishedMessage.setTime(getTimestamp(packet)); @@ -198,7 +200,8 @@ public class MessageParser extends AbstractParser implements } Element message = forwarded.findChild("message"); if ((message == null) || (!message.hasChild("body"))) { - if (status == Message.STATUS_RECEIVED && message.getAttribute("from")!=null) { + if (status == Message.STATUS_RECEIVED + && message.getAttribute("from") != null) { parseNormal(message, account); } return null; @@ -220,7 +223,7 @@ public class MessageParser extends AbstractParser implements Conversation conversation = mXmppConnectionService .findOrCreateConversation(account, parts[0], false); conversation.setLatestMarkableMessageId(getMarkableMessageId(packet)); - + String pgpBody = getPgpBody(message); Message finishedMessage; if (pgpBody != null) { @@ -243,7 +246,7 @@ public class MessageParser extends AbstractParser implements return null; } } - + return finishedMessage; } @@ -307,16 +310,18 @@ public class MessageParser extends AbstractParser implements if (node != null) { if (node.equals("urn:xmpp:avatar:metadata")) { Avatar avatar = Avatar.parseMetadata(items); - if (avatar!=null) { + if (avatar != null) { avatar.owner = from; if (mXmppConnectionService.getFileBackend().isAvatarCached( avatar)) { if (account.getJid().equals(from)) { if (account.setAvatar(avatar.getFilename())) { - mXmppConnectionService.databaseBackend.updateAccount(account); + mXmppConnectionService.databaseBackend + .updateAccount(account); } } else { - Contact contact = account.getRoster().getContact(from); + Contact contact = account.getRoster().getContact( + from); contact.setAvatar(avatar.getFilename()); } } else { @@ -324,11 +329,11 @@ public class MessageParser extends AbstractParser implements } } } else { - Log.d("xmppService", account.getJid() + ": " + node + " from " + Log.d(Config.LOGTAG, account.getJid() + ": " + node + " from " + from); } } else { - Log.d("xmppService", event.toString()); + Log.d(Config.LOGTAG, event.toString()); } } @@ -355,7 +360,7 @@ public class MessageParser extends AbstractParser implements boolean notify = true; if (mXmppConnectionService.getPreferences().getBoolean( "notification_grace_period_after_carbon_received", true)) { - notify = (SystemClock.elapsedRealtime() - lastCarbonMessageReceived) > XmppConnectionService.CARBON_GRACE_PERIOD; + notify = (SystemClock.elapsedRealtime() - lastCarbonMessageReceived) > (Config.CARBON_GRACE_PERIOD * 1000); } if ((packet.getType() == MessagePacket.TYPE_CHAT)) { @@ -430,6 +435,7 @@ public class MessageParser extends AbstractParser implements if (packet.getType() != MessagePacket.TYPE_ERROR) { mXmppConnectionService.databaseBackend.createMessage(message); } + notify = notify && !conversation.isMuted(); mXmppConnectionService.notifyUi(conversation, notify); } diff --git a/src/eu/siacs/conversations/parser/PresenceParser.java b/src/eu/siacs/conversations/parser/PresenceParser.java index 9188924f..18c23060 100644 --- a/src/eu/siacs/conversations/parser/PresenceParser.java +++ b/src/eu/siacs/conversations/parser/PresenceParser.java @@ -26,7 +26,7 @@ public class PresenceParser extends AbstractParser implements if (muc != null) { boolean before = muc.getMucOptions().online(); muc.getMucOptions().processPacket(packet, mPgpEngine); - if (before!=muc.getMucOptions().online()) { + if (before != muc.getMucOptions().online()) { mXmppConnectionService.updateConversationUi(); } } @@ -36,7 +36,7 @@ public class PresenceParser extends AbstractParser implements if (muc != null) { boolean before = muc.getMucOptions().online(); muc.getMucOptions().processPacket(packet, mPgpEngine); - if (before!=muc.getMucOptions().online()) { + if (before != muc.getMucOptions().online()) { mXmppConnectionService.updateConversationUi(); } } diff --git a/src/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/eu/siacs/conversations/persistance/DatabaseBackend.java index 62736ffa..2a403fab 100644 --- a/src/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -13,7 +13,6 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; -import android.util.Log; public class DatabaseBackend extends SQLiteOpenHelper { @@ -138,7 +137,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { public CopyOnWriteArrayList<Conversation> getConversations(int status) { CopyOnWriteArrayList<Conversation> list = new CopyOnWriteArrayList<Conversation>(); SQLiteDatabase db = this.getReadableDatabase(); - String[] selectionArgs = { "" + status }; + String[] selectionArgs = { Integer.toString(status) }; Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME + " where " + Conversation.STATUS + " = ? order by " + Conversation.CREATED + " desc", selectionArgs); @@ -164,7 +163,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { + "=?", selectionArgs, null, null, Message.TIME_SENT + " DESC", String.valueOf(limit)); } else { - String[] selectionArgs = { conversation.getUuid(), "" + timestamp }; + String[] selectionArgs = { conversation.getUuid(), Long.toString(timestamp) }; cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION + "=? and " + Message.TIME_SENT + "<?", selectionArgs, null, null, Message.TIME_SENT + " DESC", @@ -203,7 +202,6 @@ public class DatabaseBackend extends SQLiteOpenHelper { SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor = db.query(Account.TABLENAME, null, null, null, null, null, null); - Log.d("gultsch", "found " + cursor.getCount() + " accounts"); while (cursor.moveToNext()) { list.add(Account.fromCursor(cursor)); } diff --git a/src/eu/siacs/conversations/persistance/FileBackend.java b/src/eu/siacs/conversations/persistance/FileBackend.java index 8fdc7ee7..2b2aa86e 100644 --- a/src/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/eu/siacs/conversations/persistance/FileBackend.java @@ -29,6 +29,7 @@ import android.util.Base64; import android.util.Base64OutputStream; import android.util.Log; import android.util.LruCache; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; @@ -44,8 +45,9 @@ public class FileBackend { private Context context; private LruCache<String, Bitmap> thumbnailCache; - - private SimpleDateFormat imageDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS",Locale.US); + + private SimpleDateFormat imageDateFormat = new SimpleDateFormat( + "yyyyMMdd_HHmmssSSS", Locale.US); public FileBackend(Context context) { this.context = context; @@ -85,14 +87,15 @@ public class FileBackend { } return new JingleFile(path + "/" + filename); } - + public JingleFile getJingleFile(Message message) { return getJingleFile(message, true); } public JingleFile getJingleFile(Message message, boolean decrypted) { StringBuilder filename = new StringBuilder(); - filename.append(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath()); + filename.append(Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_PICTURES).getAbsolutePath()); filename.append("/Conversations/"); filename.append(message.getUuid()); if ((decrypted) || (message.getEncryption() == Message.ENCRYPTION_NONE)) { @@ -106,7 +109,7 @@ public class FileBackend { } return new JingleFile(filename.toString()); } - + public Bitmap resize(Bitmap originalBitmap, int size) { int w = originalBitmap.getWidth(); int h = originalBitmap.getHeight(); @@ -144,14 +147,15 @@ public class FileBackend { private JingleFile copyImageToPrivateStorage(Message message, Uri image, int sampleSize) throws ImageCopyException { try { - InputStream is = context.getContentResolver().openInputStream(image); + InputStream is = context.getContentResolver() + .openInputStream(image); JingleFile file = getJingleFile(message); file.getParentFile().mkdirs(); file.createNewFile(); Bitmap originalBitmap; BitmapFactory.Options options = new BitmapFactory.Options(); int inSampleSize = (int) Math.pow(2, sampleSize); - Log.d("xmppService", "reading bitmap with sample size " + Log.d(Config.LOGTAG, "reading bitmap with sample size " + inSampleSize); options.inSampleSize = inSampleSize; originalBitmap = BitmapFactory.decodeStream(is, null, options); @@ -176,7 +180,7 @@ public class FileBackend { long size = file.getSize(); int width = scalledBitmap.getWidth(); int height = scalledBitmap.getHeight(); - message.setBody("" + size + "," + width + "," + height); + message.setBody(Long.toString(size) + ',' + width + ',' + height); return file; } catch (FileNotFoundException e) { throw new ImageCopyException(R.string.error_file_not_found); @@ -194,17 +198,23 @@ public class FileBackend { } } } - + private int getRotation(Uri image) { if ("content".equals(image.getScheme())) { - Cursor cursor = context.getContentResolver().query(image, - new String[] { MediaStore.Images.ImageColumns.ORIENTATION }, null, null, null); - - if (cursor.getCount() != 1) { - return -1; - } - cursor.moveToFirst(); - return cursor.getInt(0); + try { + Cursor cursor = context + .getContentResolver() + .query(image, + new String[] { MediaStore.Images.ImageColumns.ORIENTATION }, + null, null, null); + if (cursor.getCount() != 1) { + return -1; + } + cursor.moveToFirst(); + return cursor.getInt(0); + } catch (IllegalArgumentException e) { + return -1; + } } else { ExifInterface exif; try { @@ -258,7 +268,7 @@ public class FileBackend { try { this.deleteFile(file); } catch (IOException e) { - Log.d("xmppService", + Log.d(Config.LOGTAG, "error deleting file: " + file.getAbsolutePath()); } } @@ -273,28 +283,32 @@ public class FileBackend { public Uri getTakePhotoUri() { StringBuilder pathBuilder = new StringBuilder(); - pathBuilder.append(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)); + pathBuilder.append(Environment + .getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)); pathBuilder.append('/'); pathBuilder.append("Camera"); pathBuilder.append('/'); - pathBuilder.append("IMG_"+this.imageDateFormat.format(new Date())+".jpg"); - Uri uri = Uri.parse("file://"+pathBuilder.toString()); + pathBuilder.append("IMG_" + this.imageDateFormat.format(new Date()) + + ".jpg"); + Uri uri = Uri.parse("file://" + pathBuilder.toString()); File file = new File(uri.toString()); file.getParentFile().mkdirs(); return uri; } - + public Avatar getPepAvatar(Uri image, int size, Bitmap.CompressFormat format) { try { Avatar avatar = new Avatar(); Bitmap bm = cropCenterSquare(image, size); - if (bm==null) { + if (bm == null) { return null; } ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream(); - Base64OutputStream mBase64OutputSttream = new Base64OutputStream(mByteArrayOutputStream, Base64.DEFAULT); + Base64OutputStream mBase64OutputSttream = new Base64OutputStream( + mByteArrayOutputStream, Base64.DEFAULT); MessageDigest digest = MessageDigest.getInstance("SHA-1"); - DigestOutputStream mDigestOutputStream = new DigestOutputStream(mBase64OutputSttream, digest); + DigestOutputStream mDigestOutputStream = new DigestOutputStream( + mBase64OutputSttream, digest); if (!bm.compress(format, 75, mDigestOutputStream)) { return null; } @@ -309,25 +323,26 @@ public class FileBackend { return null; } } - + public boolean isAvatarCached(Avatar avatar) { File file = new File(getAvatarPath(context, avatar.getFilename())); return file.exists(); } - + public boolean save(Avatar avatar) { if (isAvatarCached(avatar)) { return true; } String filename = getAvatarPath(context, avatar.getFilename()); - File file = new File(filename+".tmp"); + File file = new File(filename + ".tmp"); file.getParentFile().mkdirs(); try { file.createNewFile(); FileOutputStream mFileOutputStream = new FileOutputStream(file); MessageDigest digest = MessageDigest.getInstance("SHA-1"); digest.reset(); - DigestOutputStream mDigestOutputStream = new DigestOutputStream(mFileOutputStream, digest); + DigestOutputStream mDigestOutputStream = new DigestOutputStream( + mFileOutputStream, digest); mDigestOutputStream.write(avatar.getImageAsBytes()); mDigestOutputStream.flush(); mDigestOutputStream.close(); @@ -337,7 +352,7 @@ public class FileBackend { file.renameTo(new File(filename)); return true; } else { - Log.d("xmppService","sha1sum mismatch for "+avatar.owner); + Log.d(Config.LOGTAG, "sha1sum mismatch for " + avatar.owner); file.delete(); return false; } @@ -349,9 +364,9 @@ public class FileBackend { return false; } } - + public static String getAvatarPath(Context context, String avatar) { - return context.getFilesDir().getAbsolutePath() + "/avatars/"+avatar; + return context.getFilesDir().getAbsolutePath() + "/avatars/" + avatar; } public Bitmap cropCenterSquare(Uri image, int size) { @@ -361,16 +376,20 @@ public class FileBackend { InputStream is = context.getContentResolver() .openInputStream(image); Bitmap input = BitmapFactory.decodeStream(is, null, options); - if (input==null) { + if (input == null) { return null; } else { + int rotation = getRotation(image); + if (rotation > 0) { + input = rotate(input, rotation); + } return cropCenterSquare(input, size); } } catch (FileNotFoundException e) { return null; } } - + public static Bitmap cropCenterSquare(Bitmap input, int size) { int w = input.getWidth(); int h = input.getHeight(); @@ -381,8 +400,7 @@ public class FileBackend { float outHeight = scale * h; float left = (size - outWidth) / 2; float top = (size - outHeight) / 2; - RectF target = new RectF(left, top, left + outWidth, top - + outHeight); + RectF target = new RectF(left, top, left + outWidth, top + outHeight); Bitmap output = Bitmap.createBitmap(size, size, input.getConfig()); Canvas canvas = new Canvas(output); @@ -412,11 +430,11 @@ public class FileBackend { return inSampleSize; } - + public Uri getJingleFileUri(Message message) { File file = getJingleFile(message); if (file.exists()) { - return Uri.parse("file://"+file.getAbsolutePath()); + return Uri.parse("file://" + file.getAbsolutePath()); } else { return ImageProvider.getProviderUri(message); } @@ -436,8 +454,9 @@ public class FileBackend { } public static Bitmap getAvatar(String avatar, int size, Context context) { - Bitmap bm = BitmapFactory.decodeFile(FileBackend.getAvatarPath(context, avatar)); - if (bm==null) { + Bitmap bm = BitmapFactory.decodeFile(FileBackend.getAvatarPath(context, + avatar)); + if (bm == null) { return null; } return cropCenterSquare(bm, UIHelper.getRealPx(size, context)); diff --git a/src/eu/siacs/conversations/services/Defaults.java b/src/eu/siacs/conversations/services/Defaults.java deleted file mode 100644 index c942dd48..00000000 --- a/src/eu/siacs/conversations/services/Defaults.java +++ /dev/null @@ -1,11 +0,0 @@ -package eu.siacs.conversations.services; - -import android.graphics.Bitmap; - -public final class Defaults { - public static final int AVATAR_SIZE = 192; - public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.WEBP; - private Defaults() { - - } -} diff --git a/src/eu/siacs/conversations/services/EventReceiver.java b/src/eu/siacs/conversations/services/EventReceiver.java index a13d51f2..c0bf67f3 100644 --- a/src/eu/siacs/conversations/services/EventReceiver.java +++ b/src/eu/siacs/conversations/services/EventReceiver.java @@ -3,6 +3,7 @@ package eu.siacs.conversations.services; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; + public class EventReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { diff --git a/src/eu/siacs/conversations/services/ImageProvider.java b/src/eu/siacs/conversations/services/ImageProvider.java index 7ab57f5e..15b86802 100644 --- a/src/eu/siacs/conversations/services/ImageProvider.java +++ b/src/eu/siacs/conversations/services/ImageProvider.java @@ -2,8 +2,8 @@ package eu.siacs.conversations.services; import java.io.File; import java.io.FileNotFoundException; -import java.io.IOException; +import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; @@ -27,7 +27,7 @@ public class ImageProvider extends ContentProvider { DatabaseBackend databaseBackend = DatabaseBackend .getInstance(getContext()); String uuids = uri.getPath(); - Log.d("xmppService", "uuids = " + uuids+" mode="+mode); + Log.d(Config.LOGTAG, "uuids = " + uuids + " mode=" + mode); if (uuids == null) { throw new FileNotFoundException(); } @@ -37,21 +37,21 @@ public class ImageProvider extends ContentProvider { } String conversationUuid = uuidsSplited[1]; String messageUuid = uuidsSplited[2].split("\\.")[0]; - - Log.d("xmppService","messageUuid="+messageUuid); - + + Log.d(Config.LOGTAG, "messageUuid=" + messageUuid); + Conversation conversation = databaseBackend .findConversationByUuid(conversationUuid); if (conversation == null) { - throw new FileNotFoundException("conversation " + conversationUuid - + " could not be found"); + throw new FileNotFoundException("conversation " + + conversationUuid + " could not be found"); } Message message = databaseBackend.findMessageByUuid(messageUuid); if (message == null) { throw new FileNotFoundException("message " + messageUuid + " could not be found"); } - + Account account = databaseBackend.findAccountByUuid(conversation .getAccountUuid()); if (account == null) { @@ -60,7 +60,7 @@ public class ImageProvider extends ContentProvider { } message.setConversation(conversation); conversation.setAccount(account); - + File file = fileBackend.getJingleFileLegacy(message); pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); @@ -100,13 +100,10 @@ public class ImageProvider extends ContentProvider { public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) { return 0; } - + public static Uri getProviderUri(Message message) { - return Uri - .parse("content://eu.siacs.conversations.images/" - + message.getConversationUuid() - + "/" - + message.getUuid() - + ".webp"); + return Uri.parse("content://eu.siacs.conversations.images/" + + message.getConversationUuid() + "/" + message.getUuid() + + ".webp"); } }
\ No newline at end of file diff --git a/src/eu/siacs/conversations/services/XmppConnectionService.java b/src/eu/siacs/conversations/services/XmppConnectionService.java index c535f1a3..a294e970 100644 --- a/src/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/eu/siacs/conversations/services/XmppConnectionService.java @@ -20,6 +20,7 @@ import de.duenndns.ssl.MemorizingTrustManager; import net.java.otr4j.OtrException; import net.java.otr4j.session.Session; import net.java.otr4j.session.SessionStatus; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; import eu.siacs.conversations.entities.Account; @@ -49,6 +50,7 @@ import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.OnBindListener; import eu.siacs.conversations.xmpp.OnContactStatusChanged; import eu.siacs.conversations.xmpp.OnIqPacketReceived; +import eu.siacs.conversations.xmpp.OnMessageAcknowledged; import eu.siacs.conversations.xmpp.OnStatusChanged; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager; @@ -82,20 +84,13 @@ import android.util.Log; public class XmppConnectionService extends Service { - protected static final String LOGTAG = "xmppService"; public DatabaseBackend databaseBackend; private FileBackend fileBackend; public long startDate; - private static final int PING_MAX_INTERVAL = 300; - private static final int PING_MIN_INTERVAL = 30; - private static final int PING_TIMEOUT = 10; - private static final int CONNECT_TIMEOUT = 90; - public static final long CARBON_GRACE_PERIOD = 60000L; - private static String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts"; - + private MemorizingTrustManager mMemorizingTrustManager; private MessageParser mMessageParser = new MessageParser(this); @@ -103,7 +98,7 @@ public class XmppConnectionService extends Service { private IqParser mIqParser = new IqParser(this); private MessageGenerator mMessageGenerator = new MessageGenerator(); private PresenceGenerator mPresenceGenerator = new PresenceGenerator(); - + private List<Account> accounts; private CopyOnWriteArrayList<Conversation> conversations = null; private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager( @@ -118,7 +113,7 @@ public class XmppConnectionService extends Service { @Override public void onContactStatusChanged(Contact contact, boolean online) { - Conversation conversation = find(getConversations(),contact); + Conversation conversation = find(getConversations(), contact); if (conversation != null) { conversation.endOtrIfNeeded(); if (online && (contact.getPresences().size() == 1)) { @@ -146,14 +141,16 @@ public class XmppConnectionService extends Service { @Override public void onStatusChanged(Account account) { + XmppConnection connection = account.getXmppConnection(); if (mOnAccountUpdate != null) { - mOnAccountUpdate.onAccountUpdate();; + mOnAccountUpdate.onAccountUpdate(); + ; } if (account.getStatus() == Account.STATUS_ONLINE) { - for(Conversation conversation : account.pendingConferenceLeaves) { + for (Conversation conversation : account.pendingConferenceLeaves) { leaveMuc(conversation); } - for(Conversation conversation : account.pendingConferenceJoins) { + for (Conversation conversation : account.pendingConferenceJoins) { joinMuc(conversation); } mJingleConnectionManager.cancelInTransmission(); @@ -164,27 +161,36 @@ public class XmppConnectionService extends Service { sendUnsendMessages(conversations.get(i)); } } + if (connection != null && connection.getFeatures().csi()) { + if (checkListeners()) { + Log.d(Config.LOGTAG, account.getJid() + + " sending csi//inactive"); + connection.sendInactive(); + } else { + Log.d(Config.LOGTAG, account.getJid() + + " sending csi//active"); + connection.sendActive(); + } + } syncDirtyContacts(account); - scheduleWakeupCall(PING_MAX_INTERVAL, true); + scheduleWakeupCall(Config.PING_MAX_INTERVAL, true); } else if (account.getStatus() == Account.STATUS_OFFLINE) { + resetSendingToWaiting(account); if (!account.isOptionSet(Account.OPTION_DISABLED)) { int timeToReconnect = mRandom.nextInt(50) + 10; scheduleWakeupCall(timeToReconnect, false); } - } else if (account.getStatus() == Account.STATUS_REGISTRATION_SUCCESSFULL) { databaseBackend.updateAccount(account); reconnectAccount(account, true); } else if ((account.getStatus() != Account.STATUS_CONNECTING) && (account.getStatus() != Account.STATUS_NO_INTERNET)) { - XmppConnection connection = account.getXmppConnection(); - if (connection!=null) { + if (connection != null) { int next = connection.getTimeToNextAttempt(); - Log.d(LOGTAG, account.getJid() - + ": error connecting account. try again in " + next - + "s for the " - + (connection.getAttempt() + 1) - + " time"); + Log.d(Config.LOGTAG, account.getJid() + + ": error connecting account. try again in " + + next + "s for the " + + (connection.getAttempt() + 1) + " time"); scheduleWakeupCall((int) (next * 1.2), false); } } @@ -209,19 +215,39 @@ public class XmppConnectionService extends Service { private PowerManager pm; private OnBindListener mOnBindListener = new OnBindListener() { - @Override - public void onBind(final Account account) { - account.getRoster().clearPresences(); - account.clearPresences(); // self presences - account.pendingConferenceJoins.clear(); - account.pendingConferenceLeaves.clear(); - fetchRosterFromServer(account); - fetchBookmarks(account); - sendPresencePacket(account, mPresenceGenerator.sendPresence(account)); - connectMultiModeConversations(account); - updateConversationUi(); + @Override + public void onBind(final Account account) { + account.getRoster().clearPresences(); + account.clearPresences(); // self presences + account.pendingConferenceJoins.clear(); + account.pendingConferenceLeaves.clear(); + fetchRosterFromServer(account); + fetchBookmarks(account); + sendPresencePacket(account, + mPresenceGenerator.sendPresence(account)); + connectMultiModeConversations(account); + updateConversationUi(); + } + }; + + private OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() { + + @Override + public void onMessageAcknowledged(Account account, String uuid) { + for (Conversation conversation : getConversations()) { + if (conversation.getAccount() == account) { + for (Message message : conversation.getMessages()) { + if ((message.getStatus() == Message.STATUS_UNSEND || message + .getStatus() == Message.STATUS_WAITING) + && message.getUuid().equals(uuid)) { + markMessage(message, Message.STATUS_SEND); + return; + } + } + } } - }; + } + }; public PgpEngine getPgpEngine() { if (pgpServiceConnection.isBound()) { @@ -274,11 +300,11 @@ public class XmppConnectionService extends Service { } public Conversation find(Bookmark bookmark) { - return find(bookmark.getAccount(),bookmark.getJid()); + return find(bookmark.getAccount(), bookmark.getJid()); } - + public Conversation find(Account account, String jid) { - return find(getConversations(),account,jid); + return find(getConversations(), account, jid); } public class XmppConnectionBinder extends Binder { @@ -320,12 +346,15 @@ public class XmppConnectionService extends Service { } } if (account.getStatus() == Account.STATUS_ONLINE) { - long lastReceived = account.getXmppConnection().getLastPacketReceived(); - long lastSent = account.getXmppConnection().getLastPingSent(); - if (lastSent - lastReceived >= PING_TIMEOUT * 1000) { - Log.d(LOGTAG, account.getJid() + ": ping timeout"); + 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"); this.reconnectAccount(account, true); - } else if (SystemClock.elapsedRealtime() - lastReceived >= PING_MIN_INTERVAL * 1000) { + } else if (SystemClock.elapsedRealtime() - lastReceived >= Config.PING_MIN_INTERVAL * 1000) { account.getXmppConnection().sendPing(); this.scheduleWakeupCall(2, false); } @@ -337,8 +366,8 @@ public class XmppConnectionService extends Service { new Thread(account.getXmppConnection()).start(); } else if ((account.getStatus() == Account.STATUS_CONNECTING) && ((SystemClock.elapsedRealtime() - account - .getXmppConnection().getLastConnect()) / 1000 >= CONNECT_TIMEOUT)) { - Log.d(LOGTAG, account.getJid() + .getXmppConnection().getLastConnect()) / 1000 >= Config.CONNECT_TIMEOUT)) { + Log.d(Config.LOGTAG, account.getJid() + ": time out during connect reconnecting"); reconnectAccount(account, true); } else { @@ -347,7 +376,7 @@ public class XmppConnectionService extends Service { } } // in any case. reschedule wakup call - this.scheduleWakeupCall(PING_MAX_INTERVAL, true); + this.scheduleWakeupCall(Config.PING_MAX_INTERVAL, true); } if (mOnAccountUpdate != null) { mOnAccountUpdate.onAccountUpdate(); @@ -369,7 +398,8 @@ public class XmppConnectionService extends Service { ExceptionHelper.init(getApplicationContext()); PRNGFixes.apply(); this.mRandom = new SecureRandom(); - this.mMemorizingTrustManager = new MemorizingTrustManager(getApplicationContext()); + this.mMemorizingTrustManager = new MemorizingTrustManager( + getApplicationContext()); this.databaseBackend = DatabaseBackend .getInstance(getApplicationContext()); this.fileBackend = new FileBackend(getApplicationContext()); @@ -416,7 +446,7 @@ public class XmppConnectionService extends Service { .getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(context, EventReceiver.class); alarmManager.cancel(PendingIntent.getBroadcast(context, 0, intent, 0)); - Log.d(LOGTAG, "good bye"); + Log.d(Config.LOGTAG, "good bye"); stopSelf(); } @@ -466,10 +496,11 @@ public class XmppConnectionService extends Service { connection.setOnMessagePacketReceivedListener(this.mMessageParser); connection.setOnStatusChangedListener(this.statusListener); connection.setOnPresencePacketReceivedListener(this.mPresenceParser); - connection - .setOnUnregisteredIqPacketReceivedListener(this.mIqParser); + connection.setOnUnregisteredIqPacketReceivedListener(this.mIqParser); connection.setOnJinglePacketReceivedListener(this.jingleListener); connection.setOnBindListener(this.mOnBindListener); + connection + .setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener); return connection; } @@ -514,8 +545,7 @@ public class XmppConnectionService extends Service { .getUserID()); packet = mMessageGenerator.generateOtrChat(message); send = true; - message.setStatus(Message.STATUS_SEND); - + } else if (message.getPresence() == null) { message.setStatus(Message.STATUS_WAITING); } @@ -523,14 +553,10 @@ public class XmppConnectionService extends Service { message.getConversation().endOtrIfNeeded(); failWaitingOtrMessages(message.getConversation()); packet = mMessageGenerator.generatePgpChat(message); - message.setStatus(Message.STATUS_SEND); send = true; } else { message.getConversation().endOtrIfNeeded(); failWaitingOtrMessages(message.getConversation()); - if (message.getConversation().getMode() == Conversation.MODE_SINGLE || message.getType() == Message.TYPE_PRIVATE) { - message.setStatus(Message.STATUS_SEND); - } packet = mMessageGenerator.generateChat(message); send = true; } @@ -562,11 +588,14 @@ public class XmppConnectionService extends Service { databaseBackend.createMessage(message); } conv.getMessages().add(message); - updateConversationUi(); if ((send) && (packet != null)) { + if (!account.getXmppConnection().getFeatures().sm() + && conv.getMode() != Conversation.MODE_MULTI) { + message.setStatus(Message.STATUS_SEND); + } sendMessagePacket(account, packet); } - + updateConversationUi(); } private void sendUnsendMessages(Conversation conversation) { @@ -632,7 +661,7 @@ public class XmppConnectionService extends Service { } } if (packet != null) { - sendMessagePacket(account,packet); + sendMessagePacket(account, packet); markMessage(message, Message.STATUS_SEND); } } @@ -640,10 +669,10 @@ public class XmppConnectionService extends Service { public void fetchRosterFromServer(Account account) { IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET); if (!"".equals(account.getRosterVersion())) { - Log.d(LOGTAG, account.getJid() + ": fetching roster version " - + account.getRosterVersion()); + Log.d(Config.LOGTAG, account.getJid() + + ": fetching roster version " + account.getRosterVersion()); } else { - Log.d(LOGTAG, account.getJid() + ": fetching roster"); + Log.d(Config.LOGTAG, account.getJid() + ": fetching roster"); } iqPacket.query("jabber:iq:roster").setAttribute("ver", account.getRosterVersion()); @@ -661,29 +690,31 @@ public class XmppConnectionService extends Service { } }); } - + public void fetchBookmarks(Account account) { IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET); Element query = iqPacket.query("jabber:iq:private"); query.addChild("storage", "storage:bookmarks"); OnIqPacketReceived callback = new OnIqPacketReceived() { - + @Override public void onIqPacketReceived(Account account, IqPacket packet) { Element query = packet.query(); List<Bookmark> bookmarks = new CopyOnWriteArrayList<Bookmark>(); - Element storage = query.findChild("storage", "storage:bookmarks"); - if (storage!=null) { - for(Element item : storage.getChildren()) { + Element storage = query.findChild("storage", + "storage:bookmarks"); + if (storage != null) { + for (Element item : storage.getChildren()) { if (item.getName().equals("conference")) { - Bookmark bookmark = Bookmark.parse(item,account); + Bookmark bookmark = Bookmark.parse(item, account); bookmarks.add(bookmark); Conversation conversation = find(bookmark); - if (conversation!=null) { + if (conversation != null) { conversation.setBookmark(bookmark); } else { if (bookmark.autojoin()) { - conversation = findOrCreateConversation(account, bookmark.getJid(), true); + conversation = findOrCreateConversation( + account, bookmark.getJid(), true); conversation.setBookmark(bookmark); joinMuc(conversation); } @@ -695,17 +726,17 @@ public class XmppConnectionService extends Service { } }; sendIqPacket(account, iqPacket, callback); - + } - + public void pushBookmarks(Account account) { 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()) { + for (Bookmark bookmark : account.getBookmarks()) { storage.addChild(bookmark.toElement()); } - sendIqPacket(account, iqPacket,null); + sendIqPacket(account, iqPacket, null); } private void mergePhoneContactsWithRoster() { @@ -750,13 +781,25 @@ public class XmppConnectionService extends Service { conv.setMessages(databaseBackend.getMessages(conv, 50)); } } - + return this.conversations; } public void populateWithOrderedConversations(List<Conversation> list) { + populateWithOrderedConversations(list,true); + } + + public void populateWithOrderedConversations(List<Conversation> list, boolean includeConferences) { list.clear(); - list.addAll(getConversations()); + if (includeConferences) { + list.addAll(getConversations()); + } else { + for(Conversation conversation : getConversations()) { + if (conversation.getMode() == Conversation.MODE_SINGLE) { + list.add(conversation); + } + } + } Collections.sort(list, new Comparator<Conversation>() { @Override public int compare(Conversation lhs, Conversation rhs) { @@ -796,7 +839,8 @@ public class XmppConnectionService extends Service { return null; } - public Conversation find(List<Conversation> haystack, Account account, String jid) { + public Conversation find(List<Conversation> haystack, Account account, + String jid) { for (Conversation conversation : haystack) { if ((conversation.getAccount().equals(account)) && (conversation.getContactJid().split("/")[0].equals(jid))) { @@ -805,15 +849,14 @@ public class XmppConnectionService extends Service { } return null; } - - + public Conversation findOrCreateConversation(Account account, String jid, boolean muc) { Conversation conversation = find(account, jid); if (conversation != null) { return conversation; } - conversation = databaseBackend.findConversation(account,jid); + conversation = databaseBackend.findConversation(account, jid); if (conversation != null) { conversation.setStatus(Conversation.STATUS_AVAILABLE); conversation.setAccount(account); @@ -850,7 +893,7 @@ public class XmppConnectionService extends Service { public void archiveConversation(Conversation conversation) { if (conversation.getMode() == Conversation.MODE_MULTI) { Bookmark bookmark = conversation.getBookmark(); - if (bookmark!=null && bookmark.autojoin()) { + if (bookmark != null && bookmark.autojoin()) { bookmark.setAutojoin(false); pushBookmarks(bookmark.getAccount()); } @@ -890,7 +933,7 @@ public class XmppConnectionService extends Service { } public void deleteAccount(Account account) { - for(Conversation conversation : conversations) { + for (Conversation conversation : conversations) { if (conversation.getAccount() == account) { if (conversation.getMode() == Conversation.MODE_MULTI) { leaveMuc(conversation); @@ -911,6 +954,9 @@ public class XmppConnectionService extends Service { public void setOnConversationListChangedListener( OnConversationUpdate listener) { + if (checkListeners()) { + switchToForeground(); + } this.mOnConversationUpdate = listener; this.convChangedListenerCount++; } @@ -919,10 +965,16 @@ public class XmppConnectionService extends Service { this.convChangedListenerCount--; if (this.convChangedListenerCount == 0) { this.mOnConversationUpdate = null; + if (checkListeners()) { + switchToBackground(); + } } } public void setOnAccountListChangedListener(OnAccountUpdate listener) { + if (checkListeners()) { + switchToForeground(); + } this.mOnAccountUpdate = listener; this.accountChangedListenerCount++; } @@ -931,15 +983,55 @@ public class XmppConnectionService extends Service { this.accountChangedListenerCount--; if (this.accountChangedListenerCount == 0) { this.mOnAccountUpdate = null; + if (checkListeners()) { + switchToBackground(); + } } } - + public void setOnRosterUpdateListener(OnRosterUpdate listener) { + if (checkListeners()) { + switchToForeground(); + } this.mOnRosterUpdate = listener; } public void removeOnRosterUpdateListener() { this.mOnRosterUpdate = null; + if (checkListeners()) { + switchToBackground(); + } + } + + private boolean checkListeners() { + return (this.mOnAccountUpdate == null + && this.mOnConversationUpdate == null && this.mOnRosterUpdate == null); + } + + private void switchToForeground() { + for (Account account : getAccounts()) { + if (account.getStatus() == Account.STATUS_ONLINE) { + XmppConnection connection = account.getXmppConnection(); + if (connection != null && connection.getFeatures().csi()) { + connection.sendActive(); + Log.d(Config.LOGTAG, account.getJid() + + " sending csi//active"); + } + } + } + } + + private void switchToBackground() { + for (Account account : getAccounts()) { + if (account.getStatus() == Account.STATUS_ONLINE) { + XmppConnection connection = account.getXmppConnection(); + if (connection != null && connection.getFeatures().csi()) { + connection.sendInactive(); + Log.d(Config.LOGTAG, account.getJid() + + " sending csi//inactive"); + } + } + } } public void connectMultiModeConversations(Account account) { @@ -958,14 +1050,19 @@ public class XmppConnectionService extends Service { account.pendingConferenceJoins.remove(conversation); account.pendingConferenceLeaves.remove(conversation); if (account.getStatus() == Account.STATUS_ONLINE) { - Log.d(LOGTAG,"joining conversation "+conversation.getContactJid()); + Log.d(Config.LOGTAG, + "joining conversation " + conversation.getContactJid()); String nick = conversation.getMucOptions().getProposedNick(); conversation.getMucOptions().setJoinNick(nick); PresencePacket packet = new PresencePacket(); String joinJid = conversation.getMucOptions().getJoinJid(); - packet.setAttribute("to",conversation.getMucOptions().getJoinJid()); + packet.setAttribute("to", conversation.getMucOptions().getJoinJid()); Element x = new Element("x"); x.setAttribute("xmlns", "http://jabber.org/protocol/muc"); + if (conversation.getMucOptions().getPassword() != null) { + Element password = x.addChild("password"); + password.setContent(conversation.getMucOptions().getPassword()); + } String sig = account.getPgpSignature(); if (sig != null) { packet.addChild("status").setContent("online"); @@ -975,8 +1072,8 @@ public class XmppConnectionService extends Service { final SimpleDateFormat mDateFormat = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - Date date = new Date( - conversation.getLatestMessage().getTimeSent() + 1000); + Date date = new Date(conversation.getLatestMessage() + .getTimeSent() + 1000); x.addChild("history").setAttribute("since", mDateFormat.format(date)); } @@ -997,6 +1094,13 @@ public class XmppConnectionService extends Service { public void setOnRenameListener(OnRenameListener listener) { this.renameListener = listener; } + + public void providePasswordForMuc(Conversation conversation, String password) { + if (conversation.getMode() == Conversation.MODE_MULTI) { + conversation.getMucOptions().setPassword(password); + joinMuc(conversation); + } + } public void renameInMuc(final Conversation conversation, final String nick) { final MucOptions options = conversation.getMucOptions(); @@ -1011,10 +1115,11 @@ public class XmppConnectionService extends Service { renameListener.onRename(success); } if (success) { - conversation.setContactJid(conversation.getMucOptions().getJoinJid()); + conversation.setContactJid(conversation.getMucOptions() + .getJoinJid()); databaseBackend.updateConversation(conversation); Bookmark bookmark = conversation.getBookmark(); - if (bookmark!=null) { + if (bookmark != null) { bookmark.setNick(nick); pushBookmarks(bookmark.getAccount()); } @@ -1023,7 +1128,7 @@ public class XmppConnectionService extends Service { }); options.flagAboutToRename(); PresencePacket packet = new PresencePacket(); - packet.setAttribute("to",options.getJoinJid()); + packet.setAttribute("to", options.getJoinJid()); packet.setAttribute("from", conversation.getAccount().getFullJid()); String sig = account.getPgpSignature(); @@ -1031,13 +1136,13 @@ public class XmppConnectionService extends Service { packet.addChild("status").setContent("online"); packet.addChild("x", "jabber:x:signed").setContent(sig); } - sendPresencePacket(account,packet); + sendPresencePacket(account, packet); } else { conversation.setContactJid(options.getJoinJid()); databaseBackend.updateConversation(conversation); if (conversation.getAccount().getStatus() == Account.STATUS_ONLINE) { Bookmark bookmark = conversation.getBookmark(); - if (bookmark!=null) { + if (bookmark != null) { bookmark.setNick(nick); pushBookmarks(bookmark.getAccount()); } @@ -1055,10 +1160,11 @@ public class XmppConnectionService extends Service { packet.setAttribute("to", conversation.getMucOptions().getJoinJid()); packet.setAttribute("from", conversation.getAccount().getFullJid()); packet.setAttribute("type", "unavailable"); - sendPresencePacket(conversation.getAccount(),packet); + sendPresencePacket(conversation.getAccount(), packet); conversation.getMucOptions().setOffline(); conversation.deregisterWithBookmark(); - Log.d(LOGTAG,conversation.getAccount().getJid()+" leaving muc "+conversation.getContactJid()); + Log.d(Config.LOGTAG, conversation.getAccount().getJid() + + " leaving muc " + conversation.getContactJid()); } else { account.pendingConferenceLeaves.add(conversation); } @@ -1099,7 +1205,6 @@ public class XmppConnectionService extends Service { pushContactToServer(contact); } if (contact.getOption(Contact.Options.DIRTY_DELETE)) { - Log.d(LOGTAG, "dirty delete"); deleteContactOnServer(contact); } } @@ -1119,9 +1224,10 @@ public class XmppConnectionService extends Service { Account account = conversation.getAccount(); List<Message> messages = conversation.getMessages(); Session otrSession = conversation.getOtrSession(); - Log.d(LOGTAG, account.getJid() + " otr session established with " - + conversation.getContactJid() + "/" - + otrSession.getSessionID().getUserID()); + Log.d(Config.LOGTAG, + account.getJid() + " otr session established with " + + conversation.getContactJid() + "/" + + otrSession.getSessionID().getUserID()); for (int i = 0; i < messages.size(); ++i) { Message msg = messages.get(i); if ((msg.getStatus() == Message.STATUS_UNSEND || msg.getStatus() == Message.STATUS_WAITING) @@ -1133,7 +1239,7 @@ public class XmppConnectionService extends Service { if (outPacket != null) { msg.setStatus(Message.STATUS_SEND); databaseBackend.updateMessage(msg); - sendMessagePacket(account,outPacket); + sendMessagePacket(account, outPacket); } } else if (msg.getType() == Message.TYPE_IMAGE) { mJingleConnectionManager.createNewConnection(msg); @@ -1160,7 +1266,7 @@ public class XmppConnectionService extends Service { packet.setBody(otrSession .transformSending(CryptoHelper.FILETRANSFER + CryptoHelper.bytesToHex(symmetricKey))); - sendMessagePacket(account,packet); + sendMessagePacket(account, packet); conversation.setSymmetricKey(symmetricKey); return true; } catch (OtrException e) { @@ -1176,26 +1282,30 @@ public class XmppConnectionService extends Service { Account account = contact.getAccount(); if (account.getStatus() == Account.STATUS_ONLINE) { boolean ask = contact.getOption(Contact.Options.ASKING); - boolean sendUpdates = contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST) + boolean sendUpdates = contact + .getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST) && contact.getOption(Contact.Options.PREEMPTIVE_GRANT); IqPacket iq = new IqPacket(IqPacket.TYPE_SET); iq.query("jabber:iq:roster").addChild(contact.asElement()); account.getXmppConnection().sendIqPacket(iq, null); if (sendUpdates) { - sendPresencePacket(account, mPresenceGenerator.sendPresenceUpdatesTo(contact)); + sendPresencePacket(account, + mPresenceGenerator.sendPresenceUpdatesTo(contact)); } if (ask) { - sendPresencePacket(account, mPresenceGenerator.requestPresenceUpdatesFrom(contact)); + sendPresencePacket(account, + mPresenceGenerator.requestPresenceUpdatesFrom(contact)); } } } - - public void publishAvatar(Account account, Uri image, final UiCallback<Avatar> callback) { - final Bitmap.CompressFormat format = Defaults.AVATAR_FORMAT; - final int size = Defaults.AVATAR_SIZE; - final Avatar avatar = getFileBackend().getPepAvatar(image, size, format); - if (avatar!=null) { + public void publishAvatar(Account account, 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); + if (avatar != null) { avatar.height = size; avatar.width = size; if (format.equals(Bitmap.CompressFormat.WEBP)) { @@ -1211,27 +1321,33 @@ public class XmppConnectionService extends Service { } IqPacket packet = this.mIqGenerator.publishAvatar(avatar); this.sendIqPacket(account, packet, new OnIqPacketReceived() { - + @Override public void onIqPacketReceived(Account account, IqPacket result) { if (result.getType() == IqPacket.TYPE_RESULT) { - IqPacket packet = XmppConnectionService.this.mIqGenerator.publishAvatarMetadata(avatar); + IqPacket packet = XmppConnectionService.this.mIqGenerator + .publishAvatarMetadata(avatar); sendIqPacket(account, packet, new OnIqPacketReceived() { - + @Override - public void onIqPacketReceived(Account account, IqPacket result) { + public void onIqPacketReceived(Account account, + IqPacket result) { if (result.getType() == IqPacket.TYPE_RESULT) { if (account.setAvatar(avatar.getFilename())) { databaseBackend.updateAccount(account); } callback.success(avatar); } else { - callback.error(R.string.error_publish_avatar_server_reject, avatar); + callback.error( + R.string.error_publish_avatar_server_reject, + avatar); } } }); } else { - callback.error(R.string.error_publish_avatar_server_reject, avatar); + callback.error( + R.string.error_publish_avatar_server_reject, + avatar); } } }); @@ -1239,55 +1355,60 @@ public class XmppConnectionService extends Service { callback.error(R.string.error_publish_avatar_converting, null); } } - + public void fetchAvatar(Account account, Avatar avatar) { fetchAvatar(account, avatar, null); } - - public void fetchAvatar(Account account, final Avatar avatar, final UiCallback<Avatar> callback) { - Log.d(LOGTAG,account.getJid()+": retrieving avatar for "+avatar.owner); + + public void fetchAvatar(Account account, final Avatar avatar, + final UiCallback<Avatar> callback) { + Log.d(Config.LOGTAG, account.getJid() + ": retrieving avatar for " + + avatar.owner); IqPacket packet = this.mIqGenerator.retrieveAvatar(avatar); sendIqPacket(account, packet, new OnIqPacketReceived() { - + @Override public void onIqPacketReceived(Account account, IqPacket result) { avatar.image = mIqParser.avatarData(result); - if (avatar.image!=null) { + if (avatar.image != null) { if (getFileBackend().save(avatar)) { if (account.getJid().equals(avatar.owner)) { if (account.setAvatar(avatar.getFilename())) { databaseBackend.updateAccount(account); } } else { - Contact contact = account.getRoster().getContact(avatar.owner); + Contact contact = account.getRoster().getContact( + avatar.owner); contact.setAvatar(avatar.getFilename()); } - if (callback!=null) { + if (callback != null) { callback.success(avatar); } return; } } - if (callback!=null) { + if (callback != null) { callback.error(0, null); } } }); } - - public void checkForAvatar(Account account, final UiCallback<Avatar> callback) { + + public void checkForAvatar(Account account, + 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) { - Element pubsub = packet.findChild("pubsub", "http://jabber.org/protocol/pubsub"); - if (pubsub!=null) { + Element pubsub = packet.findChild("pubsub", + "http://jabber.org/protocol/pubsub"); + if (pubsub != null) { Element items = pubsub.findChild("items"); - if (items!=null) { + if (items != null) { Avatar avatar = Avatar.parseMetadata(items); - if (avatar!=null) { + if (avatar != null) { avatar.owner = account.getJid(); if (fileBackend.isAvatarCached(avatar)) { if (account.setAvatar(avatar.getFilename())) { @@ -1295,7 +1416,7 @@ public class XmppConnectionService extends Service { } callback.success(avatar); } else { - fetchAvatar(account, avatar,callback); + fetchAvatar(account, avatar, callback); } return; } @@ -1306,7 +1427,7 @@ public class XmppConnectionService extends Service { } }); } - + public void deleteContactOnServer(Contact contact) { contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); contact.resetOption(Contact.Options.DIRTY_PUSH); @@ -1339,7 +1460,8 @@ public class XmppConnectionService extends Service { } Thread thread = new Thread(account.getXmppConnection()); thread.start(); - scheduleWakeupCall((int) (CONNECT_TIMEOUT * 1.2), false); + scheduleWakeupCall((int) (Config.CONNECT_TIMEOUT * 1.2), + false); } } }).start(); @@ -1347,7 +1469,19 @@ public class XmppConnectionService extends Service { public void invite(Conversation conversation, String contact) { MessagePacket packet = mMessageGenerator.invite(conversation, contact); - sendMessagePacket(conversation.getAccount(),packet); + sendMessagePacket(conversation.getAccount(), packet); + } + + public void resetSendingToWaiting(Account account) { + for (Conversation conversation : getConversations()) { + if (conversation.getAccount() == account) { + for (Message message : conversation.getMessages()) { + if (message.getStatus() == Message.STATUS_UNSEND) { + markMessage(message, Message.STATUS_WAITING); + } + } + } + } } public boolean markMessage(Account account, String recipient, String uuid, @@ -1395,19 +1529,19 @@ public class XmppConnectionService extends Service { getConversations(), conversation, notify); } } - + public void updateConversationUi() { if (mOnConversationUpdate != null) { mOnConversationUpdate.onConversationUpdate(); } } - + public void updateAccountUi() { if (mOnAccountUpdate != null) { mOnAccountUpdate.onAccountUpdate(); } } - + public void updateRosterUi() { if (mOnRosterUpdate != null) { mOnRosterUpdate.onRosterUpdate(); @@ -1422,7 +1556,7 @@ public class XmppConnectionService extends Service { } return null; } - + public Conversation findConversationByUuid(String uuid) { for (Conversation conversation : getConversations()) { if (conversation.getUuid().equals(uuid)) { @@ -1438,10 +1572,11 @@ public class XmppConnectionService extends Service { if (confirmMessages() && id != null) { Account account = conversation.getAccount(); String to = conversation.getContactJid(); - this.sendMessagePacket(conversation.getAccount(), mMessageGenerator.confirm(account, to, id)); + this.sendMessagePacket(conversation.getAccount(), + mMessageGenerator.confirm(account, to, id)); } } - + public void failWaitingOtrMessages(Conversation conversation) { for (Message message : conversation.getMessages()) { if (message.getEncryption() == Message.ENCRYPTION_OTR @@ -1454,7 +1589,7 @@ public class XmppConnectionService extends Service { public SecureRandom getRNG() { return this.mRandom; } - + public MemorizingTrustManager getMemorizingTrustManager() { return this.mMemorizingTrustManager; } @@ -1467,7 +1602,7 @@ public class XmppConnectionService extends Service { if (account.getStatus() == Account.STATUS_ONLINE) { MessagePacket error = this.mMessageGenerator .generateNotAcceptable(packet); - sendMessagePacket(account,error); + sendMessagePacket(account, error); } } @@ -1512,43 +1647,44 @@ public class XmppConnectionService extends Service { } return mucServers; } - + public void sendMessagePacket(Account account, MessagePacket packet) { account.getXmppConnection().sendMessagePacket(packet); } - + public void sendPresencePacket(Account account, PresencePacket packet) { account.getXmppConnection().sendPresencePacket(packet); } - - public void sendIqPacket(Account account, IqPacket packet, OnIqPacketReceived callback) { + + public void sendIqPacket(Account account, IqPacket packet, + OnIqPacketReceived callback) { account.getXmppConnection().sendIqPacket(packet, callback); } - + public MessageGenerator getMessageGenerator() { return this.mMessageGenerator; } - + public PresenceGenerator getPresenceGenerator() { return this.mPresenceGenerator; } - + public IqGenerator getIqGenerator() { - return this.mIqGenerator ; + return this.mIqGenerator; } - + public JingleConnectionManager getJingleConnectionManager() { return this.mJingleConnectionManager; } - + public interface OnConversationUpdate { public void onConversationUpdate(); } - + public interface OnAccountUpdate { public void onAccountUpdate(); } - + public interface OnRosterUpdate { public void onRosterUpdate(); } diff --git a/src/eu/siacs/conversations/ui/ChooseContactActivity.java b/src/eu/siacs/conversations/ui/ChooseContactActivity.java index 4236ea70..449fc546 100644 --- a/src/eu/siacs/conversations/ui/ChooseContactActivity.java +++ b/src/eu/siacs/conversations/ui/ChooseContactActivity.java @@ -23,13 +23,13 @@ import eu.siacs.conversations.entities.ListItem; import eu.siacs.conversations.ui.adapter.ListItemAdapter; public class ChooseContactActivity extends XmppActivity { - + private ListView mListView; private ArrayList<ListItem> contacts = new ArrayList<ListItem>(); private ArrayAdapter<ListItem> mContactsAdapter; - + private EditText mSearchEditText; - + private TextWatcher mSearchTextWatcher = new TextWatcher() { @Override @@ -47,7 +47,7 @@ public class ChooseContactActivity extends XmppActivity { int count) { } }; - + private MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() { @Override @@ -76,35 +76,40 @@ public class ChooseContactActivity extends XmppActivity { return true; } }; - - - + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_choose_contact); mListView = (ListView) findViewById(R.id.choose_contact_list); - mContactsAdapter = new ListItemAdapter(getApplicationContext(), contacts); + mContactsAdapter = new ListItemAdapter(getApplicationContext(), + contacts); mListView.setAdapter(mContactsAdapter); mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override - public void onItemClick(AdapterView<?> arg0, View arg1, int position, - long arg3) { + public void onItemClick(AdapterView<?> arg0, View arg1, + int position, long arg3) { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mSearchEditText.getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY); Intent request = getIntent(); Intent data = new Intent(); - data.putExtra("contact",contacts.get(position).getJid()); - data.putExtra("account",request.getStringExtra("account")); - data.putExtra("conversation",request.getStringExtra("conversation")); + ListItem mListItem = contacts.get(position); + data.putExtra("contact", mListItem.getJid()); + String account = request.getStringExtra("account"); + if (account==null && mListItem instanceof Contact) { + account = ((Contact) mListItem).getAccount().getJid(); + } + data.putExtra("account", account); + data.putExtra("conversation", + request.getStringExtra("conversation")); setResult(RESULT_OK, data); finish(); } }); } - + @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.choose_contact, menu); @@ -121,7 +126,7 @@ public class ChooseContactActivity extends XmppActivity { void onBackendConnected() { filterContacts(null); } - + protected void filterContacts(String needle) { this.contacts.clear(); for (Account account : xmppConnectionService.getAccounts()) { diff --git a/src/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index c33277f9..2cfa1635 100644 --- a/src/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -109,7 +109,7 @@ public class ConferenceDetailsActivity extends XmppActivity { break; case R.id.action_edit_subject: if (conversation != null) { - quickEdit(conversation.getName(true), new OnValueEdited() { + quickEdit(conversation.getName(), new OnValueEdited() { @Override public void onValueEdited(String value) { @@ -200,7 +200,7 @@ public class ConferenceDetailsActivity extends XmppActivity { private void populateView() { mYourPhoto.setImageBitmap(conversation.getAccount().getImage(this, 48)); - setTitle(conversation.getName(true)); + setTitle(conversation.getName()); mFullJid.setText(conversation.getContactJid().split("/")[0]); mYourNick.setText(conversation.getMucOptions().getActualNick()); mRoleAffiliaton = (TextView) findViewById(R.id.muc_role); @@ -250,7 +250,8 @@ public class ConferenceDetailsActivity extends XmppActivity { if (contact.showInRoster()) { bm = contact.getImage(48, this); name.setText(contact.getDisplayName()); - role.setText(user.getName() + " \u2022 " + getReadableRole(user.getRole())); + role.setText(user.getName() + " \u2022 " + + getReadableRole(user.getRole())); } else { bm = UIHelper.getContactPicture(user.getName(), 48, this, false); diff --git a/src/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/eu/siacs/conversations/ui/ContactDetailsActivity.java index 7c13c518..a9153db1 100644 --- a/src/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -111,7 +111,8 @@ public class ContactDetailsActivity extends XmppActivity { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { - if (contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { + if (contact + .getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { xmppConnectionService.sendPresencePacket(contact .getAccount(), xmppConnectionService.getPresenceGenerator() @@ -146,7 +147,7 @@ public class ContactDetailsActivity extends XmppActivity { }; private OnAccountUpdate accountUpdate = new OnAccountUpdate() { - + @Override public void onAccountUpdate() { runOnUiThread(new Runnable() { @@ -269,7 +270,7 @@ public class ContactDetailsActivity extends XmppActivity { send.setOnCheckedChangeListener(this.mOnSendCheckedChange); receive.setOnCheckedChangeListener(this.mOnReceiveCheckedChange); - + lastseen.setText(UIHelper.lastseen(getApplicationContext(), contact.lastseen.time)); @@ -363,7 +364,8 @@ public class ContactDetailsActivity extends XmppActivity { @Override public void onBackendConnected() { xmppConnectionService.setOnRosterUpdateListener(this.rosterUpdate); - xmppConnectionService.setOnAccountListChangedListener(this.accountUpdate ); + xmppConnectionService + .setOnAccountListChangedListener(this.accountUpdate); if ((accountJid != null) && (contactJid != null)) { Account account = xmppConnectionService .findAccountByJid(accountJid); diff --git a/src/eu/siacs/conversations/ui/ConversationActivity.java b/src/eu/siacs/conversations/ui/ConversationActivity.java index c68063d2..17e410f2 100644 --- a/src/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/eu/siacs/conversations/ui/ConversationActivity.java @@ -1,10 +1,7 @@ package eu.siacs.conversations.ui; -import java.io.FileNotFoundException; -import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.RejectedExecutionException; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Contact; @@ -15,9 +12,8 @@ import eu.siacs.conversations.ui.adapter.ConversationAdapter; import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.utils.UIHelper; import android.net.Uri; -import android.os.AsyncTask; import android.os.Bundle; -import android.preference.PreferenceManager; +import android.os.SystemClock; import android.provider.MediaStore; import android.app.ActionBar; import android.app.AlertDialog; @@ -27,15 +23,8 @@ import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.IntentSender.SendIntentException; import android.content.Intent; -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; import android.support.v4.widget.SlidingPaneLayout; import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener; -import android.util.DisplayMetrics; -import android.util.Log; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; @@ -47,7 +36,6 @@ import android.widget.CheckBox; import android.widget.ListView; import android.widget.PopupMenu; import android.widget.PopupMenu.OnMenuItemClickListener; -import android.widget.ImageView; import android.widget.Toast; public class ConversationActivity extends XmppActivity { @@ -76,12 +64,10 @@ public class ConversationActivity extends XmppActivity { private ListView listView; private boolean paneShouldBeOpen = true; - private boolean useSubject = true; - private boolean showLastseen = false; private ArrayAdapter<Conversation> listAdapter; private OnConversationUpdate onConvChanged = new OnConversationUpdate() { - + @Override public void onConversationUpdate() { runOnUiThread(new Runnable() { @@ -109,9 +95,8 @@ public class ConversationActivity extends XmppActivity { }; protected ConversationActivity activity = this; - private DisplayMetrics metrics; private Toast prepareImageToast; - + private Uri pendingImageUri = null; public List<Conversation> getConversationList() { @@ -140,15 +125,12 @@ public class ConversationActivity extends XmppActivity { @Override protected void onCreate(Bundle savedInstanceState) { - - metrics = getResources().getDisplayMetrics(); - super.onCreate(savedInstanceState); setContentView(R.layout.fragment_conversations_overview); listView = (ListView) findViewById(R.id.list); - + getActionBar().setDisplayHomeAsUpEnabled(false); getActionBar().setHomeButtonEnabled(false); @@ -179,7 +161,7 @@ public class ConversationActivity extends XmppActivity { public void onPanelOpened(View arg0) { paneShouldBeOpen = true; ActionBar ab = getActionBar(); - if (ab!=null) { + if (ab != null) { ab.setDisplayHomeAsUpEnabled(false); ab.setHomeButtonEnabled(false); ab.setTitle(R.string.app_name); @@ -194,11 +176,10 @@ public class ConversationActivity extends XmppActivity { if ((conversationList.size() > 0) && (getSelectedConversation() != null)) { ActionBar ab = getActionBar(); - if (ab!=null) { + if (ab != null) { ab.setDisplayHomeAsUpEnabled(true); ab.setHomeButtonEnabled(true); - ab.setTitle( - getSelectedConversation().getName(useSubject)); + ab.setTitle(getSelectedConversation().getName()); } invalidateOptionsMenu(); if (!getSelectedConversation().isRead()) { @@ -232,7 +213,9 @@ public class ConversationActivity extends XmppActivity { MenuItem menuClearHistory = (MenuItem) menu .findItem(R.id.action_clear_history); MenuItem menuAdd = (MenuItem) menu.findItem(R.id.action_add); - MenuItem menuInviteContact = (MenuItem) menu.findItem(R.id.action_invite); + MenuItem menuInviteContact = (MenuItem) menu + .findItem(R.id.action_invite); + MenuItem menuMute = (MenuItem) menu.findItem(R.id.action_mute); if ((spl.isOpen() && (spl.isSlideable()))) { menuArchive.setVisible(false); @@ -242,6 +225,7 @@ public class ConversationActivity extends XmppActivity { menuInviteContact.setVisible(false); menuAttach.setVisible(false); menuClearHistory.setVisible(false); + menuMute.setVisible(false); } else { menuAdd.setVisible(!spl.isSlideable()); if (this.getSelectedConversation() != null) { @@ -267,11 +251,12 @@ public class ConversationActivity extends XmppActivity { @Override public void onPresenceSelected() { if (attachmentChoice == ATTACHMENT_CHOICE_TAKE_PHOTO) { - pendingImageUri = xmppConnectionService.getFileBackend().getTakePhotoUri(); - Log.d("xmppService",pendingImageUri.toString()); + pendingImageUri = xmppConnectionService.getFileBackend() + .getTakePhotoUri(); Intent takePictureIntent = new Intent( MediaStore.ACTION_IMAGE_CAPTURE); - takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,pendingImageUri); + takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, + pendingImageUri); if (takePictureIntent.resolveActivity(getPackageManager()) != null) { startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE); @@ -353,7 +338,7 @@ public class ConversationActivity extends XmppActivity { return true; case R.id.action_attach_file: View menuAttachFile = findViewById(R.id.action_attach_file); - if (menuAttachFile==null) { + if (menuAttachFile == null) { break; } PopupMenu attachFilePopup = new PopupMenu(this, menuAttachFile); @@ -405,7 +390,7 @@ public class ConversationActivity extends XmppActivity { case R.id.action_security: final Conversation conversation = getSelectedConversation(); View menuItemView = findViewById(R.id.action_security); - if (menuItemView==null) { + if (menuItemView == null) { break; } PopupMenu popup = new PopupMenu(this, menuItemView); @@ -481,6 +466,9 @@ public class ConversationActivity extends XmppActivity { case R.id.action_clear_history: clearHistoryDialog(getSelectedConversation()); break; + case R.id.action_mute: + muteConversationDialog(getSelectedConversation()); + break; default: break; } @@ -522,16 +510,41 @@ public class ConversationActivity extends XmppActivity { }); builder.create().show(); } + + protected void muteConversationDialog(final Conversation conversation) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.disable_notifications_for_this_conversation); + final int[] durations = getResources().getIntArray(R.array.mute_options_durations); + builder.setItems(R.array.mute_options_descriptions, new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + long till; + if (durations[which]==-1) { + till = Long.MAX_VALUE; + } else { + till = SystemClock.elapsedRealtime() + (durations[which] * 1000); + } + conversation.setMutedTill(till); + ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager() + .findFragmentByTag("conversation"); + if (selectedFragment!=null) { + selectedFragment.updateMessages(); + } + } + }); + builder.create().show(); + } protected ConversationFragment swapConversationFragment() { ConversationFragment selectedFragment = new ConversationFragment(); if (!isFinishing()) { - FragmentTransaction transaction = getFragmentManager() - .beginTransaction(); - transaction.replace(R.id.selected_conversation, selectedFragment, - "conversation"); - + FragmentTransaction transaction = getFragmentManager() + .beginTransaction(); + transaction.replace(R.id.selected_conversation, selectedFragment, + "conversation"); + transaction.commitAllowingStateLoss(); } return selectedFragment; @@ -553,7 +566,8 @@ public class ConversationActivity extends XmppActivity { if (xmppConnectionServiceBound) { if ((Intent.ACTION_VIEW.equals(intent.getAction()) && (VIEW_CONVERSATION .equals(intent.getType())))) { - String convToView = (String) intent.getExtras().get(CONVERSATION); + String convToView = (String) intent.getExtras().get( + CONVERSATION); updateConversationList(); for (int i = 0; i < conversationList.size(); ++i) { if (conversationList.get(i).getUuid().equals(convToView)) { @@ -574,10 +588,6 @@ public class ConversationActivity extends XmppActivity { @Override public void onStart() { super.onStart(); - SharedPreferences preferences = PreferenceManager - .getDefaultSharedPreferences(this); - this.useSubject = preferences.getBoolean("use_subject_in_muc", true); - this.showLastseen = preferences.getBoolean("show_last_seen", false); if (this.xmppConnectionServiceBound) { this.onBackendConnected(); } @@ -600,9 +610,10 @@ public class ConversationActivity extends XmppActivity { if (conversationList.size() == 0) { updateConversationList(); } - - if (getSelectedConversation()!=null && pendingImageUri !=null) { - attachImageToConversation(getSelectedConversation(), pendingImageUri); + + if (getSelectedConversation() != null && pendingImageUri != null) { + attachImageToConversation(getSelectedConversation(), + pendingImageUri); pendingImageUri = null; } else { pendingImageUri = null; @@ -670,7 +681,8 @@ public class ConversationActivity extends XmppActivity { } else if (requestCode == REQUEST_ATTACH_FILE_DIALOG) { pendingImageUri = data.getData(); if (xmppConnectionServiceBound) { - attachImageToConversation(getSelectedConversation(),pendingImageUri); + attachImageToConversation(getSelectedConversation(), + pendingImageUri); pendingImageUri = null; } } else if (requestCode == REQUEST_SEND_PGP_IMAGE) { @@ -686,10 +698,12 @@ public class ConversationActivity extends XmppActivity { // encryptTextMessage(); } else if (requestCode == REQUEST_IMAGE_CAPTURE) { if (xmppConnectionServiceBound) { - attachImageToConversation(getSelectedConversation(), pendingImageUri); + attachImageToConversation(getSelectedConversation(), + pendingImageUri); pendingImageUri = null; } - Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + Intent intent = new Intent( + Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); intent.setData(pendingImageUri); sendBroadcast(intent); } else if (requestCode == REQUEST_RECORD_AUDIO) { @@ -700,7 +714,7 @@ public class ConversationActivity extends XmppActivity { } private void attachAudioToConversation(Conversation conversation, Uri uri) { - + } private void attachImageToConversation(Conversation conversation, Uri uri) { @@ -744,122 +758,16 @@ public class ConversationActivity extends XmppActivity { } public void updateConversationList() { - xmppConnectionService.populateWithOrderedConversations(conversationList); + xmppConnectionService + .populateWithOrderedConversations(conversationList); listView.invalidateViews(); } - public boolean showLastseen() { - if (getSelectedConversation() == null) { - return false; - } else { - return this.showLastseen - && getSelectedConversation().getMode() == Conversation.MODE_SINGLE; - } - } - public void runIntent(PendingIntent pi, int requestCode) { try { this.startIntentSenderForResult(pi.getIntentSender(), requestCode, null, 0, 0, 0); - } catch (SendIntentException e1) {} - } - - class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> { - private final WeakReference<ImageView> imageViewReference; - private Message message = null; - - public BitmapWorkerTask(ImageView imageView) { - imageViewReference = new WeakReference<ImageView>(imageView); - } - - @Override - protected Bitmap doInBackground(Message... params) { - message = params[0]; - try { - return xmppConnectionService.getFileBackend().getThumbnail( - message, (int) (metrics.density * 288), false); - } catch (FileNotFoundException e) { - return null; - } - } - - @Override - protected void onPostExecute(Bitmap bitmap) { - if (imageViewReference != null && bitmap != null) { - final ImageView imageView = imageViewReference.get(); - if (imageView != null) { - imageView.setImageBitmap(bitmap); - imageView.setBackgroundColor(0x00000000); - } - } - } - } - - public void loadBitmap(Message message, ImageView imageView) { - Bitmap bm; - try { - bm = xmppConnectionService.getFileBackend().getThumbnail(message, - (int) (metrics.density * 288), true); - } catch (FileNotFoundException e) { - bm = null; - } - if (bm != null) { - imageView.setImageBitmap(bm); - imageView.setBackgroundColor(0x00000000); - } else { - if (cancelPotentialWork(message, imageView)) { - imageView.setBackgroundColor(0xff333333); - final BitmapWorkerTask task = new BitmapWorkerTask(imageView); - final AsyncDrawable asyncDrawable = new AsyncDrawable( - getResources(), null, task); - imageView.setImageDrawable(asyncDrawable); - try { - task.execute(message); - } catch (RejectedExecutionException e) { - return; - } - } - } - } - - public static boolean cancelPotentialWork(Message message, - ImageView imageView) { - final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); - - if (bitmapWorkerTask != null) { - final Message oldMessage = bitmapWorkerTask.message; - if (oldMessage == null || message != oldMessage) { - bitmapWorkerTask.cancel(true); - } else { - return false; - } - } - return true; - } - - private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { - if (imageView != null) { - final Drawable drawable = imageView.getDrawable(); - if (drawable instanceof AsyncDrawable) { - final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; - return asyncDrawable.getBitmapWorkerTask(); - } - } - return null; - } - - static class AsyncDrawable extends BitmapDrawable { - private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference; - - public AsyncDrawable(Resources res, Bitmap bitmap, - BitmapWorkerTask bitmapWorkerTask) { - super(res, bitmap); - bitmapWorkerTaskReference = new WeakReference<BitmapWorkerTask>( - bitmapWorkerTask); - } - - public BitmapWorkerTask getBitmapWorkerTask() { - return bitmapWorkerTaskReference.get(); + } catch (SendIntentException e1) { } } diff --git a/src/eu/siacs/conversations/ui/ConversationFragment.java b/src/eu/siacs/conversations/ui/ConversationFragment.java index 9309db9c..4ebeea19 100644 --- a/src/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/eu/siacs/conversations/ui/ConversationFragment.java @@ -15,6 +15,7 @@ import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.EditMessage.OnEnterPressed; import eu.siacs.conversations.ui.XmppActivity.OnPresenceSelected; +import eu.siacs.conversations.ui.XmppActivity.OnValueEdited; import eu.siacs.conversations.ui.adapter.MessageAdapter; import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureClicked; import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureLongClicked; @@ -26,10 +27,8 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentSender; -import android.content.SharedPreferences; import android.content.IntentSender.SendIntentException; import android.os.Bundle; -import android.preference.PreferenceManager; import android.text.Editable; import android.text.Selection; import android.view.Gravity; @@ -67,7 +66,6 @@ public class ConversationFragment extends Fragment { private TextView snackbarMessage; private TextView snackbarAction; - private boolean useSubject = true; private boolean messagesLoaded = false; private IntentSender askForPassphraseIntent = null; @@ -131,6 +129,25 @@ public class ConversationFragment extends Fragment { } }; + private OnClickListener enterPassword = new OnClickListener() { + + @Override + public void onClick(View v) { + MucOptions muc = conversation.getMucOptions(); + String password = muc.getPassword(); + if (password==null) { + password = ""; + } + activity.quickEdit(password, new OnValueEdited() { + + @Override + public void onValueEdited(String value) { + activity.xmppConnectionService.providePasswordForMuc(conversation,value); + } + }); + } + }; + private OnScrollListener mOnScrollListener = new OnScrollListener() { @Override @@ -287,23 +304,22 @@ public class ConversationFragment extends Fragment { protected void highlightInConference(String nick) { String oldString = mEditMessage.getText().toString().trim(); - if (oldString.isEmpty()) { - mEditMessage.setText(nick + ": "); + if (oldString.isEmpty() || mEditMessage.getSelectionStart() == 0) { + mEditMessage.getText().insert(0, nick + ": "); } else { - mEditMessage.setText(oldString + " " + nick + " "); + if (mEditMessage.getText().charAt( + mEditMessage.getSelectionStart() - 1) != ' ') { + nick = " " + nick; + } + mEditMessage.getText().insert(mEditMessage.getSelectionStart(), + nick + " "); } - int position = mEditMessage.length(); - Editable etext = mEditMessage.getText(); - Selection.setSelection(etext, position); } @Override public void onStart() { super.onStart(); this.activity = (ConversationActivity) getActivity(); - SharedPreferences preferences = PreferenceManager - .getDefaultSharedPreferences(activity); - this.useSubject = preferences.getBoolean("use_subject_in_muc", true); if (activity.xmppConnectionServiceBound) { this.onBackendConnected(); } @@ -343,8 +359,7 @@ public class ConversationFragment extends Fragment { activity.getSlidingPaneLayout().closePane(); activity.getActionBar().setDisplayHomeAsUpEnabled(true); activity.getActionBar().setHomeButtonEnabled(true); - activity.getActionBar().setTitle( - conversation.getName(useSubject)); + activity.getActionBar().setTitle(conversation.getName()); activity.invalidateOptionsMenu(); } } @@ -390,7 +405,17 @@ public class ConversationFragment extends Fragment { final ConversationActivity activity = (ConversationActivity) getActivity(); if (this.conversation != null) { final Contact contact = this.conversation.getContact(); - if (!contact.showInRoster() + if (this.conversation.isMuted()) { + showSnackbar(R.string.notifications_disabled, R.string.enable, + new OnClickListener() { + + @Override + public void onClick(View v) { + conversation.setMutedTill(0); + updateMessages(); + } + }); + } else if (!contact.showInRoster() && contact .getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { showSnackbar(R.string.contact_added_you, R.string.add_back, @@ -432,12 +457,20 @@ public class ConversationFragment extends Fragment { } else { if (!conversation.getMucOptions().online() && conversation.getAccount().getStatus() == Account.STATUS_ONLINE) { - if (conversation.getMucOptions().getError() == MucOptions.ERROR_NICK_IN_USE) { + int error = conversation.getMucOptions().getError(); + switch (error) { + case MucOptions.ERROR_NICK_IN_USE: showSnackbar(R.string.nick_in_use, R.string.edit, clickToMuc); - } else if (conversation.getMucOptions().getError() == MucOptions.ERROR_ROOM_NOT_FOUND) { + break; + case MucOptions.ERROR_ROOM_NOT_FOUND: showSnackbar(R.string.conference_not_found, R.string.leave, leaveMuc); + case MucOptions.ERROR_PASSWORD_REQUIRED: + showSnackbar(R.string.conference_requires_password, + R.string.enter_password, enterPassword); + default: + break; } } } @@ -445,7 +478,6 @@ public class ConversationFragment extends Fragment { updateChatMsgHint(); if (!activity.shouldPaneBeOpen()) { activity.xmppConnectionService.markRead(conversation); - // TODO update notifications UIHelper.updateNotification(getActivity(), activity.getConversationList(), null, false); activity.updateConversationList(); @@ -491,6 +523,7 @@ public class ConversationFragment extends Fragment { .getOtrFingerprints(); if ((latestEncryption == Message.ENCRYPTION_OTR) && (conversation.hasValidOtrSession() + && (!conversation.isMuted()) && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints .contains(conversation.getOtrFingerprint())))) { showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify, diff --git a/src/eu/siacs/conversations/ui/EditAccountActivity.java b/src/eu/siacs/conversations/ui/EditAccountActivity.java index 2d24aa49..6cee8386 100644 --- a/src/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/eu/siacs/conversations/ui/EditAccountActivity.java @@ -201,7 +201,12 @@ public class EditAccountActivity extends XmppActivity { this.mSaveButton.setEnabled(true); this.mSaveButton.setTextColor(getPrimaryTextColor()); if (jidToEdit != null) { - this.mSaveButton.setText(R.string.connect); + if (mAccount != null + && mAccount.getStatus() == Account.STATUS_ONLINE) { + this.mSaveButton.setText(R.string.save); + } else { + this.mSaveButton.setText(R.string.connect); + } } else { this.mSaveButton.setText(R.string.next); } diff --git a/src/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/eu/siacs/conversations/ui/PublishProfilePictureActivity.java index c4c1b45e..f46d92f9 100644 --- a/src/eu/siacs/conversations/ui/PublishProfilePictureActivity.java +++ b/src/eu/siacs/conversations/ui/PublishProfilePictureActivity.java @@ -46,7 +46,7 @@ public class PublishProfilePictureActivity extends XmppActivity { public void run() { if (mInitialAccountSetup) { startActivity(new Intent(getApplicationContext(), - StartConversationActivity.class)); + StartConversationActivity.class)); } finish(); } @@ -61,7 +61,7 @@ public class PublishProfilePictureActivity extends XmppActivity { public void run() { hintOrWarning.setText(errorCode); hintOrWarning.setTextColor(getWarningTextColor()); - publishButton.setText(R.string.publish_avatar); + publishButton.setText(R.string.publish); enablePublishButton(); } }); @@ -111,7 +111,7 @@ public class PublishProfilePictureActivity extends XmppActivity { public void onClick(View v) { if (mInitialAccountSetup) { startActivity(new Intent(getApplicationContext(), - StartConversationActivity.class)); + StartConversationActivity.class)); } finish(); } @@ -211,7 +211,7 @@ public class PublishProfilePictureActivity extends XmppActivity { this.avatar.setImageBitmap(bm); if (support) { enablePublishButton(); - this.publishButton.setText(R.string.publish_avatar); + this.publishButton.setText(R.string.publish); this.hintOrWarning.setText(R.string.publish_avatar_explanation); this.hintOrWarning.setTextColor(getPrimaryTextColor()); } else { diff --git a/src/eu/siacs/conversations/ui/SettingsActivity.java b/src/eu/siacs/conversations/ui/SettingsActivity.java index f8fd9469..64fbde08 100644 --- a/src/eu/siacs/conversations/ui/SettingsActivity.java +++ b/src/eu/siacs/conversations/ui/SettingsActivity.java @@ -14,7 +14,7 @@ public class SettingsActivity extends XmppActivity { @Override void onBackendConnected() { - + } } diff --git a/src/eu/siacs/conversations/ui/ShareWithActivity.java b/src/eu/siacs/conversations/ui/ShareWithActivity.java index 461aaec4..93831654 100644 --- a/src/eu/siacs/conversations/ui/ShareWithActivity.java +++ b/src/eu/siacs/conversations/ui/ShareWithActivity.java @@ -1,37 +1,41 @@ package eu.siacs.conversations.ui; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; import java.util.List; -import java.util.Set; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; 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.utils.UIHelper; +import eu.siacs.conversations.ui.adapter.ConversationAdapter; import android.app.PendingIntent; import android.content.Intent; -import android.content.SharedPreferences; -import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; -import android.preference.PreferenceManager; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; -import android.view.View.OnClickListener; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; import android.widget.Toast; public class ShareWithActivity extends XmppActivity { + + private class Share { + public Uri uri; + public String account; + public String contact; + public String text; + } + + private Share share; - private LinearLayout conversations; - private LinearLayout contacts; - private boolean isImage = false; + private static final int REQUEST_START_NEW_CONVERSATION = 0x0501; + private ListView mListView; + private List<Conversation> mConversations = new ArrayList<Conversation>(); private UiCallback<Message> attachImageCallback = new UiCallback<Message>() { @@ -52,115 +56,107 @@ public class ShareWithActivity extends XmppActivity { } }; + + protected void onActivityResult(int requestCode, int resultCode, + final Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_START_NEW_CONVERSATION + && resultCode == RESULT_OK) { + share.contact = data.getStringExtra("contact"); + share.account = data.getStringExtra("account"); + Log.d(Config.LOGTAG,"contact: "+share.contact+" account:"+share.account); + } + if (xmppConnectionServiceBound && share != null && share.contact != null && share.account != null) { + share(); + } + } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + getActionBar().setDisplayHomeAsUpEnabled(false); + getActionBar().setHomeButtonEnabled(false); + setContentView(R.layout.share_with); setTitle(getString(R.string.title_activity_sharewith)); - contacts = (LinearLayout) findViewById(R.id.contacts); - conversations = (LinearLayout) findViewById(R.id.conversations); + mListView = (ListView) findViewById(R.id.choose_conversation_list); + ConversationAdapter mAdapter = new ConversationAdapter(this, + this.mConversations); + mListView.setAdapter(mAdapter); + mListView.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> arg0, View arg1, + int position, long arg3) { + Conversation conversation = mConversations.get(position); + if (conversation.getMode() == Conversation.MODE_SINGLE) { + share(mConversations.get(position)); + } + } + }); + + this.share = new Share(); } - - public View createContactView(String name, String msgTxt, Bitmap bm) { - View view = (View) getLayoutInflater().inflate(R.layout.contact, null); - view.setBackgroundResource(R.drawable.greybackground); - TextView contactName = (TextView) view - .findViewById(R.id.contact_display_name); - contactName.setText(name); - TextView msg = (TextView) view.findViewById(R.id.contact_jid); - msg.setText(msgTxt); - ImageView imageView = (ImageView) view.findViewById(R.id.contact_photo); - imageView.setImageBitmap(bm); - return view; + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.share_with, menu); + return true; } @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_add: + Intent intent = new Intent(getApplicationContext(), + ChooseContactActivity.class); + startActivityForResult(intent, REQUEST_START_NEW_CONVERSATION); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onStart() { + super.onStart(); + if (getIntent().getType() != null && getIntent().getType() + .startsWith("image/")) { + this.share.uri = (Uri) getIntent().getParcelableExtra( + Intent.EXTRA_STREAM); + } else { + this.share.text = getIntent().getStringExtra(Intent.EXTRA_TEXT); + } + } + + @Override void onBackendConnected() { - this.isImage = (getIntent().getType() != null && getIntent().getType() - .startsWith("image/")); - SharedPreferences preferences = PreferenceManager - .getDefaultSharedPreferences(this); - boolean useSubject = preferences.getBoolean("use_subject_in_muc", true); - - Set<Contact> displayedContacts = new HashSet<Contact>(); - conversations.removeAllViews(); - List<Conversation> convList = new ArrayList<Conversation>(); - xmppConnectionService.populateWithOrderedConversations(convList); - Collections.sort(convList, new Comparator<Conversation>() { - @Override - public int compare(Conversation lhs, Conversation rhs) { - return (int) (rhs.getLatestMessage().getTimeSent() - lhs - .getLatestMessage().getTimeSent()); - } - }); - for (final Conversation conversation : convList) { - if (!isImage || conversation.getMode() == Conversation.MODE_SINGLE) { - View view = createContactView( - conversation.getName(useSubject), - conversation.getLatestMessage().getBody().trim(), - UIHelper.getContactPicture(conversation, 48, - this.getApplicationContext(), false)); - view.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - share(conversation); - } - }); - conversations.addView(view); - displayedContacts.add(conversation.getContact()); - } + if (xmppConnectionServiceBound && share != null && share.contact != null && share.account != null) { + share(); + return; } - contacts.removeAllViews(); - List<Contact> contactsList = new ArrayList<Contact>(); - for (Account account : xmppConnectionService.getAccounts()) { - for (Contact contact : account.getRoster().getContacts()) { - if (!displayedContacts.contains(contact) - && (contact.showInRoster())) { - contactsList.add(contact); - } + xmppConnectionService.populateWithOrderedConversations(mConversations, + false); + for (Conversation conversation : mConversations) { + if (conversation.getMode() == Conversation.MODE_MULTI) { + mConversations.remove(conversation); } } - - Collections.sort(contactsList, new Comparator<Contact>() { - @Override - public int compare(Contact lhs, Contact rhs) { - return lhs.getDisplayName().compareToIgnoreCase( - rhs.getDisplayName()); - } - }); - - for (int i = 0; i < contactsList.size(); ++i) { - final Contact con = contactsList.get(i); - View view = createContactView( - con.getDisplayName(), - con.getJid(), - UIHelper.getContactPicture(con, 48, - this.getApplicationContext(), false)); - view.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - Conversation conversation = xmppConnectionService - .findOrCreateConversation(con.getAccount(), - con.getJid(), false); - share(conversation); - } - }); - contacts.addView(view); + } + + private void share() { + Account account = xmppConnectionService.findAccountByJid(share.account); + if (account==null) { + return; } + Conversation conversation = xmppConnectionService.findOrCreateConversation(account, share.contact, false); + share(conversation); } private void share(final Conversation conversation) { - String sharedText = null; - if (isImage) { - final Uri uri = (Uri) getIntent().getParcelableExtra( - Intent.EXTRA_STREAM); + if (share.uri != null) { selectPresence(conversation, new OnPresenceSelected() { @Override public void onPresenceSelected() { @@ -168,7 +164,7 @@ public class ShareWithActivity extends XmppActivity { getText(R.string.preparing_image), Toast.LENGTH_LONG).show(); ShareWithActivity.this.xmppConnectionService - .attachImageToConversation(conversation, uri, + .attachImageToConversation(conversation, share.uri, attachImageCallback); switchToConversation(conversation, null, true); finish(); @@ -176,8 +172,7 @@ public class ShareWithActivity extends XmppActivity { }); } else { - sharedText = getIntent().getStringExtra(Intent.EXTRA_TEXT); - switchToConversation(conversation, sharedText, true); + switchToConversation(conversation,this.share.text, true); finish(); } diff --git a/src/eu/siacs/conversations/ui/StartConversationActivity.java b/src/eu/siacs/conversations/ui/StartConversationActivity.java index bcb9f1dd..6adada76 100644 --- a/src/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/eu/siacs/conversations/ui/StartConversationActivity.java @@ -340,6 +340,10 @@ public class StartConversationActivity extends XmppActivity { String contactJid = jid.getText().toString(); Account account = xmppConnectionService .findAccountByJid(accountJid); + if (account == null) { + dialog.dismiss(); + return; + } Contact contact = account.getRoster().getContact( contactJid); if (contact.showInRoster()) { diff --git a/src/eu/siacs/conversations/ui/UiCallback.java b/src/eu/siacs/conversations/ui/UiCallback.java index 05a869f7..c80199e1 100644 --- a/src/eu/siacs/conversations/ui/UiCallback.java +++ b/src/eu/siacs/conversations/ui/UiCallback.java @@ -4,6 +4,8 @@ import android.app.PendingIntent; public interface UiCallback<T> { public void success(T object); + public void error(int errorCode, T object); + public void userInputRequried(PendingIntent pi, T object); } diff --git a/src/eu/siacs/conversations/ui/XmppActivity.java b/src/eu/siacs/conversations/ui/XmppActivity.java index 44043a79..78482658 100644 --- a/src/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/eu/siacs/conversations/ui/XmppActivity.java @@ -1,5 +1,10 @@ package eu.siacs.conversations.ui; +import java.io.FileNotFoundException; +import java.lang.ref.WeakReference; +import java.util.concurrent.RejectedExecutionException; + +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; @@ -18,24 +23,29 @@ import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.IntentSender.SendIntentException; +import android.content.res.Resources; import android.content.Intent; import android.content.ServiceConnection; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.AsyncTask; import android.os.Bundle; import android.os.IBinder; +import android.util.DisplayMetrics; import android.util.Log; import android.view.MenuItem; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; +import android.widget.ImageView; public abstract class XmppActivity extends Activity { protected static final int REQUEST_ANNOUNCE_PGP = 0x0101; protected static final int REQUEST_INVITE_TO_CONVERSATION = 0x0102; - protected final static String LOGTAG = "xmppService"; - public XmppConnectionService xmppConnectionService; public boolean xmppConnectionServiceBound = false; protected boolean handledViewIntent = false; @@ -45,6 +55,8 @@ public abstract class XmppActivity extends Activity { protected int mWarningTextColor; protected int mPrimaryColor; + private DisplayMetrics metrics; + protected interface OnValueEdited { public void onValueEdited(String value); } @@ -164,6 +176,7 @@ public abstract class XmppActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + metrics = getResources().getDisplayMetrics(); ExceptionHelper.init(getApplicationContext()); mPrimaryTextColor = getResources().getColor(R.color.primarytext); mSecondaryTextColor = getResources().getColor(R.color.secondarytext); @@ -370,8 +383,8 @@ public abstract class XmppActivity extends Activity { if (conversation.getMode() == Conversation.MODE_MULTI) { xmppConnectionService.invite(conversation, contactJid); } - Log.d("xmppService", "inviting " + contactJid + " to " - + conversation.getName(true)); + Log.d(Config.LOGTAG, "inviting " + contactJid + " to " + + conversation.getName()); } } @@ -390,4 +403,103 @@ public abstract class XmppActivity extends Activity { public int getPrimaryColor() { return this.mPrimaryColor; } + + class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> { + private final WeakReference<ImageView> imageViewReference; + private Message message = null; + + public BitmapWorkerTask(ImageView imageView) { + imageViewReference = new WeakReference<ImageView>(imageView); + } + + @Override + protected Bitmap doInBackground(Message... params) { + message = params[0]; + try { + return xmppConnectionService.getFileBackend().getThumbnail( + message, (int) (metrics.density * 288), false); + } catch (FileNotFoundException e) { + return null; + } + } + + @Override + protected void onPostExecute(Bitmap bitmap) { + if (imageViewReference != null && bitmap != null) { + final ImageView imageView = imageViewReference.get(); + if (imageView != null) { + imageView.setImageBitmap(bitmap); + imageView.setBackgroundColor(0x00000000); + } + } + } + } + + public void loadBitmap(Message message, ImageView imageView) { + Bitmap bm; + try { + bm = xmppConnectionService.getFileBackend().getThumbnail(message, + (int) (metrics.density * 288), true); + } catch (FileNotFoundException e) { + bm = null; + } + if (bm != null) { + imageView.setImageBitmap(bm); + imageView.setBackgroundColor(0x00000000); + } else { + if (cancelPotentialWork(message, imageView)) { + imageView.setBackgroundColor(0xff333333); + final BitmapWorkerTask task = new BitmapWorkerTask(imageView); + final AsyncDrawable asyncDrawable = new AsyncDrawable( + getResources(), null, task); + imageView.setImageDrawable(asyncDrawable); + try { + task.execute(message); + } catch (RejectedExecutionException e) { + return; + } + } + } + } + + public static boolean cancelPotentialWork(Message message, + ImageView imageView) { + final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + if (bitmapWorkerTask != null) { + final Message oldMessage = bitmapWorkerTask.message; + if (oldMessage == null || message != oldMessage) { + bitmapWorkerTask.cancel(true); + } else { + return false; + } + } + return true; + } + + private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { + if (imageView != null) { + final Drawable drawable = imageView.getDrawable(); + if (drawable instanceof AsyncDrawable) { + final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; + return asyncDrawable.getBitmapWorkerTask(); + } + } + return null; + } + + static class AsyncDrawable extends BitmapDrawable { + private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference; + + public AsyncDrawable(Resources res, Bitmap bitmap, + BitmapWorkerTask bitmapWorkerTask) { + super(res, bitmap); + bitmapWorkerTaskReference = new WeakReference<BitmapWorkerTask>( + bitmapWorkerTask); + } + + public BitmapWorkerTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } + } } diff --git a/src/eu/siacs/conversations/ui/adapter/AccountAdapter.java b/src/eu/siacs/conversations/ui/adapter/AccountAdapter.java index cd8b376f..5c25bf34 100644 --- a/src/eu/siacs/conversations/ui/adapter/AccountAdapter.java +++ b/src/eu/siacs/conversations/ui/adapter/AccountAdapter.java @@ -14,9 +14,9 @@ import android.widget.ImageView; import android.widget.TextView; public class AccountAdapter extends ArrayAdapter<Account> { - + private XmppActivity activity; - + public AccountAdapter(XmppActivity activity, List<Account> objects) { super(activity, 0, objects); this.activity = activity; @@ -34,7 +34,7 @@ public class AccountAdapter extends ArrayAdapter<Account> { jid.setText(account.getJid()); TextView statusView = (TextView) view.findViewById(R.id.account_status); ImageView imageView = (ImageView) view.findViewById(R.id.account_image); - imageView.setImageBitmap(account.getImage(activity,48)); + imageView.setImageBitmap(account.getImage(activity, 48)); switch (account.getStatus()) { case Account.STATUS_DISABLED: statusView.setText(getContext().getString( diff --git a/src/eu/siacs/conversations/ui/adapter/ConversationAdapter.java b/src/eu/siacs/conversations/ui/adapter/ConversationAdapter.java index fcc57601..6eb9846a 100644 --- a/src/eu/siacs/conversations/ui/adapter/ConversationAdapter.java +++ b/src/eu/siacs/conversations/ui/adapter/ConversationAdapter.java @@ -2,10 +2,12 @@ package eu.siacs.conversations.ui.adapter; import java.util.List; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.ui.ConversationActivity; +import eu.siacs.conversations.ui.XmppActivity; import eu.siacs.conversations.utils.UIHelper; import android.content.Context; import android.graphics.Color; @@ -19,9 +21,9 @@ import android.widget.TextView; public class ConversationAdapter extends ArrayAdapter<Conversation> { - ConversationActivity activity; + private XmppActivity activity; - public ConversationAdapter(ConversationActivity activity, + public ConversationAdapter(XmppActivity activity, List<Conversation> conversations) { super(activity, 0, conversations); this.activity = activity; @@ -36,18 +38,21 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { parent, false); } Conversation conv = getItem(position); - if (!activity.getSlidingPaneLayout().isSlideable()) { - if (conv == activity.getSelectedConversation()) { - view.setBackgroundColor(0xffdddddd); + if (this.activity instanceof ConversationActivity) { + ConversationActivity activity = (ConversationActivity) this.activity; + if (!activity.getSlidingPaneLayout().isSlideable()) { + if (conv == activity.getSelectedConversation()) { + view.setBackgroundColor(0xffdddddd); + } else { + view.setBackgroundColor(Color.TRANSPARENT); + } } else { view.setBackgroundColor(Color.TRANSPARENT); } - } else { - view.setBackgroundColor(Color.TRANSPARENT); } TextView convName = (TextView) view .findViewById(R.id.conversation_name); - convName.setText(conv.getName(true)); + convName.setText(conv.getName()); TextView convLastMsg = (TextView) view .findViewById(R.id.conversation_lastmsg); ImageView imagePreview = (ImageView) view @@ -59,7 +64,10 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { || latestMessage.getType() == Message.TYPE_PRIVATE) { if ((latestMessage.getEncryption() != Message.ENCRYPTION_PGP) && (latestMessage.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED)) { - convLastMsg.setText(conv.getLatestMessage().getBody()); + String body = Config.PARSE_EMOTICONS ? UIHelper + .transformAsciiEmoticons(latestMessage.getBody()) + : latestMessage.getBody(); + convLastMsg.setText(body); } else { convLastMsg.setText(activity .getText(R.string.encrypted_message_received)); diff --git a/src/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java b/src/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java index d286052c..0534bc25 100644 --- a/src/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java +++ b/src/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java @@ -19,7 +19,8 @@ public class KnownHostsAdapter extends ArrayAdapter<String> { final String[] split = constraint.toString().split("@"); if (split.length == 1) { for (String domain : domains) { - suggestions.add(split[0].toLowerCase(Locale.getDefault()) + "@" + domain); + suggestions.add(split[0].toLowerCase(Locale + .getDefault()) + "@" + domain); } } else if (split.length == 2) { for (String domain : domains) { @@ -27,7 +28,8 @@ public class KnownHostsAdapter extends ArrayAdapter<String> { suggestions.clear(); break; } else if (domain.contains(split[1])) { - suggestions.add(split[0].toLowerCase(Locale.getDefault()) + "@" + domain); + suggestions.add(split[0].toLowerCase(Locale + .getDefault()) + "@" + domain); } } } else { diff --git a/src/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 2452a555..ccf25a33 100644 --- a/src/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -3,6 +3,7 @@ package eu.siacs.conversations.ui.adapter; import java.util.HashMap; import java.util.List; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -14,7 +15,6 @@ import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Typeface; -import android.text.Html; import android.text.Spannable; import android.text.SpannableString; import android.text.style.ForegroundColorSpan; @@ -34,8 +34,9 @@ import android.widget.Toast; public class MessageAdapter extends ArrayAdapter<Message> { private static final int SENT = 0; - private static final int RECIEVED = 1; + private static final int RECEIVED = 1; private static final int STATUS = 2; + private static final int NULL = 3; private ConversationActivity activity; @@ -67,22 +68,25 @@ public class MessageAdapter extends ArrayAdapter<Message> { public void setOnContactPictureClicked(OnContactPictureClicked listener) { this.mOnContactPictureClickedListener = listener; } - - public void setOnContactPictureLongClicked(OnContactPictureLongClicked listener) { + + public void setOnContactPictureLongClicked( + OnContactPictureLongClicked listener) { this.mOnContactPictureLongClickedListener = listener; } @Override public int getViewTypeCount() { - return 3; + return 4; } @Override public int getItemViewType(int position) { - if (getItem(position).getType() == Message.TYPE_STATUS) { + if (getItem(position).wasMergedIntoPrevious()) { + return NULL; + } else if (getItem(position).getType() == Message.TYPE_STATUS) { return STATUS; } else if (getItem(position).getStatus() <= Message.STATUS_RECEIVED) { - return RECIEVED; + return RECEIVED; } else { return SENT; } @@ -93,7 +97,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { String info = null; boolean error = false; boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI - && message.getStatus() <= Message.STATUS_RECEIVED; + && message.getMergedStatus() <= Message.STATUS_RECEIVED; if (message.getType() == Message.TYPE_IMAGE) { String[] fileParams = message.getBody().split(","); try { @@ -103,7 +107,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { filesize = "0 KB"; } } - switch (message.getStatus()) { + switch (message.getMergedStatus()) { case Message.STATUS_WAITING: info = getContext().getString(R.string.waiting); break; @@ -151,7 +155,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { } String formatedTime = UIHelper.readableTimeDifference(getContext(), - message.getTimeSent()); + message.getMergedTimeSent()); if (message.getStatus() <= Message.STATUS_RECEIVED) { if ((filesize != null) && (info != null)) { viewHolder.time.setText(filesize + " \u00B7 " + info); @@ -212,11 +216,15 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.messageBody.setVisibility(View.VISIBLE); if (message.getBody() != null) { if (message.getType() != Message.TYPE_PRIVATE) { - viewHolder.messageBody.setText(message.getBody().trim()); + String body = Config.PARSE_EMOTICONS ? UIHelper + .transformAsciiEmoticons(message.getMergedBody()) + : message.getMergedBody(); + viewHolder.messageBody.setText(body); } else { String privateMarker; if (message.getStatus() <= Message.STATUS_RECEIVED) { - privateMarker = activity.getString(R.string.private_message); + privateMarker = activity + .getString(R.string.private_message); } else { String to; if (message.getPresence() != null) { @@ -224,11 +232,18 @@ public class MessageAdapter extends ArrayAdapter<Message> { } else { to = message.getCounterpart(); } - privateMarker = activity.getString(R.string.private_message_to, to); + 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, privateMarker.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + 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, + privateMarker.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); viewHolder.messageBody.setText(span); } } else { @@ -301,6 +316,10 @@ public class MessageAdapter extends ArrayAdapter<Message> { if (view == null) { viewHolder = new ViewHolder(); switch (type) { + case NULL: + view = (View) activity.getLayoutInflater().inflate( + R.layout.message_null, parent, false); + break; case SENT: view = (View) activity.getLayoutInflater().inflate( R.layout.message_sent, parent, false); @@ -319,7 +338,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { .findViewById(R.id.message_time); view.setTag(viewHolder); break; - case RECIEVED: + case RECEIVED: view = (View) activity.getLayoutInflater().inflate( R.layout.message_received, parent, false); viewHolder.message_box = (LinearLayout) view @@ -362,7 +381,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { @Override public void onClick(View v) { String name = item.getConversation() - .getName(true); + .getName(); String read = getContext() .getString( R.string.contact_has_read_up_to_this_point, @@ -382,11 +401,11 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder = (ViewHolder) view.getTag(); } - if (type == STATUS) { + if (type == STATUS || type == NULL) { return view; } - if (type == RECIEVED) { + if (type == RECEIVED) { if (item.getConversation().getMode() == Conversation.MODE_MULTI) { Contact contact = item.getContact(); if (contact != null) { @@ -394,10 +413,11 @@ public class MessageAdapter extends ArrayAdapter<Message> { contact, getContext())); } else { String name = item.getPresence(); - if (name==null) { + if (name == null) { name = item.getCounterpart(); } - viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(name, getContext())); + viewHolder.contact_picture.setImageBitmap(mBitmapCache.get( + name, getContext())); } viewHolder.contact_picture .setOnClickListener(new OnClickListener() { @@ -412,18 +432,20 @@ public class MessageAdapter extends ArrayAdapter<Message> { } }); - viewHolder.contact_picture.setOnLongClickListener(new OnLongClickListener() { - - @Override - public boolean onLongClick(View v) { - if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) { - MessageAdapter.this.mOnContactPictureLongClickedListener.onContactPictureLongClicked(item); - return true; - } else { - return false; - } - } - }); + viewHolder.contact_picture + .setOnLongClickListener(new OnLongClickListener() { + + @Override + public boolean onLongClick(View v) { + if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) { + MessageAdapter.this.mOnContactPictureLongClickedListener + .onContactPictureLongClicked(item); + return true; + } else { + return false; + } + } + }); } } @@ -522,7 +544,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { public interface OnContactPictureClicked { public void onContactPictureClicked(Message message); } - + public interface OnContactPictureLongClicked { public void onContactPictureLongClicked(Message message); } diff --git a/src/eu/siacs/conversations/utils/CryptoHelper.java b/src/eu/siacs/conversations/utils/CryptoHelper.java index a70b419e..a28b519e 100644 --- a/src/eu/siacs/conversations/utils/CryptoHelper.java +++ b/src/eu/siacs/conversations/utils/CryptoHelper.java @@ -11,7 +11,7 @@ import eu.siacs.conversations.entities.Account; import android.util.Base64; public class CryptoHelper { - public static final String FILETRANSFER = "?FILETRANSFERv1:"; + 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" @@ -26,7 +26,7 @@ public class CryptoHelper { } return new String(hexChars); } - + public static byte[] hexToBytes(String hexString) { byte[] array = new BigInteger(hexString, 16).toByteArray(); if (array[0] == 0) { @@ -42,13 +42,14 @@ public class CryptoHelper { } private static byte[] concatenateByteArrays(byte[] a, byte[] b) { - byte[] result = new byte[a.length + b.length]; - System.arraycopy(a, 0, result, 0, a.length); - System.arraycopy(b, 0, result, a.length, b.length); - return result; - } - - public static String saslDigestMd5(Account account, String challenge, SecureRandom random) { + byte[] result = new byte[a.length + b.length]; + System.arraycopy(a, 0, result, 0, a.length); + System.arraycopy(b, 0, result, a.length, b.length); + return result; + } + + public static String saslDigestMd5(Account account, String challenge, + SecureRandom random) { try { String[] challengeParts = new String(Base64.decode(challenge, Base64.DEFAULT)).split(","); @@ -61,28 +62,29 @@ public class CryptoHelper { return null; } } - String digestUri = "xmpp/"+account.getServer(); + String digestUri = "xmpp/" + account.getServer(); String nonceCount = "00000001"; String x = account.getUsername() + ":" + account.getServer() + ":" + account.getPassword(); MessageDigest md = MessageDigest.getInstance("MD5"); - byte[] y = md - .digest(x.getBytes(Charset.defaultCharset())); + byte[] y = md.digest(x.getBytes(Charset.defaultCharset())); String cNonce = new BigInteger(100, random).toString(32); - byte[] a1 = concatenateByteArrays(y,(":"+nonce+":"+cNonce).getBytes(Charset.defaultCharset())); - String a2 = "AUTHENTICATE:"+digestUri; + byte[] a1 = concatenateByteArrays(y, + (":" + nonce + ":" + cNonce).getBytes(Charset + .defaultCharset())); + String a2 = "AUTHENTICATE:" + digestUri; String ha1 = bytesToHex(md.digest(a1)); String ha2 = bytesToHex(md.digest(a2.getBytes(Charset .defaultCharset()))); - String kd = ha1 + ":" + nonce + ":"+nonceCount+":" + cNonce + ":auth:" - + ha2; + String kd = ha1 + ":" + nonce + ":" + nonceCount + ":" + cNonce + + ":auth:" + ha2; String response = bytesToHex(md.digest(kd.getBytes(Charset .defaultCharset()))); String saslString = "username=\"" + account.getUsername() + "\",realm=\"" + account.getServer() + "\",nonce=\"" - + nonce + "\",cnonce=\"" + cNonce - + "\",nc="+nonceCount+",qop=auth,digest-uri=\""+digestUri+"\",response=" + response - + ",charset=utf-8"; + + nonce + "\",cnonce=\"" + cNonce + "\",nc=" + nonceCount + + ",qop=auth,digest-uri=\"" + digestUri + "\",response=" + + response + ",charset=utf-8"; return Base64.encodeToString( saslString.getBytes(Charset.defaultCharset()), Base64.NO_WRAP); diff --git a/src/eu/siacs/conversations/utils/DNSHelper.java b/src/eu/siacs/conversations/utils/DNSHelper.java index 002e124f..fd3b1953 100644 --- a/src/eu/siacs/conversations/utils/DNSHelper.java +++ b/src/eu/siacs/conversations/utils/DNSHelper.java @@ -10,6 +10,7 @@ import de.measite.minidns.record.A; import de.measite.minidns.record.AAAA; import de.measite.minidns.record.Data; import de.measite.minidns.util.NameUtil; +import eu.siacs.conversations.Config; import java.io.IOException; import java.net.InetAddress; @@ -47,14 +48,11 @@ public class DNSHelper { Bundle namePort = new Bundle(); try { String qname = "_xmpp-client._tcp." + host; - Log.d("xmppService", "using dns server: " + dnsServer.getHostAddress() - + " to look up " + host); - DNSMessage message = - client.query( - qname, - TYPE.SRV, - CLASS.IN, - dnsServer.getHostAddress()); + Log.d(Config.LOGTAG, + "using dns server: " + dnsServer.getHostAddress() + + " to look up " + host); + DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, + dnsServer.getHostAddress()); // How should we handle priorities and weight? // Wikipedia has a nice article about priorities vs. weights: @@ -64,21 +62,20 @@ public class DNSHelper { // a random order respecting the weight, and dump that priority by // priority - TreeMap<Integer, ArrayList<SRV>> priorities = - new TreeMap<Integer, ArrayList<SRV>>(); - TreeMap<String, ArrayList<String>> ips4 = - new TreeMap<String, ArrayList<String>>(); - TreeMap<String, ArrayList<String>> ips6 = - new TreeMap<String, ArrayList<String>>(); + TreeMap<Integer, ArrayList<SRV>> priorities = new TreeMap<Integer, ArrayList<SRV>>(); + TreeMap<String, ArrayList<String>> ips4 = new TreeMap<String, ArrayList<String>>(); + TreeMap<String, ArrayList<String>> ips6 = new TreeMap<String, ArrayList<String>>(); - for (Record[] rrset : new Record[][]{ message.getAnswers(), - message.getAdditionalResourceRecords()}) { + for (Record[] rrset : new Record[][] { message.getAnswers(), + message.getAdditionalResourceRecords() }) { for (Record rr : rrset) { Data d = rr.getPayload(); - if (d instanceof SRV && NameUtil.idnEquals(qname,rr.getName())) { + if (d instanceof SRV + && NameUtil.idnEquals(qname, rr.getName())) { SRV srv = (SRV) d; if (!priorities.containsKey(srv.getPriority())) { - priorities.put(srv.getPriority(), new ArrayList<SRV>(2)); + priorities.put(srv.getPriority(), + new ArrayList<SRV>(2)); } priorities.get(srv.getPriority()).add(srv); } @@ -100,8 +97,9 @@ public class DNSHelper { } Random rnd = new Random(); - ArrayList<SRV> result = new ArrayList<SRV>(priorities.size() * 2 + 1); - for (ArrayList<SRV> s: priorities.values()) { + ArrayList<SRV> result = new ArrayList<SRV>( + priorities.size() * 2 + 1); + for (ArrayList<SRV> s : priorities.values()) { // trivial case if (s.size() <= 1) { @@ -110,18 +108,20 @@ public class DNSHelper { } long totalweight = 0l; - for (SRV srv: s) { + for (SRV srv : s) { totalweight += srv.getWeight(); } while (totalweight > 0l && s.size() > 0) { - long p = (rnd.nextLong() & 0x7fffffffffffffffl) % totalweight; + long p = (rnd.nextLong() & 0x7fffffffffffffffl) + % totalweight; int i = 0; while (p > 0) { p -= s.get(i++).getPriority(); } i--; - // remove is expensive, but we have only a few entries anyway + // remove is expensive, but we have only a few entries + // anyway SRV srv = s.remove(i); totalweight -= srv.getWeight(); result.add(srv); @@ -164,10 +164,10 @@ public class DNSHelper { } } catch (SocketTimeoutException e) { - Log.d("xmppService", "timeout during dns"); + Log.d(Config.LOGTAG, "timeout during dns"); namePort.putString("error", "timeout"); } catch (Exception e) { - Log.d("xmppService","unhandled exception in sub project"); + Log.d(Config.LOGTAG, "unhandled exception in sub project"); namePort.putString("error", "unhandled"); } return namePort; diff --git a/src/eu/siacs/conversations/utils/ExceptionHandler.java b/src/eu/siacs/conversations/utils/ExceptionHandler.java index 01cfebbb..88fa18ff 100644 --- a/src/eu/siacs/conversations/utils/ExceptionHandler.java +++ b/src/eu/siacs/conversations/utils/ExceptionHandler.java @@ -11,22 +11,25 @@ import java.lang.Thread.UncaughtExceptionHandler; import android.content.Context; public class ExceptionHandler implements UncaughtExceptionHandler { - + private UncaughtExceptionHandler defaultHandler; private Context context; + public ExceptionHandler(Context context) { this.context = context; this.defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); } + @Override public void uncaughtException(Thread thread, Throwable ex) { Writer result = new StringWriter(); - PrintWriter printWriter = new PrintWriter(result); - ex.printStackTrace(printWriter); - String stacktrace = result.toString(); - printWriter.close(); - try { - OutputStream os = context.openFileOutput("stacktrace.txt",Context.MODE_PRIVATE); + PrintWriter printWriter = new PrintWriter(result); + ex.printStackTrace(printWriter); + String stacktrace = result.toString(); + printWriter.close(); + try { + OutputStream os = context.openFileOutput("stacktrace.txt", + Context.MODE_PRIVATE); os.write(stacktrace.getBytes()); } catch (FileNotFoundException e) { // TODO Auto-generated catch block diff --git a/src/eu/siacs/conversations/utils/ExceptionHelper.java b/src/eu/siacs/conversations/utils/ExceptionHelper.java index 373aadd3..b5fc88bd 100644 --- a/src/eu/siacs/conversations/utils/ExceptionHelper.java +++ b/src/eu/siacs/conversations/utils/ExceptionHelper.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.util.List; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; @@ -26,77 +27,91 @@ import android.util.Log; public class ExceptionHelper { public static void init(Context context) { - if(!(Thread.getDefaultUncaughtExceptionHandler() instanceof ExceptionHandler)) { - Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler(context)); + if (!(Thread.getDefaultUncaughtExceptionHandler() instanceof ExceptionHandler)) { + Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler( + context)); } } - - public static void checkForCrash(Context context, final XmppConnectionService service) { + + public static void checkForCrash(Context context, + final XmppConnectionService service) { try { - final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); - boolean neverSend = preferences.getBoolean("never_send",false); + final SharedPreferences preferences = PreferenceManager + .getDefaultSharedPreferences(context); + boolean neverSend = preferences.getBoolean("never_send", false); if (neverSend) { return; } List<Account> accounts = service.getAccounts(); Account account = null; - for(int i = 0; i < accounts.size(); ++i) { + for (int i = 0; i < accounts.size(); ++i) { if (!accounts.get(i).isOptionSet(Account.OPTION_DISABLED)) { account = accounts.get(i); break; } } - if (account==null) { + if (account == null) { return; } final Account finalAccount = account; FileInputStream file = context.openFileInput("stacktrace.txt"); - InputStreamReader inputStreamReader = new InputStreamReader( - file); - BufferedReader stacktrace = new BufferedReader( - inputStreamReader); - final StringBuilder report = new StringBuilder(); - PackageManager pm = context.getPackageManager(); - PackageInfo packageInfo = null; - try { - packageInfo = pm.getPackageInfo(context.getPackageName(), 0); - report.append("Version: "+packageInfo.versionName+'\n'); - report.append("Last Update: "+DateUtils.formatDateTime(context, packageInfo.lastUpdateTime, DateUtils.FORMAT_SHOW_TIME|DateUtils.FORMAT_SHOW_DATE)+'\n'); - } catch (NameNotFoundException e) {} - String line; - while((line = stacktrace.readLine()) != null) { - report.append(line); - report.append('\n'); - } - file.close(); - context.deleteFile("stacktrace.txt"); + InputStreamReader inputStreamReader = new InputStreamReader(file); + BufferedReader stacktrace = new BufferedReader(inputStreamReader); + final StringBuilder report = new StringBuilder(); + PackageManager pm = context.getPackageManager(); + PackageInfo packageInfo = null; + try { + packageInfo = pm.getPackageInfo(context.getPackageName(), 0); + report.append("Version: " + packageInfo.versionName + '\n'); + report.append("Last Update: " + + DateUtils.formatDateTime(context, + packageInfo.lastUpdateTime, + DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_SHOW_DATE) + '\n'); + } catch (NameNotFoundException e) { + } + String line; + while ((line = stacktrace.readLine()) != null) { + report.append(line); + report.append('\n'); + } + file.close(); + context.deleteFile("stacktrace.txt"); AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(context.getString(R.string.crash_report_title)); builder.setMessage(context.getText(R.string.crash_report_message)); - builder.setPositiveButton(context.getText(R.string.send_now), new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - - Log.d("xmppService","using account="+finalAccount.getJid()+" to send in stack trace"); - Conversation conversation = service.findOrCreateConversation(finalAccount, "bugs@siacs.eu", false); - Message message = new Message(conversation, report.toString(), Message.ENCRYPTION_NONE); - service.sendMessage(message); - } - }); - builder.setNegativeButton(context.getText(R.string.send_never),new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - preferences.edit().putBoolean("never_send", true).commit(); - } - }); + builder.setPositiveButton(context.getText(R.string.send_now), + new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + + Log.d(Config.LOGTAG, "using account=" + + finalAccount.getJid() + + " to send in stack trace"); + Conversation conversation = service + .findOrCreateConversation(finalAccount, + "bugs@siacs.eu", false); + Message message = new Message(conversation, report + .toString(), Message.ENCRYPTION_NONE); + service.sendMessage(message); + } + }); + builder.setNegativeButton(context.getText(R.string.send_never), + new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + preferences.edit().putBoolean("never_send", true) + .commit(); + } + }); builder.create().show(); } catch (FileNotFoundException e) { return; } catch (IOException e) { return; } - + } } diff --git a/src/eu/siacs/conversations/utils/PRNGFixes.java b/src/eu/siacs/conversations/utils/PRNGFixes.java index cf28ff30..8fe67234 100644 --- a/src/eu/siacs/conversations/utils/PRNGFixes.java +++ b/src/eu/siacs/conversations/utils/PRNGFixes.java @@ -21,306 +21,307 @@ import java.security.Security; /** * Fixes for the output of the default PRNG having low entropy. - * + * * The fixes need to be applied via {@link #apply()} before any use of Java * Cryptography Architecture primitives. A good place to invoke them is in the * application's {@code onCreate}. */ public final class PRNGFixes { - private static final int VERSION_CODE_JELLY_BEAN = 16; - private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18; - private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = - getBuildFingerprintAndDeviceSerial(); + private static final int VERSION_CODE_JELLY_BEAN = 16; + private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18; + private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = getBuildFingerprintAndDeviceSerial(); - /** Hidden constructor to prevent instantiation. */ - private PRNGFixes() {} + /** Hidden constructor to prevent instantiation. */ + private PRNGFixes() { + } - /** - * Applies all fixes. - * - * @throws SecurityException if a fix is needed but could not be applied. - */ - public static void apply() { - applyOpenSSLFix(); - installLinuxPRNGSecureRandom(); - } + /** + * Applies all fixes. + * + * @throws SecurityException + * if a fix is needed but could not be applied. + */ + public static void apply() { + applyOpenSSLFix(); + installLinuxPRNGSecureRandom(); + } - /** - * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the - * fix is not needed. - * - * @throws SecurityException if the fix is needed but could not be applied. - */ - private static void applyOpenSSLFix() throws SecurityException { - if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN) - || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) { - // No need to apply the fix - return; - } + /** + * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the + * fix is not needed. + * + * @throws SecurityException + * if the fix is needed but could not be applied. + */ + private static void applyOpenSSLFix() throws SecurityException { + if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN) + || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) { + // No need to apply the fix + return; + } - try { - // Mix in the device- and invocation-specific seed. - Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto") - .getMethod("RAND_seed", byte[].class) - .invoke(null, generateSeed()); + try { + // Mix in the device- and invocation-specific seed. + Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto") + .getMethod("RAND_seed", byte[].class) + .invoke(null, generateSeed()); - // Mix output of Linux PRNG into OpenSSL's PRNG - int bytesRead = (Integer) Class.forName( - "org.apache.harmony.xnet.provider.jsse.NativeCrypto") - .getMethod("RAND_load_file", String.class, long.class) - .invoke(null, "/dev/urandom", 1024); - if (bytesRead != 1024) { - throw new IOException( - "Unexpected number of bytes read from Linux PRNG: " - + bytesRead); - } - } catch (Exception e) { - throw new SecurityException("Failed to seed OpenSSL PRNG", e); - } - } + // Mix output of Linux PRNG into OpenSSL's PRNG + int bytesRead = (Integer) Class + .forName( + "org.apache.harmony.xnet.provider.jsse.NativeCrypto") + .getMethod("RAND_load_file", String.class, long.class) + .invoke(null, "/dev/urandom", 1024); + if (bytesRead != 1024) { + throw new IOException( + "Unexpected number of bytes read from Linux PRNG: " + + bytesRead); + } + } catch (Exception e) { + throw new SecurityException("Failed to seed OpenSSL PRNG", e); + } + } - /** - * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the - * default. Does nothing if the implementation is already the default or if - * there is not need to install the implementation. - * - * @throws SecurityException if the fix is needed but could not be applied. - */ - private static void installLinuxPRNGSecureRandom() - throws SecurityException { - if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) { - // No need to apply the fix - return; - } + /** + * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the + * default. Does nothing if the implementation is already the default or if + * there is not need to install the implementation. + * + * @throws SecurityException + * if the fix is needed but could not be applied. + */ + private static void installLinuxPRNGSecureRandom() throws SecurityException { + if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) { + // No need to apply the fix + return; + } - // Install a Linux PRNG-based SecureRandom implementation as the - // default, if not yet installed. - Provider[] secureRandomProviders = - Security.getProviders("SecureRandom.SHA1PRNG"); - if ((secureRandomProviders == null) - || (secureRandomProviders.length < 1) - || (!LinuxPRNGSecureRandomProvider.class.equals( - secureRandomProviders[0].getClass()))) { - Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1); - } + // Install a Linux PRNG-based SecureRandom implementation as the + // default, if not yet installed. + Provider[] secureRandomProviders = Security + .getProviders("SecureRandom.SHA1PRNG"); + if ((secureRandomProviders == null) + || (secureRandomProviders.length < 1) + || (!LinuxPRNGSecureRandomProvider.class + .equals(secureRandomProviders[0].getClass()))) { + Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1); + } - // Assert that new SecureRandom() and - // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed - // by the Linux PRNG-based SecureRandom implementation. - SecureRandom rng1 = new SecureRandom(); - if (!LinuxPRNGSecureRandomProvider.class.equals( - rng1.getProvider().getClass())) { - throw new SecurityException( - "new SecureRandom() backed by wrong Provider: " - + rng1.getProvider().getClass()); - } + // Assert that new SecureRandom() and + // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed + // by the Linux PRNG-based SecureRandom implementation. + SecureRandom rng1 = new SecureRandom(); + if (!LinuxPRNGSecureRandomProvider.class.equals(rng1.getProvider() + .getClass())) { + throw new SecurityException( + "new SecureRandom() backed by wrong Provider: " + + rng1.getProvider().getClass()); + } - SecureRandom rng2; - try { - rng2 = SecureRandom.getInstance("SHA1PRNG"); - } catch (NoSuchAlgorithmException e) { - throw new SecurityException("SHA1PRNG not available", e); - } - if (!LinuxPRNGSecureRandomProvider.class.equals( - rng2.getProvider().getClass())) { - throw new SecurityException( - "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" - + " Provider: " + rng2.getProvider().getClass()); - } - } + SecureRandom rng2; + try { + rng2 = SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) { + throw new SecurityException("SHA1PRNG not available", e); + } + if (!LinuxPRNGSecureRandomProvider.class.equals(rng2.getProvider() + .getClass())) { + throw new SecurityException( + "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + + " Provider: " + rng2.getProvider().getClass()); + } + } - /** - * {@code Provider} of {@code SecureRandom} engines which pass through - * all requests to the Linux PRNG. - */ - private static class LinuxPRNGSecureRandomProvider extends Provider { + /** + * {@code Provider} of {@code SecureRandom} engines which pass through all + * requests to the Linux PRNG. + */ + private static class LinuxPRNGSecureRandomProvider extends Provider { - public LinuxPRNGSecureRandomProvider() { - super("LinuxPRNG", - 1.0, - "A Linux-specific random number provider that uses" - + " /dev/urandom"); - // Although /dev/urandom is not a SHA-1 PRNG, some apps - // explicitly request a SHA1PRNG SecureRandom and we thus need to - // prevent them from getting the default implementation whose output - // may have low entropy. - put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName()); - put("SecureRandom.SHA1PRNG ImplementedIn", "Software"); - } - } + public LinuxPRNGSecureRandomProvider() { + super("LinuxPRNG", 1.0, + "A Linux-specific random number provider that uses" + + " /dev/urandom"); + // Although /dev/urandom is not a SHA-1 PRNG, some apps + // explicitly request a SHA1PRNG SecureRandom and we thus need to + // prevent them from getting the default implementation whose output + // may have low entropy. + put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName()); + put("SecureRandom.SHA1PRNG ImplementedIn", "Software"); + } + } - /** - * {@link SecureRandomSpi} which passes all requests to the Linux PRNG - * ({@code /dev/urandom}). - */ - public static class LinuxPRNGSecureRandom extends SecureRandomSpi { + /** + * {@link SecureRandomSpi} which passes all requests to the Linux PRNG ( + * {@code /dev/urandom}). + */ + public static class LinuxPRNGSecureRandom extends SecureRandomSpi { - /* - * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed - * are passed through to the Linux PRNG (/dev/urandom). Instances of - * this class seed themselves by mixing in the current time, PID, UID, - * build fingerprint, and hardware serial number (where available) into - * Linux PRNG. - * - * Concurrency: Read requests to the underlying Linux PRNG are - * serialized (on sLock) to ensure that multiple threads do not get - * duplicated PRNG output. - */ + /* + * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed + * are passed through to the Linux PRNG (/dev/urandom). Instances of + * this class seed themselves by mixing in the current time, PID, UID, + * build fingerprint, and hardware serial number (where available) into + * Linux PRNG. + * + * Concurrency: Read requests to the underlying Linux PRNG are + * serialized (on sLock) to ensure that multiple threads do not get + * duplicated PRNG output. + */ - private static final File URANDOM_FILE = new File("/dev/urandom"); + private static final File URANDOM_FILE = new File("/dev/urandom"); - private static final Object sLock = new Object(); + private static final Object sLock = new Object(); - /** - * Input stream for reading from Linux PRNG or {@code null} if not yet - * opened. - * - * @GuardedBy("sLock") - */ - private static DataInputStream sUrandomIn; + /** + * Input stream for reading from Linux PRNG or {@code null} if not yet + * opened. + * + * @GuardedBy("sLock") + */ + private static DataInputStream sUrandomIn; - /** - * Output stream for writing to Linux PRNG or {@code null} if not yet - * opened. - * - * @GuardedBy("sLock") - */ - private static OutputStream sUrandomOut; + /** + * Output stream for writing to Linux PRNG or {@code null} if not yet + * opened. + * + * @GuardedBy("sLock") + */ + private static OutputStream sUrandomOut; - /** - * Whether this engine instance has been seeded. This is needed because - * each instance needs to seed itself if the client does not explicitly - * seed it. - */ - private boolean mSeeded; + /** + * Whether this engine instance has been seeded. This is needed because + * each instance needs to seed itself if the client does not explicitly + * seed it. + */ + private boolean mSeeded; - @Override - protected void engineSetSeed(byte[] bytes) { - try { - OutputStream out; - synchronized (sLock) { - out = getUrandomOutputStream(); - } - out.write(bytes); - out.flush(); - } catch (IOException e) { - // On a small fraction of devices /dev/urandom is not writable. - // Log and ignore. - Log.w(PRNGFixes.class.getSimpleName(), - "Failed to mix seed into " + URANDOM_FILE); - } finally { - mSeeded = true; - } - } + @Override + protected void engineSetSeed(byte[] bytes) { + try { + OutputStream out; + synchronized (sLock) { + out = getUrandomOutputStream(); + } + out.write(bytes); + out.flush(); + } catch (IOException e) { + // On a small fraction of devices /dev/urandom is not writable. + // Log and ignore. + Log.w(PRNGFixes.class.getSimpleName(), + "Failed to mix seed into " + URANDOM_FILE); + } finally { + mSeeded = true; + } + } - @Override - protected void engineNextBytes(byte[] bytes) { - if (!mSeeded) { - // Mix in the device- and invocation-specific seed. - engineSetSeed(generateSeed()); - } + @Override + protected void engineNextBytes(byte[] bytes) { + if (!mSeeded) { + // Mix in the device- and invocation-specific seed. + engineSetSeed(generateSeed()); + } - try { - DataInputStream in; - synchronized (sLock) { - in = getUrandomInputStream(); - } - synchronized (in) { - in.readFully(bytes); - } - } catch (IOException e) { - throw new SecurityException( - "Failed to read from " + URANDOM_FILE, e); - } - } + try { + DataInputStream in; + synchronized (sLock) { + in = getUrandomInputStream(); + } + synchronized (in) { + in.readFully(bytes); + } + } catch (IOException e) { + throw new SecurityException("Failed to read from " + + URANDOM_FILE, e); + } + } - @Override - protected byte[] engineGenerateSeed(int size) { - byte[] seed = new byte[size]; - engineNextBytes(seed); - return seed; - } + @Override + protected byte[] engineGenerateSeed(int size) { + byte[] seed = new byte[size]; + engineNextBytes(seed); + return seed; + } - private DataInputStream getUrandomInputStream() { - synchronized (sLock) { - if (sUrandomIn == null) { - // NOTE: Consider inserting a BufferedInputStream between - // DataInputStream and FileInputStream if you need higher - // PRNG output performance and can live with future PRNG - // output being pulled into this process prematurely. - try { - sUrandomIn = new DataInputStream( - new FileInputStream(URANDOM_FILE)); - } catch (IOException e) { - throw new SecurityException("Failed to open " - + URANDOM_FILE + " for reading", e); - } - } - return sUrandomIn; - } - } + private DataInputStream getUrandomInputStream() { + synchronized (sLock) { + if (sUrandomIn == null) { + // NOTE: Consider inserting a BufferedInputStream between + // DataInputStream and FileInputStream if you need higher + // PRNG output performance and can live with future PRNG + // output being pulled into this process prematurely. + try { + sUrandomIn = new DataInputStream(new FileInputStream( + URANDOM_FILE)); + } catch (IOException e) { + throw new SecurityException("Failed to open " + + URANDOM_FILE + " for reading", e); + } + } + return sUrandomIn; + } + } - private OutputStream getUrandomOutputStream() throws IOException { - synchronized (sLock) { - if (sUrandomOut == null) { - sUrandomOut = new FileOutputStream(URANDOM_FILE); - } - return sUrandomOut; - } - } - } + private OutputStream getUrandomOutputStream() throws IOException { + synchronized (sLock) { + if (sUrandomOut == null) { + sUrandomOut = new FileOutputStream(URANDOM_FILE); + } + return sUrandomOut; + } + } + } - /** - * Generates a device- and invocation-specific seed to be mixed into the - * Linux PRNG. - */ - private static byte[] generateSeed() { - try { - ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream(); - DataOutputStream seedBufferOut = - new DataOutputStream(seedBuffer); - seedBufferOut.writeLong(System.currentTimeMillis()); - seedBufferOut.writeLong(System.nanoTime()); - seedBufferOut.writeInt(Process.myPid()); - seedBufferOut.writeInt(Process.myUid()); - seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL); - seedBufferOut.close(); - return seedBuffer.toByteArray(); - } catch (IOException e) { - throw new SecurityException("Failed to generate seed", e); - } - } + /** + * Generates a device- and invocation-specific seed to be mixed into the + * Linux PRNG. + */ + private static byte[] generateSeed() { + try { + ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream(); + DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer); + seedBufferOut.writeLong(System.currentTimeMillis()); + seedBufferOut.writeLong(System.nanoTime()); + seedBufferOut.writeInt(Process.myPid()); + seedBufferOut.writeInt(Process.myUid()); + seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL); + seedBufferOut.close(); + return seedBuffer.toByteArray(); + } catch (IOException e) { + throw new SecurityException("Failed to generate seed", e); + } + } - /** - * Gets the hardware serial number of this device. - * - * @return serial number or {@code null} if not available. - */ - private static String getDeviceSerialNumber() { - // We're using the Reflection API because Build.SERIAL is only available - // since API Level 9 (Gingerbread, Android 2.3). - try { - return (String) Build.class.getField("SERIAL").get(null); - } catch (Exception ignored) { - return null; - } - } + /** + * Gets the hardware serial number of this device. + * + * @return serial number or {@code null} if not available. + */ + private static String getDeviceSerialNumber() { + // We're using the Reflection API because Build.SERIAL is only available + // since API Level 9 (Gingerbread, Android 2.3). + try { + return (String) Build.class.getField("SERIAL").get(null); + } catch (Exception ignored) { + return null; + } + } - private static byte[] getBuildFingerprintAndDeviceSerial() { - StringBuilder result = new StringBuilder(); - String fingerprint = Build.FINGERPRINT; - if (fingerprint != null) { - result.append(fingerprint); - } - String serial = getDeviceSerialNumber(); - if (serial != null) { - result.append(serial); - } - try { - return result.toString().getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("UTF-8 encoding not supported"); - } - } + private static byte[] getBuildFingerprintAndDeviceSerial() { + StringBuilder result = new StringBuilder(); + String fingerprint = Build.FINGERPRINT; + if (fingerprint != null) { + result.append(fingerprint); + } + String serial = getDeviceSerialNumber(); + if (serial != null) { + result.append(serial); + } + try { + return result.toString().getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("UTF-8 encoding not supported"); + } + } }
\ No newline at end of file diff --git a/src/eu/siacs/conversations/utils/PhoneHelper.java b/src/eu/siacs/conversations/utils/PhoneHelper.java index 63c17761..975fa219 100644 --- a/src/eu/siacs/conversations/utils/PhoneHelper.java +++ b/src/eu/siacs/conversations/utils/PhoneHelper.java @@ -18,7 +18,7 @@ public class PhoneHelper { public static void loadPhoneContacts(Context context, final OnPhoneContactsLoadedListener listener) { final List<Bundle> phoneContacts = new ArrayList<Bundle>(); - + final String[] PROJECTION = new String[] { ContactsContract.Data._ID, ContactsContract.Data.DISPLAY_NAME, ContactsContract.Data.PHOTO_THUMBNAIL_URI, @@ -38,7 +38,7 @@ public class PhoneHelper { @Override public void onLoadComplete(Loader<Cursor> arg0, Cursor cursor) { - if (cursor==null) { + if (cursor == null) { return; } while (cursor.moveToNext()) { @@ -55,8 +55,10 @@ public class PhoneHelper { .getColumnIndex(ContactsContract.Data.PHOTO_THUMBNAIL_URI))); contact.putString("lookup", cursor.getString(cursor .getColumnIndex(ContactsContract.Data.LOOKUP_KEY))); - - contact.putString("jid",cursor.getString(cursor + + contact.putString( + "jid", + cursor.getString(cursor .getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA))); phoneContacts.add(contact); } @@ -69,8 +71,7 @@ public class PhoneHelper { } public static Uri getSefliUri(Context context) { - String[] mProjection = new String[] { Profile._ID, - Profile.PHOTO_URI }; + String[] mProjection = new String[] { Profile._ID, Profile.PHOTO_URI }; Cursor mProfileCursor = context.getContentResolver().query( Profile.CONTENT_URI, mProjection, null, null, null); diff --git a/src/eu/siacs/conversations/utils/UIHelper.java b/src/eu/siacs/conversations/utils/UIHelper.java index 7a807b2f..896bdb7d 100644 --- a/src/eu/siacs/conversations/utils/UIHelper.java +++ b/src/eu/siacs/conversations/utils/UIHelper.java @@ -118,9 +118,13 @@ public class UIHelper { } private static int getNameColor(String name) { - /*int holoColors[] = { 0xFF1da9da, 0xFFb368d9, 0xFF83b600, 0xFFffa713, - 0xFFe92727 };*/ - int holoColors[] = {0xFFe91e63, 0xFF9c27b0, 0xFF673ab7, 0xFF3f51b5, 0xFF5677fc, 0xFF03a9f4, 0xFF00bcd4, 0xFF009688, 0xFFff5722, 0xFF795548, 0xFF607d8b}; + /* + * int holoColors[] = { 0xFF1da9da, 0xFFb368d9, 0xFF83b600, 0xFFffa713, + * 0xFFe92727 }; + */ + int holoColors[] = { 0xFFe91e63, 0xFF9c27b0, 0xFF673ab7, 0xFF3f51b5, + 0xFF5677fc, 0xFF03a9f4, 0xFF00bcd4, 0xFF009688, 0xFFff5722, + 0xFF795548, 0xFF607d8b }; return holoColors[(int) ((name.hashCode() & 0xffffffffl) % holoColors.length)]; } @@ -211,14 +215,14 @@ public class UIHelper { List<User> members = conversation.getMucOptions().getUsers(); if (members.size() == 0) { return getUnknownContactPicture( - new String[] { conversation.getName(false) }, size, + new String[] { conversation.getName() }, size, bgColor, fgColor); } ArrayList<String> names = new ArrayList<String>(); names.add(conversation.getMucOptions().getActualNick()); - for(User user : members) { + for (User user : members) { names.add(user.getName()); - if (names.size() > 4 ) { + if (names.size() > 4) { break; } } @@ -328,7 +332,6 @@ public class UIHelper { SharedPreferences preferences = PreferenceManager .getDefaultSharedPreferences(context); - boolean useSubject = preferences.getBoolean("use_subject_in_muc", true); boolean showNofifications = preferences.getBoolean("show_notification", true); boolean vibrate = preferences.getBoolean("vibrate_on_notification", @@ -377,7 +380,7 @@ public class UIHelper { Conversation conversation = unread.get(0); targetUuid = conversation.getUuid(); mBuilder.setLargeIcon(conversation.getImage(context, 64)); - mBuilder.setContentTitle(conversation.getName(useSubject)); + mBuilder.setContentTitle(conversation.getName()); if (notify) { mBuilder.setTicker(conversation.getLatestMessage() .getReadableBody(context)); @@ -409,12 +412,12 @@ public class UIHelper { for (int i = 0; i < unread.size(); ++i) { targetUuid = unread.get(i).getUuid(); if (i < unread.size() - 1) { - names.append(unread.get(i).getName(useSubject) + ", "); + names.append(unread.get(i).getName() + ", "); } else { - names.append(unread.get(i).getName(useSubject)); + names.append(unread.get(i).getName()); } style.addLine(Html.fromHtml("<b>" - + unread.get(i).getName(useSubject) + + unread.get(i).getName() + "</b> " + unread.get(i).getLatestMessage() .getReadableBody(context))); @@ -541,4 +544,43 @@ public class UIHelper { return getContactPicture(account.getJid(), size, context, false); } } + + private final static class EmoticonPattern { + Pattern pattern; + String replacement; + EmoticonPattern(String ascii, int unicode) { + this.pattern = Pattern.compile("(?<=(^|\\s))" + ascii + "(?=(\\s|$))"); + this.replacement = new String(new int[]{unicode, }, 0, 1); + } + String replaceAll(String body) { + return pattern.matcher(body).replaceAll(replacement); + } + } + + private static final EmoticonPattern[] patterns = new EmoticonPattern[] { + new EmoticonPattern(":-?D", 0x1f600), + new EmoticonPattern("\\^\\^", 0x1f601), + new EmoticonPattern(":'D", 0x1f602), + new EmoticonPattern("\\]-?D", 0x1f608), + new EmoticonPattern(";-?\\)", 0x1f609), + new EmoticonPattern(":-?\\)", 0x1f60a), + new EmoticonPattern("[B8]-?\\)", 0x1f60e), + new EmoticonPattern(":-?\\|", 0x1f610), + new EmoticonPattern(":-?[/\\\\]", 0x1f615), + new EmoticonPattern(":-?\\*", 0x1f617), + new EmoticonPattern(":-?[Ppb]", 0x1f61b), + new EmoticonPattern(":-?\\(", 0x1f61e), + new EmoticonPattern(":-?[0Oo]", 0x1f62e), + new EmoticonPattern("\\\\o/", 0x1F631), + }; + + public static String transformAsciiEmoticons(String body) { + if (body != null) { + for (EmoticonPattern p: patterns) { + body = p.replaceAll(body); + } + body = body.trim(); + } + return body; + } } diff --git a/src/eu/siacs/conversations/utils/Validator.java b/src/eu/siacs/conversations/utils/Validator.java index a1a119e7..00130fa2 100644 --- a/src/eu/siacs/conversations/utils/Validator.java +++ b/src/eu/siacs/conversations/utils/Validator.java @@ -4,9 +4,9 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; public class Validator { - public static final Pattern VALID_JID = - Pattern.compile("^[^@/<>'\"\\s]+@[^@/<>'\"\\s]+$", Pattern.CASE_INSENSITIVE); - + public static final Pattern VALID_JID = Pattern.compile( + "^[^@/<>'\"\\s]+@[^@/<>'\"\\s]+$", Pattern.CASE_INSENSITIVE); + public static boolean isValidJid(String jid) { Matcher matcher = VALID_JID.matcher(jid); return matcher.find(); diff --git a/src/eu/siacs/conversations/utils/XmlHelper.java b/src/eu/siacs/conversations/utils/XmlHelper.java new file mode 100644 index 00000000..4dee07cf --- /dev/null +++ b/src/eu/siacs/conversations/utils/XmlHelper.java @@ -0,0 +1,12 @@ +package eu.siacs.conversations.utils; + +public class XmlHelper { + public static String encodeEntities(String content) { + content = content.replace("&", "&"); + content = content.replace("<", "<"); + content = content.replace(">", ">"); + content = content.replace("\"", """); + content = content.replace("'", "'"); + return content; + } +} diff --git a/src/eu/siacs/conversations/utils/zlib/ZLibInputStream.java b/src/eu/siacs/conversations/utils/zlib/ZLibInputStream.java index 2eebf6f4..b777c10c 100644 --- a/src/eu/siacs/conversations/utils/zlib/ZLibInputStream.java +++ b/src/eu/siacs/conversations/utils/zlib/ZLibInputStream.java @@ -12,41 +12,43 @@ import java.util.zip.InflaterInputStream; */ public class ZLibInputStream extends InflaterInputStream { - /** - * Construct a ZLibInputStream, reading data from the underlying stream. - * - * @param is The {@code InputStream} to read data from. - * @throws IOException If an {@code IOException} occurs. - */ - public ZLibInputStream(InputStream is) throws IOException { - super(is, new Inflater(), 512); - } + /** + * Construct a ZLibInputStream, reading data from the underlying stream. + * + * @param is + * The {@code InputStream} to read data from. + * @throws IOException + * If an {@code IOException} occurs. + */ + public ZLibInputStream(InputStream is) throws IOException { + super(is, new Inflater(), 512); + } - /** - * Provide a more InputStream compatible version of available. - * A return value of 1 means that it is likly to read one byte without - * blocking, 0 means that the system is known to block for more input. - * - * @return 0 if no data is available, 1 otherwise - * @throws IOException - */ - @Override - public int available() throws IOException { - /* This is one of the funny code blocks. - * InflaterInputStream.available violates the contract of - * InputStream.available, which breaks kXML2. - * - * I'm not sure who's to blame, oracle/sun for a broken api or the - * google guys for mixing a sun bug with a xml reader that can't handle - * it.... - * - * Anyway, this simple if breaks suns distorted reality, but helps - * to use the api as intended. - */ - if (inf.needsInput()) { - return 0; - } - return super.available(); - } + /** + * Provide a more InputStream compatible version of available. A return + * value of 1 means that it is likly to read one byte without blocking, 0 + * means that the system is known to block for more input. + * + * @return 0 if no data is available, 1 otherwise + * @throws IOException + */ + @Override + public int available() throws IOException { + /* + * This is one of the funny code blocks. InflaterInputStream.available + * violates the contract of InputStream.available, which breaks kXML2. + * + * I'm not sure who's to blame, oracle/sun for a broken api or the + * google guys for mixing a sun bug with a xml reader that can't handle + * it.... + * + * Anyway, this simple if breaks suns distorted reality, but helps to + * use the api as intended. + */ + if (inf.needsInput()) { + return 0; + } + return super.available(); + } } diff --git a/src/eu/siacs/conversations/utils/zlib/ZLibOutputStream.java b/src/eu/siacs/conversations/utils/zlib/ZLibOutputStream.java index cc64a5e6..8b3f5e68 100644 --- a/src/eu/siacs/conversations/utils/zlib/ZLibOutputStream.java +++ b/src/eu/siacs/conversations/utils/zlib/ZLibOutputStream.java @@ -9,76 +9,87 @@ import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; /** - * <p>Android 2.2 includes Java7 FLUSH_SYNC option, which will be used by this + * <p> + * Android 2.2 includes Java7 FLUSH_SYNC option, which will be used by this * Implementation, preferable via reflection. The @hide was remove in API level - * 19. This class might thus go away in the future.</p> - * <p>Please use {@link ZLibOutputStream#SUPPORTED} to check for flush - * compatibility.</p> + * 19. This class might thus go away in the future. + * </p> + * <p> + * Please use {@link ZLibOutputStream#SUPPORTED} to check for flush + * compatibility. + * </p> */ public class ZLibOutputStream extends DeflaterOutputStream { - /** - * The reflection based flush method. - */ + /** + * The reflection based flush method. + */ - private final static Method method; - /** - * SUPPORTED is true if a flush compatible method exists. - */ - public final static boolean SUPPORTED; + private final static Method method; + /** + * SUPPORTED is true if a flush compatible method exists. + */ + public final static boolean SUPPORTED; - /** - * Static block to initialize {@link #SUPPORTED} and {@link #method}. - */ - static { - Method m = null; - try { - m = Deflater.class.getMethod("deflate", byte[].class, int.class, int.class, int.class); - } catch (SecurityException e) { - } catch (NoSuchMethodException e) { - } - method = m; - SUPPORTED = (method != null); - } + /** + * Static block to initialize {@link #SUPPORTED} and {@link #method}. + */ + static { + Method m = null; + try { + m = Deflater.class.getMethod("deflate", byte[].class, int.class, + int.class, int.class); + } catch (SecurityException e) { + } catch (NoSuchMethodException e) { + } + method = m; + SUPPORTED = (method != null); + } - /** - * Create a new ZLib compatible output stream wrapping the given low level - * stream. ZLib compatiblity means we will send a zlib header. - * @param os OutputStream The underlying stream. - * @throws IOException In case of a lowlevel transfer problem. - * @throws NoSuchAlgorithmException In case of a {@link Deflater} error. - */ - public ZLibOutputStream(OutputStream os) throws IOException, - NoSuchAlgorithmException { - super(os, new Deflater(Deflater.BEST_COMPRESSION)); - } + /** + * Create a new ZLib compatible output stream wrapping the given low level + * stream. ZLib compatiblity means we will send a zlib header. + * + * @param os + * OutputStream The underlying stream. + * @throws IOException + * In case of a lowlevel transfer problem. + * @throws NoSuchAlgorithmException + * In case of a {@link Deflater} error. + */ + public ZLibOutputStream(OutputStream os) throws IOException, + NoSuchAlgorithmException { + super(os, new Deflater(Deflater.BEST_COMPRESSION)); + } - /** - * Flush the given stream, preferring Java7 FLUSH_SYNC if available. - * @throws IOException In case of a lowlevel exception. - */ - @Override - public void flush() throws IOException { - if (!SUPPORTED) { - super.flush(); - return; - } - try { - int count = 0; - do { - count = (Integer) method.invoke(def, buf, 0, buf.length, 3); - if (count > 0) { - out.write(buf, 0, count); + /** + * Flush the given stream, preferring Java7 FLUSH_SYNC if available. + * + * @throws IOException + * In case of a lowlevel exception. + */ + @Override + public void flush() throws IOException { + if (!SUPPORTED) { + super.flush(); + return; + } + try { + int count = 0; + do { + count = (Integer) method.invoke(def, buf, 0, buf.length, 3); + if (count > 0) { + out.write(buf, 0, count); + } + } while (count > 0); + } catch (IllegalArgumentException e) { + throw new IOException("Can't flush"); + } catch (IllegalAccessException e) { + throw new IOException("Can't flush"); + } catch (InvocationTargetException e) { + throw new IOException("Can't flush"); } - } while (count > 0); - } catch (IllegalArgumentException e) { - throw new IOException("Can't flush"); - } catch (IllegalAccessException e) { - throw new IOException("Can't flush"); - } catch (InvocationTargetException e) { - throw new IOException("Can't flush"); - } - super.flush(); - } + super.flush(); + } } diff --git a/src/eu/siacs/conversations/xml/Element.java b/src/eu/siacs/conversations/xml/Element.java index 3d22ba6a..4e11ee2c 100644 --- a/src/eu/siacs/conversations/xml/Element.java +++ b/src/eu/siacs/conversations/xml/Element.java @@ -4,6 +4,8 @@ import java.util.ArrayList; import java.util.Hashtable; import java.util.List; +import eu.siacs.conversations.utils.XmlHelper; + public class Element { protected String name; protected Hashtable<String, String> attributes = new Hashtable<String, String>(); @@ -116,7 +118,7 @@ public class Element { startTag.setAtttributes(this.attributes); elementOutput.append(startTag); if (content != null) { - elementOutput.append(encodeEntities(content)); + elementOutput.append(XmlHelper.encodeEntities(content)); } else { for (Element child : children) { elementOutput.append(child.toString()); @@ -132,24 +134,15 @@ public class Element { return name; } - private String encodeEntities(String content) { - content = content.replace("&", "&"); - content = content.replace("<", "<"); - content = content.replace(">", ">"); - content = content.replace("\"", """); - content = content.replace("'", "'"); - return content; - } - public void clearChildren() { this.children.clear(); } public void setAttribute(String name, long value) { - this.setAttribute(name, ""+value); + this.setAttribute(name, Long.toString(value)); } - + public void setAttribute(String name, int value) { - this.setAttribute(name, ""+value); + this.setAttribute(name, Integer.toString(value)); } } diff --git a/src/eu/siacs/conversations/xml/Tag.java b/src/eu/siacs/conversations/xml/Tag.java index 2d5ffdbc..b9ef979f 100644 --- a/src/eu/siacs/conversations/xml/Tag.java +++ b/src/eu/siacs/conversations/xml/Tag.java @@ -5,90 +5,93 @@ import java.util.Iterator; import java.util.Map.Entry; import java.util.Set; +import eu.siacs.conversations.utils.XmlHelper; + public class Tag { public static final int NO = -1; public static final int START = 0; public static final int END = 1; public static final int EMPTY = 2; - + protected int type; protected String name; protected Hashtable<String, String> attributes = new Hashtable<String, String>(); - + protected Tag(int type, String name) { this.type = type; this.name = name; } - public static Tag no(String text) { - return new Tag(NO,text); + return new Tag(NO, text); } - + public static Tag start(String name) { - return new Tag(START,name); + return new Tag(START, name); } - + public static Tag end(String name) { - return new Tag(END,name); + return new Tag(END, name); } - + public static Tag empty(String name) { - return new Tag(EMPTY,name); + return new Tag(EMPTY, name); } - + public String getName() { return name; } - + public String getAttribute(String attrName) { return this.attributes.get(attrName); } - + public Tag setAttribute(String attrName, String attrValue) { this.attributes.put(attrName, attrValue); return this; } - + public Tag setAtttributes(Hashtable<String, String> attributes) { this.attributes = attributes; return this; } - + public boolean isStart(String needle) { - if (needle==null) return false; + if (needle == null) + return false; return (this.type == START) && (needle.equals(this.name)); } - + public boolean isEnd(String needle) { - if (needle==null) return false; + if (needle == null) + return false; return (this.type == END) && (needle.equals(this.name)); } - + public boolean isNo() { return (this.type == NO); } - + public String toString() { StringBuilder tagOutput = new StringBuilder(); tagOutput.append('<'); - if (type==END) { + if (type == END) { tagOutput.append('/'); } tagOutput.append(name); - if(type!=END) { + if (type != END) { Set<Entry<String, String>> attributeSet = attributes.entrySet(); Iterator<Entry<String, String>> it = attributeSet.iterator(); - while(it.hasNext()) { - Entry<String,String> entry = it.next(); + while (it.hasNext()) { + Entry<String, String> entry = it.next(); tagOutput.append(' '); tagOutput.append(entry.getKey()); tagOutput.append("=\""); - tagOutput.append(entry.getValue()); + tagOutput.append(XmlHelper.encodeEntities(entry.getValue())); tagOutput.append('"'); } } - if (type==EMPTY) { + if (type == EMPTY) { tagOutput.append('/'); } tagOutput.append('>'); diff --git a/src/eu/siacs/conversations/xml/TagWriter.java b/src/eu/siacs/conversations/xml/TagWriter.java index 4828d5d9..f11c1846 100644 --- a/src/eu/siacs/conversations/xml/TagWriter.java +++ b/src/eu/siacs/conversations/xml/TagWriter.java @@ -8,22 +8,23 @@ import java.util.concurrent.LinkedBlockingQueue; import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; public class TagWriter { - + private OutputStream plainOutputStream; private OutputStreamWriter outputStream; private boolean finshed = false; private LinkedBlockingQueue<AbstractStanza> writeQueue = new LinkedBlockingQueue<AbstractStanza>(); private Thread asyncStanzaWriter = new Thread() { private boolean shouldStop = false; + @Override public void run() { - while(!shouldStop) { - if ((finshed)&&(writeQueue.size() == 0)) { + while (!shouldStop) { + if ((finshed) && (writeQueue.size() == 0)) { return; } try { AbstractStanza output = writeQueue.take(); - if (outputStream==null) { + if (outputStream == null) { shouldStop = true; } else { outputStream.write(output.toString()); @@ -37,12 +38,12 @@ public class TagWriter { } } }; - + public TagWriter() { } - + public void setOutputStream(OutputStream out) throws IOException { - if (out==null) { + if (out == null) { throw new IOException(); } this.plainOutputStream = out; @@ -50,23 +51,23 @@ public class TagWriter { } public OutputStream getOutputStream() throws IOException { - if (this.plainOutputStream==null) { + if (this.plainOutputStream == null) { throw new IOException(); } return this.plainOutputStream; } public TagWriter beginDocument() throws IOException { - if (outputStream==null) { + if (outputStream == null) { throw new IOException("output stream was null"); } outputStream.write("<?xml version='1.0'?>"); outputStream.flush(); return this; } - + public TagWriter writeTag(Tag tag) throws IOException { - if (outputStream==null) { + if (outputStream == null) { throw new IOException("output stream was null"); } outputStream.write(tag.toString()); @@ -75,34 +76,34 @@ public class TagWriter { } public TagWriter writeElement(Element element) throws IOException { - if (outputStream==null) { + if (outputStream == null) { throw new IOException("output stream was null"); } outputStream.write(element.toString()); outputStream.flush(); return this; } - + public TagWriter writeStanzaAsync(AbstractStanza stanza) { - if (finshed) { - return this; - } else { - if (!asyncStanzaWriter.isAlive()) { - try { - asyncStanzaWriter.start(); - } catch (IllegalThreadStateException e) { - //already started - } + if (finshed) { + return this; + } else { + if (!asyncStanzaWriter.isAlive()) { + try { + asyncStanzaWriter.start(); + } catch (IllegalThreadStateException e) { + // already started } - writeQueue.add(stanza); - return this; } + writeQueue.add(stanza); + return this; + } } - + public void finish() { this.finshed = true; } - + public boolean finished() { return (this.writeQueue.size() == 0); } diff --git a/src/eu/siacs/conversations/xml/XmlReader.java b/src/eu/siacs/conversations/xml/XmlReader.java index 1c7e94e6..52d3d46a 100644 --- a/src/eu/siacs/conversations/xml/XmlReader.java +++ b/src/eu/siacs/conversations/xml/XmlReader.java @@ -7,13 +7,14 @@ import java.io.InputStreamReader; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import eu.siacs.conversations.Config; + import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.util.Log; import android.util.Xml; public class XmlReader { - private static final String LOGTAG = "xmppService"; private XmlPullParser parser; private PowerManager.WakeLock wakeLock; private InputStream is; @@ -21,15 +22,16 @@ public class XmlReader { public XmlReader(WakeLock wakeLock) { this.parser = Xml.newPullParser(); try { - this.parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES,true); + this.parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, + true); } catch (XmlPullParserException e) { - Log.d(LOGTAG,"error setting namespace feature on parser"); + Log.d(Config.LOGTAG, "error setting namespace feature on parser"); } this.wakeLock = wakeLock; } - + public void setInputStream(InputStream inputStream) throws IOException { - if (inputStream==null) { + if (inputStream == null) { throw new IOException(); } this.is = inputStream; @@ -41,14 +43,14 @@ public class XmlReader { } public InputStream getInputStream() throws IOException { - if (this.is==null) { + if (this.is == null) { throw new IOException(); } return is; } public void reset() throws IOException { - if (this.is==null) { + if (this.is == null) { throw new IOException(); } try { @@ -57,58 +59,74 @@ public class XmlReader { throw new IOException("error resetting parser"); } } - + public Tag readTag() throws XmlPullParserException, IOException { if (wakeLock.isHeld()) { - try { wakeLock.release();} catch (RuntimeException re) {} + try { + wakeLock.release(); + } catch (RuntimeException re) { + } } try { - while(this.is != null && parser.next() != XmlPullParser.END_DOCUMENT) { - wakeLock.acquire(); - if (parser.getEventType() == XmlPullParser.START_TAG) { - Tag tag = Tag.start(parser.getName()); - for(int i = 0; i < parser.getAttributeCount(); ++i) { - tag.setAttribute(parser.getAttributeName(i), parser.getAttributeValue(i)); - } - String xmlns = parser.getNamespace(); - if (xmlns!=null) { - tag.setAttribute("xmlns",xmlns); - } - return tag; - } else if (parser.getEventType() == XmlPullParser.END_TAG) { - Tag tag = Tag.end(parser.getName()); - return tag; - } else if (parser.getEventType() == XmlPullParser.TEXT) { - Tag tag = Tag.no(parser.getText()); - return tag; + while (this.is != null + && parser.next() != XmlPullParser.END_DOCUMENT) { + wakeLock.acquire(); + if (parser.getEventType() == XmlPullParser.START_TAG) { + Tag tag = Tag.start(parser.getName()); + for (int i = 0; i < parser.getAttributeCount(); ++i) { + tag.setAttribute(parser.getAttributeName(i), + parser.getAttributeValue(i)); } + String xmlns = parser.getNamespace(); + if (xmlns != null) { + tag.setAttribute("xmlns", xmlns); + } + return tag; + } else if (parser.getEventType() == XmlPullParser.END_TAG) { + Tag tag = Tag.end(parser.getName()); + return tag; + } else if (parser.getEventType() == XmlPullParser.TEXT) { + Tag tag = Tag.no(parser.getText()); + return tag; } + } if (wakeLock.isHeld()) { - try { wakeLock.release();} catch (RuntimeException re) {} + try { + wakeLock.release(); + } catch (RuntimeException re) { + } } } catch (ArrayIndexOutOfBoundsException e) { - throw new IOException("xml parser mishandled ArrayIndexOufOfBounds", e); + throw new IOException( + "xml parser mishandled ArrayIndexOufOfBounds", e); } catch (StringIndexOutOfBoundsException e) { - throw new IOException("xml parser mishandled StringIndexOufOfBounds", e); + throw new IOException( + "xml parser mishandled StringIndexOufOfBounds", e); + } catch (NullPointerException e) { + throw new IOException("xml parser mishandled NullPointerException", + e); + } catch (IndexOutOfBoundsException e) { + throw new IOException("xml parser mishandled IndexOutOfBound", e); } return null; } - public Element readElement(Tag currentTag) throws XmlPullParserException, IOException { + public Element readElement(Tag currentTag) throws XmlPullParserException, + IOException { Element element = new Element(currentTag.getName()); element.setAttributes(currentTag.getAttributes()); Tag nextTag = this.readTag(); if (nextTag == null) { throw new IOException("unterupted mid tag"); } - if(nextTag.isNo()) { + if (nextTag.isNo()) { element.setContent(nextTag.getName()); nextTag = this.readTag(); if (nextTag == null) { throw new IOException("unterupted mid tag"); } } - while(!nextTag.isEnd(element.getName())) { + while (!nextTag.isEnd(element.getName())) { if (!nextTag.isNo()) { Element child = this.readElement(nextTag); element.addChild(child); diff --git a/src/eu/siacs/conversations/xmpp/OnMessageAcknowledged.java b/src/eu/siacs/conversations/xmpp/OnMessageAcknowledged.java new file mode 100644 index 00000000..5f670d93 --- /dev/null +++ b/src/eu/siacs/conversations/xmpp/OnMessageAcknowledged.java @@ -0,0 +1,7 @@ +package eu.siacs.conversations.xmpp; + +import eu.siacs.conversations.entities.Account; + +public interface OnMessageAcknowledged { + public void onMessageAcknowledged(Account account, String id); +} diff --git a/src/eu/siacs/conversations/xmpp/XmppConnection.java b/src/eu/siacs/conversations/xmpp/XmppConnection.java index ba7a9245..36920929 100644 --- a/src/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/eu/siacs/conversations/xmpp/XmppConnection.java @@ -31,6 +31,8 @@ import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.SystemClock; import android.util.Log; +import android.util.SparseArray; +import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; @@ -47,6 +49,8 @@ import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; import eu.siacs.conversations.xmpp.stanzas.IqPacket; import eu.siacs.conversations.xmpp.stanzas.MessagePacket; import eu.siacs.conversations.xmpp.stanzas.PresencePacket; +import eu.siacs.conversations.xmpp.stanzas.csi.ActivePacket; +import eu.siacs.conversations.xmpp.stanzas.csi.InactivePacket; import eu.siacs.conversations.xmpp.stanzas.streammgmt.AckPacket; import eu.siacs.conversations.xmpp.stanzas.streammgmt.EnablePacket; import eu.siacs.conversations.xmpp.stanzas.streammgmt.RequestPacket; @@ -55,7 +59,6 @@ import eu.siacs.conversations.xmpp.stanzas.streammgmt.ResumePacket; public class XmppConnection implements Runnable { protected Account account; - private static final String LOGTAG = "xmppService"; private WakeLock wakeLock; @@ -64,7 +67,7 @@ public class XmppConnection implements Runnable { private Socket socket; private XmlReader tagReader; private TagWriter tagWriter; - + private Features features = new Features(this); private boolean shouldBind = true; @@ -74,7 +77,8 @@ public class XmppConnection implements Runnable { private String streamId = null; private int smVersion = 3; - + private SparseArray<String> messageReceipts = new SparseArray<String>(); + private boolean usingCompression = false; private int stanzasReceived = 0; @@ -98,14 +102,15 @@ public class XmppConnection implements Runnable { private OnMessagePacketReceived messageListener = null; private OnStatusChanged statusListener = null; private OnBindListener bindListener = null; + private OnMessageAcknowledged acknowledgedListener = null; private MemorizingTrustManager mMemorizingTrustManager; public XmppConnection(Account account, XmppConnectionService service) { this.mRandom = service.getRNG(); this.mMemorizingTrustManager = service.getMemorizingTrustManager(); this.account = account; - this.wakeLock = service.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, - account.getJid()); + this.wakeLock = service.getPowerManager().newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, account.getJid()); tagWriter = new TagWriter(); } @@ -128,7 +133,7 @@ public class XmppConnection implements Runnable { } protected void connect() { - Log.d(LOGTAG, account.getJid() + ": connecting"); + Log.d(Config.LOGTAG, account.getJid() + ": connecting"); usingCompression = false; lastConnect = SystemClock.elapsedRealtime(); lastPingSent = SystemClock.elapsedRealtime(); @@ -142,7 +147,7 @@ public class XmppConnection implements Runnable { this.changeStatus(Account.STATUS_CONNECTING); Bundle namePort = DNSHelper.getSRVRecord(account.getServer()); if ("timeout".equals(namePort.getString("error"))) { - Log.d(LOGTAG, account.getJid() + ": dns timeout"); + Log.d(Config.LOGTAG, account.getJid() + ": dns timeout"); this.changeStatus(Account.STATUS_OFFLINE); return; } @@ -151,13 +156,14 @@ public class XmppConnection implements Runnable { int srvRecordPort = namePort.getInt("port"); if (srvRecordServer != null) { if (srvIpServer != null) { - Log.d(LOGTAG, account.getJid() + ": using values from dns " - + srvRecordServer + "[" + srvIpServer + "]:" - + srvRecordPort); + Log.d(Config.LOGTAG, account.getJid() + + ": using values from dns " + srvRecordServer + + "[" + srvIpServer + "]:" + srvRecordPort); socket = new Socket(srvIpServer, srvRecordPort); } else { - Log.d(LOGTAG, account.getJid() + ": using values from dns " - + srvRecordServer + ":" + srvRecordPort); + Log.d(Config.LOGTAG, account.getJid() + + ": using values from dns " + srvRecordServer + + ":" + srvRecordPort); socket = new Socket(srvRecordServer, srvRecordPort); } } else { @@ -175,7 +181,8 @@ public class XmppConnection implements Runnable { processStream(nextTag); break; } else { - Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName()); + Log.d(Config.LOGTAG, + "found unexpected tag: " + nextTag.getName()); return; } } @@ -185,27 +192,39 @@ public class XmppConnection implements Runnable { } catch (UnknownHostException e) { this.changeStatus(Account.STATUS_SERVER_NOT_FOUND); if (wakeLock.isHeld()) { - try { wakeLock.release();} catch (RuntimeException re) {} + try { + wakeLock.release(); + } catch (RuntimeException re) { + } } return; } catch (IOException e) { this.changeStatus(Account.STATUS_OFFLINE); if (wakeLock.isHeld()) { - try { wakeLock.release();} catch (RuntimeException re) {} + try { + wakeLock.release(); + } catch (RuntimeException re) { + } } return; } catch (NoSuchAlgorithmException e) { this.changeStatus(Account.STATUS_OFFLINE); - Log.d(LOGTAG, "compression exception " + e.getMessage()); + Log.d(Config.LOGTAG, "compression exception " + e.getMessage()); if (wakeLock.isHeld()) { - try { wakeLock.release();} catch (RuntimeException re) {} + try { + wakeLock.release(); + } catch (RuntimeException re) { + } } return; } catch (XmlPullParserException e) { this.changeStatus(Account.STATUS_OFFLINE); - Log.d(LOGTAG, "xml exception " + e.getMessage()); + Log.d(Config.LOGTAG, "xml exception " + e.getMessage()); if (wakeLock.isHeld()) { - try { wakeLock.release();} catch (RuntimeException re) {} + try { + wakeLock.release(); + } catch (RuntimeException re) { + } } return; } @@ -230,7 +249,7 @@ public class XmppConnection implements Runnable { } else if (nextTag.isStart("compressed")) { switchOverToZLib(nextTag); } else if (nextTag.isStart("success")) { - Log.d(LOGTAG, account.getJid() + ": logged in"); + Log.d(Config.LOGTAG, account.getJid() + ": logged in"); tagReader.readTag(); tagReader.reset(); sendStartStream(); @@ -245,18 +264,18 @@ public class XmppConnection implements Runnable { response.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl"); response.setContent(CryptoHelper.saslDigestMd5(account, - challange,mRandom)); + challange, mRandom)); tagWriter.writeElement(response); } else if (nextTag.isStart("enabled")) { - this.stanzasSent = 0; Element enabled = tagReader.readElement(nextTag); if ("true".equals(enabled.getAttribute("resume"))) { this.streamId = enabled.getAttribute("id"); - Log.d(LOGTAG, account.getJid() + ": stream managment(" - + smVersion + ") enabled (resumable)"); + Log.d(Config.LOGTAG, account.getJid() + + ": stream managment(" + smVersion + + ") enabled (resumable)"); } else { - Log.d(LOGTAG, account.getJid() + ": stream managment(" - + smVersion + ") enabled"); + Log.d(Config.LOGTAG, account.getJid() + + ": stream managment(" + smVersion + ") enabled"); } this.lastSessionStarted = SystemClock.elapsedRealtime(); this.stanzasReceived = 0; @@ -264,9 +283,30 @@ public class XmppConnection implements Runnable { tagWriter.writeStanzaAsync(r); } else if (nextTag.isStart("resumed")) { lastPaketReceived = SystemClock.elapsedRealtime(); - Log.d(LOGTAG, account.getJid() + ": session resumed"); - tagReader.readElement(nextTag); - sendPing(); + Element resumed = tagReader.readElement(nextTag); + String h = resumed.getAttribute("h"); + try { + int serverCount = Integer.parseInt(h); + if (serverCount != stanzasSent) { + Log.d(Config.LOGTAG, account.getJid() + + ": session resumed with lost packages"); + stanzasSent = serverCount; + } else { + Log.d(Config.LOGTAG, account.getJid() + + ": session resumed"); + } + if (acknowledgedListener != null) { + for (int i = 0; i < messageReceipts.size(); ++i) { + if (serverCount >= messageReceipts.keyAt(i)) { + acknowledgedListener.onMessageAcknowledged( + account, messageReceipts.valueAt(i)); + } + } + } + messageReceipts.clear(); + } catch (NumberFormatException e) { + + } changeStatus(Account.STATUS_ONLINE); } else if (nextTag.isStart("r")) { tagReader.readElement(nextTag); @@ -276,12 +316,17 @@ public class XmppConnection implements Runnable { Element ack = tagReader.readElement(nextTag); lastPaketReceived = SystemClock.elapsedRealtime(); int serverSequence = Integer.parseInt(ack.getAttribute("h")); - if (serverSequence > this.stanzasSent) { - this.stanzasSent = serverSequence; + String msgId = this.messageReceipts.get(serverSequence); + if (msgId != null) { + if (this.acknowledgedListener != null) { + this.acknowledgedListener.onMessageAcknowledged( + account, msgId); + } + this.messageReceipts.remove(serverSequence); } } else if (nextTag.isStart("failed")) { tagReader.readElement(nextTag); - Log.d(LOGTAG, account.getJid() + ": resumption failed"); + Log.d(Config.LOGTAG, account.getJid() + ": resumption failed"); streamId = null; if (account.getStatus() != Account.STATUS_ONLINE) { sendBindRequest(); @@ -321,7 +366,7 @@ public class XmppConnection implements Runnable { } element.setAttributes(currentTag.getAttributes()); Tag nextTag = tagReader.readTag(); - if (nextTag==null) { + if (nextTag == null) { throw new IOException("interrupted mid tag"); } while (!nextTag.isEnd(element.getName())) { @@ -335,7 +380,7 @@ public class XmppConnection implements Runnable { element.addChild(child); } nextTag = tagReader.readTag(); - if (nextTag==null) { + if (nextTag == null) { throw new IOException("interrupted mid tag"); } } @@ -420,7 +465,7 @@ public class XmppConnection implements Runnable { .setInputStream(new ZLibInputStream(tagReader.getInputStream())); sendStartStream(); - Log.d(LOGTAG, account.getJid() + ": compression enabled"); + Log.d(Config.LOGTAG, account.getJid() + ": compression enabled"); usingCompression = true; processStream(tagReader.readTag()); } @@ -436,23 +481,30 @@ public class XmppConnection implements Runnable { tagReader.readTag(); try { SSLContext sc = SSLContext.getInstance("TLS"); - sc.init(null, new X509TrustManager[] { this.mMemorizingTrustManager }, mRandom); + sc.init(null, + new X509TrustManager[] { this.mMemorizingTrustManager }, + mRandom); SSLSocketFactory factory = sc.getSocketFactory(); - - HostnameVerifier verifier = this.mMemorizingTrustManager.wrapHostnameVerifier(new org.apache.http.conn.ssl.StrictHostnameVerifier()); + + HostnameVerifier verifier = this.mMemorizingTrustManager + .wrapHostnameVerifier(new org.apache.http.conn.ssl.StrictHostnameVerifier()); SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true); - - if (verifier != null && !verifier.verify(account.getServer(), sslSocket.getSession())) { - Log.d(LOGTAG, account.getJid() + ": host mismatch in TLS connection"); + + if (verifier != null + && !verifier.verify(account.getServer(), + sslSocket.getSession())) { + Log.d(Config.LOGTAG, account.getJid() + + ": host mismatch in TLS connection"); sslSocket.close(); throw new IOException(); } tagReader.setInputStream(sslSocket.getInputStream()); tagWriter.setOutputStream(sslSocket.getOutputStream()); sendStartStream(); - Log.d(LOGTAG, account.getJid() + ": TLS connection established"); + Log.d(Config.LOGTAG, account.getJid() + + ": TLS connection established"); processStream(tagReader.readTag()); sslSocket.close(); } catch (NoSuchAlgorithmException e1) { @@ -578,7 +630,7 @@ public class XmppConnection implements Runnable { changeStatus(Account.STATUS_REGISTRATION_CONFLICT); } else { changeStatus(Account.STATUS_REGISTRATION_FAILED); - Log.d(LOGTAG, packet.toString()); + Log.d(Config.LOGTAG, packet.toString()); } disconnect(true); } @@ -586,7 +638,7 @@ public class XmppConnection implements Runnable { } else { changeStatus(Account.STATUS_REGISTRATION_FAILED); disconnect(true); - Log.d(LOGTAG, account.getJid() + Log.d(Config.LOGTAG, account.getJid() + ": could not register. instructions are" + instructions.getContent()); } @@ -602,18 +654,23 @@ public class XmppConnection implements Runnable { @Override public void onIqPacketReceived(Account account, IqPacket packet) { Element bind = packet.findChild("bind"); - if (bind!=null) { + if (bind != null) { Element jid = bind.findChild("jid"); - if (jid!=null) { + if (jid != null) { account.setResource(jid.getContent().split("/")[1]); if (streamFeatures.hasChild("sm", "urn:xmpp:sm:3")) { smVersion = 3; EnablePacket enable = new EnablePacket(smVersion); tagWriter.writeStanzaAsync(enable); - } else if (streamFeatures.hasChild("sm", "urn:xmpp:sm:2")) { + stanzasSent = 0; + messageReceipts.clear(); + } else if (streamFeatures.hasChild("sm", + "urn:xmpp:sm:2")) { smVersion = 2; EnablePacket enable = new EnablePacket(smVersion); tagWriter.writeStanzaAsync(enable); + stanzasSent = 0; + messageReceipts.clear(); } sendServiceDiscoveryInfo(account.getServer()); sendServiceDiscoveryItems(account.getServer()); @@ -630,7 +687,8 @@ public class XmppConnection implements Runnable { } }); if (this.streamFeatures.hasChild("session")) { - Log.d(LOGTAG, account.getJid() + ": sending deprecated session"); + Log.d(Config.LOGTAG, account.getJid() + + ": sending deprecated session"); IqPacket startSession = new IqPacket(IqPacket.TYPE_SET); startSession.addChild("session", "urn:ietf:params:xml:ns:xmpp-session"); @@ -695,18 +753,23 @@ public class XmppConnection implements Runnable { @Override public void onIqPacketReceived(Account account, IqPacket packet) { if (!packet.hasChild("error")) { - Log.d(LOGTAG, account.getJid() + Log.d(Config.LOGTAG, account.getJid() + ": successfully enabled carbons"); } else { - Log.d(LOGTAG, account.getJid() + Log.d(Config.LOGTAG, account.getJid() + ": error enableing carbons " + packet.toString()); } } }); } - private void processStreamError(Tag currentTag) { - Log.d(LOGTAG, "processStreamError"); + private void processStreamError(Tag currentTag) throws XmlPullParserException, IOException { + Element streamError = tagReader.readElement(currentTag); + if (streamError!=null && streamError.hasChild("conflict")) { + String resource = account.getResource().split("\\.")[0]; + account.setResource(resource+"."+nextRandomId()); + Log.d(Config.LOGTAG,account.getJid()+": switching resource due to conflict ("+account.getResource()+")"); + } } private void sendStartStream() throws IOException { @@ -748,12 +811,21 @@ public class XmppConnection implements Runnable { public void sendPresencePacket(PresencePacket packet) { this.sendPacket(packet, null); } - + private synchronized void sendPacket(final AbstractStanza packet, PacketReceived callback) { - // TODO dont increment stanza count if packet = request packet or ack; - ++stanzasSent; + if (packet.getName().equals("iq") || packet.getName().equals("message") + || packet.getName().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); + this.messageReceipts.put(stanzasSent, packet.getId()); + tagWriter.writeStanzaAsync(new RequestPacket(this.smVersion)); + } if (callback != null) { if (packet.getId() == null) { packet.setId(nextRandomId()); @@ -802,9 +874,13 @@ public class XmppConnection implements Runnable { this.bindListener = listener; } + public void setOnMessageAcknowledgeListener(OnMessageAcknowledged listener) { + this.acknowledgedListener = listener; + } + public void disconnect(boolean force) { changeStatus(Account.STATUS_OFFLINE); - Log.d(LOGTAG, "disconnecting"); + Log.d(Config.LOGTAG, "disconnecting"); try { if (force) { socket.close(); @@ -818,20 +894,21 @@ public class XmppConnection implements Runnable { tagWriter.finish(); try { while (!tagWriter.finished()) { - Log.d(LOGTAG, "not yet finished"); + Log.d(Config.LOGTAG, "not yet finished"); Thread.sleep(100); } tagWriter.writeTag(Tag.end("stream:stream")); } catch (IOException e) { - Log.d(LOGTAG, "io exception during disconnect"); + Log.d(Config.LOGTAG, + "io exception during disconnect"); } catch (InterruptedException e) { - Log.d(LOGTAG, "interrupted"); + Log.d(Config.LOGTAG, "interrupted"); } } } }).start(); } catch (IOException e) { - Log.d(LOGTAG, "io exception during disconnect"); + Log.d(Config.LOGTAG, "io exception during disconnect"); } } @@ -844,10 +921,10 @@ public class XmppConnection implements Runnable { } return items; } - + public String findDiscoItemByFeature(String feature) { List<String> items = findDiscoItemsByFeature(feature); - if (items.size()>=1) { + if (items.size() >= 1) { return items.get(0); } return null; @@ -856,7 +933,7 @@ public class XmppConnection implements Runnable { public void r() { this.tagWriter.writeStanzaAsync(new RequestPacket(smVersion)); } - + public String getMucServer() { return findDiscoItemByFeature("http://jabber.org/protocol/muc"); } @@ -870,28 +947,29 @@ public class XmppConnection implements Runnable { public int getAttempt() { return this.attempt; } - + public Features getFeatures() { return this.features; } - + public class Features { XmppConnection connection; + public Features(XmppConnection connection) { this.connection = connection; } - + private boolean hasDiscoFeature(String server, String feature) { if (!connection.disco.containsKey(server)) { return false; } return connection.disco.get(server).contains(feature); } - + public boolean carbons() { return hasDiscoFeature(account.getServer(), "urn:xmpp:carbons:2"); } - + public boolean sm() { if (connection.streamFeatures == null) { return false; @@ -899,11 +977,21 @@ public class XmppConnection implements Runnable { return connection.streamFeatures.hasChild("sm"); } } - + + public boolean csi() { + if (connection.streamFeatures == null) { + return false; + } else { + return connection.streamFeatures.hasChild("csi", + "urn:xmpp:csi:0"); + } + } + public boolean pubsub() { - return hasDiscoFeature(account.getServer(), "http://jabber.org/protocol/pubsub#publish"); + return hasDiscoFeature(account.getServer(), + "http://jabber.org/protocol/pubsub#publish"); } - + public boolean rosterVersioning() { if (connection.streamFeatures == null) { return false; @@ -911,11 +999,12 @@ public class XmppConnection implements Runnable { return connection.streamFeatures.hasChild("ver"); } } - + public boolean streamhost() { - return connection.findDiscoItemByFeature("http://jabber.org/protocol/bytestreams") != null; + return connection + .findDiscoItemByFeature("http://jabber.org/protocol/bytestreams") != null; } - + public boolean compression() { return connection.usingCompression; } @@ -930,16 +1019,24 @@ public class XmppConnection implements Runnable { } return System.currentTimeMillis() - diff; } - + public long getLastConnect() { return this.lastConnect; } - + public long getLastPingSent() { return this.lastPingSent; } - + public long getLastPacketReceived() { return this.lastPaketReceived; } + + public void sendActive() { + this.sendPacket(new ActivePacket(), null); + } + + public void sendInactive() { + this.sendPacket(new InactivePacket(), null); + } } diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleCandidate.java b/src/eu/siacs/conversations/xmpp/jingle/JingleCandidate.java index 80ffeaaa..3e7c7b68 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/JingleCandidate.java +++ b/src/eu/siacs/conversations/xmpp/jingle/JingleCandidate.java @@ -6,11 +6,11 @@ import java.util.List; import eu.siacs.conversations.xml.Element; public class JingleCandidate { - + public static int TYPE_UNKNOWN; public static int TYPE_DIRECT = 0; public static int TYPE_PROXY = 1; - + private boolean ours; private boolean usedByCounterpart = false; private String cid; @@ -19,12 +19,12 @@ public class JingleCandidate { private int type; private String jid; private int priority; - - public JingleCandidate(String cid,boolean ours) { + + public JingleCandidate(String cid, boolean ours) { this.ours = ours; this.cid = cid; } - + public String getCid() { return cid; } @@ -32,15 +32,15 @@ public class JingleCandidate { public void setHost(String host) { this.host = host; } - + public String getHost() { return this.host; } - + public void setJid(String jid) { this.jid = jid; } - + public String getJid() { return this.jid; } @@ -48,15 +48,15 @@ public class JingleCandidate { public void setPort(int port) { this.port = port; } - + public int getPort() { return this.port; } - + public void setType(int type) { this.type = type; } - + public void setType(String type) { if ("proxy".equals(type)) { this.type = TYPE_PROXY; @@ -70,42 +70,46 @@ public class JingleCandidate { public void setPriority(int i) { this.priority = i; } - + public int getPriority() { return this.priority; } - + public boolean equals(JingleCandidate other) { return this.getCid().equals(other.getCid()); } - + public boolean equalValues(JingleCandidate other) { - return other.getHost().equals(this.getHost())&&(other.getPort()==this.getPort()); + return other.getHost().equals(this.getHost()) + && (other.getPort() == this.getPort()); } - + public boolean isOurs() { return ours; } - + public int getType() { return this.type; } public static List<JingleCandidate> parse(List<Element> canditates) { List<JingleCandidate> parsedCandidates = new ArrayList<JingleCandidate>(); - for(Element c : canditates) { + for (Element c : canditates) { parsedCandidates.add(JingleCandidate.parse(c)); } return parsedCandidates; } - + public static JingleCandidate parse(Element candidate) { - JingleCandidate parsedCandidate = new JingleCandidate(candidate.getAttribute("cid"), false); + JingleCandidate parsedCandidate = new JingleCandidate( + candidate.getAttribute("cid"), false); parsedCandidate.setHost(candidate.getAttribute("host")); parsedCandidate.setJid(candidate.getAttribute("jid")); parsedCandidate.setType(candidate.getAttribute("type")); - parsedCandidate.setPriority(Integer.parseInt(candidate.getAttribute("priority"))); - parsedCandidate.setPort(Integer.parseInt(candidate.getAttribute("port"))); + parsedCandidate.setPriority(Integer.parseInt(candidate + .getAttribute("priority"))); + parsedCandidate + .setPort(Integer.parseInt(candidate.getAttribute("port"))); return parsedCandidate; } @@ -113,26 +117,27 @@ public class JingleCandidate { Element element = new Element("candidate"); element.setAttribute("cid", this.getCid()); element.setAttribute("host", this.getHost()); - element.setAttribute("port", ""+this.getPort()); + element.setAttribute("port", Integer.toString(this.getPort())); element.setAttribute("jid", this.getJid()); - element.setAttribute("priority",""+this.getPriority()); - if (this.getType()==TYPE_DIRECT) { - element.setAttribute("type","direct"); - } else if (this.getType()==TYPE_PROXY) { - element.setAttribute("type","proxy"); + element.setAttribute("priority", Integer.toString(this.getPriority())); + if (this.getType() == TYPE_DIRECT) { + element.setAttribute("type", "direct"); + } else if (this.getType() == TYPE_PROXY) { + element.setAttribute("type", "proxy"); } return element; } public void flagAsUsedByCounterpart() { - this.usedByCounterpart = true; + this.usedByCounterpart = true; } public boolean isUsedByCounterpart() { return this.usedByCounterpart; } - + public String toString() { - return this.getHost()+":"+this.getPort()+" (prio="+this.getPriority()+")"; + return this.getHost() + ":" + this.getPort() + " (prio=" + + this.getPriority() + ")"; } } diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java index bfdbb7cc..a5cc2c49 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java +++ b/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java @@ -12,6 +12,7 @@ import android.content.Intent; import android.graphics.BitmapFactory; import android.net.Uri; import android.util.Log; +import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; @@ -94,16 +95,17 @@ public class JingleConnection { BitmapFactory.decodeFile(file.getAbsolutePath(), options); int imageHeight = options.outHeight; int imageWidth = options.outWidth; - message.setBody("" + file.getSize() + "," + imageWidth + "," + message.setBody(Long.toString(file.getSize()) + ',' + imageWidth + ',' + imageHeight); mXmppConnectionService.databaseBackend.createMessage(message); mXmppConnectionService.markMessage(message, Message.STATUS_RECEIVED); } - Log.d("xmppService", + 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); + if (message.getEncryption() != Message.ENCRYPTION_PGP) { + Intent intent = new Intent( + Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); intent.setData(Uri.fromFile(file)); mXmppConnectionService.sendBroadcast(intent); } @@ -121,17 +123,17 @@ public class JingleConnection { @Override public void success() { if (initiator.equals(account.getFullJid())) { - Log.d("xmppService", "we were initiating. sending file"); + Log.d(Config.LOGTAG, "we were initiating. sending file"); transport.send(file, onFileTransmissionSatusChanged); } else { transport.receive(file, onFileTransmissionSatusChanged); - Log.d("xmppService", "we were responding. receiving file"); + Log.d(Config.LOGTAG, "we were responding. receiving file"); } } @Override public void failed() { - Log.d("xmppService", "proxy activation failed"); + Log.d(Config.LOGTAG, "proxy activation failed"); } }; @@ -177,13 +179,13 @@ public class JingleConnection { returnResult = this.receiveFallbackToIbb(packet); } else { returnResult = false; - Log.d("xmppService", "trying to fallback to something unknown" + Log.d(Config.LOGTAG, "trying to fallback to something unknown" + packet.toString()); } } else if (packet.isAction("transport-accept")) { returnResult = this.receiveTransportAccept(packet); } else { - Log.d("xmppService", "packet arrived in connection. action was " + Log.d(Config.LOGTAG, "packet arrived in connection. action was " + packet.getAction()); returnResult = false; } @@ -224,14 +226,14 @@ public class JingleConnection { @Override public void failed() { - Log.d("xmppService", + Log.d(Config.LOGTAG, "connection to our own primary candidete failed"); sendInitRequest(); } @Override public void established() { - Log.d("xmppService", + Log.d(Config.LOGTAG, "succesfully connected to our own primary candidate"); mergeCandidate(candidate); sendInitRequest(); @@ -239,7 +241,7 @@ public class JingleConnection { }); mergeCandidate(candidate); } else { - Log.d("xmppService", + Log.d(Config.LOGTAG, "no primary candidate of our own was found"); sendInitRequest(); } @@ -288,7 +290,7 @@ public class JingleConnection { filename[filename.length - 2])) { supportedFile = true; if (filename[filename.length - 1].equals("otr")) { - Log.d("xmppService", "receiving otr file"); + Log.d(Config.LOGTAG, "receiving otr file"); this.message .setEncryption(Message.ENCRYPTION_OTR); } else { @@ -300,17 +302,17 @@ public class JingleConnection { } if (supportedFile) { long size = Long.parseLong(fileSize.getContent()); - message.setBody("" + size); + message.setBody(Long.toString(size)); conversation.getMessages().add(message); if (size <= this.mJingleConnectionManager .getAutoAcceptFileSize()) { - Log.d("xmppService", "auto accepting file from " + Log.d(Config.LOGTAG, "auto accepting file from " + packet.getFrom()); this.acceptedAutomatically = true; this.sendAccept(); } else { message.markUnread(); - Log.d("xmppService", + Log.d(Config.LOGTAG, "not auto accepting new file offer with size: " + size + " allowed size:" @@ -400,7 +402,7 @@ public class JingleConnection { @Override public void failed() { - Log.d("xmppService", + Log.d(Config.LOGTAG, "connection to our own primary candidate failed"); content.socks5transport().setChildren( getCandidatesAsElements()); @@ -411,7 +413,7 @@ public class JingleConnection { @Override public void established() { - Log.d("xmppService", + Log.d(Config.LOGTAG, "connected to primary candidate"); mergeCandidate(candidate); content.socks5transport().setChildren( @@ -422,7 +424,7 @@ public class JingleConnection { } }); } else { - Log.d("xmppService", + Log.d(Config.LOGTAG, "did not find a primary candidate for ourself"); content.socks5transport().setChildren( getCandidatesAsElements()); @@ -446,7 +448,7 @@ public class JingleConnection { } private void sendJinglePacket(JinglePacket packet) { - // Log.d("xmppService",packet.toString()); + // Log.d(Config.LOGTAG,packet.toString()); account.getXmppConnection().sendIqPacket(packet, responseListener); } @@ -470,14 +472,14 @@ public class JingleConnection { } else { String cid = content.socks5transport() .findChild("activated").getAttribute("cid"); - Log.d("xmppService", "received proxy activated (" + cid + Log.d(Config.LOGTAG, "received proxy activated (" + cid + ")prior to choosing our own transport"); JingleSocks5Transport connection = this.connections .get(cid); if (connection != null) { connection.setActivated(true); } else { - Log.d("xmppService", "activated connection not found"); + Log.d(Config.LOGTAG, "activated connection not found"); this.sendCancel(); this.cancel(); } @@ -487,7 +489,7 @@ public class JingleConnection { onProxyActivated.failed(); return true; } else if (content.socks5transport().hasChild("candidate-error")) { - Log.d("xmppService", "received candidate error"); + Log.d(Config.LOGTAG, "received candidate error"); this.receivedCandidate = true; if ((status == STATUS_ACCEPTED) && (this.sentCandidate)) { this.connect(); @@ -497,14 +499,14 @@ public class JingleConnection { String cid = content.socks5transport() .findChild("candidate-used").getAttribute("cid"); if (cid != null) { - Log.d("xmppService", "candidate used by counterpart:" + cid); + Log.d(Config.LOGTAG, "candidate used by counterpart:" + cid); JingleCandidate candidate = getCandidate(cid); candidate.flagAsUsedByCounterpart(); this.receivedCandidate = true; if ((status == STATUS_ACCEPTED) && (this.sentCandidate)) { this.connect(); } else { - Log.d("xmppService", + Log.d(Config.LOGTAG, "ignoring because file is already in transmission or we havent sent our candidate yet"); } return true; @@ -523,7 +525,7 @@ public class JingleConnection { final JingleSocks5Transport connection = chooseConnection(); this.transport = connection; if (connection == null) { - Log.d("xmppService", "could not find suitable candidate"); + Log.d(Config.LOGTAG, "could not find suitable candidate"); this.disconnect(); if (this.initiator.equals(account.getFullJid())) { this.sendFallbackToIbb(); @@ -532,7 +534,7 @@ public class JingleConnection { this.status = STATUS_TRANSMITTING; if (connection.needsActivation()) { if (connection.getCandidate().isOurs()) { - Log.d("xmppService", "candidate " + Log.d(Config.LOGTAG, "candidate " + connection.getCandidate().getCid() + " was our proxy. going to activate"); IqPacket activation = new IqPacket(IqPacket.TYPE_SET); @@ -557,17 +559,17 @@ public class JingleConnection { } }); } else { - Log.d("xmppService", + Log.d(Config.LOGTAG, "candidate " + connection.getCandidate().getCid() + " was a proxy. waiting for other party to activate"); } } else { if (initiator.equals(account.getFullJid())) { - Log.d("xmppService", "we were initiating. sending file"); + Log.d(Config.LOGTAG, "we were initiating. sending file"); connection.send(file, onFileTransmissionSatusChanged); } else { - Log.d("xmppService", "we were responding. receiving file"); + Log.d(Config.LOGTAG, "we were responding. receiving file"); connection.receive(file, onFileTransmissionSatusChanged); } } @@ -579,11 +581,11 @@ public class JingleConnection { for (Entry<String, JingleSocks5Transport> cursor : connections .entrySet()) { JingleSocks5Transport currentConnection = cursor.getValue(); - // Log.d("xmppService","comparing candidate: "+currentConnection.getCandidate().toString()); + // Log.d(Config.LOGTAG,"comparing candidate: "+currentConnection.getCandidate().toString()); if (currentConnection.isEstablished() && (currentConnection.getCandidate().isUsedByCounterpart() || (!currentConnection .getCandidate().isOurs()))) { - // Log.d("xmppService","is usable"); + // Log.d(Config.LOGTAG,"is usable"); if (connection == null) { connection = currentConnection; } else { @@ -592,7 +594,7 @@ public class JingleConnection { connection = currentConnection; } else if (connection.getCandidate().getPriority() == currentConnection .getCandidate().getPriority()) { - // Log.d("xmppService","found two candidates with same priority"); + // Log.d(Config.LOGTAG,"found two candidates with same priority"); if (initiator.equals(account.getFullJid())) { if (currentConnection.getCandidate().isOurs()) { connection = currentConnection; @@ -628,7 +630,7 @@ public class JingleConnection { this.transportId = this.mJingleConnectionManager.nextRandomId(); content.setTransportId(this.transportId); content.ibbTransport().setAttribute("block-size", - "" + this.ibbBlockSize); + Integer.toString(this.ibbBlockSize)); packet.setContent(content); this.sendJinglePacket(packet); } @@ -650,7 +652,7 @@ public class JingleConnection { Content content = new Content("initiator", "a-file-offer"); content.setTransportId(this.transportId); content.ibbTransport().setAttribute("block-size", - "" + this.ibbBlockSize); + Integer.toString(this.ibbBlockSize)); answer.setContent(content); this.sendJinglePacket(answer); return true; @@ -672,7 +674,7 @@ public class JingleConnection { @Override public void failed() { - Log.d("xmppService", "ibb open failed"); + Log.d(Config.LOGTAG, "ibb open failed"); } @Override @@ -742,7 +744,7 @@ public class JingleConnection { @Override public void failed() { - Log.d("xmppService", + Log.d(Config.LOGTAG, "connection failed with " + candidate.getHost() + ":" + candidate.getPort()); connectNextCandidate(); @@ -750,7 +752,7 @@ public class JingleConnection { @Override public void established() { - Log.d("xmppService", + Log.d(Config.LOGTAG, "established connection with " + candidate.getHost() + ":" + candidate.getPort()); sendCandidateUsed(candidate.getCid()); @@ -874,7 +876,7 @@ public class JingleConnection { } }).start(); } else { - Log.d("xmppService", "status (" + status + ") was not ok"); + Log.d(Config.LOGTAG, "status (" + status + ") was not ok"); } } } diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index f01d7fa9..79090af6 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import android.annotation.SuppressLint; import android.util.Log; +import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.services.XmppConnectionService; @@ -33,18 +34,20 @@ public class JingleConnectionManager { public void deliverPacket(Account account, JinglePacket packet) { if (packet.isAction("session-initiate")) { JingleConnection connection = new JingleConnection(this); - connection.init(account,packet); + connection.init(account, packet); connections.add(connection); } else { for (JingleConnection connection : connections) { - if (connection.getAccountJid().equals(account.getFullJid()) && connection - .getSessionId().equals(packet.getSessionId()) && connection - .getCounterPart().equals(packet.getFrom())) { + if (connection.getAccountJid().equals(account.getFullJid()) + && connection.getSessionId().equals( + packet.getSessionId()) + && connection.getCounterPart().equals(packet.getFrom())) { connection.deliverPacket(packet); return; } } - account.getXmppConnection().sendIqPacket(packet.generateRespone(IqPacket.TYPE_ERROR), null); + account.getXmppConnection().sendIqPacket( + packet.generateRespone(IqPacket.TYPE_ERROR), null); } } @@ -60,7 +63,7 @@ public class JingleConnectionManager { this.connections.add(connection); return connection; } - + public void finishConnection(JingleConnection connection) { this.connections.remove(connection); } @@ -90,12 +93,17 @@ public class JingleConnectionManager { .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); + JingleCandidate candidate = new JingleCandidate( + nextRandomId(), true); + candidate.setHost(streamhost + .getAttribute("host")); + candidate.setPort(Integer + .parseInt(streamhost + .getAttribute("port"))); + candidate + .setType(JingleCandidate.TYPE_PROXY); candidate.setJid(proxy); - candidate.setPriority(655360+65535); + candidate.setPriority(655360 + 65535); primaryCandidates.put(account.getJid(), candidate); listener.onPrimaryCandidateFound(true, @@ -119,9 +127,10 @@ public class JingleConnectionManager { public String nextRandomId() { return new BigInteger(50, random).toString(32); } - + public long getAutoAcceptFileSize() { - String config = this.xmppConnectionService.getPreferences().getString("auto_accept_file_size", "524288"); + String config = this.xmppConnectionService.getPreferences().getString( + "auto_accept_file_size", "524288"); try { return Long.parseLong(config); } catch (NumberFormatException e) { @@ -132,32 +141,35 @@ public class JingleConnectionManager { public void deliverIbbPacket(Account account, IqPacket packet) { String sid = null; Element payload = null; - if (packet.hasChild("open","http://jabber.org/protocol/ibb")) { - payload = packet.findChild("open","http://jabber.org/protocol/ibb"); + if (packet.hasChild("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"); + } else if (packet.hasChild("data", "http://jabber.org/protocol/ibb")) { + payload = packet + .findChild("data", "http://jabber.org/protocol/ibb"); sid = payload.getAttribute("sid"); } - if (sid!=null) { + if (sid != null) { for (JingleConnection connection : connections) { if (connection.hasTransportId(sid)) { JingleTransport transport = connection.getTransport(); if (transport instanceof JingleInbandTransport) { JingleInbandTransport inbandTransport = (JingleInbandTransport) transport; - inbandTransport.deliverPayload(packet,payload); + inbandTransport.deliverPayload(packet, payload); return; } } } - Log.d("xmppService","couldnt deliver payload: "+payload.toString()); + Log.d(Config.LOGTAG, + "couldnt deliver payload: " + payload.toString()); } else { - Log.d("xmppService","no sid found in incomming ibb packet"); + Log.d(Config.LOGTAG, "no sid found in incomming ibb packet"); } } - + public void cancelInTransmission() { - for(JingleConnection connection : this.connections) { + for (JingleConnection connection : this.connections) { if (connection.getStatus() == JingleConnection.STATUS_TRANSMITTING) { connection.cancel(); } diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleFile.java b/src/eu/siacs/conversations/xmpp/jingle/JingleFile.java index 3672351b..9253814b 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/JingleFile.java +++ b/src/eu/siacs/conversations/xmpp/jingle/JingleFile.java @@ -5,60 +5,63 @@ import java.security.Key; import javax.crypto.spec.SecretKeySpec; +import eu.siacs.conversations.Config; import eu.siacs.conversations.utils.CryptoHelper; import android.util.Log; public class JingleFile extends File { - + private static final long serialVersionUID = 2247012619505115863L; - + private long expectedSize = 0; private String sha1sum; private Key aeskey; - + public JingleFile(String path) { super(path); } - + public long getSize() { return super.length(); } - + public long getExpectedSize() { - if (this.aeskey!=null) { - return (this.expectedSize/16 + 1) * 16; + if (this.aeskey != null) { + 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>=32) { + 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) { + } else if (key.length >= 16) { byte[] secretKey = new byte[16]; System.arraycopy(key, 0, secretKey, 0, 16); this.aeskey = new SecretKeySpec(secretKey, "AES"); } else { - Log.d("xmppService","weird key"); + Log.d(Config.LOGTAG, "weird key"); } - Log.d("xmppService","using aes key "+CryptoHelper.bytesToHex(this.aeskey.getEncoded())); + Log.d(Config.LOGTAG, + "using aes key " + + CryptoHelper.bytesToHex(this.aeskey.getEncoded())); } - + public Key getKey() { return this.aeskey; } diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java b/src/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java index b859e7c7..cd87552b 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java +++ b/src/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java @@ -32,7 +32,7 @@ public class JingleInbandTransport extends JingleTransport { private OutputStream fileOutputStream; private long remainingSize; private MessageDigest digest; - + private OnFileTransmissionStatusChanged onFileTransmissionStatusChanged; private OnIqPacketReceived onAckReceived = new OnIqPacketReceived() { @@ -59,7 +59,7 @@ public class JingleInbandTransport extends JingleTransport { Element open = iq.addChild("open", "http://jabber.org/protocol/ibb"); open.setAttribute("sid", this.sessionId); open.setAttribute("stanza", "iq"); - open.setAttribute("block-size", "" + this.blockSize); + open.setAttribute("block-size", Integer.toString(this.blockSize)); this.account.getXmppConnection().sendIqPacket(iq, new OnIqPacketReceived() { @@ -77,7 +77,8 @@ public class JingleInbandTransport extends JingleTransport { } @Override - public void receive(JingleFile file, OnFileTransmissionStatusChanged callback) { + public void receive(JingleFile file, + OnFileTransmissionStatusChanged callback) { this.onFileTransmissionStatusChanged = callback; this.file = file; try { @@ -86,7 +87,7 @@ public class JingleInbandTransport extends JingleTransport { file.getParentFile().mkdirs(); file.createNewFile(); this.fileOutputStream = getOutputStream(file); - if (this.fileOutputStream==null) { + if (this.fileOutputStream == null) { callback.onFileTransferAborted(); return; } @@ -106,7 +107,7 @@ public class JingleInbandTransport extends JingleTransport { this.digest = MessageDigest.getInstance("SHA-1"); this.digest.reset(); fileInputStream = this.getInputStream(file); - if (fileInputStream==null) { + if (fileInputStream == null) { callback.onFileTransferAborted(); return; } @@ -133,8 +134,8 @@ public class JingleInbandTransport extends JingleTransport { iq.setTo(this.counterpart); Element data = iq.addChild("data", "http://jabber.org/protocol/ibb"); - data.setAttribute("seq", "" + this.seq); - data.setAttribute("block-size", "" + this.blockSize); + data.setAttribute("seq", Integer.toString(this.seq)); + data.setAttribute("block-size", Integer.toString(this.blockSize)); data.setAttribute("sid", this.sessionId); data.setContent(base64); this.account.getXmppConnection().sendIqPacket(iq, @@ -150,7 +151,8 @@ public class JingleInbandTransport extends JingleTransport { try { byte[] buffer = Base64.decode(data, Base64.NO_WRAP); if (this.remainingSize < buffer.length) { - buffer = Arrays.copyOfRange(buffer, 0, (int) this.remainingSize); + buffer = Arrays + .copyOfRange(buffer, 0, (int) this.remainingSize); } this.remainingSize -= buffer.length; diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java b/src/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java index d2c84325..1a8ab9dd 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java +++ b/src/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java @@ -21,7 +21,8 @@ public class JingleSocks5Transport extends JingleTransport { private boolean activated = false; protected Socket socket; - public JingleSocks5Transport(JingleConnection jingleConnection, JingleCandidate candidate) { + public JingleSocks5Transport(JingleConnection jingleConnection, + JingleCandidate candidate) { this.candidate = candidate; try { MessageDigest mDigest = MessageDigest.getInstance("SHA-1"); @@ -44,11 +45,12 @@ public class JingleSocks5Transport extends JingleTransport { public void connect(final OnTransportConnected callback) { new Thread(new Runnable() { - + @Override public void run() { try { - socket = new Socket(candidate.getHost(), candidate.getPort()); + socket = new Socket(candidate.getHost(), + candidate.getPort()); inputStream = socket.getInputStream(); outputStream = socket.getOutputStream(); byte[] login = { 0x05, 0x01, 0x00 }; @@ -56,9 +58,10 @@ public class JingleSocks5Transport extends JingleTransport { byte[] reply = new byte[2]; outputStream.write(login); inputStream.read(reply); + final String connect = Character.toString('\u0005') + '\u0001' + '\u0000' + + '\u0003' + '\u0028' + destination + '\u0000' + + '\u0000'; if (Arrays.equals(reply, expectedReply)) { - String connect = "" + '\u0005' + '\u0001' + '\u0000' + '\u0003' - + '\u0028' + destination + '\u0000' + '\u0000'; outputStream.write(connect.getBytes()); byte[] result = new byte[2]; inputStream.read(result); @@ -80,12 +83,13 @@ public class JingleSocks5Transport extends JingleTransport { } } }).start(); - + } - public void send(final JingleFile file, final OnFileTransmissionStatusChanged callback) { + public void send(final JingleFile file, + final OnFileTransmissionStatusChanged callback) { new Thread(new Runnable() { - + @Override public void run() { InputStream fileInputStream = null; @@ -93,7 +97,7 @@ public class JingleSocks5Transport extends JingleTransport { MessageDigest digest = MessageDigest.getInstance("SHA-1"); digest.reset(); fileInputStream = getInputStream(file); - if (fileInputStream==null) { + if (fileInputStream == null) { callback.onFileTransferAborted(); return; } @@ -105,7 +109,7 @@ public class JingleSocks5Transport extends JingleTransport { } outputStream.flush(); file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest())); - if (callback!=null) { + if (callback != null) { callback.onFileTransmitted(file); } } catch (FileNotFoundException e) { @@ -125,12 +129,13 @@ public class JingleSocks5Transport extends JingleTransport { } } }).start(); - + } - - public void receive(final JingleFile file, final OnFileTransmissionStatusChanged callback) { + + public void receive(final JingleFile file, + final OnFileTransmissionStatusChanged callback) { new Thread(new Runnable() { - + @Override public void run() { try { @@ -141,22 +146,22 @@ public class JingleSocks5Transport extends JingleTransport { file.getParentFile().mkdirs(); file.createNewFile(); OutputStream fileOutputStream = getOutputStream(file); - if (fileOutputStream==null) { + if (fileOutputStream == null) { callback.onFileTransferAborted(); return; } long remainingSize = file.getExpectedSize(); byte[] buffer = new byte[8192]; int count = buffer.length; - while(remainingSize > 0) { + while (remainingSize > 0) { count = inputStream.read(buffer); - if (count==-1) { + if (count == -1) { callback.onFileTransferAborted(); return; } else { fileOutputStream.write(buffer, 0, count); digest.update(buffer, 0, count); - remainingSize-=count; + remainingSize -= count; } } fileOutputStream.flush(); @@ -177,25 +182,25 @@ public class JingleSocks5Transport extends JingleTransport { public boolean isProxy() { return this.candidate.getType() == JingleCandidate.TYPE_PROXY; } - + public boolean needsActivation() { return (this.isProxy() && !this.activated); } public void disconnect() { - if (this.socket!=null) { + if (this.socket != null) { try { this.socket.close(); } catch (IOException e) { - + } } } - + public boolean isEstablished() { return this.isEstablished; } - + public JingleCandidate getCandidate() { return this.candidate; } diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleTransport.java b/src/eu/siacs/conversations/xmpp/jingle/JingleTransport.java index 1acdfc39..07dc8ecc 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/JingleTransport.java +++ b/src/eu/siacs/conversations/xmpp/jingle/JingleTransport.java @@ -15,61 +15,72 @@ import javax.crypto.CipherInputStream; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; +import eu.siacs.conversations.Config; + import android.util.Log; public abstract class JingleTransport { public abstract void connect(final OnTransportConnected callback); - public abstract void receive(final JingleFile file, final OnFileTransmissionStatusChanged callback); - public abstract void send(final JingleFile file, final OnFileTransmissionStatusChanged callback); - private byte[] iv = {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0xf}; - - protected InputStream getInputStream(JingleFile file) throws FileNotFoundException { + + public abstract void receive(final JingleFile file, + final OnFileTransmissionStatusChanged callback); + + public abstract void send(final JingleFile file, + final OnFileTransmissionStatusChanged callback); + + private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf }; + + protected InputStream getInputStream(JingleFile file) + throws FileNotFoundException { if (file.getKey() == null) { return new FileInputStream(file); } else { try { IvParameterSpec ips = new IvParameterSpec(iv); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.ENCRYPT_MODE, file.getKey(),ips); - Log.d("xmppService","opening encrypted input stream"); + cipher.init(Cipher.ENCRYPT_MODE, file.getKey(), ips); + Log.d(Config.LOGTAG, "opening encrypted input stream"); return new CipherInputStream(new FileInputStream(file), cipher); } catch (NoSuchAlgorithmException e) { - Log.d("xmppService","no such algo: "+e.getMessage()); + Log.d(Config.LOGTAG, "no such algo: " + e.getMessage()); return null; } catch (NoSuchPaddingException e) { - Log.d("xmppService","no such padding: "+e.getMessage()); + Log.d(Config.LOGTAG, "no such padding: " + e.getMessage()); return null; } catch (InvalidKeyException e) { - Log.d("xmppService","invalid key: "+e.getMessage()); + Log.d(Config.LOGTAG, "invalid key: " + e.getMessage()); return null; } catch (InvalidAlgorithmParameterException e) { - Log.d("xmppService","invavid iv:"+e.getMessage()); + Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage()); return null; } } } - - protected OutputStream getOutputStream(JingleFile file) throws FileNotFoundException { + + protected OutputStream getOutputStream(JingleFile file) + throws FileNotFoundException { if (file.getKey() == null) { return new FileOutputStream(file); } else { try { IvParameterSpec ips = new IvParameterSpec(iv); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.DECRYPT_MODE, file.getKey(),ips); - Log.d("xmppService","opening encrypted output stream"); - return new CipherOutputStream(new FileOutputStream(file), cipher); + cipher.init(Cipher.DECRYPT_MODE, file.getKey(), ips); + Log.d(Config.LOGTAG, "opening encrypted output stream"); + return new CipherOutputStream(new FileOutputStream(file), + cipher); } catch (NoSuchAlgorithmException e) { - Log.d("xmppService","no such algo: "+e.getMessage()); + Log.d(Config.LOGTAG, "no such algo: " + e.getMessage()); return null; } catch (NoSuchPaddingException e) { - Log.d("xmppService","no such padding: "+e.getMessage()); + Log.d(Config.LOGTAG, "no such padding: " + e.getMessage()); return null; } catch (InvalidKeyException e) { - Log.d("xmppService","invalid key: "+e.getMessage()); + Log.d(Config.LOGTAG, "invalid key: " + e.getMessage()); return null; } catch (InvalidAlgorithmParameterException e) { - Log.d("xmppService","invavid iv:"+e.getMessage()); + Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage()); return null; } } diff --git a/src/eu/siacs/conversations/xmpp/jingle/OnFileTransmissionStatusChanged.java b/src/eu/siacs/conversations/xmpp/jingle/OnFileTransmissionStatusChanged.java index 84f10417..19fd4d97 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/OnFileTransmissionStatusChanged.java +++ b/src/eu/siacs/conversations/xmpp/jingle/OnFileTransmissionStatusChanged.java @@ -2,5 +2,6 @@ package eu.siacs.conversations.xmpp.jingle; public interface OnFileTransmissionStatusChanged { public void onFileTransmitted(JingleFile file); + public void onFileTransferAborted(); } diff --git a/src/eu/siacs/conversations/xmpp/jingle/OnPrimaryCandidateFound.java b/src/eu/siacs/conversations/xmpp/jingle/OnPrimaryCandidateFound.java index b91a90ff..03a437b2 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/OnPrimaryCandidateFound.java +++ b/src/eu/siacs/conversations/xmpp/jingle/OnPrimaryCandidateFound.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.xmpp.jingle; public interface OnPrimaryCandidateFound { - public void onPrimaryCandidateFound(boolean success, JingleCandidate canditate); + public void onPrimaryCandidateFound(boolean success, + JingleCandidate canditate); } diff --git a/src/eu/siacs/conversations/xmpp/jingle/OnTransportConnected.java b/src/eu/siacs/conversations/xmpp/jingle/OnTransportConnected.java index 7d9a084a..38f03c5d 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/OnTransportConnected.java +++ b/src/eu/siacs/conversations/xmpp/jingle/OnTransportConnected.java @@ -2,5 +2,6 @@ package eu.siacs.conversations.xmpp.jingle; public interface OnTransportConnected { public void failed(); + public void established(); } diff --git a/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java b/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java index 494ff0d6..d19e6dfd 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java +++ b/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java @@ -4,17 +4,17 @@ import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.jingle.JingleFile; public class Content extends Element { - + private String transportId; - + private Content(String name) { super(name); } - + public Content() { super("content"); } - + public Content(String creator, String name) { super("content"); this.setAttribute("creator", creator); @@ -24,39 +24,43 @@ public class Content extends Element { public void setTransportId(String sid) { this.transportId = sid; } - + public void setFileOffer(JingleFile actualFile, boolean otr) { - Element description = this.addChild("description", "urn:xmpp:jingle:apps:file-transfer:3"); + Element description = this.addChild("description", + "urn:xmpp:jingle:apps:file-transfer:3"); Element offer = description.addChild("offer"); Element file = offer.addChild("file"); - file.addChild("size").setContent(""+actualFile.getSize()); + file.addChild("size").setContent(Long.toString(actualFile.getSize())); if (otr) { - file.addChild("name").setContent(actualFile.getName()+".otr"); + file.addChild("name").setContent(actualFile.getName() + ".otr"); } else { file.addChild("name").setContent(actualFile.getName()); } } - + public Element getFileOffer() { - Element description = this.findChild("description", "urn:xmpp:jingle:apps:file-transfer:3"); - if (description==null) { + Element description = this.findChild("description", + "urn:xmpp:jingle:apps:file-transfer:3"); + if (description == null) { return null; } Element offer = description.findChild("offer"); - if (offer==null) { + if (offer == null) { return null; } return offer.findChild("file"); } - + public void setFileOffer(Element fileOffer) { - Element description = this.findChild("description", "urn:xmpp:jingle:apps:file-transfer:3"); - if (description==null) { - description = this.addChild("description", "urn:xmpp:jingle:apps:file-transfer:3"); + Element description = this.findChild("description", + "urn:xmpp:jingle:apps:file-transfer:3"); + if (description == null) { + description = this.addChild("description", + "urn:xmpp:jingle:apps:file-transfer:3"); } description.addChild(fileOffer); } - + public String getTransportId() { if (hasSocks5Transport()) { this.transportId = socks5transport().getAttribute("sid"); @@ -65,30 +69,34 @@ public class Content extends Element { } return this.transportId; } - + public Element socks5transport() { - Element transport = this.findChild("transport", "urn:xmpp:jingle:transports:s5b:1"); - if (transport==null) { - transport = this.addChild("transport", "urn:xmpp:jingle:transports:s5b:1"); + Element transport = this.findChild("transport", + "urn:xmpp:jingle:transports:s5b:1"); + if (transport == null) { + transport = this.addChild("transport", + "urn:xmpp:jingle:transports:s5b:1"); transport.setAttribute("sid", this.transportId); } return transport; } - + public Element ibbTransport() { - Element transport = this.findChild("transport", "urn:xmpp:jingle:transports:ibb:1"); - if (transport==null) { - transport = this.addChild("transport", "urn:xmpp:jingle:transports:ibb:1"); + Element transport = this.findChild("transport", + "urn:xmpp:jingle:transports:ibb:1"); + if (transport == null) { + transport = this.addChild("transport", + "urn:xmpp:jingle:transports:ibb:1"); transport.setAttribute("sid", this.transportId); } return transport; } - + public boolean hasSocks5Transport() { return this.hasChild("transport", "urn:xmpp:jingle:transports:s5b:1"); } - + public boolean hasIbbTransport() { - return this.hasChild("transport","urn:xmpp:jingle:transports:ibb:1"); + return this.hasChild("transport", "urn:xmpp:jingle:transports:ibb:1"); } } diff --git a/src/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java b/src/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java index 55700609..77a73643 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java +++ b/src/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java @@ -7,18 +7,18 @@ public class JinglePacket extends IqPacket { Content content = null; Reason reason = null; Element jingle = new Element("jingle"); - + @Override public Element addChild(Element child) { if ("jingle".equals(child.getName())) { Element contentElement = child.findChild("content"); - if (contentElement!=null) { + if (contentElement != null) { this.content = new Content(); this.content.setChildren(contentElement.getChildren()); this.content.setAttributes(contentElement.getAttributes()); } Element reasonElement = child.findChild("reason"); - if (reasonElement!=null) { + if (reasonElement != null) { this.reason = new Reason(); this.reason.setChildren(reasonElement.getChildren()); this.reason.setAttributes(reasonElement.getAttributes()); @@ -27,33 +27,33 @@ public class JinglePacket extends IqPacket { } return child; } - + public JinglePacket setContent(Content content) { this.content = content; return this; } - + public Content getJingleContent() { - if (this.content==null) { + if (this.content == null) { this.content = new Content(); } return this.content; } - + public JinglePacket setReason(Reason reason) { this.reason = reason; return this; } - + public Reason getReason() { return this.reason; } - + private void build() { this.children.clear(); this.jingle.clearChildren(); this.jingle.setAttribute("xmlns", "urn:xmpp:jingle:1"); - if (this.content!=null) { + if (this.content != null) { jingle.addChild(this.content); } if (this.reason != null) { @@ -66,11 +66,11 @@ public class JinglePacket extends IqPacket { public String getSessionId() { return this.jingle.getAttribute("sid"); } - + public void setSessionId(String sid) { this.jingle.setAttribute("sid", sid); } - + @Override public String toString() { this.build(); @@ -80,11 +80,11 @@ public class JinglePacket extends IqPacket { public void setAction(String action) { this.jingle.setAttribute("action", action); } - + public String getAction() { return this.jingle.getAttribute("action"); } - + public void setInitiator(String initiator) { this.jingle.setAttribute("initiator", initiator); } diff --git a/src/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java b/src/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java index 195e0db7..610d5e76 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java +++ b/src/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java @@ -6,7 +6,7 @@ public class Reason extends Element { private Reason(String name) { super(name); } - + public Reason() { super("reason"); } diff --git a/src/eu/siacs/conversations/xmpp/pep/Avatar.java b/src/eu/siacs/conversations/xmpp/pep/Avatar.java index 6d5c1431..154fadf6 100644 --- a/src/eu/siacs/conversations/xmpp/pep/Avatar.java +++ b/src/eu/siacs/conversations/xmpp/pep/Avatar.java @@ -11,48 +11,51 @@ public class Avatar { public int width; public long size; public String owner; + public byte[] getImageAsBytes() { return Base64.decode(image, Base64.DEFAULT); } + public String getFilename() { - if (type==null) { + if (type == null) { return sha1sum; } else if (type.equalsIgnoreCase("image/webp")) { - return sha1sum+".webp"; + return sha1sum + ".webp"; } else if (type.equalsIgnoreCase("image/png")) { - return sha1sum+".png"; + return sha1sum + ".png"; } else { return sha1sum; } } - + public static Avatar parseMetadata(Element items) { Element item = items.findChild("item"); - if (item==null) { + if (item == null) { return null; } Element metadata = item.findChild("metadata"); - if (metadata==null) { + if (metadata == null) { return null; } String primaryId = item.getAttribute("id"); - if (primaryId==null) { + if (primaryId == null) { return null; } - for(Element child : metadata.getChildren()) { - if (child.getName().equals("info") && primaryId.equals(child.getAttribute("id"))) { + for (Element child : metadata.getChildren()) { + if (child.getName().equals("info") + && primaryId.equals(child.getAttribute("id"))) { Avatar avatar = new Avatar(); String height = child.getAttribute("height"); String width = child.getAttribute("width"); String size = child.getAttribute("bytes"); try { - if (height!=null) { + if (height != null) { avatar.height = Integer.parseInt(height); } - if (width!=null) { + if (width != null) { avatar.width = Integer.parseInt(width); } - if (size!=null) { + if (size != null) { avatar.size = Long.parseLong(size); } } catch (NumberFormatException e) { diff --git a/src/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java b/src/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java index 204a6bec..eef41c79 100644 --- a/src/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java +++ b/src/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java @@ -15,20 +15,20 @@ public class AbstractStanza extends Element { public String getFrom() { return getAttribute("from"); } - + public String getId() { return this.getAttribute("id"); } - + public void setTo(String to) { setAttribute("to", to); } - + public void setFrom(String from) { - setAttribute("from",from); + setAttribute("from", from); } - + public void setId(String id) { - setAttribute("id",id); + setAttribute("id", id); } } diff --git a/src/eu/siacs/conversations/xmpp/stanzas/IqPacket.java b/src/eu/siacs/conversations/xmpp/stanzas/IqPacket.java index 1d4e44d1..9df05e67 100644 --- a/src/eu/siacs/conversations/xmpp/stanzas/IqPacket.java +++ b/src/eu/siacs/conversations/xmpp/stanzas/IqPacket.java @@ -2,9 +2,8 @@ package eu.siacs.conversations.xmpp.stanzas; 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; @@ -33,25 +32,25 @@ public class IqPacket extends AbstractStanza { break; } } - + public IqPacket() { super("iq"); } - + public Element query() { Element query = findChild("query"); - if (query==null) { + if (query == null) { query = addChild("query"); } return query; } - + public Element query(String xmlns) { Element query = query(); query.setAttribute("xmlns", xmlns); return query(); } - + public int getType() { String type = getAttribute("type"); if ("error".equals(type)) { @@ -66,7 +65,7 @@ public class IqPacket extends AbstractStanza { return 1000; } } - + public IqPacket generateRespone(int type) { IqPacket packet = new IqPacket(type); packet.setTo(this.getFrom()); diff --git a/src/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java b/src/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java index 433b08c9..386d9dc8 100644 --- a/src/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java +++ b/src/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java @@ -9,20 +9,20 @@ public class MessagePacket extends AbstractStanza { public static final int TYPE_GROUPCHAT = 3; public static final int TYPE_ERROR = 4; public static final int TYPE_HEADLINE = 5; - + public MessagePacket() { super("message"); } - + public String getBody() { Element body = this.findChild("body"); - if (body!=null) { + if (body != null) { return body.getContent(); } else { return null; } } - + public void setBody(String text) { this.children.remove(findChild("body")); Element body = new Element("body"); @@ -33,7 +33,7 @@ public class MessagePacket extends AbstractStanza { public void setType(int type) { switch (type) { case TYPE_CHAT: - this.setAttribute("type","chat"); + this.setAttribute("type", "chat"); break; case TYPE_GROUPCHAT: this.setAttribute("type", "groupchat"); @@ -43,14 +43,14 @@ public class MessagePacket extends AbstractStanza { case TYPE_NORMAL: break; default: - this.setAttribute("type","chat"); + this.setAttribute("type", "chat"); break; } } - + public int getType() { String type = getAttribute("type"); - if (type==null) { + if (type == null) { return TYPE_NORMAL; } else if (type.equals("normal")) { return TYPE_NORMAL; diff --git a/src/eu/siacs/conversations/xmpp/stanzas/PresencePacket.java b/src/eu/siacs/conversations/xmpp/stanzas/PresencePacket.java index dfbab78c..7ea32099 100644 --- a/src/eu/siacs/conversations/xmpp/stanzas/PresencePacket.java +++ b/src/eu/siacs/conversations/xmpp/stanzas/PresencePacket.java @@ -1,8 +1,7 @@ package eu.siacs.conversations.xmpp.stanzas; - public class PresencePacket extends AbstractStanza { - + public PresencePacket() { super("presence"); } diff --git a/src/eu/siacs/conversations/xmpp/stanzas/csi/ActivePacket.java b/src/eu/siacs/conversations/xmpp/stanzas/csi/ActivePacket.java new file mode 100644 index 00000000..78ab66d8 --- /dev/null +++ b/src/eu/siacs/conversations/xmpp/stanzas/csi/ActivePacket.java @@ -0,0 +1,10 @@ +package eu.siacs.conversations.xmpp.stanzas.csi; + +import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; + +public class ActivePacket extends AbstractStanza { + public ActivePacket() { + super("active"); + setAttribute("xmlns", "urn:xmpp:csi:0"); + } +} diff --git a/src/eu/siacs/conversations/xmpp/stanzas/csi/InactivePacket.java b/src/eu/siacs/conversations/xmpp/stanzas/csi/InactivePacket.java new file mode 100644 index 00000000..f109280f --- /dev/null +++ b/src/eu/siacs/conversations/xmpp/stanzas/csi/InactivePacket.java @@ -0,0 +1,10 @@ +package eu.siacs.conversations.xmpp.stanzas.csi; + +import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; + +public class InactivePacket extends AbstractStanza { + public InactivePacket() { + super("inactive"); + setAttribute("xmlns", "urn:xmpp:csi:0"); + } +} diff --git a/src/eu/siacs/conversations/xmpp/stanzas/streammgmt/AckPacket.java b/src/eu/siacs/conversations/xmpp/stanzas/streammgmt/AckPacket.java index 6fe3ea2b..f93b5d87 100644 --- a/src/eu/siacs/conversations/xmpp/stanzas/streammgmt/AckPacket.java +++ b/src/eu/siacs/conversations/xmpp/stanzas/streammgmt/AckPacket.java @@ -6,8 +6,8 @@ public class AckPacket extends AbstractStanza { public AckPacket(int sequence, int smVersion) { super("a"); - this.setAttribute("xmlns","urn:xmpp:sm:"+smVersion); - this.setAttribute("h", ""+sequence); + this.setAttribute("xmlns", "urn:xmpp:sm:" + smVersion); + this.setAttribute("h", Integer.toString(sequence)); } } diff --git a/src/eu/siacs/conversations/xmpp/stanzas/streammgmt/EnablePacket.java b/src/eu/siacs/conversations/xmpp/stanzas/streammgmt/EnablePacket.java index 0ca7a4fd..78cd81ed 100644 --- a/src/eu/siacs/conversations/xmpp/stanzas/streammgmt/EnablePacket.java +++ b/src/eu/siacs/conversations/xmpp/stanzas/streammgmt/EnablePacket.java @@ -6,7 +6,7 @@ public class EnablePacket extends AbstractStanza { public EnablePacket(int smVersion) { super("enable"); - this.setAttribute("xmlns","urn:xmpp:sm:"+smVersion); + this.setAttribute("xmlns", "urn:xmpp:sm:" + smVersion); this.setAttribute("resume", "true"); } diff --git a/src/eu/siacs/conversations/xmpp/stanzas/streammgmt/RequestPacket.java b/src/eu/siacs/conversations/xmpp/stanzas/streammgmt/RequestPacket.java index d42ae9b0..98cfc748 100644 --- a/src/eu/siacs/conversations/xmpp/stanzas/streammgmt/RequestPacket.java +++ b/src/eu/siacs/conversations/xmpp/stanzas/streammgmt/RequestPacket.java @@ -6,7 +6,7 @@ public class RequestPacket extends AbstractStanza { public RequestPacket(int smVersion) { super("r"); - this.setAttribute("xmlns","urn:xmpp:sm:"+smVersion); + this.setAttribute("xmlns", "urn:xmpp:sm:" + smVersion); } } diff --git a/src/eu/siacs/conversations/xmpp/stanzas/streammgmt/ResumePacket.java b/src/eu/siacs/conversations/xmpp/stanzas/streammgmt/ResumePacket.java index dcf32101..9cdcfa5e 100644 --- a/src/eu/siacs/conversations/xmpp/stanzas/streammgmt/ResumePacket.java +++ b/src/eu/siacs/conversations/xmpp/stanzas/streammgmt/ResumePacket.java @@ -6,9 +6,9 @@ public class ResumePacket extends AbstractStanza { public ResumePacket(String id, int sequence, int smVersion) { super("resume"); - this.setAttribute("xmlns","urn:xmpp:sm:"+smVersion); + this.setAttribute("xmlns", "urn:xmpp:sm:" + smVersion); this.setAttribute("previd", id); - this.setAttribute("h", ""+sequence); + this.setAttribute("h", Integer.toString(sequence)); } } |