diff options
author | Daniel Gultsch <daniel@gultsch.de> | 2015-05-20 03:39:58 +0200 |
---|---|---|
committer | Daniel Gultsch <daniel@gultsch.de> | 2015-05-20 03:39:58 +0200 |
commit | 4f36aa39a4e8aa5128c12a713bd9d3d4d9b51469 (patch) | |
tree | 1a87a4e98b88f4e0eb3fc36bcee74f403acf0abe /src/main/java/eu/siacs | |
parent | fbf81f59dbc17951faea945cca979312201d00fe (diff) | |
parent | 7824c01748a89688c5f76307cc1f4221a9e2ffb5 (diff) |
Merge branch 'development'
Diffstat (limited to 'src/main/java/eu/siacs')
28 files changed, 916 insertions, 391 deletions
diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 5cca6c0b..779cbbe8 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -11,6 +11,7 @@ public final class Config { 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 SOCKET_TIMEOUT = 15; public static final int CONNECT_TIMEOUT = 90; public static final int CARBON_GRACE_PERIOD = 60; public static final int MINI_GRACE_PERIOD = 750; @@ -28,7 +29,8 @@ public final class Config { public static final boolean NO_PROXY_LOOKUP = false; //useful to debug ibb public static final boolean DISABLE_STRING_PREP = false; // setting to true might increase startup performance - public static final boolean EXTENDED_SM_LOGGING = false; // log stanza counts + public static final boolean EXTENDED_SM_LOGGING = true; // log stanza counts + public static final boolean RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE = true; //setting to true might increase power consumption public static final long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000; public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY / 2; diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index cef03ebe..9dbca59a 100644 --- a/src/main/java/eu/siacs/conversations/entities/Contact.java +++ b/src/main/java/eu/siacs/conversations/entities/Contact.java @@ -15,6 +15,7 @@ import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; +import eu.siacs.conversations.xmpp.pep.Avatar; public class Contact implements ListItem, Blockable { public static final String TABLENAME = "contacts"; @@ -40,11 +41,11 @@ public class Contact implements ListItem, Blockable { protected int subscription = 0; protected String systemAccount; protected String photoUri; - protected String avatar; protected JSONObject keys = new JSONObject(); protected JSONArray groups = new JSONArray(); protected Presences presences = new Presences(); protected Account account; + protected Avatar avatar; public Contact(final String account, final String systemName, final String serverName, final Jid jid, final int subscription, final String photoUri, @@ -61,7 +62,11 @@ public class Contact implements ListItem, Blockable { } catch (JSONException e) { this.keys = new JSONObject(); } - this.avatar = avatar; + if (avatar != null) { + this.avatar = new Avatar(); + this.avatar.sha1sum = avatar; + this.avatar.origin = Avatar.Origin.VCARD; //always assume worst + } try { this.groups = (groups == null ? new JSONArray() : new JSONArray(groups)); } catch (JSONException e) { @@ -135,10 +140,10 @@ public class Contact implements ListItem, Blockable { tags.add(new Tag("away", 0xffff9800)); break; case Presences.XA: - tags.add(new Tag("not available", 0xffe51c23)); + tags.add(new Tag("not available", 0xfff44336)); break; case Presences.DND: - tags.add(new Tag("dnd", 0xffe51c23)); + tags.add(new Tag("dnd", 0xfff44336)); break; } if (isBlocked()) { @@ -187,7 +192,7 @@ public class Contact implements ListItem, Blockable { values.put(SYSTEMACCOUNT, systemAccount); values.put(PHOTOURI, photoUri); values.put(KEYS, keys.toString()); - values.put(AVATAR, avatar); + values.put(AVATAR, avatar == null ? null : avatar.getFilename()); values.put(LAST_PRESENCE, lastseen.presence); values.put(LAST_TIME, lastseen.time); values.put(GROUPS, groups.toString()); @@ -411,17 +416,20 @@ public class Contact implements ListItem, Blockable { return getJid().toDomainJid(); } - public boolean setAvatar(String filename) { - if (this.avatar != null && this.avatar.equals(filename)) { + public boolean setAvatar(Avatar avatar) { + if (this.avatar != null && this.avatar.equals(avatar)) { return false; } else { - this.avatar = filename; + if (this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) { + return false; + } + this.avatar = avatar; return true; } } public String getAvatar() { - return this.avatar; + return avatar == null ? null : avatar.getFilename(); } public boolean deleteOtrFingerprint(String fingerprint) { @@ -478,6 +486,10 @@ public class Contact implements ListItem, Blockable { } } + public boolean isSelf() { + return account.getJid().toBareJid().equals(getJid().toBareJid()); + } + public static class Lastseen { public long time; public String presence; diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index bfee5007..95a8c957 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -16,6 +16,7 @@ import java.security.interfaces.DSAPublicKey; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.Iterator; import java.util.List; import eu.siacs.conversations.Config; @@ -218,6 +219,11 @@ public class Conversation extends AbstractEntity implements Blockable { messages.clear(); messages.addAll(this.messages); } + for(Iterator<Message> iterator = messages.iterator(); iterator.hasNext();) { + if (iterator.next().wasMergedIntoPrevious()) { + iterator.remove(); + } + } } @Override diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 7ea3d60b..38152edb 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -9,6 +9,7 @@ import java.util.Arrays; import eu.siacs.conversations.Config; import eu.siacs.conversations.utils.GeoHelper; +import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; @@ -16,6 +17,8 @@ public class Message extends AbstractEntity { public static final String TABLENAME = "messages"; + public static final String MERGE_SEPARATOR = "\u200B\n\n"; + public static final int STATUS_RECEIVED = 0; public static final int STATUS_UNSEND = 1; public static final int STATUS_SEND = 2; @@ -95,9 +98,9 @@ public class Message extends AbstractEntity { } private Message(final String uuid, final String conversationUUid, final Jid counterpart, - final Jid trueCounterpart, final String body, final long timeSent, - final int encryption, final int status, final int type, final String remoteMsgId, - final String relativeFilePath, final String serverMsgId) { + final Jid trueCounterpart, final String body, final long timeSent, + final int encryption, final int status, final int type, final String remoteMsgId, + final String relativeFilePath, final String serverMsgId) { this.uuid = uuid; this.conversationUuid = conversationUUid; this.counterpart = counterpart; @@ -179,7 +182,7 @@ public class Message extends AbstractEntity { values.put(TYPE, type); values.put(REMOTE_MSG_ID, remoteMsgId); values.put(RELATIVE_FILE_PATH, relativeFilePath); - values.put(SERVER_MSG_ID,serverMsgId); + values.put(SERVER_MSG_ID, serverMsgId); return values; } @@ -211,7 +214,7 @@ public class Message extends AbstractEntity { return null; } else { return this.conversation.getAccount().getRoster() - .getContactFromRoster(this.trueCounterpart); + .getContactFromRoster(this.trueCounterpart); } } } @@ -359,41 +362,43 @@ public class Message extends AbstractEntity { public boolean mergeable(final Message message) { return message != null && - (message.getType() == Message.TYPE_TEXT && - this.getDownloadable() == null && - message.getDownloadable() == null && - message.getEncryption() != Message.ENCRYPTION_PGP && - this.getType() == message.getType() && - //this.getStatus() == message.getStatus() && - isStatusMergeable(this.getStatus(),message.getStatus()) && - this.getEncryption() == message.getEncryption() && - this.getCounterpart() != null && - this.getCounterpart().equals(message.getCounterpart()) && - (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) && - !GeoHelper.isGeoUri(message.getBody()) && - !GeoHelper.isGeoUri(this.body) && - !message.bodyContainsDownloadable() && - !this.bodyContainsDownloadable() && - !message.getBody().startsWith(ME_COMMAND) && - !this.getBody().startsWith(ME_COMMAND) - ); + (message.getType() == Message.TYPE_TEXT && + this.getDownloadable() == null && + message.getDownloadable() == null && + message.getEncryption() != Message.ENCRYPTION_PGP && + this.getType() == message.getType() && + //this.getStatus() == message.getStatus() && + isStatusMergeable(this.getStatus(), message.getStatus()) && + this.getEncryption() == message.getEncryption() && + this.getCounterpart() != null && + this.getCounterpart().equals(message.getCounterpart()) && + (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) && + !GeoHelper.isGeoUri(message.getBody()) && + !GeoHelper.isGeoUri(this.body) && + !message.bodyContainsDownloadable() && + !this.bodyContainsDownloadable() && + !message.getBody().startsWith(ME_COMMAND) && + !this.getBody().startsWith(ME_COMMAND) && + !this.bodyIsHeart() && + !message.bodyIsHeart() + ); } private static boolean isStatusMergeable(int a, int b) { return a == b || ( - ( a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_UNSEND) - || (a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_SEND) - || (a == Message.STATUS_UNSEND && b == Message.STATUS_SEND) - || (a == Message.STATUS_UNSEND && b == Message.STATUS_SEND_RECEIVED) - || (a == Message.STATUS_SEND && b == Message.STATUS_UNSEND) - || (a == Message.STATUS_SEND && b == Message.STATUS_SEND_RECEIVED) + (a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_UNSEND) + || (a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_SEND) + || (a == Message.STATUS_UNSEND && b == Message.STATUS_SEND) + || (a == Message.STATUS_UNSEND && b == Message.STATUS_SEND_RECEIVED) + || (a == Message.STATUS_SEND && b == Message.STATUS_UNSEND) + || (a == Message.STATUS_SEND && b == Message.STATUS_SEND_RECEIVED) ); } public String getMergedBody() { final Message next = this.next(); if (this.mergeable(next)) { - return getBody().trim() + '\n' + next.getMergedBody(); + return getBody().trim() + MERGE_SEPARATOR + next.getMergedBody(); } return getBody().trim(); } @@ -435,7 +440,7 @@ public class Message extends AbstractEntity { * "http://example.com/image.jpg text that will not be shown /abc.png" * or more than one image link in one message. */ - if (body.contains(" ")) { + if (body.trim().contains(" ")) { return false; } try { @@ -443,7 +448,7 @@ public class Message extends AbstractEntity { if (!url.getProtocol().equalsIgnoreCase("http") && !url.getProtocol().equalsIgnoreCase("https")) { return false; - } + } String sUrlPath = url.getPath(); if (sUrlPath == null || sUrlPath.isEmpty()) { @@ -457,14 +462,14 @@ public class Message extends AbstractEntity { String[] extensionParts = sLastUrlPath.split("\\."); if (extensionParts.length == 2 && Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains( - extensionParts[extensionParts.length - 1])) { + extensionParts[extensionParts.length - 1])) { return true; } else if (extensionParts.length == 3 && Arrays .asList(Downloadable.VALID_CRYPTO_EXTENSIONS) .contains(extensionParts[extensionParts.length - 1]) && Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains( - extensionParts[extensionParts.length - 2])) { + extensionParts[extensionParts.length - 2])) { return true; } else { return false; @@ -474,6 +479,10 @@ public class Message extends AbstractEntity { } } + public boolean bodyIsHeart() { + return body != null && UIHelper.HEARTS.contains(body.trim()); + } + public ImageParams getImageParams() { ImageParams params = getLegacyImageParams(); if (params != null) { diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index addee8db..d867a370 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -343,8 +343,6 @@ public class MucOptions { setError(ERROR_BANNED); } else if (error != null && error.hasChild("registration-required")) { setError(ERROR_MEMBERS_ONLY); - } else { - setError(ERROR_UNKNOWN); } } } diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 6bc629b5..d7366daa 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -91,7 +91,7 @@ public class IqGenerator extends AbstractGenerator { return publish("urn:xmpp:avatar:metadata", item); } - public IqPacket retrieveAvatar(final Avatar avatar) { + public IqPacket retrievePepAvatar(final Avatar avatar) { final Element item = new Element("item"); item.setAttribute("id", avatar.sha1sum); final IqPacket packet = retrieve("urn:xmpp:avatar:data", item); @@ -99,6 +99,13 @@ public class IqGenerator extends AbstractGenerator { return packet; } + public IqPacket retrieveVcardAvatar(final Avatar avatar) { + final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + packet.setTo(avatar.owner); + packet.addChild("vCard","vcard-temp"); + return packet; + } + public IqPacket retrieveAvatarMetaData(final Jid to) { final IqPacket packet = retrieve("urn:xmpp:avatar:metadata", null); if (to != null) { diff --git a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java index 526005f3..c40c6d05 100644 --- a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java @@ -49,7 +49,7 @@ public class PresenceGenerator extends AbstractGenerator { Element cap = packet.addChild("c", "http://jabber.org/protocol/caps"); cap.setAttribute("hash", "sha-1"); - cap.setAttribute("node", "http://conversions.im"); + cap.setAttribute("node", "http://conversations.im"); cap.setAttribute("ver", capHash); } return packet; @@ -61,4 +61,4 @@ public class PresenceGenerator extends AbstractGenerator { packet.setAttribute("type","unavailable"); return packet; } -}
\ No newline at end of file +} diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 76d01468..7870fdbf 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -494,7 +494,7 @@ public class MessageParser extends AbstractParser implements } else { Contact contact = account.getRoster().getContact( from); - contact.setAvatar(avatar.getFilename()); + contact.setAvatar(avatar); mXmppConnectionService.getAvatarService().clear( contact); mXmppConnectionService.updateConversationUi(); diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java index 7505b091..f7872210 100644 --- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java +++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java @@ -13,6 +13,7 @@ import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.OnPresencePacketReceived; import eu.siacs.conversations.xmpp.jid.Jid; +import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.stanzas.PresencePacket; public class PresenceParser extends AbstractParser implements @@ -101,6 +102,20 @@ public class PresenceParser extends AbstractParser implements if (nick != null) { contact.setPresenceName(nick.getContent()); } + Element x = packet.findChild("x","vcard-temp:x:update"); + Avatar avatar = Avatar.parsePresence(x); + if (avatar != null && !contact.isSelf()) { + avatar.owner = from.toBareJid(); + if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) { + if (contact.setAvatar(avatar)) { + mXmppConnectionService.getAvatarService().clear(contact); + mXmppConnectionService.updateConversationUi(); + mXmppConnectionService.updateRosterUi(); + } + } else { + mXmppConnectionService.fetchAvatar(account,avatar); + } + } mXmppConnectionService.updateRosterUi(); } diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 28e1c47e..ed88e434 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -4,11 +4,13 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Roster; +import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; import android.content.Context; @@ -16,13 +18,14 @@ import android.database.Cursor; import android.database.sqlite.SQLiteCantOpenDatabaseException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; public class DatabaseBackend extends SQLiteOpenHelper { private static DatabaseBackend instance = null; private static final String DATABASE_NAME = "history"; - private static final int DATABASE_VERSION = 13; + private static final int DATABASE_VERSION = 14; private static String CREATE_CONTATCS_STATEMENT = "create table " + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " @@ -130,6 +133,88 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL("delete from "+Contact.TABLENAME); db.execSQL("update "+Account.TABLENAME+" set "+Account.ROSTERVERSION+" = NULL"); } + if (oldVersion < 14 && newVersion >= 14) { + // migrate db to new, canonicalized JID domainpart representation + + // Conversation table + Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME, new String[0]); + while(cursor.moveToNext()) { + String newJid; + try { + newJid = Jid.fromString( + cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID)) + ).toString(); + } catch (InvalidJidException ignored) { + Log.e(Config.LOGTAG, "Failed to migrate Conversation CONTACTJID " + +cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID)) + +": " + ignored +". Skipping..."); + continue; + } + + String updateArgs[] = { + newJid, + cursor.getString(cursor.getColumnIndex(Conversation.UUID)), + }; + db.execSQL("update " + Conversation.TABLENAME + + " set " + Conversation.CONTACTJID + " = ? " + + " where " + Conversation.UUID + " = ?", updateArgs); + } + cursor.close(); + + // Contact table + cursor = db.rawQuery("select * from " + Contact.TABLENAME, new String[0]); + while(cursor.moveToNext()) { + String newJid; + try { + newJid = Jid.fromString( + cursor.getString(cursor.getColumnIndex(Contact.JID)) + ).toString(); + } catch (InvalidJidException ignored) { + Log.e(Config.LOGTAG, "Failed to migrate Contact JID " + +cursor.getString(cursor.getColumnIndex(Contact.JID)) + +": " + ignored +". Skipping..."); + continue; + } + + String updateArgs[] = { + newJid, + cursor.getString(cursor.getColumnIndex(Contact.ACCOUNT)), + cursor.getString(cursor.getColumnIndex(Contact.JID)), + }; + db.execSQL("update " + Contact.TABLENAME + + " set " + Contact.JID + " = ? " + + " where " + Contact.ACCOUNT + " = ? " + + " AND " + Contact.JID + " = ?", updateArgs); + } + cursor.close(); + + // Account table + cursor = db.rawQuery("select * from " + Account.TABLENAME, new String[0]); + while(cursor.moveToNext()) { + String newServer; + try { + newServer = Jid.fromParts( + cursor.getString(cursor.getColumnIndex(Account.USERNAME)), + cursor.getString(cursor.getColumnIndex(Account.SERVER)), + "mobile" + ).getDomainpart(); + } catch (InvalidJidException ignored) { + Log.e(Config.LOGTAG, "Failed to migrate Account SERVER " + +cursor.getString(cursor.getColumnIndex(Account.SERVER)) + +": " + ignored +". Skipping..."); + continue; + } + + String updateArgs[] = { + newServer, + cursor.getString(cursor.getColumnIndex(Account.UUID)), + }; + db.execSQL("update " + Account.TABLENAME + + " set " + Account.SERVER + " = ? " + + " where " + Account.UUID + " = ?", updateArgs); + } + cursor.close(); + } } public static synchronized DatabaseBackend getInstance(Context context) { diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index fc40ce75..e111da95 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -85,7 +85,11 @@ public class NotificationService { i.putExtra("messageType", "PEBBLE_ALERT"); i.putExtra("sender", "Conversations"); /* XXX: Shouldn't be hardcoded, e.g., AbstractGenerator.APP_NAME); */ i.putExtra("notificationData", notificationData); - + // notify Pebble App + i.setPackage("com.getpebble.android"); + mXmppConnectionService.sendBroadcast(i); + // notify Gadgetbridge + i.setPackage("nodomain.freeyourgadget.gadgetbridge"); mXmppConnectionService.sendBroadcast(i); } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index ec0b3f92..63d9ba7a 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -41,6 +41,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Hashtable; +import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; @@ -210,6 +211,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager( this); private AvatarService mAvatarService = new AvatarService(this); + private final List<String> mInProgressAvatarFetches = new ArrayList<>(); private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this); private OnConversationUpdate mOnConversationUpdate = null; private Integer convChangedListenerCount = 0; @@ -328,7 +330,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa message.setCounterpart(conversation.getNextCounterpart()); } if (encryption == Message.ENCRYPTION_DECRYPTED) { - getPgpEngine().encrypt(message,callback); + getPgpEngine().encrypt(message, callback); } else { callback.success(message); } @@ -347,7 +349,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } message.setCounterpart(conversation.getNextCounterpart()); message.setType(Message.TYPE_FILE); - message.setStatus(Message.STATUS_OFFERED); String path = getFileBackend().getOriginalPath(uri); if (path!=null) { message.setRelativeFilePath(path); @@ -390,7 +391,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } message.setCounterpart(conversation.getNextCounterpart()); message.setType(Message.TYPE_IMAGE); - message.setStatus(Message.STATUS_OFFERED); new Thread(new Runnable() { @Override @@ -422,6 +422,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa final String action = intent == null ? null : intent.getAction(); if (action != null) { switch (action) { + case ConnectivityManager.CONNECTIVITY_ACTION: + if (hasInternetConnection() && Config.RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE) { + resetAllAttemptCounts(true); + } + break; case ACTION_MERGE_PHONE_CONTACTS: if (mRestoredFromDatabase) { PhoneHelper.loadPhoneContacts(getApplicationContext(), @@ -440,14 +445,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa toggleForegroundService(); break; case ACTION_TRY_AGAIN: - for(Account account : accounts) { - if (account.hasErrorStatus()) { - final XmppConnection connection = account.getXmppConnection(); - if (connection != null) { - connection.resetAttemptCount(); - } - } - } + resetAllAttemptCounts(false); break; case ACTION_DISABLE_ACCOUNT: try { @@ -529,6 +527,18 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return START_STICKY; } + private void resetAllAttemptCounts(boolean reallyAll) { + Log.d(Config.LOGTAG,"resetting all attepmt counts"); + for(Account account : accounts) { + if (account.hasErrorStatus() || reallyAll) { + final XmppConnection connection = account.getXmppConnection(); + if (connection != null) { + connection.resetAttemptCount(); + } + } + } + } + public boolean hasInternetConnection() { ConnectivityManager cm = (ConnectivityManager) getApplicationContext() .getSystemService(Context.CONNECTIVITY_SERVICE); @@ -801,7 +811,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa Presences presences = contact.getPresences(); if ((message.getCounterpart() != null) && (presences.has(message.getCounterpart().getResourcepart()))) { - markMessage(message, Message.STATUS_OFFERED); mJingleConnectionManager.createNewConnection(message); } else { if (presences.size() == 1) { @@ -811,7 +820,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } catch (InvalidJidException e) { return; } - markMessage(message, Message.STATUS_OFFERED); mJingleConnectionManager.createNewConnection(message); } } @@ -1867,6 +1875,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa IqPacket result) { if (result.getType() == IqPacket.TYPE.RESULT) { if (account.setAvatar(avatar.getFilename())) { + getAvatarService().clear(account); databaseBackend.updateAccount(account); } callback.success(avatar); @@ -1893,13 +1902,39 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa fetchAvatar(account, avatar, null); } - public void fetchAvatar(Account account, final Avatar avatar, - final UiCallback<Avatar> callback) { - IqPacket packet = this.mIqGenerator.retrieveAvatar(avatar); + private static String generateFetchKey(Account account, final Avatar avatar) { + return account.getJid().toBareJid()+"_"+avatar.owner+"_"+avatar.sha1sum; + } + + public void fetchAvatar(Account account, final Avatar avatar, final UiCallback<Avatar> callback) { + final String KEY = generateFetchKey(account, avatar); + synchronized(this.mInProgressAvatarFetches) { + if (this.mInProgressAvatarFetches.contains(KEY)) { + return; + } else { + switch (avatar.origin) { + case PEP: + this.mInProgressAvatarFetches.add(KEY); + fetchAvatarPep(account, avatar, callback); + break; + case VCARD: + this.mInProgressAvatarFetches.add(KEY); + fetchAvatarVcard(account, avatar, callback); + break; + } + } + } + } + + private void fetchAvatarPep(Account account, final Avatar avatar, final UiCallback<Avatar> callback) { + IqPacket packet = this.mIqGenerator.retrievePepAvatar(avatar); sendIqPacket(account, packet, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket result) { + synchronized (mInProgressAvatarFetches) { + mInProgressAvatarFetches.remove(generateFetchKey(account, avatar)); + } final String ERROR = account.getJid().toBareJid() + ": fetching avatar for " + avatar.owner + " failed "; if (result.getType() == IqPacket.TYPE.RESULT) { @@ -1916,7 +1951,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } else { Contact contact = account.getRoster() .getContact(avatar.owner); - contact.setAvatar(avatar.getFilename()); + contact.setAvatar(avatar); getAvatarService().clear(contact); updateConversationUi(); updateRosterUi(); @@ -1925,8 +1960,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa callback.success(avatar); } Log.d(Config.LOGTAG, account.getJid().toBareJid() - + ": succesfully fetched avatar for " - + avatar.owner); + + ": succesfuly fetched pep avatar for " + avatar.owner); return; } } else { @@ -1949,8 +1983,38 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa }); } - public void checkForAvatar(Account account, - final UiCallback<Avatar> callback) { + private void fetchAvatarVcard(final Account account, final Avatar avatar, final UiCallback<Avatar> callback) { + IqPacket packet = this.mIqGenerator.retrieveVcardAvatar(avatar); + this.sendIqPacket(account,packet,new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + synchronized(mInProgressAvatarFetches) { + mInProgressAvatarFetches.remove(generateFetchKey(account,avatar)); + } + if (packet.getType() == IqPacket.TYPE.RESULT) { + Element vCard = packet.findChild("vCard","vcard-temp"); + Element photo = vCard != null ? vCard.findChild("PHOTO") : null; + Element binval = photo != null ? photo.findChild("BINVAL") : null; + String image = binval != null ? binval.getContent() : null; + if (image != null) { + avatar.image = image; + if (getFileBackend().save(avatar)) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + + ": successfully fetched vCard avatar for " + avatar.owner); + Contact contact = account.getRoster() + .getContact(avatar.owner); + contact.setAvatar(avatar); + getAvatarService().clear(contact); + updateConversationUi(); + updateRosterUi(); + } + } + } + } + }); + } + + public void checkForAvatar(Account account, final UiCallback<Avatar> callback) { IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null); this.sendIqPacket(account, packet, new OnIqPacketReceived() { @@ -1972,7 +2036,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa getAvatarService().clear(account); callback.success(avatar); } else { - fetchAvatar(account, avatar, callback); + fetchAvatarPep(account, avatar, callback); } return; } @@ -2008,6 +2072,16 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa disconnect(account, force); } if (!account.isOptionSet(Account.OPTION_DISABLED)) { + + synchronized (this.mInProgressAvatarFetches) { + for(Iterator<String> iterator = this.mInProgressAvatarFetches.iterator(); iterator.hasNext();) { + final String KEY = iterator.next(); + if (KEY.startsWith(account.getJid().toBareJid()+"_")) { + iterator.remove(); + } + } + } + if (account.getXmppConnection() == null) { account.setXmppConnection(createConnection(account)); } @@ -2031,6 +2105,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public void invite(Conversation conversation, Jid contact) { + Log.d(Config.LOGTAG,conversation.getAccount().getJid().toBareJid()+": inviting "+contact+" to "+conversation.getJid().toBareJid()); MessagePacket packet = mMessageGenerator.invite(conversation, contact); sendMessagePacket(conversation.getAccount(), packet); } diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index 8c4f6eaf..07b8819d 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -385,6 +385,10 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers @Override void onBackendConnected() { + if (mPendingConferenceInvite != null) { + mPendingConferenceInvite.execute(this); + mPendingConferenceInvite = null; + } if (getIntent().getAction().equals(ACTION_VIEW_MUC)) { this.uuid = getIntent().getExtras().getString("uuid"); } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index aec755fc..1b5e5178 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -60,11 +60,11 @@ public class ConversationActivity extends XmppActivity public static final int REQUEST_SEND_MESSAGE = 0x0201; public static final int REQUEST_DECRYPT_PGP = 0x0202; public static final int REQUEST_ENCRYPT_MESSAGE = 0x0207; - private static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x0301; - private static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302; - private static final int ATTACHMENT_CHOICE_CHOOSE_FILE = 0x0303; - private static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0304; - private static final int ATTACHMENT_CHOICE_LOCATION = 0x0305; + public static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x0301; + public static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302; + public static final int ATTACHMENT_CHOICE_CHOOSE_FILE = 0x0303; + public static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0304; + public static final int ATTACHMENT_CHOICE_LOCATION = 0x0305; private static final String STATE_OPEN_CONVERSATION = "state_open_conversation"; private static final String STATE_PANEL_OPEN = "state_panel_open"; private static final String STATE_PENDING_URI = "state_pending_uri"; @@ -398,61 +398,88 @@ public class ConversationActivity extends XmppActivity } private void selectPresenceToAttachFile(final int attachmentChoice, final int encryption) { - if (attachmentChoice == ATTACHMENT_CHOICE_LOCATION && encryption != Message.ENCRYPTION_OTR) { - getSelectedConversation().setNextCounterpart(null); - Intent intent = new Intent("eu.siacs.conversations.location.request"); - startActivityForResult(intent,attachmentChoice); - } else { - selectPresence(getSelectedConversation(), new OnPresenceSelected() { + final OnPresenceSelected callback = new OnPresenceSelected() { - @Override - public void onPresenceSelected() { - Intent intent = new Intent(); - boolean chooser = false; - switch (attachmentChoice) { - case ATTACHMENT_CHOICE_CHOOSE_IMAGE: - intent.setAction(Intent.ACTION_GET_CONTENT); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE,true); - } - intent.setType("image/*"); - chooser = true; - break; - case ATTACHMENT_CHOICE_TAKE_PHOTO: - Uri uri = xmppConnectionService.getFileBackend().getTakePhotoUri(); - intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); - intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); - mPendingImageUris.clear(); - mPendingImageUris.add(uri); - break; - case ATTACHMENT_CHOICE_CHOOSE_FILE: - chooser = true; - intent.setType("*/*"); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setAction(Intent.ACTION_GET_CONTENT); - break; - case ATTACHMENT_CHOICE_RECORD_VOICE: - intent.setAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION); - break; - case ATTACHMENT_CHOICE_LOCATION: - intent.setAction("eu.siacs.conversations.location.request"); - break; - } - if (intent.resolveActivity(getPackageManager()) != null) { - if (chooser) { - startActivityForResult( - Intent.createChooser(intent, getString(R.string.perform_action_with)), - attachmentChoice); - } else { - startActivityForResult(intent, attachmentChoice); + @Override + public void onPresenceSelected() { + Intent intent = new Intent(); + boolean chooser = false; + String fallbackPackageId = null; + switch (attachmentChoice) { + case ATTACHMENT_CHOICE_CHOOSE_IMAGE: + intent.setAction(Intent.ACTION_GET_CONTENT); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE,true); } + intent.setType("image/*"); + chooser = true; + break; + case ATTACHMENT_CHOICE_TAKE_PHOTO: + Uri uri = xmppConnectionService.getFileBackend().getTakePhotoUri(); + intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); + intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); + mPendingImageUris.clear(); + mPendingImageUris.add(uri); + break; + case ATTACHMENT_CHOICE_CHOOSE_FILE: + chooser = true; + intent.setType("*/*"); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setAction(Intent.ACTION_GET_CONTENT); + break; + case ATTACHMENT_CHOICE_RECORD_VOICE: + intent.setAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION); + fallbackPackageId = "eu.siacs.conversations.voicerecorder"; + break; + case ATTACHMENT_CHOICE_LOCATION: + intent.setAction("eu.siacs.conversations.location.request"); + fallbackPackageId = "eu.siacs.conversations.sharelocation"; + break; + } + if (intent.resolveActivity(getPackageManager()) != null) { + if (chooser) { + startActivityForResult( + Intent.createChooser(intent, getString(R.string.perform_action_with)), + attachmentChoice); + } else { + startActivityForResult(intent, attachmentChoice); } + } else if (fallbackPackageId != null) { + startActivity(getInstallApkIntent(fallbackPackageId)); } - }); + } + }; + if (attachmentChoice == ATTACHMENT_CHOICE_LOCATION && encryption != Message.ENCRYPTION_OTR) { + getSelectedConversation().setNextCounterpart(null); + callback.onPresenceSelected(); + } else { + selectPresence(getSelectedConversation(),callback); + } + } + + private Intent getInstallApkIntent(final String packageId) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse("market://details?id="+packageId)); + if (intent.resolveActivity(getPackageManager()) != null) { + return intent; + } else { + intent.setData(Uri.parse("http://play.google.com/store/apps/details?id="+packageId)); + return intent; } } - private void attachFile(final int attachmentChoice) { + public void attachFile(final int attachmentChoice) { + switch (attachmentChoice) { + case ATTACHMENT_CHOICE_LOCATION: + getPreferences().edit().putString("recently_used_quick_action","location").apply(); + break; + case ATTACHMENT_CHOICE_RECORD_VOICE: + getPreferences().edit().putString("recently_used_quick_action","voice").apply(); + break; + case ATTACHMENT_CHOICE_TAKE_PHOTO: + getPreferences().edit().putString("recently_used_quick_action","photo").apply(); + break; + } final Conversation conversation = getSelectedConversation(); final int encryption = conversation.getNextEncryption(forceEncryption()); if (encryption == Message.ENCRYPTION_PGP) { @@ -875,6 +902,12 @@ public class ConversationActivity extends XmppActivity void onBackendConnected() { this.xmppConnectionService.getNotificationService().setIsInForeground(true); updateConversationList(); + + if (mPendingConferenceInvite != null) { + mPendingConferenceInvite.execute(this); + mPendingConferenceInvite = null; + } + if (xmppConnectionService.getAccounts().size() == 0) { if (!mRedirected) { this.mRedirected = true; @@ -901,9 +934,7 @@ public class ConversationActivity extends XmppActivity } this.mConversationFragment.reInit(getSelectedConversation()); mOpenConverstaion = null; - } else if (getSelectedConversation() != null) { - this.mConversationFragment.reInit(getSelectedConversation()); - } else { + } else if (getSelectedConversation() == null) { showConversationsOverview(); mPendingImageUris.clear(); mPendingFileUris.clear(); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 5b1e9b4d..20fc1750 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -59,6 +59,7 @@ import eu.siacs.conversations.ui.adapter.MessageAdapter; import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureClicked; import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureLongClicked; import eu.siacs.conversations.utils.GeoHelper; +import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xmpp.chatstate.ChatState; import eu.siacs.conversations.xmpp.jid.Jid; @@ -117,16 +118,37 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } + private int getIndexOf(String uuid, List<Message> messages) { + if (uuid == null) { + return 0; + } + for(int i = 0; i < messages.size(); ++i) { + if (uuid.equals(messages.get(i).getUuid())) { + return i; + } else { + Message next = messages.get(i); + while(next != null && next.wasMergedIntoPrevious()) { + if (uuid.equals(next.getUuid())) { + return i; + } + next = next.next(); + } + + } + } + return 0; + } + @Override public void onScroll(AbsListView view, int firstVisibleItem, - int visibleItemCount, int totalItemCount) { + int visibleItemCount, int totalItemCount) { synchronized (ConversationFragment.this.messageList) { if (firstVisibleItem < 5 && messagesLoaded && messageList.size() > 0) { long timestamp = ConversationFragment.this.messageList.get(0).getTimeSent(); messagesLoaded = false; activity.xmppConnectionService.loadMoreMessages(conversation, timestamp, new XmppConnectionService.OnMoreMessagesLoaded() { @Override - public void onMoreMessagesLoaded(final int count, Conversation conversation) { + public void onMoreMessagesLoaded(final int c, Conversation conversation) { if (ConversationFragment.this.conversation != conversation) { return; } @@ -134,29 +156,18 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa @Override public void run() { final int oldPosition = messagesView.getFirstVisiblePosition(); + Message message = messageList.get(oldPosition); + String uuid = message != null ? message.getUuid() : null; View v = messagesView.getChildAt(0); final int pxOffset = (v == null) ? 0 : v.getTop(); ConversationFragment.this.conversation.populateWithMessages(ConversationFragment.this.messageList); updateStatusMessages(); messageListAdapter.notifyDataSetChanged(); - if (count != 0) { - final int newPosition = oldPosition + count; - int offset = 0; - try { - Message tmpMessage = messageList.get(newPosition); - - while(tmpMessage.wasMergedIntoPrevious()) { - offset++; - tmpMessage = tmpMessage.prev(); - } - } catch (final IndexOutOfBoundsException ignored) { - - } - messagesView.setSelectionFromTop(newPosition - offset, pxOffset); - messagesLoaded = true; - if (messageLoaderToast != null) { - messageLoaderToast.cancel(); - } + int pos = getIndexOf(uuid,messageList); + messagesView.setSelectionFromTop(pos, pxOffset); + messagesLoaded = true; + if (messageLoaderToast != null) { + messageLoaderToast.cancel(); } } }); @@ -174,7 +185,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (ConversationFragment.this.conversation != conversation) { return; } - messageLoaderToast = Toast.makeText(activity,resId,Toast.LENGTH_LONG); + messageLoaderToast = Toast.makeText(activity, resId, Toast.LENGTH_LONG); messageLoaderToast.show(); } }); @@ -208,7 +219,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa @Override public void onClick(View v) { - activity.verifyOtrSessionDialog(conversation,v); + activity.verifyOtrSessionDialog(conversation, v); } }; private ConcurrentLinkedQueue<Message> mEncryptedMessages = new ConcurrentLinkedQueue<>(); @@ -219,7 +230,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_SEND) { InputMethodManager imm = (InputMethodManager) v.getContext() - .getSystemService(Context.INPUT_METHOD_SERVICE); + .getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(v.getWindowToken(), 0); sendMessage(); return true; @@ -232,15 +243,39 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa @Override public void onClick(View v) { - sendMessage(); + Object tag = v.getTag(); + if (tag instanceof SendButtonAction) { + SendButtonAction action = (SendButtonAction) tag; + switch (action) { + case TAKE_PHOTO: + activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_TAKE_PHOTO); + break; + case SEND_LOCATION: + activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_LOCATION); + break; + case RECORD_VOICE: + activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_RECORD_VOICE); + break; + case CANCEL: + if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { + conversation.setNextCounterpart(null); + updateChatMsgHint(); + updateSendButton(); + } + break; + default: + sendMessage(); + } + } else { + sendMessage(); + } } }; private OnClickListener clickToMuc = new OnClickListener() { @Override public void onClick(View v) { - Intent intent = new Intent(getActivity(), - ConferenceDetailsActivity.class); + Intent intent = new Intent(getActivity(), ConferenceDetailsActivity.class); intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC); intent.putExtra("uuid", conversation.getUuid()); startActivity(intent); @@ -253,16 +288,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (this.conversation == null) { return; } - if (mEditMessage.getText().length() < 1) { - if (this.conversation.getMode() == Conversation.MODE_MULTI) { - conversation.setNextCounterpart(null); - updateChatMsgHint(); - } - return; - } Message message = new Message(conversation, mEditMessage.getText() .toString(), conversation.getNextEncryption(activity - .forceEncryption())); + .forceEncryption())); if (conversation.getMode() == Conversation.MODE_MULTI) { if (conversation.getNextCounterpart() != null) { message.setCounterpart(conversation.getNextCounterpart()); @@ -282,13 +310,13 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (conversation.getMode() == Conversation.MODE_MULTI && conversation.getNextCounterpart() != null) { this.mEditMessage.setHint(getString( - R.string.send_private_message_to, - conversation.getNextCounterpart().getResourcepart())); + R.string.send_private_message_to, + conversation.getNextCounterpart().getResourcepart())); } else { switch (conversation.getNextEncryption(activity.forceEncryption())) { case Message.ENCRYPTION_NONE: mEditMessage - .setHint(getString(R.string.send_plain_text_message)); + .setHint(getString(R.string.send_plain_text_message)); break; case Message.ENCRYPTION_OTR: mEditMessage.setHint(getString(R.string.send_otr_message)); @@ -304,7 +332,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } private void setupIme() { - if (((ConversationActivity)getActivity()).usingEnterKey()) { + if (((ConversationActivity) getActivity()).usingEnterKey()) { mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE)); } else { mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE); @@ -313,8 +341,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa @Override public View onCreateView(final LayoutInflater inflater, - ViewGroup container, Bundle savedInstanceState) { - final View view = inflater.inflate(R.layout.fragment_conversation,container, false); + ViewGroup container, Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.fragment_conversation, container, false); view.setOnClickListener(null); mEditMessage = (EditMessage) view.findViewById(R.id.textinput); setupIme(); @@ -365,21 +393,21 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } }); messageListAdapter - .setOnContactPictureLongClicked(new OnContactPictureLongClicked() { + .setOnContactPictureLongClicked(new OnContactPictureLongClicked() { - @Override - public void onContactPictureLongClicked(Message message) { - if (message.getStatus() <= Message.STATUS_RECEIVED) { - if (message.getConversation().getMode() == Conversation.MODE_MULTI) { - if (message.getCounterpart() != null) { - privateMessageWith(message.getCounterpart()); + @Override + public void onContactPictureLongClicked(Message message) { + if (message.getStatus() <= Message.STATUS_RECEIVED) { + if (message.getConversation().getMode() == Conversation.MODE_MULTI) { + if (message.getCounterpart() != null) { + privateMessageWith(message.getCounterpart()); + } } + } else { + activity.showQrCode(); } - } else { - activity.showQrCode(); } - } - }); + }); messagesView.setAdapter(messageListAdapter); registerForContextMenu(messagesView); @@ -389,7 +417,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa @Override public void onCreateContextMenu(ContextMenu menu, View v, - ContextMenuInfo menuInfo) { + ContextMenuInfo menuInfo) { synchronized (this.messageList) { super.onCreateContextMenu(menu, v, menuInfo); AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo; @@ -416,7 +444,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if ((m.getType() == Message.TYPE_TEXT || m.getType() == Message.TYPE_PRIVATE || m.getDownloadable() != null) - && (!GeoHelper.isGeoUri(m.getBody()))) { + && (!GeoHelper.isGeoUri(m.getBody()))) { shareWith.setVisible(false); } if (m.getStatus() != Message.STATUS_SEND_FAILED) { @@ -425,17 +453,17 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (((m.getType() != Message.TYPE_IMAGE && m.getDownloadable() == null) || m.getImageParams().url == null) && !GeoHelper.isGeoUri(m.getBody())) { copyUrl.setVisible(false); - } + } if (m.getType() != Message.TYPE_TEXT || m.getDownloadable() != null || !m.bodyContainsDownloadable()) { downloadImage.setVisible(false); - } + } if (!((m.getDownloadable() != null && !(m.getDownloadable() instanceof DownloadablePlaceholder)) - || (m.isFileOrImage() && (m.getStatus() == Message.STATUS_WAITING - || m.getStatus() == Message.STATUS_OFFERED)))) { + || (m.isFileOrImage() && (m.getStatus() == Message.STATUS_WAITING + || m.getStatus() == Message.STATUS_OFFERED)))) { cancelTransmission.setVisible(false); - } + } } } @@ -483,12 +511,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } shareIntent.setType(mime); } - activity.startActivity(Intent.createChooser(shareIntent,getText(R.string.share_with))); + activity.startActivity(Intent.createChooser(shareIntent, getText(R.string.share_with))); } private void copyText(Message message) { if (activity.copyTextToClipboard(message.getMergedBody(), - R.string.message_text)) { + R.string.message_text)) { Toast.makeText(activity, R.string.message_copied_to_clipboard, Toast.LENGTH_SHORT).show(); } @@ -498,7 +526,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) { DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message); if (!file.exists()) { - Toast.makeText(activity,R.string.file_deleted,Toast.LENGTH_SHORT).show(); + Toast.makeText(activity, R.string.file_deleted, Toast.LENGTH_SHORT).show(); message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_DELETED)); return; } @@ -519,20 +547,20 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (activity.copyTextToClipboard(url, resId)) { Toast.makeText(activity, R.string.url_copied_to_clipboard, Toast.LENGTH_SHORT).show(); - } + } } private void downloadImage(Message message) { activity.xmppConnectionService.getHttpConnectionManager() - .createNewConnection(message); + .createNewConnection(message); } private void cancelTransmission(Message message) { Downloadable downloadable = message.getDownloadable(); - if (downloadable!=null) { + if (downloadable != null) { downloadable.cancel(); } else { - activity.xmppConnectionService.markMessage(message,Message.STATUS_SEND_FAILED); + activity.xmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); } } @@ -540,6 +568,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa this.mEditMessage.setText(""); this.conversation.setNextCounterpart(counterpart); updateChatMsgHint(); + updateSendButton(); } protected void highlightInConference(String nick) { @@ -548,9 +577,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa mEditMessage.getText().insert(0, nick + ": "); } else { if (mEditMessage.getText().charAt( - mEditMessage.getSelectionStart() - 1) != ' ') { + mEditMessage.getSelectionStart() - 1) != ' ') { nick = " " + nick; - } + } mEditMessage.getText().insert(mEditMessage.getSelectionStart(), nick + " "); } @@ -563,7 +592,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (this.conversation != null) { final String msg = mEditMessage.getText().toString(); this.conversation.setNextMessage(msg); - updateChatState(this.conversation,msg); + updateChatState(this.conversation, msg); } } @@ -586,7 +615,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa final String msg = mEditMessage.getText().toString(); this.conversation.setNextMessage(msg); if (this.conversation != conversation) { - updateChatState(this.conversation,msg); + updateChatState(this.conversation, msg); } this.conversation.trim(); } @@ -632,7 +661,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa @Override public void onClick(View v) { - final Contact contact = conversation == null ? null :conversation.getContact(); + final Contact contact = conversation == null ? null : conversation.getContact(); if (contact != null) { activity.xmppConnectionService.createContact(contact); activity.switchToContactDetails(contact); @@ -655,7 +684,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT); intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString()); intent.putExtra("account", conversation.getAccount().getJid().toBareJid().toString()); - intent.putExtra("mode",VerifyOTRActivity.MODE_ANSWER_QUESTION); + intent.putExtra("mode", VerifyOTRActivity.MODE_ANSWER_QUESTION); startActivity(intent); } }; @@ -665,11 +694,11 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa final Contact contact = conversation.getContact(); final int mode = conversation.getMode(); if (conversation.isBlocked()) { - showSnackbar(R.string.contact_blocked, R.string.unblock,this.mUnblockClickListener); + showSnackbar(R.string.contact_blocked, R.string.unblock, this.mUnblockClickListener); } else if (!contact.showInRoster() && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { - showSnackbar(R.string.contact_added_you, R.string.add_back,this.mAddBackClickListener); + showSnackbar(R.string.contact_added_you, R.string.add_back, this.mAddBackClickListener); } else if (mode == Conversation.MODE_MULTI - &&!conversation.getMucOptions().online() + && !conversation.getMucOptions().online() && account.getStatus() == Account.State.ONLINE) { switch (conversation.getMucOptions().getError()) { case MucOptions.ERROR_NICK_IN_USE: @@ -693,18 +722,18 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa default: break; } - } else if (askForPassphraseIntent != null ) { - showSnackbar(R.string.openpgp_messages_found,R.string.decrypt, clickToDecryptListener); + } else if (askForPassphraseIntent != null) { + showSnackbar(R.string.openpgp_messages_found, R.string.decrypt, clickToDecryptListener); } else if (mode == Conversation.MODE_SINGLE && conversation.smpRequested()) { - showSnackbar(R.string.smp_requested, R.string.verify,this.mAnswerSmpClickListener); + showSnackbar(R.string.smp_requested, R.string.verify, this.mAnswerSmpClickListener); } else if (mode == Conversation.MODE_SINGLE - &&conversation.hasValidOtrSession() + && conversation.hasValidOtrSession() && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!conversation.isOtrFingerprintVerified())) { showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify, clickToVerify); } else if (conversation.isMuted()) { - showSnackbar(R.string.notifications_disabled, R.string.enable,this.mUnmuteClickListener); + showSnackbar(R.string.notifications_disabled, R.string.enable, this.mUnmuteClickListener); } else { hideSnackbar(); } @@ -722,12 +751,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa for (final Message message : this.messageList) { if (message.getEncryption() == Message.ENCRYPTION_PGP && (message.getStatus() == Message.STATUS_RECEIVED || message - .getStatus() >= Message.STATUS_SEND) + .getStatus() >= Message.STATUS_SEND) && message.getDownloadable() == null) { if (!mEncryptedMessages.contains(message)) { mEncryptedMessages.add(message); } - } + } } decryptNext(); updateStatusMessages(); @@ -790,53 +819,128 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa updateChatMsgHint(); } - public void updateSendButton() { - Conversation c = this.conversation; - if (activity.useSendButtonToIndicateStatus() && c != null - && c.getAccount().getStatus() == Account.State.ONLINE) { - if (c.getMode() == Conversation.MODE_SINGLE) { - switch (c.getContact().getMostAvailableStatus()) { + enum SendButtonAction {TEXT, TAKE_PHOTO, SEND_LOCATION, RECORD_VOICE, CANCEL} + + private int getSendButtonImageResource(SendButtonAction action, int status) { + switch (action) { + case TEXT: + switch (status) { case Presences.CHAT: - this.mSendButton - .setImageResource(R.drawable.ic_action_send_now_online); - break; case Presences.ONLINE: - this.mSendButton - .setImageResource(R.drawable.ic_action_send_now_online); - break; + return R.drawable.ic_send_text_online; case Presences.AWAY: - this.mSendButton - .setImageResource(R.drawable.ic_action_send_now_away); - break; + return R.drawable.ic_send_text_away; + case Presences.XA: + case Presences.DND: + return R.drawable.ic_send_text_dnd; + default: + return R.drawable.ic_send_text_offline; + } + case TAKE_PHOTO: + switch (status) { + case Presences.CHAT: + case Presences.ONLINE: + return R.drawable.ic_send_photo_online; + case Presences.AWAY: + return R.drawable.ic_send_photo_away; + case Presences.XA: + case Presences.DND: + return R.drawable.ic_send_photo_dnd; + default: + return R.drawable.ic_send_photo_offline; + } + case RECORD_VOICE: + switch (status) { + case Presences.CHAT: + case Presences.ONLINE: + return R.drawable.ic_send_voice_online; + case Presences.AWAY: + return R.drawable.ic_send_voice_away; case Presences.XA: - this.mSendButton - .setImageResource(R.drawable.ic_action_send_now_away); - break; case Presences.DND: - this.mSendButton - .setImageResource(R.drawable.ic_action_send_now_dnd); + return R.drawable.ic_send_voice_dnd; + default: + return R.drawable.ic_send_voice_offline; + } + case SEND_LOCATION: + switch (status) { + case Presences.CHAT: + case Presences.ONLINE: + return R.drawable.ic_send_location_online; + case Presences.AWAY: + return R.drawable.ic_send_location_away; + case Presences.XA: + case Presences.DND: + return R.drawable.ic_send_location_dnd; + default: + return R.drawable.ic_send_location_offline; + } + case CANCEL: + switch (status) { + case Presences.CHAT: + case Presences.ONLINE: + return R.drawable.ic_send_cancel_online; + case Presences.AWAY: + return R.drawable.ic_send_cancel_away; + case Presences.XA: + case Presences.DND: + return R.drawable.ic_send_cancel_dnd; + default: + return R.drawable.ic_send_cancel_offline; + } + } + return R.drawable.ic_send_text_offline; + } + + public void updateSendButton() { + final Conversation c = this.conversation; + final SendButtonAction action; + final int status; + final boolean empty = this.mEditMessage == null || this.mEditMessage.getText().length() == 0; + if (c.getMode() == Conversation.MODE_MULTI) { + if (empty && c.getNextCounterpart() != null) { + action = SendButtonAction.CANCEL; + } else { + action = SendButtonAction.TEXT; + } + } else { + if (empty) { + String setting = activity.getPreferences().getString("quick_action","recent"); + if (!setting.equals("none") && UIHelper.receivedLocationQuestion(conversation.getLatestMessage())) { + setting = "location"; + } else if (setting.equals("recent")) { + setting = activity.getPreferences().getString("recently_used_quick_action","text"); + } + switch (setting) { + case "photo": + action = SendButtonAction.TAKE_PHOTO; + break; + case "location": + action = SendButtonAction.SEND_LOCATION; + break; + case "voice": + action = SendButtonAction.RECORD_VOICE; break; default: - this.mSendButton - .setImageResource(R.drawable.ic_action_send_now_offline); + action = SendButtonAction.TEXT; break; } - } else if (c.getMode() == Conversation.MODE_MULTI) { - if (c.getMucOptions().online()) { - this.mSendButton - .setImageResource(R.drawable.ic_action_send_now_online); - } else { - this.mSendButton - .setImageResource(R.drawable.ic_action_send_now_offline); - } } else { - this.mSendButton - .setImageResource(R.drawable.ic_action_send_now_offline); + action = SendButtonAction.TEXT; + } + } + if (activity.useSendButtonToIndicateStatus() && c != null + && c.getAccount().getStatus() == Account.State.ONLINE) { + if (c.getMode() == Conversation.MODE_SINGLE) { + status = c.getContact().getMostAvailableStatus(); + } else { + status = c.getMucOptions().online() ? Presences.ONLINE : Presences.OFFLINE; } } else { - this.mSendButton - .setImageResource(R.drawable.ic_action_send_now_offline); + status = Presences.OFFLINE; } + this.mSendButton.setTag(action); + this.mSendButton.setImageResource(getSendButtonImageResource(action, status)); } protected void updateStatusMessages() { @@ -865,7 +969,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } protected void showSnackbar(final int message, final int action, - final OnClickListener clickListener) { + final OnClickListener clickListener) { snackbar.setVisibility(View.VISIBLE); snackbar.setOnClickListener(null); snackbarMessage.setText(message); @@ -897,7 +1001,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa @Override public void userInputRequried(PendingIntent pi, - Contact contact) { + Contact contact) { activity.runIntent( pi, ConversationActivity.REQUEST_ENCRYPT_MESSAGE); @@ -921,11 +1025,11 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa @Override public void onClick(DialogInterface dialog, - int which) { + int which) { conversation - .setNextEncryption(Message.ENCRYPTION_NONE); + .setNextEncryption(Message.ENCRYPTION_NONE); xmppService.databaseBackend - .updateConversation(conversation); + .updateConversation(conversation); message.setEncryption(Message.ENCRYPTION_NONE); xmppService.sendMessage(message); messageSent(); @@ -936,9 +1040,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (conversation.getMucOptions().pgpKeysInUse()) { if (!conversation.getMucOptions().everybodyHasKeys()) { Toast warning = Toast - .makeText(getActivity(), - R.string.missing_public_keys, - Toast.LENGTH_LONG); + .makeText(getActivity(), + R.string.missing_public_keys, + Toast.LENGTH_LONG); warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0); warning.show(); } @@ -950,12 +1054,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa @Override public void onClick(DialogInterface dialog, - int which) { + int which) { conversation - .setNextEncryption(Message.ENCRYPTION_NONE); + .setNextEncryption(Message.ENCRYPTION_NONE); message.setEncryption(Message.ENCRYPTION_NONE); xmppService.databaseBackend - .updateConversation(conversation); + .updateConversation(conversation); xmppService.sendMessage(message); messageSent(); } @@ -968,7 +1072,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } public void showNoPGPKeyDialog(boolean plural, - DialogInterface.OnClickListener listener) { + DialogInterface.OnClickListener listener) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setIconAttribute(android.R.attr.alertDialogIcon); if (plural) { @@ -1026,6 +1130,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (status == Account.State.ONLINE && conversation.setOutgoingChatState(ChatState.COMPOSING)) { activity.xmppConnectionService.sendChatState(conversation); } + updateSendButton(); } @Override @@ -1042,6 +1147,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (status == Account.State.ONLINE && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) { activity.xmppConnectionService.sendChatState(conversation); } + updateSendButton(); } } diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 7aa7b1c2..931a1a2f 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -223,7 +223,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate if (avatar != null) { intent = new Intent(getApplicationContext(), StartConversationActivity.class); - intent.putExtra("init",true); + if (xmppConnectionService != null && xmppConnectionService.getAccounts().size() == 1) { + intent.putExtra("init", true); + } } else { intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class); diff --git a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java index e8ab8dae..4333dbdb 100644 --- a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java @@ -116,7 +116,9 @@ public class PublishProfilePictureActivity extends XmppActivity { if (mInitialAccountSetup) { Intent intent = new Intent(getApplicationContext(), StartConversationActivity.class); - intent.putExtra("init",true); + if (xmppConnectionService != null && xmppConnectionService.getAccounts().size() == 1) { + intent.putExtra("init", true); + } startActivity(intent); } finish(); diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index a556b8b7..7863ff94 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -65,6 +65,7 @@ import eu.siacs.conversations.ui.adapter.KnownHostsAdapter; import eu.siacs.conversations.ui.adapter.ListItemAdapter; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; +import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; @@ -757,14 +758,16 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU } else { activity.contact_context_id = acmi.position; final Blockable contact = (Contact) activity.contacts.get(acmi.position); - final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock); - if (blockUnblockItem != null) { + XmppConnection xmpp = contact.getAccount().getXmppConnection(); + if (xmpp != null && xmpp.getFeatures().blocking()) { if (contact.isBlocked()) { blockUnblockItem.setTitle(R.string.unblock_contact); } else { blockUnblockItem.setTitle(R.string.block_contact); } + } else { + blockUnblockItem.setVisible(false); } } } diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 392e57a7..934c696f 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -113,6 +113,8 @@ public abstract class XmppActivity extends Activity { } }; + protected ConferenceInvite mPendingConferenceInvite = null; + protected void refreshUi() { final long diff = SystemClock.elapsedRealtime() - mLastUiRefresh; @@ -367,7 +369,7 @@ public abstract class XmppActivity extends Activity { } public void highlightInMuc(Conversation conversation, String nick) { - switchToConversation(conversation,null,nick,false); + switchToConversation(conversation, null, nick, false); } private void switchToConversation(Conversation conversation, String text, String nick, boolean newTask) { @@ -435,7 +437,7 @@ public abstract class XmppActivity extends Activity { @Override public void userInputRequried(PendingIntent pi, - Account account) { + Account account) { try { startIntentSenderForResult(pi.getIntentSender(), REQUEST_ANNOUNCE_PGP, null, 0, 0, 0); @@ -446,13 +448,13 @@ public abstract class XmppActivity extends Activity { @Override public void success(Account account) { xmppConnectionService.databaseBackend - .updateAccount(account); + .updateAccount(account); xmppConnectionService.sendPresence(account); if (conversation != null) { conversation - .setNextEncryption(Message.ENCRYPTION_PGP); + .setNextEncryption(Message.ENCRYPTION_PGP); xmppConnectionService.databaseBackend - .updateConversation(conversation); + .updateConversation(conversation); } } @@ -665,32 +667,11 @@ public abstract class XmppActivity extends Activity { protected void onActivityResult(int requestCode, int resultCode, final Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (requestCode == REQUEST_INVITE_TO_CONVERSATION - && resultCode == RESULT_OK) { - try { - String conversationUuid = data.getStringExtra("conversation"); - Conversation conversation = xmppConnectionService - .findConversationByUuid(conversationUuid); - List<Jid> jids = new ArrayList<Jid>(); - if (data.getBooleanExtra("multiple", false)) { - String[] toAdd = data.getStringArrayExtra("contacts"); - for (String item : toAdd) { - jids.add(Jid.fromString(item)); - } - } else { - jids.add(Jid.fromString(data.getStringExtra("contact"))); - } - - if (conversation.getMode() == Conversation.MODE_MULTI) { - for (Jid jid : jids) { - xmppConnectionService.invite(conversation, jid); - } - } else { - jids.add(conversation.getJid().toBareJid()); - xmppConnectionService.createAdhocConference(conversation.getAccount(), jids, adhocCallback); - } - } catch (final InvalidJidException ignored) { - + if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) { + mPendingConferenceInvite = ConferenceInvite.parse(data); + if (xmppConnectionServiceBound && mPendingConferenceInvite != null) { + mPendingConferenceInvite.execute(this); + mPendingConferenceInvite = null; } } } @@ -855,6 +836,48 @@ public abstract class XmppActivity extends Activity { } } + public static class ConferenceInvite { + private String uuid; + private List<Jid> jids = new ArrayList<>(); + + public static ConferenceInvite parse(Intent data) { + ConferenceInvite invite = new ConferenceInvite(); + invite.uuid = data.getStringExtra("conversation"); + if (invite.uuid == null) { + return null; + } + try { + if (data.getBooleanExtra("multiple", false)) { + String[] toAdd = data.getStringArrayExtra("contacts"); + for (String item : toAdd) { + invite.jids.add(Jid.fromString(item)); + } + } else { + invite.jids.add(Jid.fromString(data.getStringExtra("contact"))); + } + } catch (final InvalidJidException ignored) { + return null; + } + return invite; + } + + public void execute(XmppActivity activity) { + XmppConnectionService service = activity.xmppConnectionService; + Conversation conversation = service.findConversationByUuid(this.uuid); + if (conversation == null) { + return; + } + if (conversation.getMode() == Conversation.MODE_MULTI) { + for (Jid jid : jids) { + service.invite(conversation, jid); + } + } else { + jids.add(conversation.getJid().toBareJid()); + service.createAdhocConference(conversation.getAccount(), jids, activity.adhocCallback); + } + } + } + public AvatarService avatarService() { return xmppConnectionService.getAvatarService(); } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index da92fb18..29dfced2 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -7,10 +7,11 @@ import android.graphics.Typeface; import android.net.Uri; import android.text.Spannable; import android.text.SpannableString; +import android.text.Spanned; import android.text.style.ForegroundColorSpan; +import android.text.style.RelativeSizeSpan; import android.text.style.StyleSpan; import android.util.DisplayMetrics; -import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; @@ -24,7 +25,6 @@ import android.widget.Toast; 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.Contact; @@ -42,7 +42,6 @@ public class MessageAdapter extends ArrayAdapter<Message> { private static final int SENT = 0; private static final int RECEIVED = 1; private static final int STATUS = 2; - private static final int NULL = 3; private ConversationActivity activity; @@ -77,14 +76,12 @@ public class MessageAdapter extends ArrayAdapter<Message> { @Override public int getViewTypeCount() { - return 4; + return 3; } @Override public int getItemViewType(int position) { - if (getItem(position).wasMergedIntoPrevious()) { - return NULL; - } else if (getItem(position).getType() == Message.TYPE_STATUS) { + if (getItem(position).getType() == Message.TYPE_STATUS) { return STATUS; } else if (getItem(position).getStatus() <= Message.STATUS_RECEIVED) { return RECEIVED; @@ -207,22 +204,42 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.VISIBLE); viewHolder.messageBody.setText(getContext().getString( - R.string.decryption_failed)); + R.string.decryption_failed)); viewHolder.messageBody.setTextColor(activity.getWarningTextColor()); viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); viewHolder.messageBody.setTextIsSelectable(false); } + private void displayHeartMessage(final ViewHolder viewHolder, final String body) { + if (viewHolder.download_button != null) { + viewHolder.download_button.setVisibility(View.GONE); + } + viewHolder.image.setVisibility(View.GONE); + viewHolder.messageBody.setVisibility(View.VISIBLE); + viewHolder.messageBody.setIncludeFontPadding(false); + Spannable span = new SpannableString(body); + span.setSpan(new RelativeSizeSpan(4.0f), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + span.setSpan(new ForegroundColorSpan(activity.getWarningTextColor()), 0, body.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + viewHolder.messageBody.setText(span); + } + private void displayTextMessage(final ViewHolder viewHolder, final Message message) { if (viewHolder.download_button != null) { viewHolder.download_button.setVisibility(View.GONE); } viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.VISIBLE); + viewHolder.messageBody.setIncludeFontPadding(true); if (message.getBody() != null) { final String nick = UIHelper.getMessageDisplayName(message); - final String formattedBody = message.getMergedBody().replaceAll("^" + Message.ME_COMMAND, - nick + " "); + final String body = message.getMergedBody().replaceAll("^" + Message.ME_COMMAND,nick + " "); + final SpannableString formattedBody = new SpannableString(body); + int i = body.indexOf(Message.MERGE_SEPARATOR); + while(i >= 0) { + final int end = i + Message.MERGE_SEPARATOR.length(); + formattedBody.setSpan(new RelativeSizeSpan(0.3f),i,end,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + i = body.indexOf(Message.MERGE_SEPARATOR,end); + } if (message.getType() != Message.TYPE_PRIVATE) { if (message.hasMeCommand()) { final Spannable span = new SpannableString(formattedBody); @@ -230,7 +247,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); viewHolder.messageBody.setText(span); } else { - viewHolder.messageBody.setText(message.getMergedBody()); + viewHolder.messageBody.setText(formattedBody); } } else { String privateMarker; @@ -289,7 +306,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.GONE); viewHolder.download_button.setVisibility(View.VISIBLE); - viewHolder.download_button.setText(activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity,message))); + viewHolder.download_button.setText(activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity, message))); viewHolder.download_button.setOnClickListener(new OnClickListener() { @Override @@ -334,7 +351,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { scalledH = (int) (params.height / ((double) params.width / target)); } viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams( - scalledW, scalledH)); + scalledW, scalledH)); activity.loadBitmap(message, viewHolder.image); viewHolder.image.setOnClickListener(new OnClickListener() { @@ -359,10 +376,6 @@ public class MessageAdapter extends ArrayAdapter<Message> { if (view == null) { viewHolder = new ViewHolder(); switch (type) { - case NULL: - view = activity.getLayoutInflater().inflate( - R.layout.message_null, parent, false); - break; case SENT: view = activity.getLayoutInflater().inflate( R.layout.message_sent, parent, false); @@ -429,25 +442,6 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.status_message.setText(message.getBody()); } return view; - } else if (type == NULL) { - if (viewHolder.message_box != null) { - Log.e(Config.LOGTAG, "detected type=NULL but with wrong cached view"); - view = activity.getLayoutInflater().inflate(R.layout.message_null, parent, false); - view.setTag(new ViewHolder()); - } - if (position == getCount() - 1) { - view.getLayoutParams().height = 1; - } else { - view.getLayoutParams().height = 0; - - } - view.setLayoutParams(view.getLayoutParams()); - return view; - } else if (message.wasMergedIntoPrevious()) { - Log.e(Config.LOGTAG,"detected wasMergedIntoPrevious with wrong type"); - return view; - } else if (viewHolder.messageBody == null || viewHolder.image == null) { - return view; //avoiding weird platform bugs } else if (type == RECEIVED) { Contact contact = message.getContact(); if (contact != null) { @@ -528,7 +522,11 @@ public class MessageAdapter extends ArrayAdapter<Message> { if (GeoHelper.isGeoUri(message.getBody())) { displayLocationMessage(viewHolder,message); } else { - displayTextMessage(viewHolder, message); + if (message.bodyIsHeart()) { + displayHeartMessage(viewHolder, message.getBody().trim()); + } else { + displayTextMessage(viewHolder, message); + } } } diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index c3195d86..2f96a83a 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -1,8 +1,11 @@ package eu.siacs.conversations.utils; import java.net.URLConnection; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.Date; +import java.util.Locale; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Contact; @@ -17,6 +20,33 @@ import android.text.format.DateUtils; import android.util.Pair; public class UIHelper { + + private static String BLACK_HEART_SUIT = "\u2665"; + private static String HEAVY_BLACK_HEART_SUIT = "\u2764"; + private static String WHITE_HEART_SUIT = "\u2661"; + + public static final ArrayList<String> HEARTS = new ArrayList<>(Arrays.asList(BLACK_HEART_SUIT,HEAVY_BLACK_HEART_SUIT,WHITE_HEART_SUIT)); + + private static final ArrayList<String> LOCATION_QUESTIONS = new ArrayList<>(Arrays.asList( + "where are you", //en + "where are you now", //en + "where are you right now", //en + "whats your 20", //en + "what is your 20", //en + "what's your 20", //en + "whats your twenty", //en + "what is your twenty", //en + "what's your twenty", //en + "wo bist du", //de + "wo bist du jetzt", //de + "wo bist du gerade", //de + "wo seid ihr", //de + "wo seid ihr jetzt", //de + "wo seid ihr gerade", //de + "dónde estás", //es + "donde estas" //es + )); + private static final int SHORT_DATE_FLAGS = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR | DateUtils.FORMAT_ABBREV_ALL; private static final int FULL_DATE_FLAGS = DateUtils.FORMAT_SHOW_TIME @@ -225,4 +255,15 @@ public class UIHelper { return counterpart.toString().trim(); } } + + public static boolean receivedLocationQuestion(Message message) { + if (message == null + || message.getStatus() != Message.STATUS_RECEIVED + || message.getType() != Message.TYPE_TEXT) { + return false; + } + String body = message.getBody() == null ? null : message.getBody().trim().toLowerCase(Locale.getDefault()); + body = body.replace("?","").replace("¿",""); + return LOCATION_QUESTIONS.contains(body); + } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index a2b58a14..9bda3c09 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -167,7 +167,7 @@ public class XmppConnection implements Runnable { try { String srvRecordServer; try { - srvRecordServer=IDN.toASCII(namePort.getString("name")); + srvRecordServer = IDN.toASCII(namePort.getString("name")); } catch (final IllegalArgumentException e) { // TODO: Handle me?` srvRecordServer = ""; @@ -187,7 +187,7 @@ public class XmppConnection implements Runnable { + srvRecordServer + ":" + srvRecordPort); } socket = new Socket(); - socket.connect(addr, 20000); + socket.connect(addr, Config.SOCKET_TIMEOUT * 1000); socketError = false; } catch (final UnknownHostException e) { Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage()); @@ -224,6 +224,12 @@ public class XmppConnection implements Runnable { if (socket.isConnected()) { socket.close(); } + } catch (final IncompatibleServerException e) { + this.changeStatus(Account.State.INCOMPATIBLE_SERVER); + } catch (final SecurityException e) { + this.changeStatus(Account.State.SECURITY_ERROR); + } catch (final UnauthorizedException e) { + this.changeStatus(Account.State.UNAUTHORIZED); } catch (final UnknownHostException | ConnectException e) { this.changeStatus(Account.State.SERVER_NOT_FOUND); } catch (final IOException | XmlPullParserException | NoSuchAlgorithmException e) { @@ -231,6 +237,13 @@ public class XmppConnection implements Runnable { this.changeStatus(Account.State.OFFLINE); this.attempt--; //don't count attempt when reconnecting instantly anyway } finally { + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + + } + } if (wakeLock.isHeld()) { try { wakeLock.release(); @@ -279,8 +292,7 @@ public class XmppConnection implements Runnable { processStream(tagReader.readTag()); break; } else if (nextTag.isStart("failure")) { - tagReader.readElement(nextTag); - changeStatus(Account.State.UNAUTHORIZED); + throw new UnauthorizedException(); } else if (nextTag.isStart("challenge")) { final String challenge = tagReader.readElement(nextTag).getContent(); final Element response = new Element("response"); @@ -437,6 +449,10 @@ public class XmppConnection implements Runnable { throw new IOException("interrupted mid tag"); } } + if (stanzasReceived == Integer.MAX_VALUE) { + resetStreamId(); + throw new IOException("time to restart the session. cant handle >2 billion pcks"); + } ++stanzasReceived; lastPacketReceived = SystemClock.elapsedRealtime(); return element; @@ -538,8 +554,7 @@ public class XmppConnection implements Runnable { if (!verifier.verify(account.getServer().getDomainpart(),sslSocket.getSession())) { Log.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed"); - disconnect(true); - changeStatus(Account.State.SECURITY_ERROR); + throw new SecurityException(); } tagReader.setInputStream(sslSocket.getInputStream()); tagWriter.setOutputStream(sslSocket.getOutputStream()); @@ -550,8 +565,7 @@ public class XmppConnection implements Runnable { sslSocket.close(); } catch (final NoSuchAlgorithmException | KeyManagementException e1) { Log.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed"); - disconnect(true); - changeStatus(Account.State.SECURITY_ERROR); + throw new SecurityException(); } } @@ -590,8 +604,7 @@ public class XmppConnection implements Runnable { " has lower priority (" + String.valueOf(saslMechanism.getPriority()) + ") than pinned priority (" + keys.getInt(Account.PINNED_MECHANISM_KEY) + "). Possible downgrade attack?"); - disconnect(true); - changeStatus(Account.State.SECURITY_ERROR); + throw new SecurityException(); } } catch (final JSONException e) { Log.d(Config.LOGTAG, "Parse error while checking pinned auth mechanism"); @@ -603,8 +616,7 @@ public class XmppConnection implements Runnable { } tagWriter.writeElement(auth); } else { - disconnect(true); - changeStatus(Account.State.INCOMPATIBLE_SERVER); + throw new IncompatibleServerException(); } } else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:" + smVersion) @@ -643,10 +655,8 @@ public class XmppConnection implements Runnable { if (packet.query().hasChild("username") && (packet.query().hasChild("password"))) { final IqPacket register = new IqPacket(IqPacket.TYPE.SET); - final Element username = new Element("username") - .setContent(account.getUsername()); - final Element password = new Element("password") - .setContent(account.getPassword()); + final Element username = new Element("username").setContent(account.getUsername()); + final Element password = new Element("password").setContent(account.getPassword()); register.query("jabber:iq:register").addChild(username); register.query().addChild(password); sendIqPacket(register, new OnIqPacketReceived() { @@ -659,7 +669,7 @@ public class XmppConnection implements Runnable { changeStatus(Account.State.REGISTRATION_SUCCESSFUL); } else if (packet.hasChild("error") && (packet.findChild("error") - .hasChild("conflict"))) { + .hasChild("conflict"))) { changeStatus(Account.State.REGISTRATION_CONFLICT); } else { changeStatus(Account.State.REGISTRATION_FAILED); @@ -673,7 +683,7 @@ public class XmppConnection implements Runnable { disconnect(true); Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not register. instructions are" - + instructions.getContent()); + + (instructions != null ? instructions.getContent() : "")); } } }); @@ -688,7 +698,7 @@ public class XmppConnection implements Runnable { } final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); iq.addChild("bind", "urn:ietf:params:xml:ns:xmpp-bind") - .addChild("resource").setContent(account.getResource()); + .addChild("resource").setContent(account.getResource()); this.sendUnmodifiedIqPacket(iq, new OnIqPacketReceived() { @Override public void onIqPacketReceived(final Account account, final IqPacket packet) { @@ -901,6 +911,11 @@ public class XmppConnection implements Runnable { } private synchronized void sendPacket(final AbstractStanza packet) { + if (stanzasSent == Integer.MAX_VALUE) { + resetStreamId(); + disconnect(true); + return; + } final String name = packet.getName(); if (name.equals("iq") || name.equals("message") || name.equals("presence")) { ++stanzasSent; @@ -1091,6 +1106,18 @@ public class XmppConnection implements Runnable { public final ArrayList<Pair<String,String>> identities = new ArrayList<>(); } + private class UnauthorizedException extends IOException { + + } + + private class SecurityException extends IOException { + + } + + private class IncompatibleServerException extends IOException { + + } + public class Features { XmppConnection connection; private boolean carbonsEnabled = false; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java b/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java index 295e067a..f989c0c2 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java @@ -130,12 +130,19 @@ public final class Jid { if (resourcepart.isEmpty() || resourcepart.length() > 1023) { throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH); } - dp = IDN.toUnicode(jid.substring(domainpartStart, slashLoc), IDN.USE_STD3_ASCII_RULES); + try { + dp = IDN.toUnicode(Stringprep.nameprep(jid.substring(domainpartStart, slashLoc)), IDN.USE_STD3_ASCII_RULES); + } catch (final StringprepException e) { + throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e); + } finaljid = finaljid + dp + "/" + rp; } else { resourcepart = ""; - dp = IDN.toUnicode(jid.substring(domainpartStart, jid.length()), - IDN.USE_STD3_ASCII_RULES); + try{ + dp = IDN.toUnicode(Stringprep.nameprep(jid.substring(domainpartStart, jid.length())), IDN.USE_STD3_ASCII_RULES); + } catch (final StringprepException e) { + throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e); + } finaljid = finaljid + dp; } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java index e448f947..c9bb9c93 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java @@ -99,7 +99,7 @@ public class JingleConnection implements Downloadable { file.delete(); } } - Log.d(Config.LOGTAG,"sucessfully transmitted file:" + file.getAbsolutePath()); + Log.d(Config.LOGTAG,"successfully transmitted file:" + file.getAbsolutePath()+" ("+file.getSha1Sum()+")"); if (message.getEncryption() != Message.ENCRYPTION_PGP) { Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); intent.setData(Uri.fromFile(file)); @@ -213,7 +213,7 @@ public class JingleConnection implements Downloadable { @Override public void onPrimaryCandidateFound(boolean success, - final JingleCandidate candidate) { + final JingleCandidate candidate) { if (success) { final JingleSocks5Transport socksConnection = new JingleSocks5Transport( JingleConnection.this, candidate); @@ -271,6 +271,9 @@ public class JingleConnection implements Downloadable { this.mergeCandidates(JingleCandidate.parse(content.socks5transport() .getChildren())); this.fileOffer = packet.getJingleContent().getFileOffer(); + + mXmppConnectionService.sendIqPacket(account,packet.generateResponse(IqPacket.TYPE.RESULT),null); + if (fileOffer != null) { Element fileSize = fileOffer.findChild("size"); Element fileNameElement = fileOffer.findChild("name"); @@ -381,6 +384,7 @@ public class JingleConnection implements Downloadable { @Override public void onIqPacketReceived(Account account, IqPacket packet) { if (packet.getType() != IqPacket.TYPE.ERROR) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": other party received offer"); mJingleStatus = JINGLE_STATUS_INITIATED; mXmppConnectionService.markMessage(message, Message.STATUS_OFFERED); } else { @@ -395,7 +399,9 @@ public class JingleConnection implements Downloadable { private List<Element> getCandidatesAsElements() { List<Element> elements = new ArrayList<>(); for (JingleCandidate c : this.candidates) { - elements.add(c.toElement()); + if (c.isOurs()) { + elements.add(c.toElement()); + } } return elements; } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index 5dfa3ff4..c19dd04c 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -9,6 +9,7 @@ import android.annotation.SuppressLint; import android.util.Log; import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Downloadable; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; @@ -58,7 +59,12 @@ public class JingleConnectionManager extends AbstractConnectionManager { } public JingleConnection createNewConnection(Message message) { + Downloadable old = message.getDownloadable(); + if (old != null) { + old.cancel(); + } JingleConnection connection = new JingleConnection(this); + mXmppConnectionService.markMessage(message,Message.STATUS_WAITING); connection.init(message); this.connections.add(connection); return connection; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java index 9866af03..29efcf8f 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java @@ -8,7 +8,9 @@ import java.security.NoSuchAlgorithmException; import java.util.Arrays; import android.util.Base64; +import android.util.Log; +import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.persistance.FileBackend; @@ -95,11 +97,13 @@ public class JingleInbandTransport extends JingleTransport { file.createNewFile(); this.fileOutputStream = file.createOutputStream(); if (this.fileOutputStream == null) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not create output stream"); callback.onFileTransferAborted(); return; } this.remainingSize = this.fileSize = file.getExpectedSize(); } catch (final NoSuchAlgorithmException | IOException e) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+" "+e.getMessage()); callback.onFileTransferAborted(); } } @@ -110,12 +114,17 @@ public class JingleInbandTransport extends JingleTransport { this.onFileTransmissionStatusChanged = callback; this.file = file; try { - this.remainingSize = this.file.getSize(); + if (this.file.getKey() != null) { + this.remainingSize = (this.file.getSize() / 16 + 1) * 16; + } else { + this.remainingSize = this.file.getSize(); + } this.fileSize = this.remainingSize; this.digest = MessageDigest.getInstance("SHA-1"); this.digest.reset(); fileInputStream = this.file.createInputStream(); if (fileInputStream == null) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could no create input stream"); callback.onFileTransferAborted(); return; } @@ -124,6 +133,7 @@ public class JingleInbandTransport extends JingleTransport { } } catch (NoSuchAlgorithmException e) { callback.onFileTransferAborted(); + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage()); } } @@ -150,29 +160,33 @@ public class JingleInbandTransport extends JingleTransport { byte[] buffer = new byte[this.bufferSize]; try { int count = fileInputStream.read(buffer); - if (count == -1) { + this.remainingSize -= count; + if (count != buffer.length && count != -1) { + int rem = fileInputStream.read(buffer,count,buffer.length-count); + if (rem > 0) { + count += rem; + } + } + this.digest.update(buffer,0,count); + String base64 = Base64.encodeToString(buffer,0,count, Base64.NO_WRAP); + IqPacket iq = new IqPacket(IqPacket.TYPE.SET); + iq.setTo(this.counterpart); + Element data = iq.addChild("data", "http://jabber.org/protocol/ibb"); + 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, this.onAckReceived); + this.seq++; + if (this.remainingSize > 0) { + connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100)); + } else { file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest())); - fileInputStream.close(); this.onFileTransmissionStatusChanged.onFileTransmitted(file); - } else { - this.remainingSize -= count; - this.digest.update(buffer); - String base64 = Base64.encodeToString(buffer, Base64.NO_WRAP); - IqPacket iq = new IqPacket(IqPacket.TYPE.SET); - iq.setTo(this.counterpart); - Element data = iq.addChild("data", - "http://jabber.org/protocol/ibb"); - 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, - this.onAckReceived); - this.seq++; - connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100)); + fileInputStream.close(); } } catch (IOException e) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage()); FileBackend.close(fileInputStream); this.onFileTransmissionStatusChanged.onFileTransferAborted(); } @@ -182,14 +196,10 @@ 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; - - this.fileOutputStream.write(buffer); - this.digest.update(buffer); if (this.remainingSize <= 0) { file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest())); @@ -200,6 +210,7 @@ public class JingleInbandTransport extends JingleTransport { connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100)); } } catch (IOException e) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage()); FileBackend.close(fileOutputStream); this.onFileTransmissionStatusChanged.onFileTransferAborted(); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java index 72015a05..8d74f44e 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java @@ -1,15 +1,20 @@ package eu.siacs.conversations.xmpp.jingle; +import android.util.Log; + import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.InetSocketAddress; import java.net.Socket; +import java.net.SocketAddress; import java.net.UnknownHostException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; +import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.utils.CryptoHelper; @@ -53,8 +58,9 @@ public class JingleSocks5Transport extends JingleTransport { @Override public void run() { try { - socket = new Socket(candidate.getHost(), - candidate.getPort()); + socket = new Socket(); + SocketAddress address = new InetSocketAddress(candidate.getHost(),candidate.getPort()); + socket.connect(address,Config.SOCKET_TIMEOUT * 1000); inputStream = socket.getInputStream(); outputStream = socket.getOutputStream(); byte[] login = { 0x05, 0x01, 0x00 }; @@ -102,6 +108,7 @@ public class JingleSocks5Transport extends JingleTransport { digest.reset(); fileInputStream = file.createInputStream(); if (fileInputStream == null) { + Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream"); callback.onFileTransferAborted(); return; } @@ -121,10 +128,13 @@ public class JingleSocks5Transport extends JingleTransport { callback.onFileTransmitted(file); } } catch (FileNotFoundException e) { + Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage()); callback.onFileTransferAborted(); } catch (IOException e) { + Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage()); callback.onFileTransferAborted(); } catch (NoSuchAlgorithmException e) { + Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage()); callback.onFileTransferAborted(); } finally { FileBackend.close(fileInputStream); @@ -150,6 +160,7 @@ public class JingleSocks5Transport extends JingleTransport { fileOutputStream = file.createOutputStream(); if (fileOutputStream == null) { callback.onFileTransferAborted(); + Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream"); return; } double size = file.getExpectedSize(); @@ -160,6 +171,7 @@ public class JingleSocks5Transport extends JingleTransport { count = inputStream.read(buffer); if (count == -1) { callback.onFileTransferAborted(); + Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": file ended prematurely with "+remainingSize+" bytes remaining"); return; } else { fileOutputStream.write(buffer, 0, count); @@ -173,10 +185,13 @@ public class JingleSocks5Transport extends JingleTransport { file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest())); callback.onFileTransmitted(file); } catch (FileNotFoundException e) { + Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage()); callback.onFileTransferAborted(); } catch (IOException e) { + Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage()); callback.onFileTransferAborted(); } catch (NoSuchAlgorithmException e) { + Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage()); callback.onFileTransferAborted(); } finally { FileBackend.close(fileOutputStream); diff --git a/src/main/java/eu/siacs/conversations/xmpp/pep/Avatar.java b/src/main/java/eu/siacs/conversations/xmpp/pep/Avatar.java index 9f5ac988..04d55bbe 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/pep/Avatar.java +++ b/src/main/java/eu/siacs/conversations/xmpp/pep/Avatar.java @@ -6,6 +6,9 @@ import eu.siacs.conversations.xmpp.jid.Jid; import android.util.Base64; public class Avatar { + + public enum Origin { PEP, VCARD }; + public String type; public String sha1sum; public String image; @@ -13,21 +16,14 @@ public class Avatar { public int width; public long size; public Jid owner; + public Origin origin = Origin.PEP; //default to maintain compat public byte[] getImageAsBytes() { return Base64.decode(image, Base64.DEFAULT); } public String getFilename() { - if (type == null) { - return sha1sum; - } else if (type.equalsIgnoreCase("image/webp")) { - return sha1sum + ".webp"; - } else if (type.equalsIgnoreCase("image/png")) { - return sha1sum + ".png"; - } else { - return sha1sum; - } + return sha1sum; } public static Avatar parseMetadata(Element items) { @@ -64,10 +60,44 @@ public class Avatar { return null; } avatar.type = child.getAttribute("type"); - avatar.sha1sum = child.getAttribute("id"); + String hash = child.getAttribute("id"); + if (!isValidSHA1(hash)) { + return null; + } + avatar.sha1sum = hash; + avatar.origin = Origin.PEP; return avatar; } } return null; } + + @Override + public boolean equals(Object object) { + if (object != null && object instanceof Avatar) { + Avatar other = (Avatar) object; + return other.getFilename().equals(this.getFilename()); + } else { + return false; + } + } + + public static Avatar parsePresence(Element x) { + Element photo = x != null ? x.findChild("photo") : null; + String hash = photo != null ? photo.getContent() : null; + if (hash == null) { + return null; + } + if (!isValidSHA1(hash)) { + return null; + } + Avatar avatar = new Avatar(); + avatar.sha1sum = hash; + avatar.origin = Origin.VCARD; + return avatar; + } + + private static boolean isValidSHA1(String s) { + return s != null && s.matches("[a-fA-F0-9]{40}"); + } } |