aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/eu
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/eu')
-rw-r--r--src/main/java/eu/siacs/conversations/Config.java5
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/OtrService.java (renamed from src/main/java/eu/siacs/conversations/crypto/OtrEngine.java)48
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/PgpEngine.java19
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java2
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Account.java34
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Bookmark.java14
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Contact.java42
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Conversation.java16
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Downloadable.java28
-rw-r--r--src/main/java/eu/siacs/conversations/entities/DownloadableFile.java17
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Message.java339
-rw-r--r--src/main/java/eu/siacs/conversations/entities/MucOptions.java2
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Roster.java17
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Transferable.java28
-rw-r--r--src/main/java/eu/siacs/conversations/entities/TransferablePlaceholder.java (renamed from src/main/java/eu/siacs/conversations/entities/DownloadablePlaceholder.java)9
-rw-r--r--src/main/java/eu/siacs/conversations/generator/IqGenerator.java19
-rw-r--r--src/main/java/eu/siacs/conversations/generator/MessageGenerator.java28
-rw-r--r--src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java11
-rw-r--r--src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java74
-rw-r--r--src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java (renamed from src/main/java/eu/siacs/conversations/http/HttpConnection.java)135
-rw-r--r--src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java204
-rw-r--r--src/main/java/eu/siacs/conversations/parser/AbstractParser.java43
-rw-r--r--src/main/java/eu/siacs/conversations/parser/MessageParser.java802
-rw-r--r--src/main/java/eu/siacs/conversations/parser/PresenceParser.java60
-rw-r--r--src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java90
-rw-r--r--src/main/java/eu/siacs/conversations/persistance/FileBackend.java154
-rw-r--r--src/main/java/eu/siacs/conversations/services/MessageArchiveService.java12
-rw-r--r--src/main/java/eu/siacs/conversations/services/NotificationService.java23
-rw-r--r--src/main/java/eu/siacs/conversations/services/XmppConnectionService.java613
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java7
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java54
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationActivity.java413
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationFragment.java493
-rw-r--r--src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java40
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java8
-rw-r--r--src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java7
-rw-r--r--src/main/java/eu/siacs/conversations/ui/SettingsActivity.java129
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java124
-rw-r--r--src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java17
-rw-r--r--src/main/java/eu/siacs/conversations/ui/XmppActivity.java120
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java46
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java24
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java105
-rw-r--r--src/main/java/eu/siacs/conversations/utils/CryptoHelper.java5
-rw-r--r--src/main/java/eu/siacs/conversations/utils/DNSHelper.java136
-rw-r--r--src/main/java/eu/siacs/conversations/utils/GeoHelper.java12
-rw-r--r--src/main/java/eu/siacs/conversations/utils/MimeUtils.java487
-rw-r--r--src/main/java/eu/siacs/conversations/utils/SerialSingleThreadExecutor.java34
-rw-r--r--src/main/java/eu/siacs/conversations/utils/UIHelper.java79
-rw-r--r--src/main/java/eu/siacs/conversations/utils/Xmlns.java1
-rw-r--r--src/main/java/eu/siacs/conversations/xml/Element.java10
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java322
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java13
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java78
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java15
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java70
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java36
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/pep/Avatar.java49
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java4
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java39
60 files changed, 3755 insertions, 2110 deletions
diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java
index fa9345a2..00225f7b 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;
@@ -30,6 +31,10 @@ 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 = 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 boolean ENCRYPT_ON_HTTP_UPLOADED = false;
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/crypto/OtrEngine.java b/src/main/java/eu/siacs/conversations/crypto/OtrService.java
index 20427d7b..c23a7fc8 100644
--- a/src/main/java/eu/siacs/conversations/crypto/OtrEngine.java
+++ b/src/main/java/eu/siacs/conversations/crypto/OtrService.java
@@ -36,14 +36,14 @@ import net.java.otr4j.session.InstanceTag;
import net.java.otr4j.session.SessionID;
import net.java.otr4j.session.FragmenterInstructions;
-public class OtrEngine extends OtrCryptoEngineImpl implements OtrEngineHost {
+public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
private Account account;
private OtrPolicy otrPolicy;
private KeyPair keyPair;
private XmppConnectionService mXmppConnectionService;
- public OtrEngine(XmppConnectionService service, Account account) {
+ public OtrService(XmppConnectionService service, Account account) {
this.account = account;
this.otrPolicy = new OtrPolicyImpl();
this.otrPolicy.setAllowV1(false);
@@ -182,8 +182,8 @@ public class OtrEngine extends OtrCryptoEngineImpl implements OtrEngineHost {
packet.setBody(body);
packet.addChild("private", "urn:xmpp:carbons:2");
packet.addChild("no-copy", "urn:xmpp:hints");
- packet.addChild("no-store", "urn:xmpp:hints");
-
+ packet.addChild("no-permanent-store", "urn:xmpp:hints");
+ packet.addChild("no-permanent-storage", "urn:xmpp:hints");
try {
Jid jid = Jid.fromSessionID(session);
Conversation conversation = mXmppConnectionService.find(account,jid);
@@ -202,20 +202,7 @@ public class OtrEngine extends OtrCryptoEngineImpl implements OtrEngineHost {
@Override
public void messageFromAnotherInstanceReceived(SessionID session) {
- try {
- Jid jid = Jid.fromSessionID(session);
- Conversation conversation = mXmppConnectionService.find(account, jid);
- String id = conversation == null ? null : conversation.getLastReceivedOtrMessageId();
- if (id != null) {
- MessagePacket packet = mXmppConnectionService.getMessageGenerator().generateOtrError(jid,id);
- packet.setFrom(account.getJid());
- mXmppConnectionService.sendMessagePacket(account,packet);
- Log.d(Config.LOGTAG,packet.toString());
- Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": unreadable OTR message in "+conversation.getName());
- }
- } catch (InvalidJidException e) {
- return;
- }
+ sendOtrErrorMessage(session, "Message from another OTR-instance received");
}
@Override
@@ -267,9 +254,28 @@ public class OtrEngine extends OtrCryptoEngineImpl implements OtrEngineHost {
}
@Override
- public void unreadableMessageReceived(SessionID arg0) throws OtrException {
+ public void unreadableMessageReceived(SessionID session) throws OtrException {
Log.d(Config.LOGTAG,"unreadable message received");
- throw new OtrException(new Exception("unreadable message received"));
+ sendOtrErrorMessage(session, "You sent me an unreadable OTR-encrypted message");
+ }
+
+ public void sendOtrErrorMessage(SessionID session, String errorText) {
+ try {
+ Jid jid = Jid.fromSessionID(session);
+ Conversation conversation = mXmppConnectionService.find(account, jid);
+ String id = conversation == null ? null : conversation.getLastReceivedOtrMessageId();
+ if (id != null) {
+ MessagePacket packet = mXmppConnectionService.getMessageGenerator()
+ .generateOtrError(jid, id, errorText);
+ packet.setFrom(account.getJid());
+ mXmppConnectionService.sendMessagePacket(account,packet);
+ Log.d(Config.LOGTAG,packet.toString());
+ Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()
+ +": unreadable OTR message in "+conversation.getName());
+ }
+ } catch (InvalidJidException e) {
+ return;
+ }
}
@Override
@@ -279,7 +285,7 @@ public class OtrEngine extends OtrCryptoEngineImpl implements OtrEngineHost {
@Override
public void verify(SessionID id, String fingerprint, boolean approved) {
- Log.d(Config.LOGTAG,"OtrEngine.verify("+id.toString()+","+fingerprint+","+String.valueOf(approved)+")");
+ Log.d(Config.LOGTAG,"OtrService.verify("+id.toString()+","+fingerprint+","+String.valueOf(approved)+")");
try {
final Jid jid = Jid.fromSessionID(id);
Conversation conversation = this.mXmppConnectionService.find(this.account,jid);
diff --git a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java
index 3dc3fd34..101d44bb 100644
--- a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java
+++ b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java
@@ -60,11 +60,12 @@ public class PgpEngine {
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager();
if (message.trusted()
+ && message.treatAsDownloadable() != Message.Decision.NEVER
&& Settings.DOWNLOAD_IMAGE_LINKS
&& mXmppConnectionService.isDownloadAllowedInConnection()
&& message.bodyContainsDownloadable()
&& manager.getAutoAcceptFileSize() > 0) {
- manager.createNewConnection(message);
+ manager.createNewDownloadConnection(message);
}
callback.success(message);
}
@@ -101,7 +102,7 @@ public class PgpEngine {
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
OpenPgpApi.RESULT_CODE_ERROR)) {
case OpenPgpApi.RESULT_CODE_SUCCESS:
- URL url = message.getImageParams().url;
+ URL url = message.getFileParams().url;
mXmppConnectionService.getFileBackend().updateFileParams(message,url);
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
PgpEngine.this.mXmppConnectionService
@@ -146,11 +147,15 @@ public class PgpEngine {
params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message
.getConversation().getAccount().getJid().toBareJid().toString());
- if (message.getType() == Message.TYPE_TEXT) {
+ if (!message.needsUploading()) {
params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
-
- InputStream is = new ByteArrayInputStream(message.getBody()
- .getBytes());
+ String body;
+ if (message.hasFileOnRemoteHost()) {
+ body = message.getFileParams().url.toString();
+ } else {
+ body = message.getBody();
+ }
+ InputStream is = new ByteArrayInputStream(body.getBytes());
final OutputStream os = new ByteArrayOutputStream();
api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
@@ -187,7 +192,7 @@ public class PgpEngine {
}
}
});
- } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
+ } else {
try {
DownloadableFile inputFile = this.mXmppConnectionService
.getFileBackend().getFile(message, true);
diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java
index 10cd3167..c95a62df 100644
--- a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java
+++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java
@@ -185,7 +185,7 @@ public class ScramSha1 extends SaslMechanism {
case RESPONSE_SENT:
final String clientCalculatedServerFinalMessage = "v=" +
Base64.encodeToString(serverSignature, Base64.NO_WRAP);
- if (!clientCalculatedServerFinalMessage.equals(new String(Base64.decode(challenge, Base64.DEFAULT)))) {
+ if (challenge == null || !clientCalculatedServerFinalMessage.equals(new String(Base64.decode(challenge, Base64.DEFAULT)))) {
throw new AuthenticationException("Server final message does not match calculated final message");
}
state = State.VALID_SERVER_RESPONSE;
diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java
index 2bc2c954..f472361f 100644
--- a/src/main/java/eu/siacs/conversations/entities/Account.java
+++ b/src/main/java/eu/siacs/conversations/entities/Account.java
@@ -19,7 +19,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
-import eu.siacs.conversations.crypto.OtrEngine;
+import eu.siacs.conversations.crypto.OtrService;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xmpp.XmppConnection;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
@@ -44,6 +44,10 @@ public class Account extends AbstractEntity {
public static final int OPTION_REGISTER = 2;
public static final int OPTION_USECOMPRESSION = 3;
+ public boolean httpUploadAvailable() {
+ return xmppConnection != null && xmppConnection.getFeatures().httpUpload();
+ }
+
public static enum State {
DISABLED,
OFFLINE,
@@ -117,7 +121,7 @@ public class Account extends AbstractEntity {
protected JSONObject keys = new JSONObject();
protected String avatar;
protected boolean online = false;
- private OtrEngine otrEngine = null;
+ private OtrService mOtrService = null;
private XmppConnection xmppConnection = null;
private long mEndGracePeriod = 0L;
private String otrFingerprint;
@@ -229,11 +233,17 @@ public class Account extends AbstractEntity {
return jid.getResourcepart();
}
- public void setResource(final String resource) {
- try {
- jid = Jid.fromParts(jid.getLocalpart(), jid.getDomainpart(), resource);
- } catch (final InvalidJidException ignored) {
+ public boolean setResource(final String resource) {
+ final String oldResource = jid.getResourcepart();
+ if (oldResource == null || !oldResource.equals(resource)) {
+ try {
+ jid = Jid.fromParts(jid.getLocalpart(), jid.getDomainpart(), resource);
+ return true;
+ } catch (final InvalidJidException ignored) {
+ return true;
+ }
}
+ return false;
}
public Jid getJid() {
@@ -267,12 +277,12 @@ public class Account extends AbstractEntity {
return values;
}
- public void initOtrEngine(final XmppConnectionService context) {
- this.otrEngine = new OtrEngine(context, this);
+ public void initAccountServices(final XmppConnectionService context) {
+ this.mOtrService = new OtrService(context, this);
}
- public OtrEngine getOtrEngine() {
- return this.otrEngine;
+ public OtrService getOtrService() {
+ return this.mOtrService;
}
public XmppConnection getXmppConnection() {
@@ -286,10 +296,10 @@ public class Account extends AbstractEntity {
public String getOtrFingerprint() {
if (this.otrFingerprint == null) {
try {
- if (this.otrEngine == null) {
+ if (this.mOtrService == null) {
return null;
}
- final PublicKey publicKey = this.otrEngine.getPublicKey();
+ final PublicKey publicKey = this.mOtrService.getPublicKey();
if (publicKey == null || !(publicKey instanceof DSAPublicKey)) {
return null;
}
diff --git a/src/main/java/eu/siacs/conversations/entities/Bookmark.java b/src/main/java/eu/siacs/conversations/entities/Bookmark.java
index cc3d3424..06c46c1c 100644
--- a/src/main/java/eu/siacs/conversations/entities/Bookmark.java
+++ b/src/main/java/eu/siacs/conversations/entities/Bookmark.java
@@ -82,12 +82,7 @@ public class Bookmark extends Element implements ListItem {
}
public String getNick() {
- Element nick = this.findChild("nick");
- if (nick != null) {
- return nick.getContent();
- } else {
- return null;
- }
+ return this.findChildContent("nick");
}
public void setNick(String nick) {
@@ -103,12 +98,7 @@ public class Bookmark extends Element implements ListItem {
}
public String getPassword() {
- Element password = this.findChild("password");
- if (password != null) {
- return password.getContent();
- } else {
- return null;
- }
+ return this.findChildContent("password");
}
public void setPassword(String password) {
diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java
index 26145d82..a215874f 100644
--- a/src/main/java/eu/siacs/conversations/entities/Contact.java
+++ b/src/main/java/eu/siacs/conversations/entities/Contact.java
@@ -16,6 +16,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";
@@ -41,11 +42,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,
@@ -62,7 +63,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) {
@@ -136,10 +141,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()) {
@@ -193,7 +198,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());
@@ -238,8 +243,16 @@ public class Contact implements ListItem, Blockable {
return this.presences.getMostAvailableStatus();
}
- public void setPhotoUri(String uri) {
- this.photoUri = uri;
+ public boolean setPhotoUri(String uri) {
+ if (uri != null && !uri.equals(this.photoUri)) {
+ this.photoUri = uri;
+ return true;
+ } else if (this.photoUri != null && uri == null) {
+ this.photoUri = null;
+ return true;
+ } else {
+ return false;
+ }
}
public void setServerName(String serverName) {
@@ -417,17 +430,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) {
@@ -484,6 +500,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..289ed4ea 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
@@ -243,6 +249,12 @@ public class Conversation extends AbstractEntity implements Blockable {
this.mLastReceivedOtrMessageId = id;
}
+ public int countMessages() {
+ synchronized (this.messages) {
+ return this.messages.size();
+ }
+ }
+
public interface OnMessageFound {
public void onMessageFound(final Message message);
@@ -413,7 +425,7 @@ public class Conversation extends AbstractEntity implements Blockable {
final SessionID sessionId = new SessionID(this.getJid().toBareJid().toString(),
presence,
"xmpp");
- this.otrSession = new SessionImpl(sessionId, getAccount().getOtrEngine());
+ this.otrSession = new SessionImpl(sessionId, getAccount().getOtrService());
try {
if (sendStart) {
this.otrSession.startSession();
@@ -485,7 +497,7 @@ public class Conversation extends AbstractEntity implements Blockable {
return null;
}
DSAPublicKey remotePubKey = (DSAPublicKey) getOtrSession().getRemotePublicKey();
- this.otrFingerprint = getAccount().getOtrEngine().getFingerprint(remotePubKey);
+ this.otrFingerprint = getAccount().getOtrService().getFingerprint(remotePubKey);
} catch (final OtrCryptoException | UnsupportedOperationException ignored) {
return null;
}
diff --git a/src/main/java/eu/siacs/conversations/entities/Downloadable.java b/src/main/java/eu/siacs/conversations/entities/Downloadable.java
deleted file mode 100644
index d25bf93a..00000000
--- a/src/main/java/eu/siacs/conversations/entities/Downloadable.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package eu.siacs.conversations.entities;
-
-public interface Downloadable {
-
- public final String[] VALID_IMAGE_EXTENSIONS = {"webp", "jpeg", "jpg", "png", "jpe"};
- public final String[] VALID_CRYPTO_EXTENSIONS = {"pgp", "gpg", "otr"};
-
- public static final int STATUS_UNKNOWN = 0x200;
- public static final int STATUS_CHECKING = 0x201;
- public static final int STATUS_FAILED = 0x202;
- public static final int STATUS_OFFER = 0x203;
- public static final int STATUS_DOWNLOADING = 0x204;
- public static final int STATUS_DELETED = 0x205;
- public static final int STATUS_OFFER_CHECK_FILESIZE = 0x206;
- public static final int STATUS_UPLOADING = 0x207;
-
- public boolean start();
-
- public int getStatus();
-
- public long getFileSize();
-
- public int getProgress();
-
- public String getMimeType();
-
- public void cancel();
-}
diff --git a/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java b/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java
index 7c8f95d1..ae9ba1f1 100644
--- a/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java
+++ b/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java
@@ -20,6 +20,8 @@ import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import eu.siacs.conversations.Config;
+import eu.siacs.conversations.utils.MimeUtils;
+
import android.util.Log;
public class DownloadableFile extends File {
@@ -56,16 +58,11 @@ public class DownloadableFile extends File {
public String getMimeType() {
String path = this.getAbsolutePath();
- try {
- String mime = URLConnection.guessContentTypeFromName(path.replace("#",""));
- if (mime != null) {
- return mime;
- } else if (mime == null && path.endsWith(".webp")) {
- return "image/webp";
- } else {
- return "";
- }
- } catch (final StringIndexOutOfBoundsException e) {
+ int start = path.lastIndexOf('.') + 1;
+ if (start < path.length()) {
+ String mime = MimeUtils.guessMimeTypeFromExtension(path.substring(start));
+ return mime == null ? "" : mime;
+ } else {
return "";
}
}
diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java
index bcb1ca24..9a904cab 100644
--- a/src/main/java/eu/siacs/conversations/entities/Message.java
+++ b/src/main/java/eu/siacs/conversations/entities/Message.java
@@ -9,6 +9,8 @@ import java.util.Arrays;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.utils.GeoHelper;
+import eu.siacs.conversations.utils.MimeUtils;
+import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
@@ -16,6 +18,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;
@@ -66,7 +70,7 @@ public class Message extends AbstractEntity {
protected String remoteMsgId = null;
protected String serverMsgId = null;
protected Conversation conversation = null;
- protected Downloadable downloadable = null;
+ protected Transferable transferable = null;
private Message mNextMessage = null;
private Message mPreviousMessage = null;
@@ -95,9 +99,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 +183,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 +215,7 @@ public class Message extends AbstractEntity {
return null;
} else {
return this.conversation.getAccount().getRoster()
- .getContactFromRoster(this.trueCounterpart);
+ .getContactFromRoster(this.trueCounterpart);
}
}
}
@@ -304,12 +308,12 @@ public class Message extends AbstractEntity {
this.trueCounterpart = trueCounterpart;
}
- public Downloadable getDownloadable() {
- return this.downloadable;
+ public Transferable getTransferable() {
+ return this.transferable;
}
- public void setDownloadable(Downloadable downloadable) {
- this.downloadable = downloadable;
+ public void setTransferable(Transferable transferable) {
+ this.transferable = transferable;
}
public boolean equals(Message message) {
@@ -317,15 +321,25 @@ public class Message extends AbstractEntity {
return this.serverMsgId.equals(message.getServerMsgId());
} else if (this.body == null || this.counterpart == null) {
return false;
- } else if (message.getRemoteMsgId() != null) {
- return (message.getRemoteMsgId().equals(this.remoteMsgId) || message.getRemoteMsgId().equals(this.uuid))
- && this.counterpart.equals(message.getCounterpart())
- && this.body.equals(message.getBody());
} else {
- return this.remoteMsgId == null
- && this.counterpart.equals(message.getCounterpart())
- && this.body.equals(message.getBody())
- && Math.abs(this.getTimeSent() - message.getTimeSent()) < Config.PING_TIMEOUT * 500;
+ String body, otherBody;
+ if (this.hasFileOnRemoteHost()) {
+ body = getFileParams().url.toString();
+ otherBody = message.body == null ? null : message.body.trim();
+ } else {
+ body = this.body;
+ otherBody = message.body;
+ }
+ if (message.getRemoteMsgId() != null) {
+ return (message.getRemoteMsgId().equals(this.remoteMsgId) || message.getRemoteMsgId().equals(this.uuid))
+ && this.counterpart.equals(message.getCounterpart())
+ && body.equals(otherBody);
+ } else {
+ return this.remoteMsgId == null
+ && this.counterpart.equals(message.getCounterpart())
+ && body.equals(otherBody)
+ && Math.abs(this.getTimeSent() - message.getTimeSent()) < Config.MESSAGE_MERGE_WINDOW * 1000;
+ }
}
}
@@ -359,41 +373,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.getTransferable() == null &&
+ message.getTransferable() == 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.treatAsDownloadable() == Decision.NEVER &&
+ this.treatAsDownloadable() == Decision.NEVER &&
+ !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() + '\n' + next.getMergedBody();
+ return getBody() + MERGE_SEPARATOR + next.getMergedBody();
}
return getBody();
}
@@ -429,119 +445,175 @@ public class Message extends AbstractEntity {
return (status > STATUS_RECEIVED || (contact != null && contact.trusted()));
}
- public boolean bodyContainsDownloadable() {
- /**
- * there are a few cases where spaces result in an unwanted behavior, e.g.
- * "http://upload.mitsu-freunde-bw.de/uploads/2015/03/i43b4bpr8.png /abc.png"
- * or more than one image link in one message.
- */
- if (body.contains(" ")) {
- return false;
- }
- try {
- URL url = new URL(body);
- if (!url.getProtocol().equalsIgnoreCase("http")
- && !url.getProtocol().equalsIgnoreCase("https")) {
- return false;
- }
-
- String sUrlPath = url.getPath();
- if (sUrlPath == null || sUrlPath.isEmpty()) {
+ public boolean fixCounterpart() {
+ Presences presences = conversation.getContact().getPresences();
+ if (counterpart != null && presences.has(counterpart.getResourcepart())) {
+ return true;
+ } else if (presences.size() >= 1) {
+ try {
+ counterpart = Jid.fromParts(conversation.getJid().getLocalpart(),
+ conversation.getJid().getDomainpart(),
+ presences.asStringArray()[0]);
+ return true;
+ } catch (InvalidJidException e) {
+ counterpart = null;
return false;
}
+ } else {
+ counterpart = null;
+ return false;
+ }
+ }
- int iSlashIndex = sUrlPath.lastIndexOf('/') + 1;
+ public enum Decision {
+ MUST,
+ SHOULD,
+ NEVER,
+ }
- String sLastUrlPath = sUrlPath.substring(iSlashIndex).toLowerCase();
+ private static String extractRelevantExtension(URL url) {
+ String path = url.getPath();
+ if (path == null || path.isEmpty()) {
+ return null;
+ }
+ String filename = path.substring(path.lastIndexOf('/') + 1).toLowerCase();
+ String[] extensionParts = filename.split("\\.");
+ if (extensionParts.length == 2) {
+ return extensionParts[extensionParts.length - 1];
+ } else if (extensionParts.length == 3 && Arrays
+ .asList(Transferable.VALID_CRYPTO_EXTENSIONS)
+ .contains(extensionParts[extensionParts.length - 1])) {
+ return extensionParts[extensionParts.length -2];
+ }
+ return null;
+ }
- String[] extensionParts = sLastUrlPath.split("\\.");
- if (extensionParts.length == 2
- && Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains(
- extensionParts[extensionParts.length - 1])) {
- return true;
- } else if (extensionParts.length == 3
- && Arrays
- .asList(Downloadable.VALID_CRYPTO_EXTENSIONS)
- .contains(extensionParts[extensionParts.length - 1])
- && Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains(
- extensionParts[extensionParts.length - 2])) {
- return true;
+ public String getMimeType() {
+ if (relativeFilePath != null) {
+ int start = relativeFilePath.lastIndexOf('.') + 1;
+ if (start < relativeFilePath.length()) {
+ return MimeUtils.guessMimeTypeFromExtension(relativeFilePath.substring(start));
} else {
- return false;
+ return null;
+ }
+ } else {
+ try {
+ return MimeUtils.guessMimeTypeFromExtension(extractRelevantExtension(new URL(body.trim())));
+ } catch (MalformedURLException e) {
+ return null;
}
+ }
+ }
+
+ public Decision treatAsDownloadable() {
+ if (body.trim().contains(" ")) {
+ return Decision.NEVER;
+ }
+ try {
+ URL url = new URL(body);
+ if (!url.getProtocol().equalsIgnoreCase("http") && !url.getProtocol().equalsIgnoreCase("https")) {
+ return Decision.NEVER;
+ }
+ String extension = extractRelevantExtension(url);
+ if (extension == null) {
+ return Decision.NEVER;
+ }
+ String ref = url.getRef();
+ boolean encrypted = ref != null && ref.matches("([A-Fa-f0-9]{2}){48}");
+
+ if (encrypted) {
+ if (MimeUtils.guessMimeTypeFromExtension(extension) != null) {
+ return Decision.MUST;
+ } else {
+ return Decision.NEVER;
+ }
+ } else if (Arrays.asList(Transferable.VALID_IMAGE_EXTENSIONS).contains(extension)
+ || Arrays.asList(Transferable.WELL_KNOWN_EXTENSIONS).contains(extension)) {
+ return Decision.SHOULD;
+ } else {
+ return Decision.NEVER;
+ }
+
} catch (MalformedURLException e) {
- return false;
+ return Decision.NEVER;
}
}
- public ImageParams getImageParams() {
- ImageParams params = getLegacyImageParams();
+ public boolean bodyIsHeart() {
+ return body != null && UIHelper.HEARTS.contains(body.trim());
+ }
+
+ public FileParams getFileParams() {
+ FileParams params = getLegacyFileParams();
if (params != null) {
return params;
}
- params = new ImageParams();
- if (this.downloadable != null) {
- params.size = this.downloadable.getFileSize();
+ params = new FileParams();
+ if (this.transferable != null) {
+ params.size = this.transferable.getFileSize();
}
if (body == null) {
return params;
}
String parts[] = body.split("\\|");
- if (parts.length == 1) {
- try {
- params.size = Long.parseLong(parts[0]);
- } catch (NumberFormatException e) {
- params.origin = parts[0];
+ switch (parts.length) {
+ case 1:
+ try {
+ params.size = Long.parseLong(parts[0]);
+ } catch (NumberFormatException e) {
+ try {
+ params.url = new URL(parts[0]);
+ } catch (MalformedURLException e1) {
+ params.url = null;
+ }
+ }
+ break;
+ case 2:
+ case 4:
try {
params.url = new URL(parts[0]);
} catch (MalformedURLException e1) {
params.url = null;
}
- }
- } else if (parts.length == 3) {
- try {
- params.size = Long.parseLong(parts[0]);
- } catch (NumberFormatException e) {
- params.size = 0;
- }
- try {
- params.width = Integer.parseInt(parts[1]);
- } catch (NumberFormatException e) {
- params.width = 0;
- }
- try {
- params.height = Integer.parseInt(parts[2]);
- } catch (NumberFormatException e) {
- params.height = 0;
- }
- } else if (parts.length == 4) {
- params.origin = parts[0];
- try {
- params.url = new URL(parts[0]);
- } catch (MalformedURLException e1) {
- params.url = null;
- }
- try {
- params.size = Long.parseLong(parts[1]);
- } catch (NumberFormatException e) {
- params.size = 0;
- }
- try {
- params.width = Integer.parseInt(parts[2]);
- } catch (NumberFormatException e) {
- params.width = 0;
- }
- try {
- params.height = Integer.parseInt(parts[3]);
- } catch (NumberFormatException e) {
- params.height = 0;
- }
+ try {
+ params.size = Long.parseLong(parts[1]);
+ } catch (NumberFormatException e) {
+ params.size = 0;
+ }
+ try {
+ params.width = Integer.parseInt(parts[2]);
+ } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
+ params.width = 0;
+ }
+ try {
+ params.height = Integer.parseInt(parts[3]);
+ } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
+ params.height = 0;
+ }
+ break;
+ case 3:
+ try {
+ params.size = Long.parseLong(parts[0]);
+ } catch (NumberFormatException e) {
+ params.size = 0;
+ }
+ try {
+ params.width = Integer.parseInt(parts[1]);
+ } catch (NumberFormatException e) {
+ params.width = 0;
+ }
+ try {
+ params.height = Integer.parseInt(parts[2]);
+ } catch (NumberFormatException e) {
+ params.height = 0;
+ }
+ break;
}
return params;
}
- public ImageParams getLegacyImageParams() {
- ImageParams params = new ImageParams();
+ public FileParams getLegacyFileParams() {
+ FileParams params = new FileParams();
if (body == null) {
return params;
}
@@ -577,11 +649,18 @@ public class Message extends AbstractEntity {
return type == TYPE_FILE || type == TYPE_IMAGE;
}
- public class ImageParams {
+ public boolean hasFileOnRemoteHost() {
+ return isFileOrImage() && getFileParams().url != null;
+ }
+
+ public boolean needsUploading() {
+ return isFileOrImage() && getFileParams().url == null;
+ }
+
+ public class FileParams {
public URL url;
public long size = 0;
public int width = 0;
public int height = 0;
- public String origin;
}
}
diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java
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/entities/Roster.java b/src/main/java/eu/siacs/conversations/entities/Roster.java
index ce058004..628a31d1 100644
--- a/src/main/java/eu/siacs/conversations/entities/Roster.java
+++ b/src/main/java/eu/siacs/conversations/entities/Roster.java
@@ -2,6 +2,7 @@ package eu.siacs.conversations.entities;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import eu.siacs.conversations.xmpp.jid.Jid;
@@ -55,12 +56,15 @@ public class Roster {
}
}
- public void clearSystemAccounts() {
- for (Contact contact : getContacts()) {
- contact.setPhotoUri(null);
- contact.setSystemName(null);
- contact.setSystemAccount(null);
+ public List<Contact> getWithSystemAccounts() {
+ List<Contact> with = getContacts();
+ for(Iterator<Contact> iterator = with.iterator(); iterator.hasNext();) {
+ Contact contact = iterator.next();
+ if (contact.getSystemAccount() == null) {
+ iterator.remove();
+ }
}
+ return with;
}
public List<Contact> getContacts() {
@@ -70,6 +74,9 @@ public class Roster {
}
public void initContact(final Contact contact) {
+ if (contact == null) {
+ return;
+ }
contact.setAccount(account);
contact.setOption(Contact.Options.IN_ROSTER);
synchronized (this.contacts) {
diff --git a/src/main/java/eu/siacs/conversations/entities/Transferable.java b/src/main/java/eu/siacs/conversations/entities/Transferable.java
new file mode 100644
index 00000000..2db6e3c9
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/entities/Transferable.java
@@ -0,0 +1,28 @@
+package eu.siacs.conversations.entities;
+
+public interface Transferable {
+
+ String[] VALID_IMAGE_EXTENSIONS = {"webp", "jpeg", "jpg", "png", "jpe"};
+ String[] VALID_CRYPTO_EXTENSIONS = {"pgp", "gpg", "otr"};
+ String[] WELL_KNOWN_EXTENSIONS = {"pdf","m4a"};
+
+ int STATUS_UNKNOWN = 0x200;
+ int STATUS_CHECKING = 0x201;
+ int STATUS_FAILED = 0x202;
+ int STATUS_OFFER = 0x203;
+ int STATUS_DOWNLOADING = 0x204;
+ int STATUS_DELETED = 0x205;
+ int STATUS_OFFER_CHECK_FILESIZE = 0x206;
+ int STATUS_UPLOADING = 0x207;
+
+
+ boolean start();
+
+ int getStatus();
+
+ long getFileSize();
+
+ int getProgress();
+
+ void cancel();
+}
diff --git a/src/main/java/eu/siacs/conversations/entities/DownloadablePlaceholder.java b/src/main/java/eu/siacs/conversations/entities/TransferablePlaceholder.java
index 03fceceb..e065953e 100644
--- a/src/main/java/eu/siacs/conversations/entities/DownloadablePlaceholder.java
+++ b/src/main/java/eu/siacs/conversations/entities/TransferablePlaceholder.java
@@ -1,10 +1,10 @@
package eu.siacs.conversations.entities;
-public class DownloadablePlaceholder implements Downloadable {
+public class TransferablePlaceholder implements Transferable {
private int status;
- public DownloadablePlaceholder(int status) {
+ public TransferablePlaceholder(int status) {
this.status = status;
}
@Override
@@ -28,11 +28,6 @@ public class DownloadablePlaceholder implements Downloadable {
}
@Override
- public String getMimeType() {
- return "";
- }
-
- @Override
public void cancel() {
}
diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
index 6bc629b5..47915e3f 100644
--- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
@@ -6,6 +6,7 @@ import java.util.List;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.services.MessageArchiveService;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.PhoneHelper;
@@ -91,7 +92,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 +100,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) {
@@ -187,4 +195,13 @@ public class IqGenerator extends AbstractGenerator {
item.setAttribute("role", role);
return packet;
}
+
+ public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file) {
+ IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
+ packet.setTo(host);
+ Element request = packet.addChild("request",Xmlns.HTTP_UPLOAD);
+ request.addChild("filename").setContent(file.getName());
+ request.addChild("size").setContent(String.valueOf(file.getExpectedSize()));
+ return packet;
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java
index 8f6a90b9..e698c151 100644
--- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java
@@ -71,8 +71,16 @@ public class MessageGenerator extends AbstractGenerator {
MessagePacket packet = preparePacket(message, addDelay);
packet.addChild("private", "urn:xmpp:carbons:2");
packet.addChild("no-copy", "urn:xmpp:hints");
+ packet.addChild("no-permanent-store", "urn:xmpp:hints");
+ packet.addChild("no-permanent-storage", "urn:xmpp:hints");
try {
- packet.setBody(otrSession.transformSending(message.getBody())[0]);
+ String content;
+ if (message.hasFileOnRemoteHost()) {
+ content = message.getFileParams().url.toString();
+ } else {
+ content = message.getBody();
+ }
+ packet.setBody(otrSession.transformSending(content)[0]);
return packet;
} catch (OtrException e) {
return null;
@@ -85,7 +93,11 @@ public class MessageGenerator extends AbstractGenerator {
public MessagePacket generateChat(Message message, boolean addDelay) {
MessagePacket packet = preparePacket(message, addDelay);
- packet.setBody(message.getBody());
+ if (message.hasFileOnRemoteHost()) {
+ packet.setBody(message.getFileParams().url.toString());
+ } else {
+ packet.setBody(message.getBody());
+ }
return packet;
}
@@ -95,13 +107,11 @@ public class MessageGenerator extends AbstractGenerator {
public MessagePacket generatePgpChat(Message message, boolean addDelay) {
MessagePacket packet = preparePacket(message, addDelay);
- packet.setBody("This is an XEP-0027 encryted message");
+ packet.setBody("This is an XEP-0027 encrypted message");
if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
- packet.addChild("x", "jabber:x:encrypted").setContent(
- message.getEncryptedBody());
+ packet.addChild("x", "jabber:x:encrypted").setContent(message.getEncryptedBody());
} else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
- packet.addChild("x", "jabber:x:encrypted").setContent(
- message.getBody());
+ packet.addChild("x", "jabber:x:encrypted").setContent(message.getBody());
}
return packet;
}
@@ -172,7 +182,7 @@ public class MessageGenerator extends AbstractGenerator {
return receivedPacket;
}
- public MessagePacket generateOtrError(Jid to, String id) {
+ public MessagePacket generateOtrError(Jid to, String id, String errorText) {
MessagePacket packet = new MessagePacket();
packet.setType(MessagePacket.TYPE_ERROR);
packet.setAttribute("id",id);
@@ -181,7 +191,7 @@ public class MessageGenerator extends AbstractGenerator {
error.setAttribute("code","406");
error.setAttribute("type","modify");
error.addChild("not-acceptable","urn:ietf:params:xml:ns:xmpp-stanzas");
- error.addChild("text").setContent("unreadable OTR message received");
+ error.addChild("text").setContent("?OTR Error:" + errorText);
return packet;
}
}
diff --git a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java
index 1e896724..c40c6d05 100644
--- a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java
@@ -49,9 +49,16 @@ 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;
}
-} \ No newline at end of file
+
+ public PresencePacket sendOfflinePresence(Account account) {
+ PresencePacket packet = new PresencePacket();
+ packet.setFrom(account.getJid());
+ packet.setAttribute("type","unavailable");
+ return packet;
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java
index 9a2a2405..58a6d1e3 100644
--- a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java
+++ b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java
@@ -1,11 +1,22 @@
package eu.siacs.conversations.http;
+import org.apache.http.conn.ssl.StrictHostnameVerifier;
+
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.X509TrustManager;
+
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.AbstractConnectionManager;
import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.utils.CryptoHelper;
public class HttpConnectionManager extends AbstractConnectionManager {
@@ -13,16 +24,67 @@ public class HttpConnectionManager extends AbstractConnectionManager {
super(service);
}
- private List<HttpConnection> connections = new CopyOnWriteArrayList<HttpConnection>();
+ private List<HttpDownloadConnection> downloadConnections = new CopyOnWriteArrayList<>();
+ private List<HttpUploadConnection> uploadConnections = new CopyOnWriteArrayList<>();
+
+ public HttpDownloadConnection createNewDownloadConnection(Message message) {
+ return this.createNewDownloadConnection(message, false);
+ }
+
+ public HttpDownloadConnection createNewDownloadConnection(Message message, boolean interactive) {
+ HttpDownloadConnection connection = new HttpDownloadConnection(this);
+ connection.init(message,interactive);
+ this.downloadConnections.add(connection);
+ return connection;
+ }
- public HttpConnection createNewConnection(Message message) {
- HttpConnection connection = new HttpConnection(this);
+ public HttpUploadConnection createNewUploadConnection(Message message) {
+ HttpUploadConnection connection = new HttpUploadConnection(this);
connection.init(message);
- this.connections.add(connection);
+ this.uploadConnections.add(connection);
return connection;
}
- public void finishConnection(HttpConnection connection) {
- this.connections.remove(connection);
+ public void finishConnection(HttpDownloadConnection connection) {
+ this.downloadConnections.remove(connection);
+ }
+
+ public void finishUploadConnection(HttpUploadConnection httpUploadConnection) {
+ this.uploadConnections.remove(httpUploadConnection);
+ }
+
+ public void setupTrustManager(final HttpsURLConnection connection, final boolean interactive) {
+ final X509TrustManager trustManager;
+ final HostnameVerifier hostnameVerifier;
+ if (interactive) {
+ trustManager = mXmppConnectionService.getMemorizingTrustManager();
+ hostnameVerifier = mXmppConnectionService
+ .getMemorizingTrustManager().wrapHostnameVerifier(
+ new StrictHostnameVerifier());
+ } else {
+ trustManager = mXmppConnectionService.getMemorizingTrustManager()
+ .getNonInteractive();
+ hostnameVerifier = mXmppConnectionService
+ .getMemorizingTrustManager()
+ .wrapHostnameVerifierNonInteractive(
+ new StrictHostnameVerifier());
+ }
+ try {
+ final SSLContext sc = SSLContext.getInstance("TLS");
+ sc.init(null, new X509TrustManager[]{trustManager},
+ mXmppConnectionService.getRNG());
+
+ final SSLSocketFactory sf = sc.getSocketFactory();
+ final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites(
+ sf.getSupportedCipherSuites());
+ if (cipherSuites.length > 0) {
+ sc.getDefaultSSLParameters().setCipherSuites(cipherSuites);
+
+ }
+
+ connection.setSSLSocketFactory(sf);
+ connection.setHostnameVerifier(hostnameVerifier);
+ } catch (final KeyManagementException | NoSuchAlgorithmException ignored) {
+ }
}
}
diff --git a/src/main/java/eu/siacs/conversations/http/HttpConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java
index e7d30919..62fe4191 100644
--- a/src/main/java/eu/siacs/conversations/http/HttpConnection.java
+++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java
@@ -3,8 +3,7 @@ package eu.siacs.conversations.http;
import android.content.Intent;
import android.net.Uri;
import android.os.SystemClock;
-
-import org.apache.http.conn.ssl.StrictHostnameVerifier;
+import android.util.Log;
import java.io.BufferedInputStream;
import java.io.IOException;
@@ -12,25 +11,20 @@ import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
-import java.security.KeyManagementException;
-import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
-import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.X509TrustManager;
import eu.siacs.conversations.Config;
-import eu.siacs.conversations.entities.Downloadable;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.CryptoHelper;
-public class HttpConnection implements Downloadable {
+public class HttpDownloadConnection implements Transferable {
private HttpConnectionManager mHttpConnectionManager;
private XmppConnectionService mXmppConnectionService;
@@ -38,12 +32,12 @@ public class HttpConnection implements Downloadable {
private URL mUrl;
private Message message;
private DownloadableFile file;
- private int mStatus = Downloadable.STATUS_UNKNOWN;
+ private int mStatus = Transferable.STATUS_UNKNOWN;
private boolean acceptedAutomatically = false;
private int mProgress = 0;
private long mLastGuiRefresh = 0;
- public HttpConnection(HttpConnectionManager manager) {
+ public HttpDownloadConnection(HttpConnectionManager manager) {
this.mHttpConnectionManager = manager;
this.mXmppConnectionService = manager.getXmppConnectionService();
}
@@ -63,8 +57,12 @@ public class HttpConnection implements Downloadable {
}
public void init(Message message) {
+ init(message, false);
+ }
+
+ public void init(Message message, boolean interactive) {
this.message = message;
- this.message.setDownloadable(this);
+ this.message.setTransferable(this);
try {
mUrl = new URL(message.getBody());
String[] parts = mUrl.getPath().toLowerCase().split("\\.");
@@ -92,7 +90,7 @@ public class HttpConnection implements Downloadable {
&& this.file.getKey() == null) {
this.message.setEncryption(Message.ENCRYPTION_NONE);
}
- checkFileSize(false);
+ checkFileSize(interactive);
} catch (MalformedURLException e) {
this.cancel();
}
@@ -104,7 +102,7 @@ public class HttpConnection implements Downloadable {
public void cancel() {
mHttpConnectionManager.finishConnection(this);
- message.setDownloadable(null);
+ message.setTransferable(null);
mXmppConnectionService.updateConversationUi();
}
@@ -112,7 +110,7 @@ public class HttpConnection implements Downloadable {
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(Uri.fromFile(file));
mXmppConnectionService.sendBroadcast(intent);
- message.setDownloadable(null);
+ message.setTransferable(null);
mHttpConnectionManager.finishConnection(this);
mXmppConnectionService.updateConversationUi();
if (acceptedAutomatically) {
@@ -125,42 +123,6 @@ public class HttpConnection implements Downloadable {
mXmppConnectionService.updateConversationUi();
}
- private void setupTrustManager(final HttpsURLConnection connection,
- final boolean interactive) {
- final X509TrustManager trustManager;
- final HostnameVerifier hostnameVerifier;
- if (interactive) {
- trustManager = mXmppConnectionService.getMemorizingTrustManager();
- hostnameVerifier = mXmppConnectionService
- .getMemorizingTrustManager().wrapHostnameVerifier(
- new StrictHostnameVerifier());
- } else {
- trustManager = mXmppConnectionService.getMemorizingTrustManager()
- .getNonInteractive();
- hostnameVerifier = mXmppConnectionService
- .getMemorizingTrustManager()
- .wrapHostnameVerifierNonInteractive(
- new StrictHostnameVerifier());
- }
- try {
- final SSLContext sc = SSLContext.getInstance("TLS");
- sc.init(null, new X509TrustManager[]{trustManager},
- mXmppConnectionService.getRNG());
-
- final SSLSocketFactory sf = sc.getSocketFactory();
- final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites(
- sf.getSupportedCipherSuites());
- if (cipherSuites.length > 0) {
- sc.getDefaultSSLParameters().setCipherSuites(cipherSuites);
-
- }
-
- connection.setSSLSocketFactory(sf);
- connection.setHostnameVerifier(hostnameVerifier);
- } catch (final KeyManagementException | NoSuchAlgorithmException ignored) {
- }
- }
-
private class FileSizeChecker implements Runnable {
private boolean interactive = false;
@@ -176,43 +138,46 @@ public class HttpConnection implements Downloadable {
size = retrieveFileSize();
} catch (SSLHandshakeException e) {
changeStatus(STATUS_OFFER_CHECK_FILESIZE);
- HttpConnection.this.acceptedAutomatically = false;
- HttpConnection.this.mXmppConnectionService.getNotificationService().push(message);
+ HttpDownloadConnection.this.acceptedAutomatically = false;
+ HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message);
return;
} catch (IOException e) {
+ Log.d(Config.LOGTAG, "io exception in http file size checker: " + e.getMessage());
+ if (interactive) {
+ mXmppConnectionService.showErrorToastInUi(R.string.file_not_found_on_remote_host);
+ }
cancel();
return;
}
file.setExpectedSize(size);
if (size <= mHttpConnectionManager.getAutoAcceptFileSize()) {
- HttpConnection.this.acceptedAutomatically = true;
+ HttpDownloadConnection.this.acceptedAutomatically = true;
new Thread(new FileDownloader(interactive)).start();
} else {
changeStatus(STATUS_OFFER);
- HttpConnection.this.acceptedAutomatically = false;
- HttpConnection.this.mXmppConnectionService.getNotificationService().push(message);
+ HttpDownloadConnection.this.acceptedAutomatically = false;
+ HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message);
}
}
- private long retrieveFileSize() throws IOException,
- SSLHandshakeException {
- changeStatus(STATUS_CHECKING);
- HttpURLConnection connection = (HttpURLConnection) mUrl
- .openConnection();
- connection.setRequestMethod("HEAD");
- if (connection instanceof HttpsURLConnection) {
- setupTrustManager((HttpsURLConnection) connection, interactive);
- }
- connection.connect();
- String contentLength = connection.getHeaderField("Content-Length");
- if (contentLength == null) {
- throw new IOException();
- }
- try {
- return Long.parseLong(contentLength, 10);
- } catch (NumberFormatException e) {
- throw new IOException();
- }
+ private long retrieveFileSize() throws IOException {
+ Log.d(Config.LOGTAG,"retrieve file size. interactive:"+String.valueOf(interactive));
+ changeStatus(STATUS_CHECKING);
+ HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
+ connection.setRequestMethod("HEAD");
+ if (connection instanceof HttpsURLConnection) {
+ mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
+ }
+ connection.connect();
+ String contentLength = connection.getHeaderField("Content-Length");
+ if (contentLength == null) {
+ throw new IOException();
+ }
+ try {
+ return Long.parseLong(contentLength, 10);
+ } catch (NumberFormatException e) {
+ throw new IOException();
+ }
}
}
@@ -235,19 +200,18 @@ public class HttpConnection implements Downloadable {
} catch (SSLHandshakeException e) {
changeStatus(STATUS_OFFER);
} catch (IOException e) {
+ mXmppConnectionService.showErrorToastInUi(R.string.file_not_found_on_remote_host);
cancel();
}
}
private void download() throws SSLHandshakeException, IOException {
- HttpURLConnection connection = (HttpURLConnection) mUrl
- .openConnection();
+ HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
if (connection instanceof HttpsURLConnection) {
- setupTrustManager((HttpsURLConnection) connection, interactive);
+ mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
}
connection.connect();
- BufferedInputStream is = new BufferedInputStream(
- connection.getInputStream());
+ BufferedInputStream is = new BufferedInputStream(connection.getInputStream());
file.getParentFile().mkdirs();
file.createNewFile();
OutputStream os = file.createOutputStream();
@@ -269,8 +233,8 @@ public class HttpConnection implements Downloadable {
}
private void updateImageBounds() {
- message.setType(Message.TYPE_IMAGE);
- mXmppConnectionService.getFileBackend().updateFileParams(message,mUrl);
+ message.setType(Message.TYPE_FILE);
+ mXmppConnectionService.getFileBackend().updateFileParams(message, mUrl);
mXmppConnectionService.updateMessage(message);
}
@@ -302,9 +266,4 @@ public class HttpConnection implements Downloadable {
public int getProgress() {
return this.mProgress;
}
-
- @Override
- public String getMimeType() {
- return "";
- }
}
diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java
new file mode 100644
index 00000000..a3ab8dab
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java
@@ -0,0 +1,204 @@
+package eu.siacs.conversations.http;
+
+import android.app.PendingIntent;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.net.ssl.HttpsURLConnection;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Transferable;
+import eu.siacs.conversations.entities.DownloadableFile;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.persistance.FileBackend;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.ui.UiCallback;
+import eu.siacs.conversations.utils.CryptoHelper;
+import eu.siacs.conversations.utils.Xmlns;
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.OnIqPacketReceived;
+import eu.siacs.conversations.xmpp.jid.Jid;
+import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+
+public class HttpUploadConnection implements Transferable {
+
+ private HttpConnectionManager mHttpConnectionManager;
+ private XmppConnectionService mXmppConnectionService;
+
+ private boolean canceled = false;
+ private Account account;
+ private DownloadableFile file;
+ private Message message;
+ private URL mGetUrl;
+ private URL mPutUrl;
+
+ private byte[] key = null;
+
+ private long transmitted = 0;
+ private long expected = 1;
+
+ public HttpUploadConnection(HttpConnectionManager httpConnectionManager) {
+ this.mHttpConnectionManager = httpConnectionManager;
+ this.mXmppConnectionService = httpConnectionManager.getXmppConnectionService();
+ }
+
+ @Override
+ public boolean start() {
+ return false;
+ }
+
+ @Override
+ public int getStatus() {
+ return STATUS_UPLOADING;
+ }
+
+ @Override
+ public long getFileSize() {
+ return this.file.getExpectedSize();
+ }
+
+ @Override
+ public int getProgress() {
+ return (int) ((((double) transmitted) / expected) * 100);
+ }
+
+ @Override
+ public void cancel() {
+ this.canceled = true;
+ }
+
+ private void fail() {
+ mHttpConnectionManager.finishUploadConnection(this);
+ message.setTransferable(null);
+ mXmppConnectionService.markMessage(message,Message.STATUS_SEND_FAILED);
+ }
+
+ public void init(Message message) {
+ this.message = message;
+ message.setTransferable(this);
+ mXmppConnectionService.markMessage(message,Message.STATUS_UNSEND);
+ this.account = message.getConversation().getAccount();
+ this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
+ this.file.setExpectedSize(this.file.getSize());
+
+ if (Config.ENCRYPT_ON_HTTP_UPLOADED) {
+ this.key = new byte[48];
+ mXmppConnectionService.getRNG().nextBytes(this.key);
+ this.file.setKey(this.key);
+ }
+
+ Jid host = account.getXmppConnection().findDiscoItemByFeature(Xmlns.HTTP_UPLOAD);
+ IqPacket request = mXmppConnectionService.getIqGenerator().requestHttpUploadSlot(host,file);
+ mXmppConnectionService.sendIqPacket(account, request, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
+ Element slot = packet.findChild("slot",Xmlns.HTTP_UPLOAD);
+ if (slot != null) {
+ try {
+ mGetUrl = new URL(slot.findChildContent("get"));
+ mPutUrl = new URL(slot.findChildContent("put"));
+ if (!canceled) {
+ new Thread(new FileUploader()).start();
+ }
+ } catch (MalformedURLException e) {
+ fail();
+ }
+ } else {
+ fail();
+ }
+ } else {
+ fail();
+ }
+ }
+ });
+ }
+
+ private class FileUploader implements Runnable {
+
+ @Override
+ public void run() {
+ this.upload();
+ }
+
+ private void upload() {
+ OutputStream os = null;
+ InputStream is = null;
+ HttpURLConnection connection = null;
+ try {
+ Log.d(Config.LOGTAG, "uploading to " + mPutUrl.toString());
+ connection = (HttpURLConnection) mPutUrl.openConnection();
+ if (connection instanceof HttpsURLConnection) {
+ mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true);
+ }
+ connection.setRequestMethod("PUT");
+ connection.setFixedLengthStreamingMode((int) file.getExpectedSize());
+ connection.setDoOutput(true);
+ connection.connect();
+ os = connection.getOutputStream();
+ is = file.createInputStream();
+ transmitted = 0;
+ expected = file.getExpectedSize();
+ int count = -1;
+ byte[] buffer = new byte[4096];
+ while (((count = is.read(buffer)) != -1) && !canceled) {
+ transmitted += count;
+ os.write(buffer, 0, count);
+ mXmppConnectionService.updateConversationUi();
+ }
+ os.flush();
+ os.close();
+ is.close();
+ int code = connection.getResponseCode();
+ if (code == 200 || code == 201) {
+ Log.d(Config.LOGTAG, "finished uploading file");
+ Message.FileParams params = message.getFileParams();
+ if (key != null) {
+ mGetUrl = new URL(mGetUrl.toString() + "#" + CryptoHelper.bytesToHex(key));
+ }
+ mXmppConnectionService.getFileBackend().updateFileParams(message, mGetUrl);
+ message.setTransferable(null);
+ message.setCounterpart(message.getConversation().getJid().toBareJid());
+ if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
+ mXmppConnectionService.getPgpEngine().encrypt(message, new UiCallback<Message>() {
+ @Override
+ public void success(Message message) {
+ mXmppConnectionService.resendMessage(message);
+ }
+
+ @Override
+ public void error(int errorCode, Message object) {
+ fail();
+ }
+
+ @Override
+ public void userInputRequried(PendingIntent pi, Message object) {
+ fail();
+ }
+ });
+ } else {
+ mXmppConnectionService.resendMessage(message);
+ }
+ } else {
+ fail();
+ }
+ } catch (IOException e) {
+ Log.d(Config.LOGTAG, e.getMessage());
+ fail();
+ } finally {
+ FileBackend.close(is);
+ FileBackend.close(os);
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java
index 473195bd..5f085925 100644
--- a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java
@@ -15,6 +15,7 @@ import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.jid.Jid;
+import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
public abstract class AbstractParser {
@@ -33,24 +34,23 @@ public abstract class AbstractParser {
* attribute or the current time is less than the value of the 'stamp'
* attribute the current time is returned.
*/
- protected long getTimestamp(Element packet) {
- long now = System.currentTimeMillis();
- Element delay = packet.findChild("delay");
- if (delay == null) {
- return now;
- }
- String stamp = delay.getAttribute("stamp");
- if (stamp == null) {
- return now;
+ public static Long getTimestamp(Element element, Long defaultValue) {
+ Element delay = element.findChild("delay","urn:xmpp:delay");
+ if (delay != null) {
+ String stamp = delay.getAttribute("stamp");
+ if (stamp != null) {
+ try {
+ return AbstractParser.parseTimestamp(delay.getAttribute("stamp")).getTime();
+ } catch (ParseException e) {
+ return defaultValue;
+ }
+ }
}
- /*long time = parseTimestamp(stamp).getTime();
- return now < time ? now : time;*/
- try {
- long time = parseTimestamp(stamp).getTime();
- return now < time ? now : time;
- } catch (ParseException e) {
- return now;
- }
+ return defaultValue;
+ }
+
+ protected long getTimestamp(Element packet) {
+ return getTimestamp(packet,System.currentTimeMillis());
}
/**
@@ -82,8 +82,7 @@ public abstract class AbstractParser {
updateLastseen(packet, account, from, presenceOverwrite);
}
- protected void updateLastseen(final Element packet, final Account account, final Jid from,
- final boolean presenceOverwrite) {
+ protected void updateLastseen(final Element packet, final Account account, final Jid from, final boolean presenceOverwrite) {
final String presence = from == null || from.isBareJid() ? "" : from.getResourcepart();
final Contact contact = account.getRoster().getContact(from);
final long timestamp = getTimestamp(packet);
@@ -100,10 +99,6 @@ public abstract class AbstractParser {
if (item == null) {
return null;
}
- Element data = item.findChild("data", "urn:xmpp:avatar:data");
- if (data == null) {
- return null;
- }
- return data.getContent();
+ return item.findChildContent("data", "urn:xmpp:avatar:data");
}
}
diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
index 9f36f1e9..81fd89cd 100644
--- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
@@ -1,9 +1,13 @@
package eu.siacs.conversations.parser;
+import android.util.Log;
+import android.util.Pair;
+
import net.java.otr4j.session.Session;
import net.java.otr4j.session.SessionStatus;
import de.tzur.conversations.Settings;
+import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
@@ -26,12 +30,12 @@ public class MessageParser extends AbstractParser implements
super(service);
}
- private boolean extractChatState(Conversation conversation, final Element element) {
- ChatState state = ChatState.parse(element);
+ private boolean extractChatState(Conversation conversation, final MessagePacket packet) {
+ ChatState state = ChatState.parse(packet);
if (state != null && conversation != null) {
final Account account = conversation.getAccount();
- Jid from = element.getAttributeAsJid("from");
- if (from != null && from.toBareJid().equals(account.getJid().toBareJid())) {
+ Jid from = packet.getFrom();
+ if (from.toBareJid().equals(account.getJid().toBareJid())) {
conversation.setOutgoingChatState(state);
return false;
} else {
@@ -41,99 +45,40 @@ public class MessageParser extends AbstractParser implements
return false;
}
- private Message parseChat(MessagePacket packet, Account account) {
- final Jid jid = packet.getFrom();
- if (jid == null) {
- return null;
- }
- Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, jid.toBareJid(), false);
- String pgpBody = getPgpBody(packet);
- Message finishedMessage;
- if (pgpBody != null) {
- finishedMessage = new Message(conversation,
- pgpBody, Message.ENCRYPTION_PGP, Message.STATUS_RECEIVED);
- } else {
- finishedMessage = new Message(conversation,
- packet.getBody(), Message.ENCRYPTION_NONE,
- Message.STATUS_RECEIVED);
- }
- finishedMessage.setRemoteMsgId(packet.getId());
- finishedMessage.markable = isMarkable(packet);
- if (conversation.getMode() == Conversation.MODE_MULTI
- && !jid.isBareJid()) {
- final Jid trueCounterpart = conversation.getMucOptions()
- .getTrueCounterpart(jid.getResourcepart());
- if (trueCounterpart != null) {
- updateLastseen(packet, account, trueCounterpart, false);
- }
- finishedMessage.setType(Message.TYPE_PRIVATE);
- finishedMessage.setTrueCounterpart(trueCounterpart);
- if (conversation.hasDuplicateMessage(finishedMessage)) {
- return null;
- }
- } else {
- updateLastseen(packet, account, true);
- }
- finishedMessage.setCounterpart(jid);
- finishedMessage.setTime(getTimestamp(packet));
- extractChatState(conversation,packet);
- return finishedMessage;
- }
-
- private Message parseOtrChat(MessagePacket packet, Account account) {
- final Jid to = packet.getTo();
- final Jid from = packet.getFrom();
- if (to == null || from == null) {
- return null;
- }
- boolean properlyAddressed = !to.isBareJid() || account.countPresences() == 1;
- Conversation conversation = mXmppConnectionService
- .findOrCreateConversation(account, from.toBareJid(), false);
+ private Message parseOtrChat(String body, Jid from, String id, Conversation conversation) {
String presence;
if (from.isBareJid()) {
presence = "";
} else {
presence = from.getResourcepart();
}
- extractChatState(conversation, packet);
- updateLastseen(packet, account, true);
- String body = packet.getBody();
if (body.matches("^\\?OTRv\\d{1,2}\\?.*")) {
conversation.endOtrIfNeeded();
}
if (!conversation.hasValidOtrSession()) {
- if (properlyAddressed) {
- conversation.startOtrSession(presence,false);
- } else {
- return null;
- }
+ conversation.startOtrSession(presence,false);
} else {
- String foreignPresence = conversation.getOtrSession()
- .getSessionID().getUserID();
+ String foreignPresence = conversation.getOtrSession().getSessionID().getUserID();
if (!foreignPresence.equals(presence)) {
conversation.endOtrIfNeeded();
- if (properlyAddressed) {
- conversation.startOtrSession(presence, false);
- } else {
- return null;
- }
+ conversation.startOtrSession(presence, false);
}
}
try {
- conversation.setLastReceivedOtrMessageId(packet.getId());
+ conversation.setLastReceivedOtrMessageId(id);
Session otrSession = conversation.getOtrSession();
- SessionStatus before = otrSession.getSessionStatus();
body = otrSession.transformReceiving(body);
- SessionStatus after = otrSession.getSessionStatus();
- if ((before != after) && (after == SessionStatus.ENCRYPTED)) {
+ SessionStatus status = otrSession.getSessionStatus();
+ if (body == null && status == SessionStatus.ENCRYPTED) {
conversation.setNextEncryption(Message.ENCRYPTION_OTR);
mXmppConnectionService.onOtrSessionEstablished(conversation);
- } else if ((before != after) && (after == SessionStatus.FINISHED)) {
+ return null;
+ } else if (body == null && status == SessionStatus.FINISHED) {
conversation.setNextEncryption(Message.ENCRYPTION_NONE);
conversation.resetOtrSession();
mXmppConnectionService.updateConversationUi();
- }
- if ((body == null) || (body.isEmpty())) {
+ return null;
+ } else if (body == null || (body.isEmpty())) {
return null;
}
if (body.startsWith(CryptoHelper.FILETRANSFER)) {
@@ -141,12 +86,7 @@ public class MessageParser extends AbstractParser implements
conversation.setSymmetricKey(CryptoHelper.hexToBytes(key));
return null;
}
- Message finishedMessage = new Message(conversation, body, Message.ENCRYPTION_OTR,
- Message.STATUS_RECEIVED);
- finishedMessage.setTime(getTimestamp(packet));
- finishedMessage.setRemoteMsgId(packet.getId());
- finishedMessage.markable = isMarkable(packet);
- finishedMessage.setCounterpart(from);
+ Message finishedMessage = new Message(conversation, body, Message.ENCRYPTION_OTR, Message.STATUS_RECEIVED);
conversation.setLastReceivedOtrMessageId(null);
return finishedMessage;
} catch (Exception e) {
@@ -155,499 +95,343 @@ public class MessageParser extends AbstractParser implements
}
}
- private Message parseGroupchat(MessagePacket packet, Account account) {
- int status;
- final Jid from = packet.getFrom();
- if (from == null) {
- return null;
- }
- if (mXmppConnectionService.find(account.pendingConferenceLeaves,
- account, from.toBareJid()) != null) {
- return null;
- }
- Conversation conversation = mXmppConnectionService
- .findOrCreateConversation(account, from.toBareJid(), true);
- final Jid trueCounterpart = conversation.getMucOptions().getTrueCounterpart(from.getResourcepart());
- if (trueCounterpart != null) {
- updateLastseen(packet, account, trueCounterpart, false);
- }
- if (packet.hasChild("subject")) {
- conversation.setHasMessagesLeftOnServer(true);
- conversation.getMucOptions().setSubject(packet.findChild("subject").getContent());
- mXmppConnectionService.updateConversationUi();
- return null;
+ private class Invite {
+ Jid jid;
+ String password;
+ Invite(Jid jid, String password) {
+ this.jid = jid;
+ this.password = password;
}
- final Element x = packet.findChild("x", "http://jabber.org/protocol/muc#user");
- if (from.isBareJid() && (x == null || !x.hasChild("status"))) {
- return null;
- } else if (from.isBareJid() && x.hasChild("status")) {
- for(Element child : x.getChildren()) {
- if (child.getName().equals("status")) {
- String code = child.getAttribute("code");
- if (code.contains(MucOptions.STATUS_CODE_ROOM_CONFIG_CHANGED)) {
- mXmppConnectionService.fetchConferenceConfiguration(conversation);
- }
+ public boolean execute(Account account) {
+ if (jid != null) {
+ Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, jid, true);
+ if (!conversation.getMucOptions().online()) {
+ conversation.getMucOptions().setPassword(password);
+ mXmppConnectionService.databaseBackend.updateConversation(conversation);
+ mXmppConnectionService.joinMuc(conversation);
+ mXmppConnectionService.updateConversationUi();
}
+ return true;
}
- return null;
+ return false;
}
+ }
- if (from.getResourcepart().equals(conversation.getMucOptions().getActualNick())) {
- if (mXmppConnectionService.markMessage(conversation,
- packet.getId(), Message.STATUS_SEND_RECEIVED)) {
- return null;
- } else if (packet.getId() == null) {
- Message message = conversation.findSentMessageWithBody(packet.getBody());
- if (message != null) {
- mXmppConnectionService.markMessage(message,Message.STATUS_SEND_RECEIVED);
- return null;
- } else {
- status = Message.STATUS_SEND;
- }
- } else {
- status = Message.STATUS_SEND;
+ private Invite extractInvite(Element message) {
+ Element x = message.findChild("x", "http://jabber.org/protocol/muc#user");
+ if (x != null) {
+ Element invite = x.findChild("invite");
+ if (invite != null) {
+ Element pw = x.findChild("password");
+ return new Invite(message.getAttributeAsJid("from"), pw != null ? pw.getContent(): null);
}
} else {
- status = Message.STATUS_RECEIVED;
- }
- String pgpBody = getPgpBody(packet);
- Message finishedMessage;
- if (pgpBody == null) {
- finishedMessage = new Message(conversation,
- packet.getBody(), Message.ENCRYPTION_NONE, status);
- } else {
- finishedMessage = new Message(conversation, pgpBody,
- Message.ENCRYPTION_PGP, status);
- }
- finishedMessage.setRemoteMsgId(packet.getId());
- finishedMessage.markable = isMarkable(packet);
- finishedMessage.setCounterpart(from);
- if (status == Message.STATUS_RECEIVED) {
- finishedMessage.setTrueCounterpart(conversation.getMucOptions()
- .getTrueCounterpart(from.getResourcepart()));
- }
- if (packet.hasChild("delay")
- && conversation.hasDuplicateMessage(finishedMessage)) {
- return null;
+ x = message.findChild("x","jabber:x:conference");
+ if (x != null) {
+ return new Invite(x.getAttributeAsJid("jid"),x.getAttribute("password"));
+ }
}
- finishedMessage.setTime(getTimestamp(packet));
- return finishedMessage;
+ return null;
}
- private Message parseCarbonMessage(final MessagePacket packet, final Account account) {
- int status;
- final Jid fullJid;
- Element forwarded;
- if (packet.hasChild("received", "urn:xmpp:carbons:2")) {
- forwarded = packet.findChild("received", "urn:xmpp:carbons:2")
- .findChild("forwarded", "urn:xmpp:forward:0");
- status = Message.STATUS_RECEIVED;
- } else if (packet.hasChild("sent", "urn:xmpp:carbons:2")) {
- forwarded = packet.findChild("sent", "urn:xmpp:carbons:2")
- .findChild("forwarded", "urn:xmpp:forward:0");
- status = Message.STATUS_SEND;
- } else {
- return null;
- }
- if (forwarded == null) {
- return null;
- }
- Element message = forwarded.findChild("message");
- if (message == null) {
- return null;
- }
- if (!message.hasChild("body")) {
- if (status == Message.STATUS_RECEIVED
- && message.getAttribute("from") != null) {
- parseNonMessage(message, account);
- } else if (status == Message.STATUS_SEND
- && message.hasChild("displayed", "urn:xmpp:chat-markers:0")) {
- final Jid to = message.getAttributeAsJid("to");
- if (to != null) {
- final Conversation conversation = mXmppConnectionService.find(
- mXmppConnectionService.getConversations(), account,
- to.toBareJid());
- if (conversation != null) {
- mXmppConnectionService.markRead(conversation);
+ private void parseEvent(final Element event, final Jid from, final Account account) {
+ Element items = event.findChild("items");
+ String node = items == null ? null : items.getAttribute("node");
+ if ("urn:xmpp:avatar:metadata".equals(node)) {
+ Avatar avatar = Avatar.parseMetadata(items);
+ if (avatar != null) {
+ avatar.owner = from;
+ if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
+ if (account.getJid().toBareJid().equals(from)) {
+ if (account.setAvatar(avatar.getFilename())) {
+ mXmppConnectionService.databaseBackend.updateAccount(account);
+ }
+ mXmppConnectionService.getAvatarService().clear(account);
+ mXmppConnectionService.updateConversationUi();
+ mXmppConnectionService.updateAccountUi();
+ } else {
+ Contact contact = account.getRoster().getContact(from);
+ contact.setAvatar(avatar);
+ mXmppConnectionService.getAvatarService().clear(contact);
+ mXmppConnectionService.updateConversationUi();
+ mXmppConnectionService.updateRosterUi();
}
+ } else {
+ mXmppConnectionService.fetchAvatar(account, avatar);
}
}
- return null;
- }
- if (status == Message.STATUS_RECEIVED) {
- fullJid = message.getAttributeAsJid("from");
- if (fullJid == null) {
- return null;
- } else {
- updateLastseen(message, account, true);
- }
- } else {
- fullJid = message.getAttributeAsJid("to");
- if (fullJid == null) {
- return null;
+ } else if ("http://jabber.org/protocol/nick".equals(node)) {
+ Element i = items.findChild("item");
+ Element nick = i == null ? null : i.findChild("nick", "http://jabber.org/protocol/nick");
+ if (nick != null) {
+ Contact contact = account.getRoster().getContact(from);
+ contact.setPresenceName(nick.getContent());
+ mXmppConnectionService.getAvatarService().clear(account);
+ mXmppConnectionService.updateConversationUi();
+ mXmppConnectionService.updateAccountUi();
}
}
- if (message.hasChild("x","http://jabber.org/protocol/muc#user")
- && "chat".equals(message.getAttribute("type"))) {
- return null;
- }
- Conversation conversation = mXmppConnectionService
- .findOrCreateConversation(account, fullJid.toBareJid(), false);
- String pgpBody = getPgpBody(message);
- Message finishedMessage;
- if (pgpBody != null) {
- finishedMessage = new Message(conversation, pgpBody,
- Message.ENCRYPTION_PGP, status);
- } else {
- String body = message.findChild("body").getContent();
- finishedMessage = new Message(conversation, body,
- Message.ENCRYPTION_NONE, status);
- }
- extractChatState(conversation,message);
- finishedMessage.setTime(getTimestamp(message));
- finishedMessage.setRemoteMsgId(message.getAttribute("id"));
- finishedMessage.markable = isMarkable(message);
- finishedMessage.setCounterpart(fullJid);
- if (conversation.getMode() == Conversation.MODE_MULTI
- && !fullJid.isBareJid()) {
- finishedMessage.setType(Message.TYPE_PRIVATE);
- finishedMessage.setTrueCounterpart(conversation.getMucOptions()
- .getTrueCounterpart(fullJid.getResourcepart()));
- if (conversation.hasDuplicateMessage(finishedMessage)) {
- return null;
+ }
+
+ private boolean handleErrorMessage(Account account, MessagePacket packet) {
+ if (packet.getType() == MessagePacket.TYPE_ERROR) {
+ Jid from = packet.getFrom();
+ if (from != null) {
+ Message message = mXmppConnectionService.markMessage(account,
+ from.toBareJid(),
+ packet.getId(),
+ Message.STATUS_SEND_FAILED);
+ if (message != null && message.getEncryption() == Message.ENCRYPTION_OTR) {
+ message.getConversation().endOtrIfNeeded();
+ }
}
+ return true;
}
- return finishedMessage;
+ return false;
}
- private Message parseMamMessage(MessagePacket packet, final Account account) {
- final Element result = packet.findChild("result","urn:xmpp:mam:0");
- if (result == null ) {
- return null;
- }
- final MessageArchiveService.Query query = this.mXmppConnectionService.getMessageArchiveService().findQuery(result.getAttribute("queryid"));
- if (query!=null) {
- query.incrementTotalCount();
- }
- final Element forwarded = result.findChild("forwarded","urn:xmpp:forward:0");
- if (forwarded == null) {
- return null;
- }
- final Element message = forwarded.findChild("message");
- if (message == null) {
- return null;
+ @Override
+ public void onMessagePacketReceived(Account account, MessagePacket original) {
+ if (handleErrorMessage(account, original)) {
+ return;
}
- final Element body = message.findChild("body");
- if (body == null || message.hasChild("private","urn:xmpp:carbons:2") || message.hasChild("no-copy","urn:xmpp:hints")) {
- return null;
+ final MessagePacket packet;
+ Long timestamp = null;
+ final boolean isForwarded;
+ String serverMsgId = null;
+ final Element fin = original.findChild("fin", "urn:xmpp:mam:0");
+ if (fin != null) {
+ mXmppConnectionService.getMessageArchiveService().processFin(fin,original.getFrom());
+ return;
}
- int encryption;
- String content = getPgpBody(message);
- if (content != null) {
- encryption = Message.ENCRYPTION_PGP;
+ final Element result = original.findChild("result","urn:xmpp:mam:0");
+ final MessageArchiveService.Query query = result == null ? null : mXmppConnectionService.getMessageArchiveService().findQuery(result.getAttribute("queryid"));
+ if (query != null && query.validFrom(original.getFrom())) {
+ Pair<MessagePacket, Long> f = original.getForwardedMessagePacket("result", "urn:xmpp:mam:0");
+ if (f == null) {
+ return;
+ }
+ timestamp = f.second;
+ packet = f.first;
+ isForwarded = true;
+ serverMsgId = result.getAttribute("id");
+ query.incrementTotalCount();
+ } else if (query != null) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": received mam result from invalid sender");
+ return;
+ } else if (original.fromServer(account)) {
+ Pair<MessagePacket, Long> f;
+ f = original.getForwardedMessagePacket("received", "urn:xmpp:carbons:2");
+ f = f == null ? original.getForwardedMessagePacket("sent", "urn:xmpp:carbons:2") : f;
+ packet = f != null ? f.first : original;
+ if (handleErrorMessage(account, packet)) {
+ return;
+ }
+ timestamp = f != null ? f.second : null;
+ isForwarded = f != null;
} else {
- encryption = Message.ENCRYPTION_NONE;
- content = body.getContent();
+ packet = original;
+ isForwarded = false;
}
- if (content == null) {
- return null;
+
+ if (timestamp == null) {
+ timestamp = AbstractParser.getTimestamp(packet, System.currentTimeMillis());
}
- final long timestamp = getTimestamp(forwarded);
- final Jid to = message.getAttributeAsJid("to");
- final Jid from = message.getAttributeAsJid("from");
- Jid counterpart;
+ final String body = packet.getBody();
+ final String encrypted = packet.findChildContent("x", "jabber:x:encrypted");
+ final Element mucUserElement = packet.findChild("x","http://jabber.org/protocol/muc#user");
int status;
- Conversation conversation;
- if (from!=null && to != null && from.toBareJid().equals(account.getJid().toBareJid())) {
+ final Jid counterpart;
+ final Jid to = packet.getTo();
+ final Jid from = packet.getFrom();
+ final String remoteMsgId = packet.getId();
+
+ if (from == null || to == null) {
+ Log.d(Config.LOGTAG,"no to or from in: "+packet.toString());
+ return;
+ }
+
+ boolean isTypeGroupChat = packet.getType() == MessagePacket.TYPE_GROUPCHAT;
+ boolean isProperlyAddressed = !to.isBareJid() || account.countPresences() == 1;
+ boolean isMucStatusMessage = from.isBareJid() && mucUserElement != null && mucUserElement.hasChild("status");
+ if (packet.fromAccount(account)) {
status = Message.STATUS_SEND;
- conversation = this.mXmppConnectionService.findOrCreateConversation(account,to.toBareJid(),false,query);
counterpart = to;
- } else if (from !=null && to != null) {
+ } else {
status = Message.STATUS_RECEIVED;
- conversation = this.mXmppConnectionService.findOrCreateConversation(account,from.toBareJid(),false,query);
counterpart = from;
- } else {
- return null;
- }
- Message finishedMessage = new Message(conversation,content,encryption,status);
- finishedMessage.setTime(timestamp);
- finishedMessage.setCounterpart(counterpart);
- finishedMessage.setRemoteMsgId(message.getAttribute("id"));
- finishedMessage.setServerMsgId(result.getAttribute("id"));
- if (conversation.hasDuplicateMessage(finishedMessage)) {
- return null;
}
- if (query!=null) {
- query.incrementMessageCount();
- }
- return finishedMessage;
- }
-
- private void parseError(final MessagePacket packet, final Account account) {
- final Jid from = packet.getFrom();
- mXmppConnectionService.markMessage(account, from.toBareJid(),
- packet.getId(), Message.STATUS_SEND_FAILED);
- }
- private void parseNonMessage(Element packet, Account account) {
- final Jid from = packet.getAttributeAsJid("from");
- if (extractChatState(from == null ? null : mXmppConnectionService.find(account,from), packet)) {
- mXmppConnectionService.updateConversationUi();
- }
- Element invite = extractInvite(packet);
- if (invite != null) {
- Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, from, true);
- if (!conversation.getMucOptions().online()) {
- Element password = invite.findChild("password");
- conversation.getMucOptions().setPassword(password == null ? null : password.getContent());
- mXmppConnectionService.databaseBackend.updateConversation(conversation);
- mXmppConnectionService.joinMuc(conversation);
- mXmppConnectionService.updateConversationUi();
- }
- }
- if (packet.hasChild("event", "http://jabber.org/protocol/pubsub#event")) {
- Element event = packet.findChild("event",
- "http://jabber.org/protocol/pubsub#event");
- parseEvent(event, from, account);
- } else if (from != null && packet.hasChild("displayed", "urn:xmpp:chat-markers:0")) {
- String id = packet
- .findChild("displayed", "urn:xmpp:chat-markers:0")
- .getAttribute("id");
- updateLastseen(packet, account, true);
- final Message displayedMessage = mXmppConnectionService.markMessage(account, from.toBareJid(), id, Message.STATUS_SEND_DISPLAYED);
- Message message = displayedMessage == null ? null :displayedMessage.prev();
- while (message != null
- && message.getStatus() == Message.STATUS_SEND_RECEIVED
- && message.getTimeSent() < displayedMessage.getTimeSent()) {
- mXmppConnectionService.markMessage(message, Message.STATUS_SEND_DISPLAYED);
- message = message.prev();
- }
- } else if (from != null
- && packet.hasChild("received", "urn:xmpp:chat-markers:0")) {
- String id = packet.findChild("received", "urn:xmpp:chat-markers:0")
- .getAttribute("id");
- updateLastseen(packet, account, false);
- mXmppConnectionService.markMessage(account, from.toBareJid(),
- id, Message.STATUS_SEND_RECEIVED);
- } else if (from != null
- && packet.hasChild("received", "urn:xmpp:receipts")) {
- String id = packet.findChild("received", "urn:xmpp:receipts")
- .getAttribute("id");
- updateLastseen(packet, account, false);
- mXmppConnectionService.markMessage(account, from.toBareJid(),
- id, Message.STATUS_SEND_RECEIVED);
+ Invite invite = extractInvite(packet);
+ if (invite != null && invite.execute(account)) {
+ return;
}
- }
- private Element extractInvite(Element message) {
- Element x = message.findChild("x","http://jabber.org/protocol/muc#user");
- if (x == null) {
- x = message.findChild("x","jabber:x:conference");
- }
- if (x != null && x.hasChild("invite")) {
- return x;
- } else {
- return null;
+ if (extractChatState(mXmppConnectionService.find(account,from), packet)) {
+ mXmppConnectionService.updateConversationUi();
}
- }
- private void parseEvent(final Element event, final Jid from, final Account account) {
- Element items = event.findChild("items");
- if (items == null) {
- return;
- }
- String node = items.getAttribute("node");
- if (node == null) {
- return;
- }
- if (node.equals("urn:xmpp:avatar:metadata")) {
- Avatar avatar = Avatar.parseMetadata(items);
- if (avatar != null) {
- avatar.owner = from;
- if (mXmppConnectionService.getFileBackend().isAvatarCached(
- avatar)) {
- if (account.getJid().toBareJid().equals(from)) {
- if (account.setAvatar(avatar.getFilename())) {
- mXmppConnectionService.databaseBackend
- .updateAccount(account);
- }
- mXmppConnectionService.getAvatarService().clear(
- account);
- mXmppConnectionService.updateConversationUi();
- mXmppConnectionService.updateAccountUi();
+ if ((body != null || encrypted != null) && !isMucStatusMessage) {
+ Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.toBareJid(), isTypeGroupChat);
+ if (isTypeGroupChat) {
+ if (counterpart.getResourcepart().equals(conversation.getMucOptions().getActualNick())) {
+ status = Message.STATUS_SEND_RECEIVED;
+ if (mXmppConnectionService.markMessage(conversation, remoteMsgId, status)) {
+ return;
} else {
- Contact contact = account.getRoster().getContact(
- from);
- contact.setAvatar(avatar.getFilename());
- mXmppConnectionService.getAvatarService().clear(
- contact);
- mXmppConnectionService.updateConversationUi();
- mXmppConnectionService.updateRosterUi();
+ Message message = conversation.findSentMessageWithBody(body);
+ if (message != null) {
+ message.setRemoteMsgId(remoteMsgId);
+ mXmppConnectionService.markMessage(message, status);
+ return;
+ }
}
} else {
- mXmppConnectionService.fetchAvatar(account, avatar);
+ status = Message.STATUS_RECEIVED;
}
}
- } else if (node.equals("http://jabber.org/protocol/nick")) {
- Element item = items.findChild("item");
- if (item != null) {
- Element nick = item.findChild("nick",
- "http://jabber.org/protocol/nick");
- if (nick != null) {
- if (from != null) {
- Contact contact = account.getRoster().getContact(
- from);
- contact.setPresenceName(nick.getContent());
- mXmppConnectionService.getAvatarService().clear(account);
- mXmppConnectionService.updateConversationUi();
- mXmppConnectionService.updateAccountUi();
+ Message message;
+ if (body != null && body.startsWith("?OTR")) {
+ if (!isForwarded && !isTypeGroupChat && isProperlyAddressed) {
+ message = parseOtrChat(body, from, remoteMsgId, conversation);
+ if (message == null) {
+ return;
}
+ } else {
+ message = new Message(conversation, body, Message.ENCRYPTION_NONE, status);
}
+ } else if (encrypted != null) {
+ message = new Message(conversation, encrypted, Message.ENCRYPTION_PGP, status);
+ } else {
+ message = new Message(conversation, body, Message.ENCRYPTION_NONE, status);
}
- }
- }
-
- private String getPgpBody(Element message) {
- Element child = message.findChild("x", "jabber:x:encrypted");
- if (child == null) {
- return null;
- } else {
- return child.getContent();
- }
- }
-
- private boolean isMarkable(Element message) {
- return message.hasChild("markable", "urn:xmpp:chat-markers:0");
- }
-
- @Override
- public void onMessagePacketReceived(Account account, MessagePacket packet) {
- Message message = null;
- this.parseNick(packet, account);
- if ((packet.getType() == MessagePacket.TYPE_CHAT || packet.getType() == MessagePacket.TYPE_NORMAL)) {
- if ((packet.getBody() != null)
- && (packet.getBody().startsWith("?OTR"))) {
- message = this.parseOtrChat(packet, account);
- if (message != null) {
- message.markUnread();
+ message.setCounterpart(counterpart);
+ message.setRemoteMsgId(remoteMsgId);
+ message.setServerMsgId(serverMsgId);
+ message.setTime(timestamp);
+ message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
+ if (conversation.getMode() == Conversation.MODE_MULTI) {
+ message.setTrueCounterpart(conversation.getMucOptions().getTrueCounterpart(counterpart.getResourcepart()));
+ if (!isTypeGroupChat) {
+ message.setType(Message.TYPE_PRIVATE);
}
- } else if (packet.hasChild("body") && extractInvite(packet) == null) {
- message = this.parseChat(packet, account);
- if (message != null) {
+ }
+ updateLastseen(packet,account,true);
+ boolean checkForDuplicates = serverMsgId != null
+ || (isTypeGroupChat && packet.hasChild("delay","urn:xmpp:delay"))
+ || message.getType() == Message.TYPE_PRIVATE;
+ if (checkForDuplicates && conversation.hasDuplicateMessage(message)) {
+ Log.d(Config.LOGTAG,"skipping duplicate message from "+message.getCounterpart().toString()+" "+message.getBody());
+ return;
+ }
+ if (query != null) {
+ query.incrementMessageCount();
+ }
+ conversation.add(message);
+ if (serverMsgId == null) {
+ if (status == Message.STATUS_SEND || status == Message.STATUS_SEND_RECEIVED) {
+ mXmppConnectionService.markRead(conversation);
+ account.activateGracePeriod();
+ } else {
message.markUnread();
}
- } else if (packet.hasChild("received", "urn:xmpp:carbons:2")
- || (packet.hasChild("sent", "urn:xmpp:carbons:2"))) {
- message = this.parseCarbonMessage(packet, account);
- if (message != null) {
- if (message.getStatus() == Message.STATUS_SEND) {
- account.activateGracePeriod();
- mXmppConnectionService.markRead(message.getConversation());
- } else {
- message.markUnread();
- }
+ mXmppConnectionService.updateConversationUi();
+ }
+
+ if (mXmppConnectionService.confirmMessages() && remoteMsgId != null && !isForwarded) {
+ if (packet.hasChild("markable", "urn:xmpp:chat-markers:0")) {
+ MessagePacket receipt = mXmppConnectionService
+ .getMessageGenerator().received(account, packet, "urn:xmpp:chat-markers:0");
+ mXmppConnectionService.sendMessagePacket(account, receipt);
}
- } else if (packet.hasChild("result","urn:xmpp:mam:0")) {
- message = parseMamMessage(packet, account);
- if (message != null) {
- Conversation conversation = message.getConversation();
- conversation.add(message);
- mXmppConnectionService.databaseBackend.createMessage(message);
+ if (packet.hasChild("request", "urn:xmpp:receipts")) {
+ MessagePacket receipt = mXmppConnectionService
+ .getMessageGenerator().received(account, packet, "urn:xmpp:receipts");
+ mXmppConnectionService.sendMessagePacket(account, receipt);
}
- return;
- } else if (packet.hasChild("fin","urn:xmpp:mam:0")) {
- Element fin = packet.findChild("fin","urn:xmpp:mam:0");
- mXmppConnectionService.getMessageArchiveService().processFin(fin);
- } else {
- parseNonMessage(packet, account);
}
- } else if (packet.getType() == MessagePacket.TYPE_GROUPCHAT) {
- message = this.parseGroupchat(packet, account);
- if (message != null) {
- if (message.getStatus() == Message.STATUS_RECEIVED) {
- message.markUnread();
- } else {
- mXmppConnectionService.markRead(message.getConversation());
- account.activateGracePeriod();
+ if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().advancedStreamFeaturesLoaded()) {
+ if (conversation.setLastMessageTransmitted(System.currentTimeMillis())) {
+ mXmppConnectionService.updateConversation(conversation);
}
}
- } else if (packet.getType() == MessagePacket.TYPE_ERROR) {
- this.parseError(packet, account);
- return;
- } else if (packet.getType() == MessagePacket.TYPE_HEADLINE) {
- this.parseHeadline(packet, account);
- return;
- }
- if ((message == null) || (message.getBody() == null)) {
- return;
- }
- if ((Settings.CONFIRM_MESSAGE_RECEIVED)
- && ((packet.getId() != null))) {
- if (packet.hasChild("markable", "urn:xmpp:chat-markers:0")) {
- MessagePacket receipt = mXmppConnectionService
- .getMessageGenerator().received(account, packet,
- "urn:xmpp:chat-markers:0");
- mXmppConnectionService.sendMessagePacket(account, receipt);
+
+ if (message.getStatus() == Message.STATUS_RECEIVED
+ && conversation.getOtrSession() != null
+ && !conversation.getOtrSession().getSessionID().getUserID()
+ .equals(message.getCounterpart().getResourcepart())) {
+ conversation.endOtrIfNeeded();
}
- if (packet.hasChild("request", "urn:xmpp:receipts")) {
- MessagePacket receipt = mXmppConnectionService
- .getMessageGenerator().received(account, packet,
- "urn:xmpp:receipts");
- mXmppConnectionService.sendMessagePacket(account, receipt);
+
+ if (message.getEncryption() == Message.ENCRYPTION_NONE || mXmppConnectionService.saveEncryptedMessages()) {
+ mXmppConnectionService.databaseBackend.createMessage(message);
}
- }
- Conversation conversation = message.getConversation();
- conversation.add(message);
- if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().advancedStreamFeaturesLoaded()) {
- if (conversation.setLastMessageTransmitted(System.currentTimeMillis())) {
- mXmppConnectionService.updateConversation(conversation);
+ final HttpConnectionManager manager = this.mXmppConnectionService.getHttpConnectionManager();
+ if (message.trusted() && message.treatAsDownloadable() != Message.Decision.NEVER && manager.getAutoAcceptFileSize() > 0) {
+ manager.createNewDownloadConnection(message);
+ } else if (!message.isRead()) {
+ mXmppConnectionService.getNotificationService().push(message);
}
- }
+ } else { //no body
+ if (isTypeGroupChat) {
+ Conversation conversation = mXmppConnectionService.find(account, from.toBareJid());
+ if (packet.hasChild("subject")) {
+ if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
+ conversation.setHasMessagesLeftOnServer(conversation.countMessages() > 0);
+ conversation.getMucOptions().setSubject(packet.findChildContent("subject"));
+ mXmppConnectionService.updateConversationUi();
+ return;
+ }
+ }
- if (message.getStatus() == Message.STATUS_RECEIVED
- && conversation.getOtrSession() != null
- && !conversation.getOtrSession().getSessionID().getUserID()
- .equals(message.getCounterpart().getResourcepart())) {
- conversation.endOtrIfNeeded();
+ if (conversation != null && isMucStatusMessage) {
+ for (Element child : mucUserElement.getChildren()) {
+ if (child.getName().equals("status")
+ && MucOptions.STATUS_CODE_ROOM_CONFIG_CHANGED.equals(child.getAttribute("code"))) {
+ mXmppConnectionService.fetchConferenceConfiguration(conversation);
+ }
+ }
+ }
+ }
}
- if (packet.getType() != MessagePacket.TYPE_ERROR) {
- if (message.getEncryption() == Message.ENCRYPTION_NONE
- || mXmppConnectionService.saveEncryptedMessages()) {
- mXmppConnectionService.databaseBackend.createMessage(message);
+ Element received = packet.findChild("received", "urn:xmpp:chat-markers:0");
+ if (received == null) {
+ received = packet.findChild("received", "urn:xmpp:receipts");
+ }
+ if (received != null && !packet.fromAccount(account)) {
+ mXmppConnectionService.markMessage(account, from.toBareJid(), received.getAttribute("id"), Message.STATUS_SEND_RECEIVED);
+ }
+ Element displayed = packet.findChild("displayed", "urn:xmpp:chat-markers:0");
+ if (displayed != null) {
+ if (packet.fromAccount(account)) {
+ Conversation conversation = mXmppConnectionService.find(account,counterpart.toBareJid());
+ if (conversation != null) {
+ mXmppConnectionService.markRead(conversation);
+ }
+ } else {
+ updateLastseen(packet, account, true);
+ final Message displayedMessage = mXmppConnectionService.markMessage(account, from.toBareJid(), displayed.getAttribute("id"), Message.STATUS_SEND_DISPLAYED);
+ Message message = displayedMessage == null ? null : displayedMessage.prev();
+ while (message != null
+ && message.getStatus() == Message.STATUS_SEND_RECEIVED
+ && message.getTimeSent() < displayedMessage.getTimeSent()) {
+ mXmppConnectionService.markMessage(message, Message.STATUS_SEND_DISPLAYED);
+ message = message.prev();
+ }
}
}
- final HttpConnectionManager manager = this.mXmppConnectionService.getHttpConnectionManager();
- if (message.trusted()
- && Settings.DOWNLOAD_IMAGE_LINKS
- && mXmppConnectionService.isDownloadAllowedInConnection()
- && message.bodyContainsDownloadable()
- && manager.getAutoAcceptFileSize() > 0) {
- manager.createNewConnection(message);
- } else if (!message.isRead()) {
- mXmppConnectionService.getNotificationService().push(message);
- }
- mXmppConnectionService.updateConversationUi();
- }
- private void parseHeadline(MessagePacket packet, Account account) {
- if (packet.hasChild("event", "http://jabber.org/protocol/pubsub#event")) {
- Element event = packet.findChild("event",
- "http://jabber.org/protocol/pubsub#event");
- parseEvent(event, packet.getFrom(), account);
+ Element event = packet.findChild("event", "http://jabber.org/protocol/pubsub#event");
+ if (event != null) {
+ parseEvent(event, from, account);
}
- }
- private void parseNick(MessagePacket packet, Account account) {
- Element nick = packet.findChild("nick",
- "http://jabber.org/protocol/nick");
+ String nick = packet.findChildContent("nick", "http://jabber.org/protocol/nick");
if (nick != null) {
- if (packet.getFrom() != null) {
- Contact contact = account.getRoster().getContact(
- packet.getFrom());
- contact.setPresenceName(nick.getContent());
- }
+ Contact contact = account.getRoster().getContact(from);
+ contact.setPresenceName(nick);
}
}
}
diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java
index 7505b091..d83347d8 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
@@ -44,38 +45,37 @@ public class PresenceParser extends AbstractParser implements
}
public void parseContactPresence(PresencePacket packet, Account account) {
- PresenceGenerator mPresenceGenerator = mXmppConnectionService
- .getPresenceGenerator();
- if (packet.getFrom() == null) {
+ PresenceGenerator mPresenceGenerator = mXmppConnectionService.getPresenceGenerator();
+ final Jid from = packet.getFrom();
+ if (from == null) {
return;
}
- final Jid from = packet.getFrom();
- String type = packet.getAttribute("type");
- Contact contact = account.getRoster().getContact(packet.getFrom());
+ final String type = packet.getAttribute("type");
+ final Contact contact = account.getRoster().getContact(from);
if (type == null) {
- String presence;
- if (!from.isBareJid()) {
- presence = from.getResourcepart();
- } else {
- presence = "";
+ String presence = from.isBareJid() ? "" : from.getResourcepart();
+ contact.setPresenceName(packet.findChildContent("nick", "http://jabber.org/protocol/nick"));
+ Avatar avatar = Avatar.parsePresence(packet.findChild("x", "vcard-temp:x:update"));
+ 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);
+ }
}
int sizeBefore = contact.getPresences().size();
- contact.updatePresence(presence,
- Presences.parseShow(packet.findChild("show")));
+ contact.updatePresence(presence, Presences.parseShow(packet.findChild("show")));
PgpEngine pgp = mXmppConnectionService.getPgpEngine();
- if (pgp != null) {
- Element x = packet.findChild("x", "jabber:x:signed");
- if (x != null) {
- Element status = packet.findChild("status");
- String msg;
- if (status != null) {
- msg = status.getContent();
- } else {
- msg = "";
- }
- contact.setPgpKeyId(pgp.fetchKeyId(account, msg,
- x.getContent()));
- }
+ Element x = packet.findChild("x", "jabber:x:signed");
+ if (pgp != null && x != null) {
+ Element status = packet.findChild("status");
+ String msg = status != null ? status.getContent() : "";
+ contact.setPgpKeyId(pgp.fetchKeyId(account, msg, x.getContent()));
}
boolean online = sizeBefore < contact.getPresences().size();
updateLastseen(packet, account, false);
@@ -86,8 +86,7 @@ public class PresenceParser extends AbstractParser implements
} else {
contact.removePresence(from.getResourcepart());
}
- mXmppConnectionService.onContactStatusChanged
- .onContactStatusChanged(contact, false);
+ mXmppConnectionService.onContactStatusChanged.onContactStatusChanged(contact, false);
} else if (type.equals("subscribe")) {
if (contact.getOption(Contact.Options.PREEMPTIVE_GRANT)) {
mXmppConnectionService.sendPresencePacket(account,
@@ -96,11 +95,6 @@ public class PresenceParser extends AbstractParser implements
contact.setOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST);
}
}
- Element nick = packet.findChild("nick",
- "http://jabber.org/protocol/nick");
- if (nick != null) {
- contact.setPresenceName(nick.getContent());
- }
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..d11b02fa 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) {
@@ -301,8 +386,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor;
String args[] = { roster.getAccount().getUuid() };
- cursor = db.query(Contact.TABLENAME, null, Contact.ACCOUNT + "=?",
- args, null, null, null);
+ cursor = db.query(Contact.TABLENAME, null, Contact.ACCOUNT + "=?", args, null, null, null);
while (cursor.moveToNext()) {
roster.initContact(Contact.fromCursor(cursor));
}
diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
index c499d499..ab191285 100644
--- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
+++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
@@ -1,6 +1,7 @@
package eu.siacs.conversations.persistance;
import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@@ -12,6 +13,7 @@ import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
+import java.util.Arrays;
import java.util.Date;
import java.util.Locale;
@@ -31,6 +33,7 @@ import android.webkit.MimeTypeMap;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
+import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.XmppConnectionService;
@@ -42,8 +45,7 @@ public class FileBackend {
private static int IMAGE_SIZE = 1920;
- private SimpleDateFormat imageDateFormat = new SimpleDateFormat(
- "yyyyMMdd_HHmmssSSS", Locale.US);
+ private final SimpleDateFormat imageDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
private XmppConnectionService mXmppConnectionService;
@@ -78,10 +80,10 @@ public class FileBackend {
if (path.startsWith("/")) {
return new DownloadableFile(path);
} else {
- if (message.getType() == Message.TYPE_FILE) {
+ if (Arrays.asList(Transferable.VALID_IMAGE_EXTENSIONS).contains(extension)) {
return new DownloadableFile(getConversationsFileDirectory() + path);
} else {
- return new DownloadableFile(getConversationsImageDirectory()+path);
+ return new DownloadableFile(getConversationsImageDirectory() + path);
}
}
}
@@ -110,9 +112,7 @@ public class FileBackend {
scalledW = size;
scalledH = (int) (h / ((double) w / size));
}
- Bitmap scalledBitmap = Bitmap.createScaledBitmap(originalBitmap,
- scalledW, scalledH, true);
- return scalledBitmap;
+ return Bitmap.createScaledBitmap(originalBitmap, scalledW, scalledH, true);
} else {
return originalBitmap;
}
@@ -148,31 +148,35 @@ public class FileBackend {
}
public DownloadableFile copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException {
+ Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage");
+ String mime = mXmppConnectionService.getContentResolver().getType(uri);
+ String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime);
+ message.setRelativeFilePath(message.getUuid() + "." + extension);
+ DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message);
+ file.getParentFile().mkdirs();
+ OutputStream os = null;
+ InputStream is = null;
try {
- Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage");
- String mime = mXmppConnectionService.getContentResolver().getType(uri);
- String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime);
- message.setRelativeFilePath(message.getUuid() + "." + extension);
- DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message);
- file.getParentFile().mkdirs();
file.createNewFile();
- OutputStream os = new FileOutputStream(file);
- InputStream is = mXmppConnectionService.getContentResolver().openInputStream(uri);
+ os = new FileOutputStream(file);
+ is = mXmppConnectionService.getContentResolver().openInputStream(uri);
byte[] buffer = new byte[1024];
- int length;
- while ((length = is.read(buffer)) > 0) {
+ int length;
+ while ((length = is.read(buffer)) > 0) {
os.write(buffer, 0, length);
- }
+ }
os.flush();
- os.close();
- is.close();
- Log.d(Config.LOGTAG, "output file name " + mXmppConnectionService.getFileBackend().getFile(message));
- return file;
- } catch (FileNotFoundException e) {
+ } catch(FileNotFoundException e) {
throw new FileCopyException(R.string.error_file_not_found);
} catch (IOException e) {
+ e.printStackTrace();
throw new FileCopyException(R.string.error_io_exception);
+ } finally {
+ close(os);
+ close(is);
}
+ Log.d(Config.LOGTAG, "output file name " + mXmppConnectionService.getFileBackend().getFile(message));
+ return file;
}
public DownloadableFile copyImageToPrivateStorage(Message message, Uri image)
@@ -182,49 +186,48 @@ public class FileBackend {
private DownloadableFile copyImageToPrivateStorage(Message message,
Uri image, int sampleSize) throws FileCopyException {
+ DownloadableFile file = getFile(message);
+ file.getParentFile().mkdirs();
+ InputStream is = null;
+ OutputStream os = null;
try {
- InputStream is = mXmppConnectionService.getContentResolver()
- .openInputStream(image);
- DownloadableFile file = getFile(message);
- file.getParentFile().mkdirs();
file.createNewFile();
+ is = mXmppConnectionService.getContentResolver().openInputStream(image);
+ os = new FileOutputStream(file);
+
Bitmap originalBitmap;
BitmapFactory.Options options = new BitmapFactory.Options();
int inSampleSize = (int) Math.pow(2, sampleSize);
- Log.d(Config.LOGTAG, "reading bitmap with sample size "
- + inSampleSize);
+ Log.d(Config.LOGTAG, "reading bitmap with sample size " + inSampleSize);
options.inSampleSize = inSampleSize;
originalBitmap = BitmapFactory.decodeStream(is, null, options);
is.close();
if (originalBitmap == null) {
throw new FileCopyException(R.string.error_not_an_image_file);
}
- Bitmap scalledBitmap = resize(originalBitmap, IMAGE_SIZE);
- originalBitmap = null;
+ Bitmap scaledBitmap = resize(originalBitmap, IMAGE_SIZE);
int rotation = getRotation(image);
if (rotation > 0) {
- scalledBitmap = rotate(scalledBitmap, rotation);
+ scaledBitmap = rotate(scaledBitmap, rotation);
}
- OutputStream os = new FileOutputStream(file);
- boolean success = scalledBitmap.compress(
- Bitmap.CompressFormat.WEBP, 75, os);
+
+ boolean success = scaledBitmap.compress(Bitmap.CompressFormat.WEBP, 75, os);
if (!success) {
throw new FileCopyException(R.string.error_compressing_image);
}
os.flush();
- os.close();
long size = file.getSize();
- int width = scalledBitmap.getWidth();
- int height = scalledBitmap.getHeight();
- message.setBody(Long.toString(size) + ',' + width + ',' + height);
+ int width = scaledBitmap.getWidth();
+ int height = scaledBitmap.getHeight();
+ message.setBody(Long.toString(size) + '|' + width + '|' + height);
return file;
} catch (FileNotFoundException e) {
throw new FileCopyException(R.string.error_file_not_found);
} catch (IOException e) {
+ e.printStackTrace();
throw new FileCopyException(R.string.error_io_exception);
} catch (SecurityException e) {
- throw new FileCopyException(
- R.string.error_security_exception_during_image_copy);
+ throw new FileCopyException(R.string.error_security_exception_during_image_copy);
} catch (OutOfMemoryError e) {
++sampleSize;
if (sampleSize <= 3) {
@@ -232,23 +235,26 @@ public class FileBackend {
} else {
throw new FileCopyException(R.string.error_out_of_memory);
}
+ } catch (NullPointerException e) {
+ throw new FileCopyException(R.string.error_io_exception);
+ } finally {
+ close(os);
+ close(is);
}
}
private int getRotation(Uri image) {
+ InputStream is = null;
try {
- InputStream is = mXmppConnectionService.getContentResolver()
- .openInputStream(image);
+ is = mXmppConnectionService.getContentResolver().openInputStream(image);
return ExifHelper.getOrientation(is);
} catch (FileNotFoundException e) {
return 0;
+ } finally {
+ close(is);
}
}
- public Bitmap getImageFromMessage(Message message) {
- return BitmapFactory.decodeFile(getFile(message).getAbsolutePath());
- }
-
public Bitmap getThumbnail(Message message, int size, boolean cacheOnly)
throws FileNotFoundException {
Bitmap thumbnail = mXmppConnectionService.getBitmapCache().get(
@@ -257,8 +263,7 @@ public class FileBackend {
File file = getFile(message);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = calcSampleSize(file, size);
- Bitmap fullsize = BitmapFactory.decodeFile(file.getAbsolutePath(),
- options);
+ Bitmap fullsize = BitmapFactory.decodeFile(file.getAbsolutePath(),options);
if (fullsize == null) {
throw new FileNotFoundException();
}
@@ -271,13 +276,11 @@ public class FileBackend {
public Uri getTakePhotoUri() {
StringBuilder pathBuilder = new StringBuilder();
- pathBuilder.append(Environment
- .getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM));
+ pathBuilder.append(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM));
pathBuilder.append('/');
pathBuilder.append("Camera");
pathBuilder.append('/');
- pathBuilder.append("IMG_" + this.imageDateFormat.format(new Date())
- + ".jpg");
+ pathBuilder.append("IMG_" + this.imageDateFormat.format(new Date()) + ".jpg");
Uri uri = Uri.parse("file://" + pathBuilder.toString());
File file = new File(uri.toString());
file.getParentFile().mkdirs();
@@ -325,13 +328,13 @@ public class FileBackend {
String filename = getAvatarPath(avatar.getFilename());
file = new File(filename + ".tmp");
file.getParentFile().mkdirs();
+ OutputStream os = null;
try {
file.createNewFile();
- FileOutputStream mFileOutputStream = new FileOutputStream(file);
+ os = new FileOutputStream(file);
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.reset();
- DigestOutputStream mDigestOutputStream = new DigestOutputStream(
- mFileOutputStream, digest);
+ DigestOutputStream mDigestOutputStream = new DigestOutputStream(os, digest);
mDigestOutputStream.write(avatar.getImageAsBytes());
mDigestOutputStream.flush();
mDigestOutputStream.close();
@@ -349,6 +352,8 @@ public class FileBackend {
return false;
} catch (NoSuchAlgorithmException e) {
return false;
+ } finally {
+ close(os);
}
}
avatar.size = file.length();
@@ -356,8 +361,7 @@ public class FileBackend {
}
public String getAvatarPath(String avatar) {
- return mXmppConnectionService.getFilesDir().getAbsolutePath()
- + "/avatars/" + avatar;
+ return mXmppConnectionService.getFilesDir().getAbsolutePath()+ "/avatars/" + avatar;
}
public Uri getAvatarUri(String avatar) {
@@ -368,10 +372,11 @@ public class FileBackend {
if (image == null) {
return null;
}
+ InputStream is = null;
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = calcSampleSize(image, size);
- InputStream is = mXmppConnectionService.getContentResolver().openInputStream(image);
+ is = mXmppConnectionService.getContentResolver().openInputStream(image);
Bitmap input = BitmapFactory.decodeStream(is, null, options);
if (input == null) {
return null;
@@ -384,6 +389,8 @@ public class FileBackend {
}
} catch (FileNotFoundException e) {
return null;
+ } finally {
+ close(is);
}
}
@@ -391,12 +398,15 @@ public class FileBackend {
if (image == null) {
return null;
}
+ InputStream is = null;
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = calcSampleSize(image,Math.max(newHeight, newWidth));
- InputStream is = mXmppConnectionService.getContentResolver().openInputStream(image);
+ is = mXmppConnectionService.getContentResolver().openInputStream(image);
Bitmap source = BitmapFactory.decodeStream(is, null, options);
-
+ if (source == null) {
+ return null;
+ }
int sourceWidth = source.getWidth();
int sourceHeight = source.getHeight();
float xScale = (float) newWidth / sourceWidth;
@@ -408,14 +418,15 @@ public class FileBackend {
float top = (newHeight - scaledHeight) / 2;
RectF targetRect = new RectF(left, top, left + scaledWidth, top + scaledHeight);
- Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, source.getConfig());
+ Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(dest);
canvas.drawBitmap(source, null, targetRect, null);
return dest;
} catch (FileNotFoundException e) {
return null;
+ } finally {
+ close(is);
}
-
}
public Bitmap cropCenterSquare(Bitmap input, int size) {
@@ -430,7 +441,7 @@ public class FileBackend {
float top = (size - outHeight) / 2;
RectF target = new RectF(left, top, left + outWidth, top + outHeight);
- Bitmap output = Bitmap.createBitmap(size, size, input.getConfig());
+ Bitmap output = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output);
canvas.drawBitmap(input, null, target, null);
return output;
@@ -490,7 +501,11 @@ public class FileBackend {
message.setBody(url.toString()+"|"+Long.toString(file.getSize()) + '|' + imageWidth + '|' + imageHeight);
}
} else {
- message.setBody(Long.toString(file.getSize()));
+ if (url != null) {
+ message.setBody(url.toString()+"|"+Long.toString(file.getSize()));
+ } else {
+ message.setBody(Long.toString(file.getSize()));
+ }
}
}
@@ -522,4 +537,13 @@ public class FileBackend {
public boolean isFileAvailable(Message message) {
return getFile(message).exists();
}
+
+ public static void close(Closeable stream) {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java
index f97077c4..0bc428c8 100644
--- a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java
+++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java
@@ -166,12 +166,12 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
}
}
- public void processFin(Element fin) {
+ public void processFin(Element fin, Jid from) {
if (fin == null) {
return;
}
Query query = findQuery(fin.getAttribute("queryid"));
- if (query == null) {
+ if (query == null || !query.validFrom(from)) {
return;
}
boolean complete = fin.getAttributeAsBoolean("complete");
@@ -336,6 +336,14 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
return this.messageCount;
}
+ public boolean validFrom(Jid from) {
+ if (muc()) {
+ return getWith().equals(from);
+ } else {
+ return (from == null) || account.getJid().toBareJid().equals(from.toBareJid());
+ }
+ }
+
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java
index 642021bf..09d223b3 100644
--- a/src/main/java/eu/siacs/conversations/services/NotificationService.java
+++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java
@@ -18,7 +18,6 @@ import android.support.v4.app.NotificationCompat.Builder;
import android.support.v4.app.TaskStackBuilder;
import android.text.Html;
import android.util.DisplayMetrics;
-import android.util.Log;
import org.json.JSONArray;
import org.json.JSONObject;
@@ -43,7 +42,6 @@ import eu.siacs.conversations.ui.ManageAccountActivity;
import eu.siacs.conversations.ui.TimePreference;
import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.utils.UIHelper;
-import eu.siacs.conversations.xmpp.XmppConnection;
public class NotificationService {
@@ -86,7 +84,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);
}
@@ -131,6 +133,7 @@ public class NotificationService {
}
public void push(final Message message) {
+ mXmppConnectionService.updateUnreadCountBadge();
if (!notify(message)) {
return;
}
@@ -176,7 +179,7 @@ public class NotificationService {
}
private void setNotificationColor(final Builder mBuilder) {
- mBuilder.setColor(mXmppConnectionService.getResources().getColor(R.color.primary));
+ mBuilder.setColor(mXmppConnectionService.getResources().getColor(R.color.green500));
}
private void updateNotification(final boolean notify) {
@@ -299,7 +302,7 @@ public class NotificationService {
final ArrayList<Message> tmp = new ArrayList<>();
for (final Message msg : messages) {
if (msg.getType() == Message.TYPE_TEXT
- && msg.getDownloadable() == null) {
+ && msg.getTransferable() == null) {
tmp.add(msg);
}
}
@@ -331,7 +334,7 @@ public class NotificationService {
private Message getImage(final Iterable<Message> messages) {
for (final Message message : messages) {
if (message.getType() == Message.TYPE_IMAGE
- && message.getDownloadable() == null
+ && message.getTransferable() == null
&& message.getEncryption() != Message.ENCRYPTION_PGP) {
return message;
}
@@ -342,7 +345,7 @@ public class NotificationService {
private Message getFirstDownloadableMessage(final Iterable<Message> messages) {
for (final Message message : messages) {
if ((message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) &&
- message.getDownloadable() != null) {
+ message.getTransferable() != null) {
return message;
}
}
@@ -455,7 +458,7 @@ public class NotificationService {
// nick (matched in case-insensitive manner), followed by optional
// punctuation (for example "bob: i disagree" or "how are you alice?"),
// followed by another word boundary.
- return Pattern.compile("\\b" + nick + "\\p{Punct}?\\b",
+ return Pattern.compile("\\b" + Pattern.quote(nick) + "\\p{Punct}?\\b",
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
}
@@ -494,7 +497,7 @@ public class NotificationService {
final int cancelIcon;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mBuilder.setCategory(Notification.CATEGORY_SERVICE);
- mBuilder.setSmallIcon(R.drawable.ic_import_export_white_48dp);
+ mBuilder.setSmallIcon(R.drawable.ic_import_export_white_24dp);
cancelIcon = R.drawable.ic_cancel_white_24dp;
} else {
mBuilder.setSmallIcon(R.drawable.ic_stat_communication_import_export);
@@ -541,7 +544,7 @@ public class NotificationService {
mBuilder.setOngoing(true);
//mBuilder.setLights(0xffffffff, 2000, 4000);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- mBuilder.setSmallIcon(R.drawable.ic_warning_white_36dp);
+ mBuilder.setSmallIcon(R.drawable.ic_warning_white_24dp);
} else {
mBuilder.setSmallIcon(R.drawable.ic_stat_alert_warning);
}
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index fc670efd..c2b04722 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -28,6 +28,7 @@ import android.util.LruCache;
import net.java.otr4j.OtrException;
import net.java.otr4j.session.Session;
import net.java.otr4j.session.SessionID;
+import net.java.otr4j.session.SessionImpl;
import net.java.otr4j.session.SessionStatus;
import org.openintents.openpgp.util.OpenPgpApi;
@@ -36,10 +37,12 @@ import org.openintents.openpgp.util.OpenPgpServiceConnection;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.ArrayList;
+import java.util.Arrays;
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;
@@ -55,12 +58,11 @@ import eu.siacs.conversations.entities.Blockable;
import eu.siacs.conversations.entities.Bookmark;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
-import eu.siacs.conversations.entities.Downloadable;
-import eu.siacs.conversations.entities.DownloadablePlaceholder;
+import eu.siacs.conversations.entities.Transferable;
+import eu.siacs.conversations.entities.TransferablePlaceholder;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.MucOptions.OnRenameListener;
-import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.generator.IqGenerator;
import eu.siacs.conversations.generator.MessageGenerator;
import eu.siacs.conversations.generator.PresenceGenerator;
@@ -76,6 +78,7 @@ import eu.siacs.conversations.utils.ExceptionHelper;
import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener;
import eu.siacs.conversations.utils.PRNGFixes;
import eu.siacs.conversations.utils.PhoneHelper;
+import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
import eu.siacs.conversations.utils.Xmlns;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnBindListener;
@@ -99,6 +102,7 @@ import eu.siacs.conversations.xmpp.pep.Avatar;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
+import me.leolin.shortcutbadger.ShortcutBadger;
public class XmppConnectionService extends Service implements OnPhoneContactsLoadedListener {
@@ -117,6 +121,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
startService(intent);
}
};
+
+ private final SerialSingleThreadExecutor mFileAddingExecutor = new SerialSingleThreadExecutor();
+ private final SerialSingleThreadExecutor mDatabaseExecutor = new SerialSingleThreadExecutor();
+
private final IBinder mBinder = new XmppConnectionBinder();
private final List<Conversation> conversations = new CopyOnWriteArrayList<>();
private final FileObserver fileObserver = new FileObserver(
@@ -175,13 +183,22 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void onContactStatusChanged(Contact contact, boolean online) {
Conversation conversation = find(getConversations(), contact);
if (conversation != null) {
- if (online && contact.getPresences().size() > 1) {
+ if (online) {
conversation.endOtrIfNeeded();
+ if (contact.getPresences().size() == 1) {
+ sendUnsentMessages(conversation);
+ }
} else {
- conversation.resetOtrSession();
- }
- if (online && (contact.getPresences().size() == 1)) {
- sendUnsentMessages(conversation);
+ if (contact.getPresences().size() >= 1) {
+ if (conversation.hasValidOtrSession()) {
+ String otrResource = conversation.getOtrSession().getSessionID().getUserID();
+ if (!(Arrays.asList(contact.getPresences().asStringArray()).contains(otrResource))) {
+ conversation.endOtrIfNeeded();
+ }
+ }
+ } else {
+ conversation.endOtrIfNeeded();
+ }
}
}
}
@@ -193,6 +210,18 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
private OnMessagePacketReceived mMessageParser = new MessageParser(this);
private OnPresencePacketReceived mPresenceParser = new PresenceParser(this);
private IqParser mIqParser = new IqParser(this);
+ private OnIqPacketReceived mDefaultIqHandler = new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE.ERROR) {
+ Element error = packet.findChild("error");
+ String text = error != null ? error.findChildContent("text") : null;
+ if (text != null) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": received iq error - "+text);
+ }
+ }
+ }
+ };
private MessageGenerator mMessageGenerator = new MessageGenerator(this);
private PresenceGenerator mPresenceGenerator = new PresenceGenerator(this);
private List<Account> accounts;
@@ -201,9 +230,13 @@ 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;
+ private int convChangedListenerCount = 0;
+ private OnShowErrorToast mOnShowErrorToast = null;
+ private int showErrorToastListenerCount = 0;
+ private int unreadCount = -1;
private OnAccountUpdate mOnAccountUpdate = null;
private OnStatusChanged statusListener = new OnStatusChanged() {
@@ -319,7 +352,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);
}
@@ -338,7 +371,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);
@@ -349,7 +381,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
callback.success(message);
}
} else {
- new Thread(new Runnable() {
+ mFileAddingExecutor.execute(new Runnable() {
@Override
public void run() {
try {
@@ -361,11 +393,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
callback.success(message);
}
} catch (FileBackend.FileCopyException e) {
- callback.error(e.getResId(),message);
+ callback.error(e.getResId(), message);
}
}
- }).start();
-
+ });
}
}
@@ -381,8 +412,7 @@ 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() {
+ mFileAddingExecutor.execute(new Runnable() {
@Override
public void run() {
@@ -397,7 +427,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
callback.error(e.getResId(), message);
}
}
- }).start();
+ });
}
public Conversation find(Bookmark bookmark) {
@@ -413,6 +443,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(),
@@ -431,14 +466,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 {
@@ -475,9 +503,15 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
long lastSent = account.getXmppConnection().getLastPingSent();
long pingInterval = "ui".equals(action) ? Config.PING_MIN_INTERVAL * 1000 : Config.PING_MAX_INTERVAL * 1000;
long msToNextPing = (Math.max(lastReceived,lastSent) + pingInterval) - SystemClock.elapsedRealtime();
- if (lastSent > lastReceived && (lastSent + Config.PING_TIMEOUT * 1000) < SystemClock.elapsedRealtime()) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid()+ ": ping timeout");
- this.reconnectAccount(account, true);
+ long pingTimeoutIn = (lastSent + Config.PING_TIMEOUT * 1000) - SystemClock.elapsedRealtime();
+ if (lastSent > lastReceived) {
+ if (pingTimeoutIn < 0) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": ping timeout");
+ this.reconnectAccount(account, true);
+ } else {
+ int secs = (int) (pingTimeoutIn / 1000);
+ this.scheduleWakeUpCall(secs,account.getUuid().hashCode());
+ }
} else if (msToNextPing <= 0) {
account.getXmppConnection().sendPing();
Log.d(Config.LOGTAG, account.getJid().toBareJid()+" send ping");
@@ -520,6 +554,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);
@@ -552,9 +598,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
ExceptionHelper.init(getApplicationContext());
PRNGFixes.apply();
this.mRandom = new SecureRandom();
- this.mMemorizingTrustManager = new MemorizingTrustManager(
- getApplicationContext());
-
+ updateMemorizingTrustmanager();
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
final int cacheSize = maxMemory / 8;
this.mBitmapCache = new LruCache<String, Bitmap>(cacheSize) {
@@ -568,7 +612,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
this.accounts = databaseBackend.getAccounts();
for (final Account account : this.accounts) {
- account.initOtrEngine(this);
+ account.initAccountServices(this);
}
restoreFromDatabase();
@@ -580,6 +624,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
this.pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"XmppConnectionService");
toggleForegroundService();
+ updateUnreadCountBadge();
}
public void toggleForegroundService() {
@@ -607,7 +652,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
Context context = getApplicationContext();
AlarmManager alarmManager = (AlarmManager) context
- .getSystemService(Context.ALARM_SERVICE);
+ .getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, EventReceiver.class);
alarmManager.cancel(PendingIntent.getBroadcast(context, 0, intent, 0));
Log.d(Config.LOGTAG, "good bye");
@@ -615,7 +660,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
protected void scheduleWakeUpCall(int seconds, int requestCode) {
- final long timeToWake = SystemClock.elapsedRealtime() + (seconds + 1) * 1000;
+ final long timeToWake = SystemClock.elapsedRealtime() + (seconds < 0 ? 1 : seconds + 1) * 1000;
Context context = getApplicationContext();
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
@@ -649,114 +694,138 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
+ private void sendFileMessage(final Message message) {
+ Log.d(Config.LOGTAG, "send file message");
+ final Account account = message.getConversation().getAccount();
+ final XmppConnection connection = account.getXmppConnection();
+ if (connection != null && connection.getFeatures().httpUpload()) {
+ mHttpConnectionManager.createNewUploadConnection(message);
+ } else {
+ mJingleConnectionManager.createNewConnection(message);
+ }
+ }
+
public void sendMessage(final Message message) {
+ sendMessage(message, false);
+ }
+
+ private void sendMessage(final Message message, final boolean resend) {
final Account account = message.getConversation().getAccount();
+ final Conversation conversation = message.getConversation();
account.deactivateGracePeriod();
- final Conversation conv = message.getConversation();
MessagePacket packet = null;
boolean saveInDb = true;
- boolean send = false;
- if (account.getStatus() == Account.State.ONLINE
- && account.getXmppConnection() != null) {
- if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
- if (message.getCounterpart() != null) {
- if (message.getEncryption() == Message.ENCRYPTION_OTR) {
- if (!conv.hasValidOtrSession()) {
- conv.startOtrSession(message.getCounterpart().getResourcepart(),true);
- message.setStatus(Message.STATUS_WAITING);
- } else if (conv.hasValidOtrSession()
- && conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
- mJingleConnectionManager
- .createNewConnection(message);
- }
+ message.setStatus(Message.STATUS_WAITING);
+
+ if (!resend && message.getEncryption() != Message.ENCRYPTION_OTR) {
+ message.getConversation().endOtrIfNeeded();
+ message.getConversation().findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() {
+ @Override
+ public void onMessageFound(Message message) {
+ markMessage(message,Message.STATUS_SEND_FAILED);
+ }
+ });
+ }
+
+ if (account.isOnlineAndConnected()) {
+ switch (message.getEncryption()) {
+ case Message.ENCRYPTION_NONE:
+ if (message.needsUploading()) {
+ if (account.httpUploadAvailable() || message.fixCounterpart()) {
+ this.sendFileMessage(message);
+ } else {
+ break;
+ }
} else {
- mJingleConnectionManager.createNewConnection(message);
- }
- } else {
- if (message.getEncryption() == Message.ENCRYPTION_OTR) {
- conv.startOtrIfNeeded();
+ packet = mMessageGenerator.generateChat(message,resend);
}
- message.setStatus(Message.STATUS_WAITING);
- }
- } else {
- if (message.getEncryption() == Message.ENCRYPTION_OTR) {
- if (!conv.hasValidOtrSession() && (message.getCounterpart() != null)) {
- conv.startOtrSession(message.getCounterpart().getResourcepart(), true);
- message.setStatus(Message.STATUS_WAITING);
- } else if (conv.hasValidOtrSession()) {
- if (conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
- packet = mMessageGenerator.generateOtrChat(message);
- send = true;
+ break;
+ case Message.ENCRYPTION_PGP:
+ case Message.ENCRYPTION_DECRYPTED:
+ if (message.needsUploading()) {
+ if (account.httpUploadAvailable() || message.fixCounterpart()) {
+ this.sendFileMessage(message);
} else {
- message.setStatus(Message.STATUS_WAITING);
- conv.startOtrIfNeeded();
+ break;
}
} else {
- message.setStatus(Message.STATUS_WAITING);
+ packet = mMessageGenerator.generatePgpChat(message,resend);
}
- } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
- message.getConversation().endOtrIfNeeded();
- message.getConversation().findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() {
- @Override
- public void onMessageFound(Message message) {
- markMessage(message,Message.STATUS_SEND_FAILED);
+ break;
+ case Message.ENCRYPTION_OTR:
+ SessionImpl otrSession = conversation.getOtrSession();
+ if (otrSession != null && otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) {
+ try {
+ message.setCounterpart(Jid.fromSessionID(otrSession.getSessionID()));
+ } catch (InvalidJidException e) {
+ break;
}
- });
- packet = mMessageGenerator.generatePgpChat(message);
- send = true;
- } else {
- message.getConversation().endOtrIfNeeded();
- message.getConversation().findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() {
- @Override
- public void onMessageFound(Message message) {
- markMessage(message,Message.STATUS_SEND_FAILED);
+ if (message.needsUploading()) {
+ mJingleConnectionManager.createNewConnection(message);
+ } else {
+ packet = mMessageGenerator.generateOtrChat(message,resend);
}
- });
- packet = mMessageGenerator.generateChat(message);
- send = true;
+ } else if (otrSession == null) {
+ if (message.fixCounterpart()) {
+ conversation.startOtrSession(message.getCounterpart().getResourcepart(), true);
+ } else {
+ break;
+ }
+ }
+ break;
+ }
+ if (packet != null) {
+ if (account.getXmppConnection().getFeatures().sm() || conversation.getMode() == Conversation.MODE_MULTI) {
+ message.setStatus(Message.STATUS_UNSEND);
+ } else {
+ message.setStatus(Message.STATUS_SEND);
}
}
- if (!account.getXmppConnection().getFeatures().sm()
- && conv.getMode() != Conversation.MODE_MULTI) {
- message.setStatus(Message.STATUS_SEND);
- }
} else {
- message.setStatus(Message.STATUS_WAITING);
- if (message.getType() == Message.TYPE_TEXT) {
- if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
- String pgpBody = message.getEncryptedBody();
- String decryptedBody = message.getBody();
- message.setBody(pgpBody);
- message.setEncryption(Message.ENCRYPTION_PGP);
- databaseBackend.createMessage(message);
- saveInDb = false;
- message.setBody(decryptedBody);
- message.setEncryption(Message.ENCRYPTION_DECRYPTED);
- } else if (message.getEncryption() == Message.ENCRYPTION_OTR) {
- if (!conv.hasValidOtrSession()
- && message.getCounterpart() != null) {
- conv.startOtrSession(message.getCounterpart().getResourcepart(), false);
- }
- }
+ switch(message.getEncryption()) {
+ case Message.ENCRYPTION_DECRYPTED:
+ if (!message.needsUploading()) {
+ String pgpBody = message.getEncryptedBody();
+ String decryptedBody = message.getBody();
+ message.setBody(pgpBody);
+ message.setEncryption(Message.ENCRYPTION_PGP);
+ databaseBackend.createMessage(message);
+ saveInDb = false;
+ message.setBody(decryptedBody);
+ message.setEncryption(Message.ENCRYPTION_DECRYPTED);
+ }
+ break;
+ case Message.ENCRYPTION_OTR:
+ if (!conversation.hasValidOtrSession() && message.getCounterpart() != null) {
+ conversation.startOtrSession(message.getCounterpart().getResourcepart(), false);
+ }
+ break;
}
-
}
- conv.add(message);
- if (saveInDb) {
- if (message.getEncryption() == Message.ENCRYPTION_NONE
- || saveEncryptedMessages()) {
+
+ if (resend) {
+ if (packet != null) {
+ if (account.getXmppConnection().getFeatures().sm() || conversation.getMode() == Conversation.MODE_MULTI) {
+ markMessage(message,Message.STATUS_UNSEND);
+ } else {
+ markMessage(message,Message.STATUS_SEND);
+ }
+ }
+ } else {
+ conversation.add(message);
+ if (saveInDb && (message.getEncryption() == Message.ENCRYPTION_NONE || saveEncryptedMessages())) {
databaseBackend.createMessage(message);
- }
+ }
+ updateConversationUi();
}
- if ((send) && (packet != null)) {
- if (conv.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
+ if (packet != null) {
+ if (conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
if (this.sendChatStates()) {
- packet.addChild(ChatState.toElement(conv.getOutgoingChatState()));
+ packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
}
}
sendMessagePacket(account, packet);
}
- updateConversationUi();
}
private void sendUnsentMessages(final Conversation conversation) {
@@ -769,79 +838,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
});
}
- private void resendMessage(final Message message) {
- Account account = message.getConversation().getAccount();
- MessagePacket packet = null;
- if (message.getEncryption() == Message.ENCRYPTION_OTR) {
- Presences presences = message.getConversation().getContact()
- .getPresences();
- if (!message.getConversation().hasValidOtrSession()) {
- if ((message.getCounterpart() != null)
- && (presences.has(message.getCounterpart().getResourcepart()))) {
- message.getConversation().startOtrSession(message.getCounterpart().getResourcepart(), true);
- } else {
- if (presences.size() == 1) {
- String presence = presences.asStringArray()[0];
- message.getConversation().startOtrSession(presence, true);
- }
- }
- } else {
- if (message.getConversation().getOtrSession()
- .getSessionStatus() == SessionStatus.ENCRYPTED) {
- try {
- message.setCounterpart(Jid.fromSessionID(message.getConversation().getOtrSession().getSessionID()));
- if (message.getType() == Message.TYPE_TEXT) {
- packet = mMessageGenerator.generateOtrChat(message,
- true);
- } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
- mJingleConnectionManager.createNewConnection(message);
- }
- } catch (final InvalidJidException ignored) {
-
- }
- }
- }
- } else if (message.getType() == Message.TYPE_TEXT) {
- if (message.getEncryption() == Message.ENCRYPTION_NONE) {
- packet = mMessageGenerator.generateChat(message, true);
- } else if ((message.getEncryption() == Message.ENCRYPTION_DECRYPTED)
- || (message.getEncryption() == Message.ENCRYPTION_PGP)) {
- packet = mMessageGenerator.generatePgpChat(message, true);
- }
- } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
- Contact contact = message.getConversation().getContact();
- 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) {
- String presence = presences.asStringArray()[0];
- try {
- message.setCounterpart(Jid.fromParts(contact.getJid().getLocalpart(), contact.getJid().getDomainpart(), presence));
- } catch (InvalidJidException e) {
- return;
- }
- markMessage(message, Message.STATUS_OFFERED);
- mJingleConnectionManager.createNewConnection(message);
- }
- }
- }
- if (packet != null) {
- if (!account.getXmppConnection().getFeatures().sm()
- && message.getConversation().getMode() != Conversation.MODE_MULTI) {
- markMessage(message, Message.STATUS_SEND);
- } else {
- markMessage(message, Message.STATUS_UNSEND);
- }
- if (message.getConversation().setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
- if (this.sendChatStates()) {
- packet.addChild(ChatState.toElement(message.getConversation().getOutgoingChatState()));
- }
- }
- sendMessagePacket(account, packet);
- }
+ public void resendMessage(final Message message) {
+ sendMessage(message, true);
}
public void fetchRosterFromServer(final Account account) {
@@ -852,9 +850,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
} else {
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": fetching roster");
}
- iqPacket.query(Xmlns.ROSTER).setAttribute("ver",
- account.getRosterVersion());
- account.getXmppConnection().sendIqPacket(iqPacket, mIqParser);
+ iqPacket.query(Xmlns.ROSTER).setAttribute("ver",account.getRosterVersion());
+ sendIqPacket(account,iqPacket,mIqParser);
}
public void fetchBookmarks(final Account account) {
@@ -899,7 +896,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
for (Bookmark bookmark : account.getBookmarks()) {
storage.addChild(bookmark);
}
- sendIqPacket(account, iqPacket, null);
+ sendIqPacket(account, iqPacket, mDefaultIqHandler);
}
public void onPhoneContactsLoaded(final List<Bundle> phoneContacts) {
@@ -911,7 +908,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void run() {
Log.d(Config.LOGTAG,"start merging phone contacts with roster");
for (Account account : accounts) {
- account.getRoster().clearSystemAccounts();
+ List<Contact> withSystemAccounts = account.getRoster().getWithSystemAccounts();
for (Bundle phoneContact : phoneContacts) {
if (Thread.interrupted()) {
Log.d(Config.LOGTAG,"interrupted merging phone contacts");
@@ -928,9 +925,18 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
+ "#"
+ phoneContact.getString("lookup");
contact.setSystemAccount(systemAccount);
- contact.setPhotoUri(phoneContact.getString("photouri"));
- getAvatarService().clear(contact);
+ if (contact.setPhotoUri(phoneContact.getString("photouri"))) {
+ getAvatarService().clear(contact);
+ }
contact.setSystemName(phoneContact.getString("displayname"));
+ withSystemAccounts.remove(contact);
+ }
+ for(Contact contact : withSystemAccounts) {
+ contact.setSystemAccount(null);
+ contact.setSystemName(null);
+ if (contact.setPhotoUri(null)) {
+ getAvatarService().clear(contact);
+ }
}
}
Log.d(Config.LOGTAG,"finished merging phone contacts");
@@ -951,7 +957,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
Account account = accountLookupTable.get(conversation.getAccountUuid());
conversation.setAccount(account);
}
- new Thread(new Runnable() {
+ Runnable runnable =new Runnable() {
@Override
public void run() {
Log.d(Config.LOGTAG,"restoring roster");
@@ -972,7 +978,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
Log.d(Config.LOGTAG,"restored all messages");
updateConversationUi();
}
- }).start();
+ };
+ mDatabaseExecutor.execute(runnable);
}
}
@@ -986,7 +993,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
@Override
public void onMessageFound(Message message) {
if (!getFileBackend().isFileAvailable(message)) {
- message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_DELETED));
+ message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED));
}
}
});
@@ -997,7 +1004,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
Message message = conversation.findMessageWithFileAndUuid(uuid);
if (message != null) {
if (!getFileBackend().isFileAvailable(message)) {
- message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_DELETED));
+ message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED));
updateConversationUi();
}
return;
@@ -1009,13 +1016,14 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
populateWithOrderedConversations(list, true);
}
- public void populateWithOrderedConversations(final List<Conversation> list, boolean includeConferences) {
+ public void populateWithOrderedConversations(final List<Conversation> list, boolean includeNoFileUpload) {
list.clear();
- if (includeConferences) {
+ if (includeNoFileUpload) {
list.addAll(getConversations());
} else {
for (Conversation conversation : getConversations()) {
- if (conversation.getMode() == Conversation.MODE_SINGLE) {
+ if (conversation.getMode() == Conversation.MODE_SINGLE
+ || conversation.getAccount().httpUploadAvailable()) {
list.add(conversation);
}
}
@@ -1037,11 +1045,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void loadMoreMessages(final Conversation conversation, final long timestamp, final OnMoreMessagesLoaded callback) {
- Log.d(Config.LOGTAG,"load more messages for "+conversation.getName() + " prior to "+MessageGenerator.getTimestamp(timestamp));
+ Log.d(Config.LOGTAG, "load more messages for " + conversation.getName() + " prior to " + MessageGenerator.getTimestamp(timestamp));
if (XmppConnectionService.this.getMessageArchiveService().queryInProgress(conversation,callback)) {
return;
}
- new Thread(new Runnable() {
+ Runnable runnable = new Runnable() {
@Override
public void run() {
final Account account = conversation.getAccount();
@@ -1060,7 +1068,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
callback.informUser(R.string.fetching_history_from_server);
}
}
- }).start();
+ };
+ mDatabaseExecutor.execute(runnable);
}
public List<Account> getAccounts() {
@@ -1149,6 +1158,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void archiveConversation(Conversation conversation) {
+ getNotificationService().clear(conversation);
conversation.setStatus(Conversation.STATUS_ARCHIVED);
conversation.setNextEncryption(-1);
synchronized (this.conversations) {
@@ -1171,7 +1181,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void createAccount(final Account account) {
- account.initOtrEngine(this);
+ account.initAccountServices(this);
databaseBackend.createAccount(account);
this.accounts.add(account);
this.reconnectAccountInBackground(account);
@@ -1251,6 +1261,32 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
+ public void setOnShowErrorToastListener(OnShowErrorToast onShowErrorToast) {
+ synchronized (this) {
+ if (checkListeners()) {
+ switchToForeground();
+ }
+ this.mOnShowErrorToast = onShowErrorToast;
+ if (this.showErrorToastListenerCount < 2) {
+ this.showErrorToastListenerCount++;
+ }
+ }
+ this.mOnShowErrorToast = onShowErrorToast;
+ }
+
+ public void removeOnShowErrorToastListener() {
+ synchronized (this) {
+ this.showErrorToastListenerCount--;
+ if (this.showErrorToastListenerCount <= 0) {
+ this.showErrorToastListenerCount = 0;
+ this.mOnShowErrorToast = null;
+ if (checkListeners()) {
+ switchToBackground();
+ }
+ }
+ }
+ }
+
public void setOnAccountListChangedListener(OnAccountUpdate listener) {
synchronized (this) {
if (checkListeners()) {
@@ -1355,7 +1391,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return (this.mOnAccountUpdate == null
&& this.mOnConversationUpdate == null
&& this.mOnRosterUpdate == null
- && this.mOnUpdateBlocklist == null);
+ && this.mOnUpdateBlocklist == null
+ && this.mOnShowErrorToast == null);
}
private void switchToForeground() {
@@ -1558,6 +1595,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
for (Jid invite : jids) {
invite(conversation, invite);
}
+ if (account.countPresences() > 1) {
+ directInvite(conversation, account.getJid().toBareJid());
+ }
if (callback != null) {
callback.success(conversation);
}
@@ -1678,12 +1718,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void changeAffiliationsInConference(final Conversation conference, MucOptions.Affiliation before, MucOptions.Affiliation after) {
List<Jid> jids = new ArrayList<>();
for (MucOptions.User user : conference.getMucOptions().getUsers()) {
- if (user.getAffiliation() == before) {
+ if (user.getAffiliation() == before && user.getJid() != null) {
jids.add(user.getJid());
}
}
IqPacket request = this.mIqGenerator.changeAffiliation(conference, jids, after.toString());
- sendIqPacket(conference.getAccount(), request, null);
+ sendIqPacket(conference.getAccount(), request, mDefaultIqHandler);
}
public void changeRoleInConference(final Conversation conference, final String nick, MucOptions.Role role, final OnRoleChanged callback) {
@@ -1720,6 +1760,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
}
+ sendOfflinePresence(account);
}
account.getXmppConnection().disconnect(force);
}
@@ -1773,15 +1814,15 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
} catch (InvalidJidException e) {
return;
}
- if (message.getType() == Message.TYPE_TEXT) {
+ if (message.needsUploading()) {
+ mJingleConnectionManager.createNewConnection(message);
+ } else {
MessagePacket outPacket = mMessageGenerator.generateOtrChat(message, true);
if (outPacket != null) {
message.setStatus(Message.STATUS_SEND);
databaseBackend.updateMessage(message);
sendMessagePacket(account, outPacket);
}
- } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
- mJingleConnectionManager.createNewConnection(message);
}
updateConversationUi();
}
@@ -1826,7 +1867,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
&& contact.getOption(Contact.Options.PREEMPTIVE_GRANT);
final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
iq.query(Xmlns.ROSTER).addChild(contact.asElement());
- account.getXmppConnection().sendIqPacket(iq, null);
+ account.getXmppConnection().sendIqPacket(iq, mDefaultIqHandler);
if (sendUpdates) {
sendPresencePacket(account,
mPresenceGenerator.sendPresenceUpdatesTo(contact));
@@ -1874,6 +1915,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);
@@ -1900,13 +1942,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) {
@@ -1923,7 +1991,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();
@@ -1932,8 +2000,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 {
@@ -1956,8 +2023,37 @@ 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;
+ String image = photo != null ? photo.findChildContent("BINVAL") : 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() {
@@ -1979,7 +2075,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
getAvatarService().clear(account);
callback.success(avatar);
} else {
- fetchAvatar(account, avatar, callback);
+ fetchAvatarPep(account, avatar, callback);
}
return;
}
@@ -2001,7 +2097,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
Element item = iq.query(Xmlns.ROSTER).addChild("item");
item.setAttribute("jid", contact.getJid().toString());
item.setAttribute("subscription", "remove");
- account.getXmppConnection().sendIqPacket(iq, null);
+ account.getXmppConnection().sendIqPacket(iq, mDefaultIqHandler);
}
}
@@ -2015,6 +2111,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));
}
@@ -2038,10 +2144,16 @@ 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);
}
+ public void directInvite(Conversation conversation, Jid jid) {
+ MessagePacket packet = mMessageGenerator.directInvite(conversation, jid);
+ sendMessagePacket(conversation.getAccount(),packet);
+ }
+
public void resetSendingToWaiting(Account account) {
for (Conversation conversation : getConversations()) {
if (conversation.getAccount() == account) {
@@ -2127,6 +2239,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return count;
}
+
+ public void showErrorToastInUi(int resId) {
+ if (mOnShowErrorToast != null) {
+ mOnShowErrorToast.onShowErrorToast(resId);
+ }
+ }
+
public void updateConversationUi() {
if (mOnConversationUpdate != null) {
mOnConversationUpdate.onConversationUpdate();
@@ -2178,6 +2297,20 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void markRead(final Conversation conversation) {
mNotificationService.clear(conversation);
conversation.markRead();
+ updateUnreadCountBadge();
+ }
+
+ public synchronized void updateUnreadCountBadge() {
+ int count = unreadCount();
+ if (unreadCount != count) {
+ Log.d(Config.LOGTAG, "update unread count to " + count);
+ if (count > 0) {
+ ShortcutBadger.with(getApplicationContext()).count(count);
+ } else {
+ ShortcutBadger.with(getApplicationContext()).remove();
+ }
+ unreadCount = count;
+ }
}
public void sendReadMarker(final Conversation conversation) {
@@ -2201,6 +2334,21 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return this.mMemorizingTrustManager;
}
+ public void setMemorizingTrustManager(MemorizingTrustManager trustManager) {
+ this.mMemorizingTrustManager = trustManager;
+ }
+
+ public void updateMemorizingTrustmanager() {
+ final MemorizingTrustManager tm;
+ final boolean dontTrustSystemCAs = getPreferences().getBoolean("dont_trust_system_cas", false);
+ if (dontTrustSystemCAs) {
+ tm = new MemorizingTrustManager(getApplicationContext(), null);
+ } else {
+ tm = new MemorizingTrustManager(getApplicationContext());
+ }
+ setMemorizingTrustManager(tm);
+ }
+
public PowerManager getPowerManager() {
return this.pm;
}
@@ -2210,13 +2358,14 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void syncRosterToDisk(final Account account) {
- new Thread(new Runnable() {
+ Runnable runnable = new Runnable() {
@Override
public void run() {
databaseBackend.writeRoster(account.getRoster());
}
- }).start();
+ };
+ mDatabaseExecutor.execute(runnable);
}
@@ -2276,6 +2425,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
sendPresencePacket(account, mPresenceGenerator.sendPresence(account));
}
+ public void sendOfflinePresence(final Account account) {
+ sendPresencePacket(account, mPresenceGenerator.sendOfflinePresence(account));
+ }
+
public MessageGenerator getMessageGenerator() {
return this.mMessageGenerator;
}
@@ -2426,6 +2579,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void onPushFailed();
}
+ public interface OnShowErrorToast {
+ void onShowErrorToast(int resId);
+ }
+
public class XmppConnectionBinder extends Binder {
public XmppConnectionService getService() {
return XmppConnectionService.this;
diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
index e4bfd6ff..07b8819d 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
@@ -237,6 +237,9 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
MenuItem menuItemDeleteBookmark = menu.findItem(R.id.action_delete_bookmark);
MenuItem menuItemAdvancedMode = menu.findItem(R.id.action_advanced_mode);
menuItemAdvancedMode.setChecked(mAdvancedMode);
+ if (mConversation == null) {
+ return true;
+ }
Account account = mConversation.getAccount();
if (account.hasBookmarkFor(mConversation.getJid().toBareJid())) {
menuItemSaveBookmark.setVisible(false);
@@ -382,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/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
index 40a4587c..c190caed 100644
--- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
@@ -10,6 +10,7 @@ import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
+import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Intents;
@@ -126,14 +127,23 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
@Override
public void onClick(View v) {
- AlertDialog.Builder builder = new AlertDialog.Builder(
- ContactDetailsActivity.this);
- builder.setTitle(getString(R.string.action_add_phone_book));
- builder.setMessage(getString(R.string.add_phone_book_text,
+ if (contact.getSystemAccount() == null) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(
+ ContactDetailsActivity.this);
+ builder.setTitle(getString(R.string.action_add_phone_book));
+ builder.setMessage(getString(R.string.add_phone_book_text,
contact.getJid()));
- builder.setNegativeButton(getString(R.string.cancel), null);
- builder.setPositiveButton(getString(R.string.add), addToPhonebook);
- builder.create().show();
+ builder.setNegativeButton(getString(R.string.cancel), null);
+ builder.setPositiveButton(getString(R.string.add), addToPhonebook);
+ builder.create().show();
+ } else {
+ String[] systemAccount = contact.getSystemAccount().split("#");
+ long id = Long.parseLong(systemAccount[0]);
+ Uri uri = ContactsContract.Contacts.getLookupUri(id, systemAccount[1]);
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(uri);
+ startActivity(intent);
+ }
}
};
@@ -256,16 +266,19 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
MenuItem unblock = menu.findItem(R.id.action_unblock);
MenuItem edit = menu.findItem(R.id.action_edit_contact);
MenuItem delete = menu.findItem(R.id.action_delete_contact);
+ if (contact == null) {
+ return true;
+ }
final XmppConnection connection = contact.getAccount().getXmppConnection();
if (connection != null && connection.getFeatures().blocking()) {
if (this.contact.isBlocked()) {
- menu.findItem(R.id.action_block).setVisible(false);
+ block.setVisible(false);
} else {
- menu.findItem(R.id.action_unblock).setVisible(false);
+ unblock.setVisible(false);
}
} else {
- menu.findItem(R.id.action_unblock).setVisible(false);
- menu.findItem(R.id.action_block).setVisible(false);
+ unblock.setVisible(false);
+ block.setVisible(false);
}
if (!contact.showInRoster()) {
edit.setVisible(false);
@@ -275,6 +288,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
}
private void populateView() {
+ invalidateOptionsMenu();
setTitle(contact.getDisplayName());
if (contact.showInRoster()) {
send.setVisibility(View.VISIBLE);
@@ -336,12 +350,9 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
} else {
contactJidTv.setText(contact.getJid().toString());
}
- accountJidTv.setText(getString(R.string.using_account, contact
- .getAccount().getJid().toBareJid()));
- prepareContactBadge(badge, contact);
- if (contact.getSystemAccount() == null) {
- badge.setOnClickListener(onBadgeClick);
- }
+ accountJidTv.setText(getString(R.string.using_account, contact.getAccount().getJid().toBareJid()));
+ badge.setImageBitmap(avatarService().get(contact, getPixel(72)));
+ badge.setOnClickListener(this.onBadgeClick);
keys.removeAllViews();
boolean hasKeys = false;
@@ -415,15 +426,6 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
}
}
- private void prepareContactBadge(QuickContactBadge badge, Contact contact) {
- if (contact.getSystemAccount() != null) {
- String[] systemAccount = contact.getSystemAccount().split("#");
- long id = Long.parseLong(systemAccount[0]);
- badge.assignContactUri(Contacts.getLookupUri(id, systemAccount[1]));
- }
- badge.setImageBitmap(avatarService().get(contact, getPixel(72)));
- }
-
protected void confirmToDeleteFingerprint(final String fingerprint) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.delete_fingerprint);
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
index 0fbaa479..db4fd712 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
@@ -5,6 +5,7 @@ import android.app.ActionBar;
import android.app.AlertDialog;
import android.app.FragmentTransaction;
import android.app.PendingIntent;
+import android.content.ClipData;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
@@ -22,21 +23,24 @@ import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
-import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnMenuItemClickListener;
import android.widget.Toast;
import net.java.otr4j.session.SessionStatus;
+import de.timroes.android.listview.EnhancedListView;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import eu.siacs.conversations.R;
+import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Blockable;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdate;
import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
@@ -46,7 +50,7 @@ import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
import github.ankushsachdeva.emojicon.EmojiconEditText;
public class ConversationActivity extends XmppActivity
- implements OnAccountUpdate, OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist {
+ implements OnAccountUpdate, OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast {
public static final String ACTION_DOWNLOAD = "eu.siacs.conversations.action.DOWNLOAD";
@@ -59,26 +63,27 @@ 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";
private String mOpenConverstaion = null;
private boolean mPanelOpen = true;
- private Uri mPendingImageUri = null;
- private Uri mPendingFileUri = null;
+ final private List<Uri> mPendingImageUris = new ArrayList<>();
+ final private List<Uri> mPendingFileUris = new ArrayList<>();
private Uri mPendingGeoUri = null;
private View mContentView;
private List<Conversation> conversationList = new ArrayList<>();
+ private Conversation swipedConversation = null;
private Conversation mSelectedConversation = null;
- private ListView listView;
+ private EnhancedListView listView;
private ConversationFragment mConversationFragment;
private ArrayAdapter<Conversation> listAdapter;
@@ -142,12 +147,13 @@ public class ConversationActivity extends XmppActivity
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
- mOpenConverstaion = savedInstanceState.getString(STATE_OPEN_CONVERSATION, null);
- mPanelOpen = savedInstanceState.getBoolean(STATE_PANEL_OPEN, true);
- String pending = savedInstanceState.getString(STATE_PENDING_URI, null);
- if (pending != null) {
- mPendingImageUri = Uri.parse(pending);
- }
+ mOpenConverstaion = savedInstanceState.getString(STATE_OPEN_CONVERSATION, null);
+ mPanelOpen = savedInstanceState.getBoolean(STATE_PANEL_OPEN, true);
+ String pending = savedInstanceState.getString(STATE_PENDING_URI, null);
+ if (pending != null) {
+ mPendingImageUris.clear();
+ mPendingImageUris.add(Uri.parse(pending));
+ }
}
setContentView(R.layout.fragment_conversations_overview);
@@ -157,7 +163,7 @@ public class ConversationActivity extends XmppActivity
transaction.replace(R.id.selected_conversation, this.mConversationFragment, "conversation");
transaction.commit();
- listView = (ListView) findViewById(R.id.list);
+ listView = (EnhancedListView) findViewById(R.id.list);
this.listAdapter = new ConversationAdapter(this, conversationList);
listView.setAdapter(this.listAdapter);
@@ -179,6 +185,73 @@ public class ConversationActivity extends XmppActivity
openConversation();
}
});
+
+ listView.setDismissCallback(new EnhancedListView.OnDismissCallback() {
+
+ @Override
+ public EnhancedListView.Undoable onDismiss(final EnhancedListView enhancedListView, final int position) {
+
+ final int index = listView.getFirstVisiblePosition();
+ View v = listView.getChildAt(0);
+ final int top = (v == null) ? 0 : (v.getTop() - listView.getPaddingTop());
+
+ swipedConversation = listAdapter.getItem(position);
+ listAdapter.remove(swipedConversation);
+ swipedConversation.markRead();
+ xmppConnectionService.getNotificationService().clear(swipedConversation);
+
+ final boolean formerlySelected = (getSelectedConversation() == swipedConversation);
+ if (position == 0 && listAdapter.getCount() == 0) {
+ endConversation(swipedConversation, false, true);
+ return null;
+ } else if (formerlySelected) {
+ setSelectedConversation(listAdapter.getItem(0));
+ ConversationActivity.this.mConversationFragment
+ .reInit(getSelectedConversation());
+ }
+
+ return new EnhancedListView.Undoable() {
+
+ @Override
+ public void undo() {
+ listAdapter.insert(swipedConversation, position);
+ if (formerlySelected) {
+ setSelectedConversation(swipedConversation);
+ ConversationActivity.this.mConversationFragment
+ .reInit(getSelectedConversation());
+ }
+ swipedConversation = null;
+ listView.setSelectionFromTop(index + (listView.getChildCount() < position ? 1 : 0), top);
+ }
+
+ @Override
+ public void discard() {
+ if (!swipedConversation.isRead()
+ && swipedConversation.getMode() == Conversation.MODE_SINGLE) {
+ swipedConversation = null;
+ return;
+ }
+ endConversation(swipedConversation, false, false);
+ swipedConversation = null;
+ }
+
+ @Override
+ public String getTitle() {
+ if (swipedConversation.getMode() == Conversation.MODE_MULTI) {
+ return getResources().getString(R.string.title_undo_swipe_out_muc);
+ } else {
+ return getResources().getString(R.string.title_undo_swipe_out_conversation);
+ }
+ }
+ };
+ }
+ });
+ listView.enableSwipeToDismiss();
+ listView.setSwipingLayout(R.id.swipeable_item);
+ listView.setUndoStyle(EnhancedListView.UndoStyle.SINGLE_POPUP);
+ listView.setUndoHideDelay(5000);
+ listView.setRequireTouchBeforeDismiss(false);
+
mContentView = findViewById(R.id.content_view_spl);
if (mContentView == null) {
mContentView = findViewById(R.id.content_view_ll);
@@ -205,6 +278,7 @@ public class ConversationActivity extends XmppActivity
@Override
public void onPanelClosed(View arg0) {
+ listView.discardUndo();
openConversation();
}
@@ -301,17 +375,16 @@ public class ConversationActivity extends XmppActivity
} else {
menuAdd.setVisible(!isConversationsOverviewHideable());
if (this.getSelectedConversation() != null) {
- if (this.getSelectedConversation().getLatestMessage()
- .getEncryption() != Message.ENCRYPTION_NONE) {
+ if (this.getSelectedConversation().getNextEncryption(forceEncryption()) != Message.ENCRYPTION_NONE) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- menuSecure.setIcon(R.drawable.ic_lock_outline_white_48dp);
+ menuSecure.setIcon(R.drawable.ic_lock_white_24dp);
} else {
menuSecure.setIcon(R.drawable.ic_action_secure);
}
}
if (this.getSelectedConversation().getMode() == Conversation.MODE_MULTI) {
menuContactDetails.setVisible(false);
- menuAttach.setVisible(false);
+ menuAttach.setVisible(getSelectedConversation().getAccount().httpUploadAvailable());
menuInviteContact.setVisible(getSelectedConversation().getMucOptions().canInvite());
} else {
menuMucDetails.setVisible(false);
@@ -327,56 +400,93 @@ 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 Conversation conversation = getSelectedConversation();
+ final Account account = conversation.getAccount();
+ 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);
- intent.setType("image/*");
- chooser = true;
- break;
- case ATTACHMENT_CHOICE_TAKE_PHOTO:
- mPendingImageUri = xmppConnectionService.getFileBackend().getTakePhotoUri();
- intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
- intent.putExtra(MediaStore.EXTRA_OUTPUT, mPendingImageUri);
- 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 ((account.httpUploadAvailable() || attachmentChoice == ATTACHMENT_CHOICE_LOCATION) && encryption != Message.ENCRYPTION_OTR) {
+ conversation.setNextCounterpart(null);
+ callback.onPresenceSelected();
+ } else {
+ selectPresence(conversation,callback);
}
}
- private void attachFile(final int attachmentChoice) {
+ 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;
+ }
+ }
+
+ 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;
+ case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
+ getPreferences().edit().putString("recently_used_quick_action","picture").apply();
+ break;
+ }
final Conversation conversation = getSelectedConversation();
final int encryption = conversation.getNextEncryption(forceEncryption());
if (encryption == Message.ENCRYPTION_PGP) {
@@ -486,13 +596,21 @@ public class ConversationActivity extends XmppActivity
}
public void endConversation(Conversation conversation) {
- showConversationsOverview();
+ endConversation(conversation, true, true);
+ }
+
+ public void endConversation(Conversation conversation, boolean showOverview, boolean reinit) {
+ if (showOverview) {
+ showConversationsOverview();
+ }
xmppConnectionService.archiveConversation(conversation);
- if (conversationList.size() > 0) {
- setSelectedConversation(conversationList.get(0));
- this.mConversationFragment.reInit(getSelectedConversation());
- } else {
- setSelectedConversation(null);
+ if (reinit) {
+ if (conversationList.size() > 0) {
+ setSelectedConversation(conversationList.get(0));
+ this.mConversationFragment.reInit(getSelectedConversation());
+ } else {
+ setSelectedConversation(null);
+ }
}
}
@@ -622,14 +740,11 @@ public class ConversationActivity extends XmppActivity
break;
case R.id.encryption_choice_pgp:
if (hasPgp()) {
- if (conversation.getAccount().getKeys()
- .has("pgp_signature")) {
- conversation
- .setNextEncryption(Message.ENCRYPTION_PGP);
+ if (conversation.getAccount().getKeys().has("pgp_signature")) {
+ conversation.setNextEncryption(Message.ENCRYPTION_PGP);
item.setChecked(true);
} else {
- announcePgp(conversation.getAccount(),
- conversation);
+ announcePgp(conversation.getAccount(),conversation);
}
} else {
showInstallPgpDialog();
@@ -639,16 +754,16 @@ public class ConversationActivity extends XmppActivity
conversation.setNextEncryption(Message.ENCRYPTION_NONE);
break;
}
- xmppConnectionService.databaseBackend
- .updateConversation(conversation);
+ xmppConnectionService.databaseBackend.updateConversation(conversation);
fragment.updateChatMsgHint();
+ invalidateOptionsMenu();
return true;
}
});
popup.inflate(R.menu.encryption_choices);
MenuItem otr = popup.getMenu().findItem(R.id.encryption_choice_otr);
- MenuItem none = popup.getMenu().findItem(
- R.id.encryption_choice_none);
+ MenuItem none = popup.getMenu().findItem(R.id.encryption_choice_none);
+ MenuItem pgp = popup.getMenu().findItem(R.id.encryption_choice_pgp);
if (conversation.getMode() == Conversation.MODE_MULTI) {
otr.setEnabled(false);
} else {
@@ -664,12 +779,10 @@ public class ConversationActivity extends XmppActivity
otr.setChecked(true);
break;
case Message.ENCRYPTION_PGP:
- popup.getMenu().findItem(R.id.encryption_choice_pgp)
- .setChecked(true);
+ pgp.setChecked(true);
break;
default:
- popup.getMenu().findItem(R.id.encryption_choice_none)
- .setChecked(true);
+ none.setChecked(true);
break;
}
popup.show();
@@ -745,6 +858,7 @@ public class ConversationActivity extends XmppActivity
@Override
public void onPause() {
+ listView.discardUndo();
super.onPause();
this.mActivityPaused = true;
if (this.xmppConnectionServiceBound) {
@@ -780,8 +894,8 @@ public class ConversationActivity extends XmppActivity
}
savedInstanceState.putBoolean(STATE_PANEL_OPEN,
isConversationsOverviewVisable());
- if (this.mPendingImageUri != null) {
- savedInstanceState.putString(STATE_PENDING_URI, this.mPendingImageUri.toString());
+ if (this.mPendingImageUris.size() >= 1) {
+ savedInstanceState.putString(STATE_PENDING_URI, this.mPendingImageUris.get(0).toString());
}
super.onSaveInstanceState(savedInstanceState);
}
@@ -790,6 +904,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;
@@ -816,25 +936,25 @@ 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();
- mPendingImageUri = null;
- mPendingFileUri = null;
+ mPendingImageUris.clear();
+ mPendingFileUris.clear();
mPendingGeoUri = null;
setSelectedConversation(conversationList.get(0));
this.mConversationFragment.reInit(getSelectedConversation());
}
- if (mPendingImageUri != null) {
- attachImageToConversation(getSelectedConversation(),mPendingImageUri);
- mPendingImageUri = null;
- } else if (mPendingFileUri != null) {
- attachFileToConversation(getSelectedConversation(),mPendingFileUri);
- mPendingFileUri = null;
- } else if (mPendingGeoUri != null) {
- attachLocationToConversation(getSelectedConversation(),mPendingGeoUri);
+ for(Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) {
+ attachImageToConversation(getSelectedConversation(),i.next());
+ }
+
+ for(Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) {
+ attachFileToConversation(getSelectedConversation(),i.next());
+ }
+
+ if (mPendingGeoUri != null) {
+ attachLocationToConversation(getSelectedConversation(), mPendingGeoUri);
mPendingGeoUri = null;
}
ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
@@ -842,10 +962,10 @@ public class ConversationActivity extends XmppActivity
}
private void handleViewConversationIntent(final Intent intent) {
- final String uuid = (String) intent.getExtras().get(CONVERSATION);
- final String downloadUuid = (String) intent.getExtras().get(MESSAGE);
- final String text = intent.getExtras().getString(TEXT, "");
- final String nick = intent.getExtras().getString(NICK, null);
+ final String uuid = intent.getStringExtra(CONVERSATION);
+ final String downloadUuid = intent.getStringExtra(MESSAGE);
+ final String text = intent.getStringExtra(TEXT);
+ final String nick = intent.getStringExtra(NICK);
if (selectConversationByUuid(uuid)) {
this.mConversationFragment.reInit(getSelectedConversation());
if (nick != null) {
@@ -886,6 +1006,21 @@ public class ConversationActivity extends XmppActivity
xmppConnectionService.getNotificationService().setOpenConversation(null);
}
+ @SuppressLint("NewApi")
+ private static List<Uri> extractUriFromIntent(final Intent intent) {
+ List<Uri> uris = new ArrayList<>();
+ Uri uri = intent.getData();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && uri == null) {
+ ClipData clipData = intent.getClipData();
+ for(int i = 0; i < clipData.getItemCount(); ++i) {
+ uris.add(clipData.getItemAt(i).getUri());
+ }
+ } else {
+ uris.add(uri);
+ }
+ return uris;
+ }
+
@Override
protected void onActivityResult(int requestCode, int resultCode,
final Intent data) {
@@ -895,25 +1030,34 @@ public class ConversationActivity extends XmppActivity
mConversationFragment.hideSnackbar();
mConversationFragment.updateMessages();
} else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_IMAGE) {
- mPendingImageUri = data.getData();
+ mPendingImageUris.clear();
+ mPendingImageUris.addAll(extractUriFromIntent(data));
if (xmppConnectionServiceBound) {
- attachImageToConversation(getSelectedConversation(),mPendingImageUri);
- mPendingImageUri = null;
+ for(Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) {
+ attachImageToConversation(getSelectedConversation(),i.next());
+ }
}
} else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_FILE || requestCode == ATTACHMENT_CHOICE_RECORD_VOICE) {
- mPendingFileUri = data.getData();
+ mPendingFileUris.clear();
+ mPendingFileUris.addAll(extractUriFromIntent(data));
if (xmppConnectionServiceBound) {
- attachFileToConversation(getSelectedConversation(),mPendingFileUri);
- mPendingFileUri = null;
+ for(Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) {
+ attachFileToConversation(getSelectedConversation(), i.next());
+ }
}
- } else if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO && mPendingImageUri != null) {
- if (xmppConnectionServiceBound) {
- attachImageToConversation(getSelectedConversation(),mPendingImageUri);
- mPendingImageUri = null;
+ } else if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO) {
+ if (mPendingImageUris.size() == 1) {
+ Uri uri = mPendingImageUris.get(0);
+ if (xmppConnectionServiceBound) {
+ attachImageToConversation(getSelectedConversation(), uri);
+ mPendingImageUris.clear();
+ }
+ Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
+ intent.setData(uri);
+ sendBroadcast(intent);
+ } else {
+ mPendingImageUris.clear();
}
- Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
- intent.setData(mPendingImageUri);
- sendBroadcast(intent);
} else if (requestCode == ATTACHMENT_CHOICE_LOCATION) {
double latitude = data.getDoubleExtra("latitude",0);
double longitude = data.getDoubleExtra("longitude",0);
@@ -924,13 +1068,15 @@ public class ConversationActivity extends XmppActivity
}
}
} else {
- if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO) {
- mPendingImageUri = null;
- }
+ mPendingImageUris.clear();
+ mPendingFileUris.clear();
}
}
private void attachLocationToConversation(Conversation conversation, Uri uri) {
+ if (conversation == null) {
+ return;
+ }
xmppConnectionService.attachLocationToConversation(conversation,uri, new UiCallback<Message>() {
@Override
@@ -951,8 +1097,10 @@ public class ConversationActivity extends XmppActivity
}
private void attachFileToConversation(Conversation conversation, Uri uri) {
- prepareFileToast = Toast.makeText(getApplicationContext(),
- getText(R.string.preparing_file), Toast.LENGTH_LONG);
+ if (conversation == null) {
+ return;
+ }
+ prepareFileToast = Toast.makeText(getApplicationContext(),getText(R.string.preparing_file), Toast.LENGTH_LONG);
prepareFileToast.show();
xmppConnectionService.attachFileToConversation(conversation,uri, new UiCallback<Message>() {
@Override
@@ -974,8 +1122,10 @@ public class ConversationActivity extends XmppActivity
}
private void attachImageToConversation(Conversation conversation, Uri uri) {
- prepareFileToast = Toast.makeText(getApplicationContext(),
- getText(R.string.preparing_image), Toast.LENGTH_LONG);
+ if (conversation == null) {
+ return;
+ }
+ prepareFileToast = Toast.makeText(getApplicationContext(),getText(R.string.preparing_image), Toast.LENGTH_LONG);
prepareFileToast.show();
xmppConnectionService.attachImageToConversation(conversation, uri,
new UiCallback<Message>() {
@@ -1014,6 +1164,13 @@ public class ConversationActivity extends XmppActivity
public void updateConversationList() {
xmppConnectionService
.populateWithOrderedConversations(conversationList);
+ if (swipedConversation != null) {
+ if (swipedConversation.isRead()) {
+ conversationList.remove(swipedConversation);
+ } else {
+ listView.discardUndo();
+ }
+ }
listAdapter.notifyDataSetChanged();
}
@@ -1113,4 +1270,14 @@ public class ConversationActivity extends XmppActivity
public boolean enterIsSend() {
return getPreferences().getBoolean("enter_is_send",false);
}
+
+ @Override
+ public void onShowErrorToast(final int resId) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(ConversationActivity.this,resId,Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
index 4f359d9c..5a672520 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
@@ -3,12 +3,12 @@ package eu.siacs.conversations.ui;
import android.app.AlertDialog;
import android.app.Fragment;
import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
-import android.net.Uri;
import android.os.Bundle;
import android.text.InputType;
import android.view.ContextMenu;
@@ -37,7 +37,6 @@ import android.widget.Toast;
import net.java.otr4j.session.SessionStatus;
-import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
@@ -50,9 +49,9 @@ import eu.siacs.conversations.crypto.PgpEngine;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
-import eu.siacs.conversations.entities.Downloadable;
+import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.entities.DownloadableFile;
-import eu.siacs.conversations.entities.DownloadablePlaceholder;
+import eu.siacs.conversations.entities.TransferablePlaceholder;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.Presences;
@@ -63,6 +62,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;
import github.ankushsachdeva.emojicon.EmojiconEditText;
@@ -128,16 +128,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;
}
@@ -145,29 +166,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();
}
}
});
@@ -185,7 +195,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();
}
});
@@ -219,7 +229,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<>();
@@ -230,7 +240,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;
@@ -243,15 +253,42 @@ 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 CHOOSE_PICTURE:
+ activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_CHOOSE_IMAGE);
+ 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);
@@ -261,24 +298,15 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
private Message selectedMessage;
private void sendMessage() {
- if (this.conversation == null) {
+ final String body = mEditMessage.getText().toString();
+ if (body.length() == 0 || 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()));
+ Message message = new Message(conversation, body, conversation.getNextEncryption(activity.forceEncryption()));
if (conversation.getMode() == Conversation.MODE_MULTI) {
if (conversation.getNextCounterpart() != null) {
message.setCounterpart(conversation.getNextCounterpart());
message.setType(Message.TYPE_PRIVATE);
- conversation.setNextCounterpart(null);
}
}
if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_OTR) {
@@ -294,13 +322,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));
@@ -316,7 +344,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);
@@ -325,9 +353,9 @@ 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();
mEditMessage.setOnClickListener(new OnClickListener() {
@@ -486,21 +514,21 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
}
});
messageListAdapter
- .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());
+ .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());
+ }
}
+ } else {
+ activity.showQrCode();
}
- } else {
- activity.showQrCode();
}
- }
- });
+ });
messagesView.setAdapter(messageListAdapter);
registerForContextMenu(messagesView);
@@ -510,7 +538,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;
@@ -528,35 +556,37 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
MenuItem shareWith = menu.findItem(R.id.share_with);
MenuItem sendAgain = menu.findItem(R.id.send_again);
MenuItem copyUrl = menu.findItem(R.id.copy_url);
- MenuItem downloadImage = menu.findItem(R.id.download_image);
+ MenuItem downloadFile = menu.findItem(R.id.download_file);
MenuItem cancelTransmission = menu.findItem(R.id.cancel_transmission);
- if ((m.getType() != Message.TYPE_TEXT && m.getType() != Message.TYPE_PRIVATE)
- || m.getDownloadable() != null || GeoHelper.isGeoUri(m.getBody())) {
- copyText.setVisible(false);
+ if ((m.getType() == Message.TYPE_TEXT || m.getType() == Message.TYPE_PRIVATE)
+ && m.getTransferable() == null
+ && !GeoHelper.isGeoUri(m.getBody())
+ && m.treatAsDownloadable() != Message.Decision.MUST) {
+ copyText.setVisible(true);
}
- if ((m.getType() == Message.TYPE_TEXT
- || m.getType() == Message.TYPE_PRIVATE
- || m.getDownloadable() != null)
- && (!GeoHelper.isGeoUri(m.getBody()))) {
- shareWith.setVisible(false);
+ if ((m.getType() != Message.TYPE_TEXT
+ && m.getType() != Message.TYPE_PRIVATE
+ && m.getTransferable() == null)
+ || (GeoHelper.isGeoUri(m.getBody()))) {
+ shareWith.setVisible(true);
}
- if (m.getStatus() != Message.STATUS_SEND_FAILED) {
- sendAgain.setVisible(false);
+ if (m.getStatus() == Message.STATUS_SEND_FAILED) {
+ sendAgain.setVisible(true);
+ }
+ if (m.hasFileOnRemoteHost()
+ || GeoHelper.isGeoUri(m.getBody())
+ || m.treatAsDownloadable() == Message.Decision.MUST) {
+ copyUrl.setVisible(true);
+ }
+ if (m.getType() == Message.TYPE_TEXT && m.getTransferable() == null && m.treatAsDownloadable() != Message.Decision.NEVER) {
+ downloadFile.setVisible(true);
+ downloadFile.setTitle(activity.getString(R.string.download_x_file,UIHelper.getFileDescriptionString(activity, m)));
+ }
+ if ((m.getTransferable() != null && !(m.getTransferable() instanceof TransferablePlaceholder))
+ || (m.isFileOrImage() && (m.getStatus() == Message.STATUS_WAITING
+ || m.getStatus() == Message.STATUS_OFFERED))) {
+ cancelTransmission.setVisible(true);
}
- 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)))) {
- cancelTransmission.setVisible(false);
- }
}
}
@@ -575,8 +605,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
case R.id.copy_url:
copyUrl(selectedMessage);
return true;
- case R.id.download_image:
- downloadImage(selectedMessage);
+ case R.id.download_file:
+ downloadFile(selectedMessage);
return true;
case R.id.cancel_transmission:
cancelTransmission(selectedMessage);
@@ -597,19 +627,23 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
activity.xmppConnectionService.getFileBackend()
.getJingleFileUri(message));
shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- String path = message.getRelativeFilePath();
- String mime = path == null ? null : URLConnection.guessContentTypeFromName(path);
+ String mime = message.getMimeType();
if (mime == null) {
mime = "image/webp";
}
shareIntent.setType(mime);
}
- activity.startActivity(Intent.createChooser(shareIntent,getText(R.string.share_with)));
+ try {
+ activity.startActivity(Intent.createChooser(shareIntent, getText(R.string.share_with)));
+ } catch (ActivityNotFoundException e) {
+ //This should happen only on faulty androids because normally chooser is always available
+ Toast.makeText(activity,R.string.no_application_found_to_open_file,Toast.LENGTH_SHORT).show();
+ }
}
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();
}
@@ -619,8 +653,8 @@ 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();
- message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_DELETED));
+ Toast.makeText(activity, R.string.file_deleted, Toast.LENGTH_SHORT).show();
+ message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED));
return;
}
}
@@ -633,27 +667,30 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (GeoHelper.isGeoUri(message.getBody())) {
resId = R.string.location;
url = message.getBody();
+ } else if (message.hasFileOnRemoteHost()) {
+ resId = R.string.file_url;
+ url = message.getFileParams().url.toString();
} else {
- resId = R.string.image_url;
- url = message.getImageParams().url.toString();
+ url = message.getBody().trim();
+ resId = R.string.file_url;
}
if (activity.copyTextToClipboard(url, resId)) {
Toast.makeText(activity, R.string.url_copied_to_clipboard,
Toast.LENGTH_SHORT).show();
- }
+ }
}
- private void downloadImage(Message message) {
+ private void downloadFile(Message message) {
activity.xmppConnectionService.getHttpConnectionManager()
- .createNewConnection(message);
+ .createNewDownloadConnection(message);
}
private void cancelTransmission(Message message) {
- Downloadable downloadable = message.getDownloadable();
- if (downloadable!=null) {
- downloadable.cancel();
+ Transferable transferable = message.getTransferable();
+ if (transferable != null) {
+ transferable.cancel();
} else {
- activity.xmppConnectionService.markMessage(message,Message.STATUS_SEND_FAILED);
+ activity.xmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
}
}
@@ -661,6 +698,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
this.mEditMessage.setText("");
this.conversation.setNextCounterpart(counterpart);
updateChatMsgHint();
+ updateSendButton();
}
protected void highlightInConference(String nick) {
@@ -669,9 +707,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 + " ");
}
@@ -684,7 +722,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);
}
}
@@ -707,7 +745,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();
}
@@ -753,7 +791,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);
@@ -776,7 +814,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);
}
};
@@ -786,11 +824,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:
@@ -814,18 +852,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();
}
@@ -839,31 +877,16 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
final ConversationActivity activity = (ConversationActivity) getActivity();
if (this.conversation != null) {
updateSnackBar(this.conversation);
- final Contact contact = this.conversation.getContact();
- if (this.conversation.isBlocked()) {
-
- } else if (!contact.showInRoster()
- && contact
- .getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
-
- } else if (conversation.getMode() == Conversation.MODE_SINGLE) {
- makeFingerprintWarning();
- } else if (!conversation.getMucOptions().online()
- && conversation.getAccount().getStatus() == Account.State.ONLINE) {
-
- } else if (this.conversation.isMuted()) {
-
- }
conversation.populateWithMessages(ConversationFragment.this.messageList);
for (final Message message : this.messageList) {
if (message.getEncryption() == Message.ENCRYPTION_PGP
&& (message.getStatus() == Message.STATUS_RECEIVED || message
- .getStatus() >= Message.STATUS_SEND)
- && message.getDownloadable() == null) {
+ .getStatus() >= Message.STATUS_SEND)
+ && message.getTransferable() == null) {
if (!mEncryptedMessages.contains(message)) {
mEncryptedMessages.add(message);
}
- }
+ }
}
decryptNext();
updateStatusMessages();
@@ -900,6 +923,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
} catch (final NoSuchElementException ignored) {
}
+ askForPassphraseIntent = null;
activity.xmppConnectionService.updateMessage(message);
}
@@ -925,53 +949,149 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
updateChatMsgHint();
}
- public void updateSendButton() {
- Conversation c = this.conversation;
- if (Settings.SHOW_ONLINE_STATUS && 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, CHOOSE_PICTURE}
+
+ 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:
- this.mSendButton
- .setImageResource(R.drawable.ic_action_send_now_away);
- break;
case Presences.DND:
- this.mSendButton
- .setImageResource(R.drawable.ic_action_send_now_dnd);
- break;
+ return R.drawable.ic_send_text_dnd;
default:
- this.mSendButton
- .setImageResource(R.drawable.ic_action_send_now_offline);
- break;
+ 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:
+ case Presences.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;
}
- } else if (c.getMode() == Conversation.MODE_MULTI) {
- if (c.getMucOptions().online()) {
- this.mSendButton
- .setImageResource(R.drawable.ic_action_send_now_online);
+ case CHOOSE_PICTURE:
+ switch (status) {
+ case Presences.CHAT:
+ case Presences.ONLINE:
+ return R.drawable.ic_send_picture_online;
+ case Presences.AWAY:
+ return R.drawable.ic_send_picture_away;
+ case Presences.XA:
+ case Presences.DND:
+ return R.drawable.ic_send_picture_dnd;
+ default:
+ return R.drawable.ic_send_picture_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;
+ final boolean conference = c.getMode() == Conversation.MODE_MULTI;
+ if (conference && !c.getAccount().httpUploadAvailable()) {
+ if (empty && c.getNextCounterpart() != null) {
+ action = SendButtonAction.CANCEL;
+ } else {
+ action = SendButtonAction.TEXT;
+ }
+ } else {
+ if (empty) {
+ if (conference && c.getNextCounterpart() != null) {
+ action = SendButtonAction.CANCEL;
} else {
- this.mSendButton
- .setImageResource(R.drawable.ic_action_send_now_offline);
+ 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;
+ case "picture":
+ action = SendButtonAction.CHOOSE_PICTURE;
+ break;
+ default:
+ action = SendButtonAction.TEXT;
+ break;
+ }
}
} 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() {
@@ -999,12 +1119,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
}
}
- protected void makeFingerprintWarning() {
-
- }
-
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);
@@ -1036,7 +1152,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);
@@ -1060,11 +1176,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();
@@ -1075,9 +1191,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();
}
@@ -1089,12 +1205,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();
}
@@ -1107,7 +1223,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) {
@@ -1139,6 +1255,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
}
public void appendText(String text) {
+ if (text == null) {
+ return;
+ }
String previous = this.mEditMessage.getText().toString();
if (previous.length() != 0 && !previous.endsWith(" ")) {
text = " " + text;
@@ -1162,6 +1281,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (status == Account.State.ONLINE && conversation.setOutgoingChatState(ChatState.COMPOSING)) {
activity.xmppConnectionService.sendChatState(conversation);
}
+ updateSendButton();
}
@Override
@@ -1178,6 +1298,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (status == Account.State.ONLINE && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
activity.xmppConnectionService.sendChatState(conversation);
}
+ updateSendButton();
}
private void changeEmojiKeyboardIcon(ImageView iconToBeChanged, int drawableResourceId){
diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
index 27dfc492..908c29d2 100644
--- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
@@ -67,7 +67,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
@Override
public void onClick(final View v) {
- if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED) {
+ if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !accountInfoEdited()) {
mAccount.setOption(Account.OPTION_DISABLED, false);
xmppConnectionService.updateAccount(mAccount);
return;
@@ -123,11 +123,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
xmppConnectionService.createAccount(mAccount);
}
- if (jidToEdit != null) {
+ if (jidToEdit != null && !mAccount.isOptionSet(Account.OPTION_DISABLED)) {
finish();
} else {
updateSaveButton();
- updateAccountInformation();
+ updateAccountInformation(true);
}
}
@@ -163,7 +163,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
updateSaveButton();
}
if (mAccount != null) {
- updateAccountInformation();
+ updateAccountInformation(false);
}
}
});
@@ -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);
@@ -237,7 +239,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
}
protected void updateSaveButton() {
- if (mAccount != null && (mAccount.getStatus() == Account.State.CONNECTING || mFetchingAvatar)) {
+ if (accountInfoEdited() && jidToEdit != null) {
+ this.mSaveButton.setText(R.string.save);
+ this.mSaveButton.setEnabled(true);
+ this.mSaveButton.setTextColor(getPrimaryTextColor());
+ } else if (mAccount != null && (mAccount.getStatus() == Account.State.CONNECTING || mFetchingAvatar)) {
this.mSaveButton.setEnabled(false);
this.mSaveButton.setTextColor(getSecondaryTextColor());
this.mSaveButton.setText(R.string.account_status_connecting);
@@ -265,9 +271,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
}
protected boolean accountInfoEdited() {
- return (!this.mAccount.getJid().toBareJid().toString().equals(
- this.mAccountJid.getText().toString()))
- || (!this.mAccount.getPassword().equals(
+ return this.mAccount != null && (!this.mAccount.getJid().toBareJid().toString().equals(
+ this.mAccountJid.getText().toString())
+ || !this.mAccount.getPassword().equals(
this.mPassword.getText().toString()));
}
@@ -378,7 +384,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
xmppConnectionService.getKnownHosts());
if (this.jidToEdit != null) {
this.mAccount = xmppConnectionService.findAccountByJid(jidToEdit);
- updateAccountInformation();
+ updateAccountInformation(true);
} else if (this.xmppConnectionService.getAccounts().size() == 0) {
if (getActionBar() != null) {
getActionBar().setDisplayHomeAsUpEnabled(false);
@@ -413,9 +419,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
return super.onOptionsItemSelected(item);
}
- private void updateAccountInformation() {
- this.mAccountJid.setText(this.mAccount.getJid().toBareJid().toString());
- this.mPassword.setText(this.mAccount.getPassword());
+ private void updateAccountInformation(boolean init) {
+ if (init) {
+ this.mAccountJid.setText(this.mAccount.getJid().toBareJid().toString());
+ this.mPassword.setText(this.mAccount.getPassword());
+ }
if (this.jidToEdit != null) {
this.mAvatar.setVisibility(View.VISIBLE);
this.mAvatar.setImageBitmap(avatarService().get(this.mAccount, getPixel(72)));
@@ -464,7 +472,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
} else {
this.mServerInfoSm.setText(R.string.server_info_unavailable);
}
- if (features.pubsub()) {
+ if (features.pep()) {
this.mServerInfoPep.setText(R.string.server_info_available);
} else {
this.mServerInfoPep.setText(R.string.server_info_unavailable);
@@ -495,7 +503,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
} else {
if (this.mAccount.errorStatus()) {
this.mAccountJid.setError(getString(this.mAccount.getStatus().getReadableId()));
- this.mAccountJid.requestFocus();
+ if (init || !accountInfoEdited()) {
+ this.mAccountJid.requestFocus();
+ }
} else {
this.mAccountJid.setError(null);
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java
index b2d5ddfd..56dbc55e 100644
--- a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java
@@ -168,6 +168,14 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
}
}
+ public void onClickTglAccountState(Account account, boolean enable) {
+ if (enable) {
+ enableAccount(account);
+ } else {
+ disableAccount(account);
+ }
+ }
+
private void publishAvatar(Account account) {
Intent 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 3f72b723..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();
@@ -163,8 +165,7 @@ public class PublishProfilePictureActivity extends XmppActivity {
if (jid != null) {
this.account = xmppConnectionService.findAccountByJid(jid);
if (this.account.getXmppConnection() != null) {
- this.support = this.account.getXmppConnection()
- .getFeatures().pubsub();
+ this.support = this.account.getXmppConnection().getFeatures().pep();
}
if (this.avatarUri == null) {
if (this.account.getAvatar() != null
diff --git a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
index 2115b23b..d12ff4f2 100644
--- a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
@@ -1,18 +1,30 @@
package eu.siacs.conversations.ui;
+import java.security.KeyStoreException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Locale;
import de.tzur.conversations.Settings;
+import de.duenndns.ssl.MemorizingTrustManager;
+
+import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.xmpp.XmppConnection;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Build;
import android.os.Bundle;
import android.preference.ListPreference;
+import android.preference.Preference;
import android.preference.PreferenceManager;
+import android.widget.Toast;
public class SettingsActivity extends XmppActivity implements
OnSharedPreferenceChangeListener {
@@ -21,9 +33,12 @@ public class SettingsActivity extends XmppActivity implements
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mSettingsFragment = new SettingsFragment();
- getFragmentManager().beginTransaction()
- .replace(android.R.id.content, mSettingsFragment).commit();
+ FragmentManager fm = getFragmentManager();
+ mSettingsFragment = (SettingsFragment) fm.findFragmentById(android.R.id.content);
+ if (mSettingsFragment == null || !mSettingsFragment.getClass().equals(SettingsFragment.class)) {
+ mSettingsFragment = new SettingsFragment();
+ fm.beginTransaction().replace(android.R.id.content, mSettingsFragment).commit();
+ }
}
@Override
@@ -34,19 +49,78 @@ public class SettingsActivity extends XmppActivity implements
@Override
public void onStart() {
super.onStart();
- PreferenceManager.getDefaultSharedPreferences(this)
- .registerOnSharedPreferenceChangeListener(this);
- ListPreference resources = (ListPreference) mSettingsFragment
- .findPreference("resource");
+ PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this);
+ ListPreference resources = (ListPreference) mSettingsFragment.findPreference("resource");
if (resources != null) {
- ArrayList<CharSequence> entries = new ArrayList<CharSequence>(
- Arrays.asList(resources.getEntries()));
- entries.add(0, Build.MODEL);
- resources.setEntries(entries.toArray(new CharSequence[entries
- .size()]));
- resources.setEntryValues(entries.toArray(new CharSequence[entries
- .size()]));
+ ArrayList<CharSequence> entries = new ArrayList<>(Arrays.asList(resources.getEntries()));
+ if (!entries.contains(Build.MODEL)) {
+ entries.add(0, Build.MODEL);
+ resources.setEntries(entries.toArray(new CharSequence[entries.size()]));
+ resources.setEntryValues(entries.toArray(new CharSequence[entries.size()]));
+ }
}
+
+ final Preference removeCertsPreference = mSettingsFragment.findPreference("remove_trusted_certificates");
+ removeCertsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ final MemorizingTrustManager mtm = xmppConnectionService.getMemorizingTrustManager();
+ final ArrayList<String> aliases = Collections.list(mtm.getCertificates());
+ if (aliases.size() == 0) {
+ displayToast(getString(R.string.toast_no_trusted_certs));
+ return true;
+ }
+ final ArrayList selectedItems = new ArrayList<Integer>();
+ final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(SettingsActivity.this);
+ dialogBuilder.setTitle(getResources().getString(R.string.dialog_manage_certs_title));
+ dialogBuilder.setMultiChoiceItems(aliases.toArray(new CharSequence[aliases.size()]), null,
+ new DialogInterface.OnMultiChoiceClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int indexSelected,
+ boolean isChecked) {
+ if (isChecked) {
+ selectedItems.add(indexSelected);
+ } else if (selectedItems.contains(indexSelected)) {
+ selectedItems.remove(Integer.valueOf(indexSelected));
+ }
+ if (selectedItems.size() > 0)
+ ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true);
+ else {
+ ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
+ }
+ }
+ });
+
+ dialogBuilder.setPositiveButton(
+ getResources().getString(R.string.dialog_manage_certs_positivebutton), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ int count = selectedItems.size();
+ if (count > 0) {
+ for (int i = 0; i < count; i++) {
+ try {
+ Integer item = Integer.valueOf(selectedItems.get(i).toString());
+ String alias = aliases.get(item);
+ mtm.deleteCertificate(alias);
+ } catch (KeyStoreException e) {
+ e.printStackTrace();
+ displayToast("Error: " + e.getLocalizedMessage());
+ }
+ }
+ if (xmppConnectionServiceBound) {
+ reconnectAccounts();
+ }
+ displayToast(getResources().getQuantityString(R.plurals.toast_delete_certificates, count, count));
+ }
+ }
+ });
+ dialogBuilder.setNegativeButton(getResources().getString(R.string.dialog_manage_certs_negativebutton), null);
+ AlertDialog removeCertsDialog = dialogBuilder.create();
+ removeCertsDialog.show();
+ removeCertsDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
+ return true;
+ }
+ });
}
@Override
@@ -69,6 +143,10 @@ public class SettingsActivity extends XmppActivity implements
for (Account account : xmppConnectionService.getAccounts()) {
account.setResource(resource);
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
+ XmppConnection connection = account.getXmppConnection();
+ if (connection != null) {
+ connection.resetStreamId();
+ }
xmppConnectionService.reconnectAccountInBackground(account);
}
}
@@ -77,7 +155,7 @@ public class SettingsActivity extends XmppActivity implements
case "keep_foreground_service":
xmppConnectionService.toggleForegroundService();
break;
- case "confirm_messages_list":
+ case "confirm_messages":
if (xmppConnectionServiceBound) {
for (Account account : xmppConnectionService.getAccounts()) {
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
@@ -86,7 +164,28 @@ public class SettingsActivity extends XmppActivity implements
}
}
break;
+ case "dont_trust_system_cas":
+ xmppConnectionService.updateMemorizingTrustmanager();
+ reconnectAccounts();
+ break;
}
+
+ private void displayToast(final String msg) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(SettingsActivity.this, msg, Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+
+ private void reconnectAccounts() {
+ for (Account account : xmppConnectionService.getAccounts()) {
+ if (!account.isOptionSet(Account.OPTION_DISABLED)) {
+ xmppConnectionService.reconnectAccountInBackground(account);
+ }
+ }
+ }
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java
index 6be238dc..351f1dfc 100644
--- a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java
@@ -4,7 +4,6 @@ import android.app.PendingIntent;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
-import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -13,11 +12,9 @@ import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.Toast;
-import java.io.UnsupportedEncodingException;
import java.net.URLConnection;
-import java.net.URLDecoder;
-import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import eu.siacs.conversations.Config;
@@ -32,7 +29,7 @@ import eu.siacs.conversations.xmpp.jid.Jid;
public class ShareWithActivity extends XmppActivity {
private class Share {
- public Uri uri;
+ public List<Uri> uris = new ArrayList<>();
public boolean image;
public String account;
public String contact;
@@ -65,18 +62,17 @@ public class ShareWithActivity extends XmppActivity {
}
};
- protected void onActivityResult(int requestCode, int resultCode,
- final Intent data) {
+ protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_START_NEW_CONVERSATION
&& resultCode == RESULT_OK) {
share.contact = data.getStringExtra("contact");
share.account = data.getStringExtra("account");
- Log.d(Config.LOGTAG, "contact: " + share.contact + " account:"
- + share.account);
}
- if (xmppConnectionServiceBound && share != null
- && share.contact != null && share.account != null) {
+ if (xmppConnectionServiceBound
+ && share != null
+ && share.contact != null
+ && share.account != null) {
share();
}
}
@@ -100,13 +96,8 @@ public class ShareWithActivity extends XmppActivity {
mListView.setOnItemClickListener(new OnItemClickListener() {
@Override
- public void onItemClick(AdapterView<?> arg0, View arg1,
- int position, long arg3) {
- Conversation conversation = mConversations.get(position);
- if (conversation.getMode() == Conversation.MODE_SINGLE
- || share.uri == null) {
- share(mConversations.get(position));
- }
+ public void onItemClick(AdapterView<?> arg0, View arg1, int position, long arg3) {
+ share(mConversations.get(position));
}
});
@@ -122,29 +113,42 @@ public class ShareWithActivity extends XmppActivity {
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
- case R.id.action_add:
- final Intent intent = new Intent(getApplicationContext(),
- ChooseContactActivity.class);
- startActivityForResult(intent, REQUEST_START_NEW_CONVERSATION);
- return true;
+ case R.id.action_add:
+ final Intent intent = new Intent(getApplicationContext(), ChooseContactActivity.class);
+ startActivityForResult(intent, REQUEST_START_NEW_CONVERSATION);
+ return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onStart() {
- final String type = getIntent().getType();
- final Uri uri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
- if (type != null && uri != null && !type.equalsIgnoreCase("text/plain")) {
- this.share.uri = uri;
- this.share.image = type.startsWith("image/") || isImage(uri);
- } else {
- this.share.text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
+ super.onStart();
+ Intent intent = getIntent();
+ if (intent == null) {
+ return;
+ }
+ final String type = intent.getType();
+ if (Intent.ACTION_SEND.equals(intent.getAction())) {
+ final Uri uri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
+ if (type != null && uri != null && !type.equalsIgnoreCase("text/plain")) {
+ this.share.uris.add(uri);
+ this.share.image = type.startsWith("image/") || isImage(uri);
+ } else {
+ this.share.text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
+ }
+ } else if (Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) {
+ this.share.image = type != null && type.startsWith("image/");
+ if (!this.share.image) {
+ return;
+ }
+
+ this.share.uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
}
if (xmppConnectionServiceBound) {
- xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uri == null);
+ xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uris.size() == 0);
}
- super.onStart();
+
}
protected boolean isImage(Uri uri) {
@@ -164,54 +168,60 @@ public class ShareWithActivity extends XmppActivity {
return;
}
xmppConnectionService.populateWithOrderedConversations(mConversations,
- this.share != null && this.share.uri == null);
+ this.share != null && this.share.uris.size() == 0);
}
private void share() {
- Account account;
- try {
- account = xmppConnectionService.findAccountByJid(Jid.fromString(share.account));
- } catch (final InvalidJidException e) {
- account = null;
- }
- if (account == null) {
+ Account account;
+ try {
+ account = xmppConnectionService.findAccountByJid(Jid.fromString(share.account));
+ } catch (final InvalidJidException e) {
+ account = null;
+ }
+ if (account == null) {
+ return;
+ }
+ final Conversation conversation;
+ try {
+ conversation = xmppConnectionService
+ .findOrCreateConversation(account, Jid.fromString(share.contact), false);
+ } catch (final InvalidJidException e) {
return;
}
- final Conversation conversation;
- try {
- conversation = xmppConnectionService
- .findOrCreateConversation(account, Jid.fromString(share.contact), false);
- } catch (final InvalidJidException e) {
- return;
- }
- share(conversation);
+ share(conversation);
}
private void share(final Conversation conversation) {
- if (share.uri != null) {
- selectPresence(conversation, new OnPresenceSelected() {
+ if (share.uris.size() != 0) {
+ OnPresenceSelected callback = new OnPresenceSelected() {
@Override
public void onPresenceSelected() {
if (share.image) {
Toast.makeText(getApplicationContext(),
getText(R.string.preparing_image),
Toast.LENGTH_LONG).show();
- ShareWithActivity.this.xmppConnectionService
- .attachImageToConversation(conversation, share.uri,
- attachFileCallback);
+ for (Iterator<Uri> i = share.uris.iterator(); i.hasNext(); i.remove()) {
+ ShareWithActivity.this.xmppConnectionService
+ .attachImageToConversation(conversation, i.next(),
+ attachFileCallback);
+ }
} else {
Toast.makeText(getApplicationContext(),
getText(R.string.preparing_file),
Toast.LENGTH_LONG).show();
ShareWithActivity.this.xmppConnectionService
- .attachFileToConversation(conversation, share.uri,
- attachFileCallback);
+ .attachFileToConversation(conversation, share.uris.get(0),
+ attachFileCallback);
}
switchToConversation(conversation, null, true);
finish();
}
- });
-
+ };
+ if (conversation.getAccount().httpUploadAvailable()) {
+ callback.onPresenceSelected();
+ } else {
+ selectPresence(conversation, callback);
+ }
} else {
switchToConversation(conversation, this.share.text, true);
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..68e77af4 100644
--- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
@@ -42,6 +42,7 @@ import android.widget.Checkable;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Spinner;
+import android.widget.Toast;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;
@@ -65,6 +66,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;
@@ -262,9 +264,12 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
protected void openConversationForBookmark(int position) {
Bookmark bookmark = (Bookmark) conferences.get(position);
- Conversation conversation = xmppConnectionService
- .findOrCreateConversation(bookmark.getAccount(),
- bookmark.getJid(), true);
+ Jid jid = bookmark.getJid();
+ if (jid == null) {
+ Toast.makeText(this,R.string.invalid_jid,Toast.LENGTH_SHORT).show();
+ return;
+ }
+ Conversation conversation = xmppConnectionService.findOrCreateConversation(bookmark.getAccount(),jid, true);
conversation.setBookmark(bookmark);
if (!conversation.getMucOptions().online()) {
xmppConnectionService.joinMuc(conversation);
@@ -757,14 +762,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 62f62b9a..4157035b 100644
--- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
@@ -91,6 +91,7 @@ public abstract class XmppActivity extends Activity {
protected int mPrimaryTextColor;
protected int mSecondaryTextColor;
+ protected int mPrimaryBackgroundColor;
protected int mSecondaryBackgroundColor;
protected int mColorRed;
protected int mColorOrange;
@@ -113,6 +114,8 @@ public abstract class XmppActivity extends Activity {
}
};
+ protected ConferenceInvite mPendingConferenceInvite = null;
+
protected void refreshUi() {
final long diff = SystemClock.elapsedRealtime() - mLastUiRefresh;
@@ -282,6 +285,9 @@ public abstract class XmppActivity extends Activity {
if (this instanceof OnUpdateBlocklist) {
this.xmppConnectionService.setOnUpdateBlocklistListener((OnUpdateBlocklist) this);
}
+ if (this instanceof XmppConnectionService.OnShowErrorToast) {
+ this.xmppConnectionService.setOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this);
+ }
}
protected void unregisterListeners() {
@@ -300,6 +306,9 @@ public abstract class XmppActivity extends Activity {
if (this instanceof OnUpdateBlocklist) {
this.xmppConnectionService.removeOnUpdateBlocklistListener();
}
+ if (this instanceof XmppConnectionService.OnShowErrorToast) {
+ this.xmppConnectionService.removeOnShowErrorToastListener();
+ }
}
@Override
@@ -326,13 +335,14 @@ public abstract class XmppActivity extends Activity {
super.onCreate(savedInstanceState);
metrics = getResources().getDisplayMetrics();
ExceptionHelper.init(getApplicationContext());
- mPrimaryTextColor = getResources().getColor(R.color.primarytext);
- mSecondaryTextColor = getResources().getColor(R.color.secondarytext);
- mColorRed = getResources().getColor(R.color.red);
- mColorOrange = getResources().getColor(R.color.orange);
- mColorGreen = getResources().getColor(R.color.green);
- mPrimaryColor = getResources().getColor(R.color.primary);
- mSecondaryBackgroundColor = getResources().getColor(R.color.secondarybackground);
+ mPrimaryTextColor = getResources().getColor(R.color.black87);
+ mSecondaryTextColor = getResources().getColor(R.color.black54);
+ mColorRed = getResources().getColor(R.color.red500);
+ mColorOrange = getResources().getColor(R.color.orange500);
+ mColorGreen = getResources().getColor(R.color.green500);
+ mPrimaryColor = getResources().getColor(R.color.green500);
+ mPrimaryBackgroundColor = getResources().getColor(R.color.grey50);
+ mSecondaryBackgroundColor = getResources().getColor(R.color.grey200);
this.mTheme = findTheme();
setTheme(this.mTheme);
this.mUsingEnterKey = usingEnterKey();
@@ -369,7 +379,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) {
@@ -437,7 +447,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);
@@ -447,14 +457,11 @@ public abstract class XmppActivity extends Activity {
@Override
public void success(Account account) {
- xmppConnectionService.databaseBackend
- .updateAccount(account);
+ xmppConnectionService.databaseBackend.updateAccount(account);
xmppConnectionService.sendPresence(account);
if (conversation != null) {
- conversation
- .setNextEncryption(Message.ENCRYPTION_PGP);
- xmppConnectionService.databaseBackend
- .updateConversation(conversation);
+ conversation.setNextEncryption(Message.ENCRYPTION_PGP);
+ xmppConnectionService.databaseBackend.updateConversation(conversation);
}
}
@@ -667,32 +674,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;
}
}
}
@@ -737,14 +723,14 @@ public abstract class XmppActivity extends Activity {
return this.mColorRed;
}
- public int getPrimaryColor() {
- return this.mPrimaryColor;
- }
-
public int getOnlineColor() {
return this.mColorGreen;
}
-
+
+ public int getPrimaryBackgroundColor() {
+ return this.mPrimaryBackgroundColor;
+ }
+
public int getSecondaryBackgroundColor() {
return this.mSecondaryBackgroundColor;
}
@@ -853,6 +839,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/AccountAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java
index 29730914..782a1231 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java
@@ -5,13 +5,16 @@ import java.util.List;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.ui.XmppActivity;
+import eu.siacs.conversations.ui.ManageAccountActivity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
+import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.TextView;
+import android.widget.Switch;
public class AccountAdapter extends ArrayAdapter<Account> {
@@ -24,7 +27,7 @@ public class AccountAdapter extends ArrayAdapter<Account> {
@Override
public View getView(int position, View view, ViewGroup parent) {
- Account account = getItem(position);
+ final Account account = getItem(position);
if (view == null) {
LayoutInflater inflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@@ -34,21 +37,32 @@ public class AccountAdapter extends ArrayAdapter<Account> {
jid.setText(account.getJid().toBareJid().toString());
TextView statusView = (TextView) view.findViewById(R.id.account_status);
ImageView imageView = (ImageView) view.findViewById(R.id.account_image);
- imageView.setImageBitmap(activity.avatarService().get(account,
- activity.getPixel(48)));
- statusView.setText(getContext().getString(account.getStatus().getReadableId()));
- switch (account.getStatus()) {
- case ONLINE:
- statusView.setTextColor(activity.getOnlineColor());
- break;
- case DISABLED:
- case CONNECTING:
- statusView.setTextColor(activity.getSecondaryTextColor());
- break;
- default:
- statusView.setTextColor(activity.getWarningTextColor());
- break;
- }
+ imageView.setImageBitmap(activity.avatarService().get(account, activity.getPixel(48)));
+ statusView.setText(getContext().getString(account.getStatus().getReadableId()));
+ switch (account.getStatus()) {
+ case ONLINE:
+ statusView.setTextColor(activity.getOnlineColor());
+ break;
+ case DISABLED:
+ case CONNECTING:
+ statusView.setTextColor(activity.getSecondaryTextColor());
+ break;
+ default:
+ statusView.setTextColor(activity.getWarningTextColor());
+ break;
+ }
+ final Switch tglAccountState = (Switch) view.findViewById(R.id.tgl_account_status);
+ final boolean isDisabled = (account.getStatus() == Account.State.DISABLED);
+ tglAccountState.setOnCheckedChangeListener(null);
+ tglAccountState.setChecked(!isDisabled);
+ tglAccountState.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
+ if (b == isDisabled && activity instanceof ManageAccountActivity) {
+ ((ManageAccountActivity) activity).onClickTglAccountState(account,b);
+ }
+ }
+ });
return view;
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java
index b6f88356..c42dd305 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java
@@ -3,7 +3,6 @@ package eu.siacs.conversations.ui.adapter;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
@@ -25,7 +24,7 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
-import eu.siacs.conversations.entities.Downloadable;
+import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.ui.ConversationActivity;
@@ -63,17 +62,10 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
}
Conversation conversation = getItem(position);
if (this.activity instanceof ConversationActivity) {
- ConversationActivity activity = (ConversationActivity) this.activity;
- if (!activity.isConversationsOverviewHideable()) {
- if (conversation == activity.getSelectedConversation()) {
- view.setBackgroundColor(activity
- .getSecondaryBackgroundColor());
- } else {
- view.setBackgroundColor(Color.TRANSPARENT);
- }
- } else {
- view.setBackgroundColor(Color.TRANSPARENT);
- }
+ View swipeableItem = view.findViewById(R.id.swipeable_item);
+ ConversationActivity a = (ConversationActivity) this.activity;
+ int c = !a.isConversationsOverviewHideable() && conversation == a.getSelectedConversation() ? a.getSecondaryBackgroundColor() : a.getPrimaryBackgroundColor();
+ swipeableItem.setBackgroundColor(c);
}
TextView convName = (TextView) view.findViewById(R.id.conversation_name);
if (conversation.getMode() == Conversation.MODE_SINGLE || activity.useSubjectToIdentifyConference()) {
@@ -117,9 +109,9 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
convName.setTypeface(null, Typeface.NORMAL);
}
- if (message.getImageParams().width > 0
- && (message.getDownloadable() == null
- || message.getDownloadable().getStatus() != Downloadable.STATUS_DELETED)) {
+ if (message.getFileParams().width > 0
+ && (message.getTransferable() == null
+ || message.getTransferable().getStatus() != Transferable.STATUS_DELETED)) {
mLastMessage.setVisibility(View.GONE);
imagePreview.setVisibility(View.VISIBLE);
activity.loadBitmap(message, imagePreview);
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 933ca1f8..b7ac3092 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
@@ -8,10 +8,11 @@ import android.net.Uri;
import android.preference.PreferenceManager;
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;
@@ -31,10 +32,10 @@ import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
-import eu.siacs.conversations.entities.Downloadable;
+import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
-import eu.siacs.conversations.entities.Message.ImageParams;
+import eu.siacs.conversations.entities.Message.FileParams;
import eu.siacs.conversations.ui.ConversationActivity;
import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.utils.UIHelper;
@@ -45,7 +46,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;
@@ -80,14 +80,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;
@@ -105,14 +103,14 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
&& message.getMergedStatus() <= Message.STATUS_RECEIVED;
- if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getDownloadable() != null) {
- ImageParams params = message.getImageParams();
+ if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getTransferable() != null) {
+ FileParams params = message.getFileParams();
if (params.size > (1.5 * 1024 * 1024)) {
filesize = params.size / (1024 * 1024)+ " MiB";
} else if (params.size > 0) {
filesize = params.size / 1024 + " KiB";
}
- if (message.getDownloadable() != null && message.getDownloadable().getStatus() == Downloadable.STATUS_FAILED) {
+ if (message.getTransferable() != null && message.getTransferable().getStatus() == Transferable.STATUS_FAILED) {
error = true;
}
}
@@ -121,7 +119,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
info = getContext().getString(R.string.waiting);
break;
case Message.STATUS_UNSEND:
- Downloadable d = message.getDownloadable();
+ Transferable d = message.getTransferable();
if (d!=null) {
info = getContext().getString(R.string.sending_file,d.getProgress());
} else {
@@ -166,7 +164,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
message.getMergedTimeSent());
if (message.getStatus() <= Message.STATUS_RECEIVED) {
if ((filesize != null) && (info != null)) {
- viewHolder.time.setText(filesize + " \u00B7 " + info);
+ viewHolder.time.setText(formatedTime + " \u00B7 " + filesize +" \u00B7 " + info);
} else if ((filesize == null) && (info != null)) {
viewHolder.time.setText(formatedTime + " \u00B7 " + info);
} else if ((filesize != null) && (info == null)) {
@@ -210,22 +208,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()) {
@@ -237,7 +255,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
boolean parseEmoticons = Settings.PARSE_EMOTICONS;
viewHolder.messageBody.setText(parseEmoticons ? UIHelper
.transformAsciiEmoticons(getContext(), message.getMergedBody())
- : message.getMergedBody());
+ : formattedBody);
}
} else {
String privateMarker;
@@ -296,7 +314,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
@@ -329,7 +347,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
viewHolder.messageBody.setVisibility(View.GONE);
viewHolder.image.setVisibility(View.VISIBLE);
- ImageParams params = message.getImageParams();
+ FileParams params = message.getFileParams();
double target = metrics.density * 288;
int scalledW;
int scalledH;
@@ -341,7 +359,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() {
@@ -366,10 +384,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);
@@ -436,25 +450,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) {
@@ -495,19 +490,19 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
});
- final Downloadable downloadable = message.getDownloadable();
- if (downloadable != null && downloadable.getStatus() != Downloadable.STATUS_UPLOADING) {
- if (downloadable.getStatus() == Downloadable.STATUS_OFFER) {
+ final Transferable transferable = message.getTransferable();
+ if (transferable != null && transferable.getStatus() != Transferable.STATUS_UPLOADING) {
+ if (transferable.getStatus() == Transferable.STATUS_OFFER) {
displayDownloadableMessage(viewHolder,message,activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, message)));
- } else if (downloadable.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) {
- displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_image_filesize));
+ } else if (transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) {
+ displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message)));
} else {
displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first);
}
} else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
displayImageMessage(viewHolder, message);
} else if (message.getType() == Message.TYPE_FILE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
- if (message.getImageParams().width > 0) {
+ if (message.getFileParams().width > 0) {
displayImageMessage(viewHolder,message);
} else {
displayOpenableMessage(viewHolder, message);
@@ -534,6 +529,10 @@ public class MessageAdapter extends ArrayAdapter<Message> {
} else {
if (GeoHelper.isGeoUri(message.getBody())) {
displayLocationMessage(viewHolder,message);
+ } else if (message.bodyIsHeart()) {
+ displayHeartMessage(viewHolder, message.getBody().trim());
+ } else if (message.treatAsDownloadable() == Message.Decision.MUST) {
+ displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message)));
} else {
displayTextMessage(viewHolder, message);
}
@@ -545,12 +544,14 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
public void startDownloadable(Message message) {
- Downloadable downloadable = message.getDownloadable();
- if (downloadable != null) {
- if (!downloadable.start()) {
+ Transferable transferable = message.getTransferable();
+ if (transferable != null) {
+ if (!transferable.start()) {
Toast.makeText(activity, R.string.not_connected_try_again,
Toast.LENGTH_SHORT).show();
}
+ } else if (message.treatAsDownloadable() != Message.Decision.NEVER) {
+ activity.xmppConnectionService.getHttpConnectionManager().createNewDownloadConnection(message);
}
}
diff --git a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java
index eb7e2c3c..2dec203d 100644
--- a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java
@@ -91,6 +91,11 @@ public final class CryptoHelper {
}
public static String prettifyFingerprint(String fingerprint) {
+ if (fingerprint==null) {
+ return "";
+ } else if (fingerprint.length() < 40) {
+ return fingerprint;
+ }
StringBuilder builder = new StringBuilder(fingerprint);
builder.insert(8, " ");
builder.insert(17, " ");
diff --git a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
index bcb2ca44..5a47bb3c 100644
--- a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
@@ -20,11 +20,19 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
import java.util.TreeMap;
+import java.util.regex.Pattern;
import android.os.Bundle;
import android.util.Log;
public class DNSHelper {
+
+ public static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+ public static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+ public static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+ public static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z");
+ public static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z");
+
protected static Client client = new Client();
public static Bundle getSRVRecord(final Jid jid) throws IOException {
@@ -37,9 +45,6 @@ public class DNSHelper {
Bundle b = queryDNS(host, ip);
if (b.containsKey("values")) {
return b;
- } else if (b.containsKey("error")
- && "nosrv".equals(b.getString("error", null))) {
- return b;
}
}
}
@@ -50,113 +55,96 @@ public class DNSHelper {
Bundle bundle = new Bundle();
try {
String qname = "_xmpp-client._tcp." + host;
- Log.d(Config.LOGTAG,
- "using dns server: " + dnsServer.getHostAddress()
- + " to look up " + host);
- DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN,
- dnsServer.getHostAddress());
-
- // How should we handle priorities and weight?
- // Wikipedia has a nice article about priorities vs. weights:
- // https://en.wikipedia.org/wiki/SRV_record#Provisioning_for_high_service_availability
-
- // we bucket the SRV records based on priority, pick per priority
- // a random order respecting the weight, and dump that priority by
- // priority
+ Log.d(Config.LOGTAG, "using dns server: " + dnsServer.getHostAddress() + " to look up " + host);
+ DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, dnsServer.getHostAddress());
TreeMap<Integer, ArrayList<SRV>> priorities = new TreeMap<>();
TreeMap<String, ArrayList<String>> ips4 = new TreeMap<>();
TreeMap<String, ArrayList<String>> ips6 = new TreeMap<>();
- for (Record[] rrset : new Record[][] { message.getAnswers(),
- message.getAdditionalResourceRecords() }) {
+ for (Record[] rrset : new Record[][] { message.getAnswers(), message.getAdditionalResourceRecords() }) {
for (Record rr : rrset) {
Data d = rr.getPayload();
- if (d instanceof SRV
- && NameUtil.idnEquals(qname, rr.getName())) {
+ if (d instanceof SRV && NameUtil.idnEquals(qname, rr.getName())) {
SRV srv = (SRV) d;
if (!priorities.containsKey(srv.getPriority())) {
- priorities.put(srv.getPriority(),
- new ArrayList<SRV>(2));
+ priorities.put(srv.getPriority(),new ArrayList<SRV>());
}
priorities.get(srv.getPriority()).add(srv);
}
if (d instanceof A) {
- A arecord = (A) d;
+ A a = (A) d;
if (!ips4.containsKey(rr.getName())) {
- ips4.put(rr.getName(), new ArrayList<String>(3));
+ ips4.put(rr.getName(), new ArrayList<String>());
}
- ips4.get(rr.getName()).add(arecord.toString());
+ ips4.get(rr.getName()).add(a.toString());
}
if (d instanceof AAAA) {
AAAA aaaa = (AAAA) d;
if (!ips6.containsKey(rr.getName())) {
- ips6.put(rr.getName(), new ArrayList<String>(3));
+ ips6.put(rr.getName(), new ArrayList<String>());
}
ips6.get(rr.getName()).add("[" + aaaa.toString() + "]");
}
}
}
- Random rnd = new Random();
- ArrayList<SRV> result = new ArrayList<>(
- priorities.size() * 2 + 1);
+ ArrayList<SRV> result = new ArrayList<>();
for (ArrayList<SRV> s : priorities.values()) {
-
- // trivial case
- if (s.size() <= 1) {
- result.addAll(s);
- continue;
- }
-
- long totalweight = 0l;
- for (SRV srv : s) {
- totalweight += srv.getWeight();
- }
-
- while (totalweight > 0l && s.size() > 0) {
- long p = (rnd.nextLong() & 0x7fffffffffffffffl)
- % totalweight;
- int i = 0;
- while (p > 0) {
- p -= s.get(i++).getPriority();
- }
- i--;
- // remove is expensive, but we have only a few entries
- // anyway
- SRV srv = s.remove(i);
- totalweight -= srv.getWeight();
- result.add(srv);
- }
-
- Collections.shuffle(s, rnd);
result.addAll(s);
-
}
+ ArrayList<Bundle> values = new ArrayList<>();
if (result.size() == 0) {
- bundle.putString("error", "nosrv");
+ DNSMessage response;
+ response = client.query(host, TYPE.A, CLASS.IN, dnsServer.getHostAddress());
+ for(int i = 0; i < response.getAnswers().length; ++i) {
+ values.add(createNamePortBundle(host,5222,response.getAnswers()[i].getPayload()));
+ }
+ response = client.query(host, TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress());
+ for(int i = 0; i < response.getAnswers().length; ++i) {
+ values.add(createNamePortBundle(host,5222,response.getAnswers()[i].getPayload()));
+ }
+ values.add(createNamePortBundle(host,5222));
+ bundle.putParcelableArrayList("values", values);
return bundle;
}
- ArrayList<Bundle> values = new ArrayList<>();
for (SRV srv : result) {
if (ips6.containsKey(srv.getName())) {
values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips6));
+ } else {
+ DNSMessage response = client.query(srv.getName(), TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress());
+ for(int i = 0; i < response.getAnswers().length; ++i) {
+ values.add(createNamePortBundle(srv.getName(),srv.getPort(),response.getAnswers()[i].getPayload()));
+ }
}
if (ips4.containsKey(srv.getName())) {
values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips4));
+ } else {
+ DNSMessage response = client.query(srv.getName(), TYPE.A, CLASS.IN, dnsServer.getHostAddress());
+ for(int i = 0; i < response.getAnswers().length; ++i) {
+ values.add(createNamePortBundle(srv.getName(),srv.getPort(),response.getAnswers()[i].getPayload()));
+ }
}
- values.add(createNamePortBundle(srv.getName(),srv.getPort(),null));
+ values.add(createNamePortBundle(srv.getName(), srv.getPort()));
}
bundle.putParcelableArrayList("values", values);
} catch (SocketTimeoutException e) {
bundle.putString("error", "timeout");
} catch (Exception e) {
+ e.printStackTrace();
bundle.putString("error", "unhandled");
}
return bundle;
}
+ private static Bundle createNamePortBundle(String name, int port) {
+ Bundle namePort = new Bundle();
+ namePort.putString("name", name);
+ namePort.putInt("port", port);
+ return namePort;
+ }
+
private static Bundle createNamePortBundle(String name, int port, TreeMap<String, ArrayList<String>> ips) {
Bundle namePort = new Bundle();
namePort.putString("name", name);
@@ -169,15 +157,23 @@ public class DNSHelper {
return namePort;
}
- final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
-
- public static String bytesToHex(byte[] bytes) {
- char[] hexChars = new char[bytes.length * 2];
- for (int j = 0; j < bytes.length; j++) {
- int v = bytes[j] & 0xFF;
- hexChars[j * 2] = hexArray[v >>> 4];
- hexChars[j * 2 + 1] = hexArray[v & 0x0F];
+ private static Bundle createNamePortBundle(String name, int port, Data data) {
+ Bundle namePort = new Bundle();
+ namePort.putString("name", name);
+ namePort.putInt("port", port);
+ if (data instanceof A) {
+ namePort.putString("ip", data.toString());
+ } else if (data instanceof AAAA) {
+ namePort.putString("ip","["+data.toString()+"]");
}
- return new String(hexChars);
+ return namePort;
+ }
+
+ public static boolean isIp(final String server) {
+ return PATTERN_IPV4.matcher(server).matches()
+ || PATTERN_IPV6.matcher(server).matches()
+ || PATTERN_IPV6_6HEX4DEC.matcher(server).matches()
+ || PATTERN_IPV6_HEX4DECCOMPRESSED.matcher(server).matches()
+ || PATTERN_IPV6_HEXCOMPRESSED.matcher(server).matches();
}
}
diff --git a/src/main/java/eu/siacs/conversations/utils/GeoHelper.java b/src/main/java/eu/siacs/conversations/utils/GeoHelper.java
index f7dda936..74f91a98 100644
--- a/src/main/java/eu/siacs/conversations/utils/GeoHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/GeoHelper.java
@@ -20,7 +20,7 @@ public class GeoHelper {
}
public static ArrayList<Intent> createGeoIntentsFromMessage(Message message) {
- final ArrayList<Intent> intents = new ArrayList();
+ final ArrayList<Intent> intents = new ArrayList<>();
Matcher matcher = GEO_URI.matcher(message.getBody());
if (!matcher.matches()) {
return intents;
@@ -54,8 +54,14 @@ public class GeoHelper {
Intent locationPluginIntent = new Intent("eu.siacs.conversations.location.show");
locationPluginIntent.putExtra("latitude",latitude);
locationPluginIntent.putExtra("longitude",longitude);
- if (conversation.getMode() == Conversation.MODE_SINGLE && message.getStatus() == Message.STATUS_RECEIVED) {
- locationPluginIntent.putExtra("name",conversation.getName());
+ if (conversation.getMode() == Conversation.MODE_SINGLE) {
+ if (message.getStatus() == Message.STATUS_RECEIVED) {
+ locationPluginIntent.putExtra("name",conversation.getName());
+ locationPluginIntent.putExtra("jid",message.getCounterpart().toString());
+ }
+ else {
+ locationPluginIntent.putExtra("jid",conversation.getAccount().getJid().toString());
+ }
}
intents.add(locationPluginIntent);
diff --git a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java
new file mode 100644
index 00000000..a9e89d1b
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java
@@ -0,0 +1,487 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package eu.siacs.conversations.utils;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+/**
+ * Utilities for dealing with MIME types.
+ * Used to implement java.net.URLConnection and android.webkit.MimeTypeMap.
+ */
+public final class MimeUtils {
+ private static final Map<String, String> mimeTypeToExtensionMap = new HashMap<String, String>();
+ private static final Map<String, String> extensionToMimeTypeMap = new HashMap<String, String>();
+ static {
+ // The following table is based on /etc/mime.types data minus
+ // chemical/* MIME types and MIME types that don't map to any
+ // file extensions. We also exclude top-level domain names to
+ // deal with cases like:
+ //
+ // mail.google.com/a/google.com
+ //
+ // and "active" MIME types (due to potential security issues).
+ // Note that this list is _not_ in alphabetical order and must not be sorted.
+ // The "most popular" extension must come first, so that it's the one returned
+ // by guessExtensionFromMimeType.
+ add("application/andrew-inset", "ez");
+ add("application/dsptype", "tsp");
+ add("application/hta", "hta");
+ add("application/mac-binhex40", "hqx");
+ add("application/mathematica", "nb");
+ add("application/msaccess", "mdb");
+ add("application/oda", "oda");
+ add("application/ogg", "ogg");
+ add("application/ogg", "oga");
+ add("application/pdf", "pdf");
+ add("application/pgp-keys", "key");
+ add("application/pgp-signature", "pgp");
+ add("application/pics-rules", "prf");
+ add("application/pkix-cert", "cer");
+ add("application/rar", "rar");
+ add("application/rdf+xml", "rdf");
+ add("application/rss+xml", "rss");
+ add("application/zip", "zip");
+ add("application/vnd.android.package-archive", "apk");
+ add("application/vnd.cinderella", "cdy");
+ add("application/vnd.ms-pki.stl", "stl");
+ add("application/vnd.oasis.opendocument.database", "odb");
+ add("application/vnd.oasis.opendocument.formula", "odf");
+ add("application/vnd.oasis.opendocument.graphics", "odg");
+ add("application/vnd.oasis.opendocument.graphics-template", "otg");
+ add("application/vnd.oasis.opendocument.image", "odi");
+ add("application/vnd.oasis.opendocument.spreadsheet", "ods");
+ add("application/vnd.oasis.opendocument.spreadsheet-template", "ots");
+ add("application/vnd.oasis.opendocument.text", "odt");
+ add("application/vnd.oasis.opendocument.text-master", "odm");
+ add("application/vnd.oasis.opendocument.text-template", "ott");
+ add("application/vnd.oasis.opendocument.text-web", "oth");
+ add("application/vnd.google-earth.kml+xml", "kml");
+ add("application/vnd.google-earth.kmz", "kmz");
+ add("application/msword", "doc");
+ add("application/msword", "dot");
+ add("application/vnd.openxmlformats-officedocument.wordprocessingml.document", "docx");
+ add("application/vnd.openxmlformats-officedocument.wordprocessingml.template", "dotx");
+ add("application/vnd.ms-excel", "xls");
+ add("application/vnd.ms-excel", "xlt");
+ add("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx");
+ add("application/vnd.openxmlformats-officedocument.spreadsheetml.template", "xltx");
+ add("application/vnd.ms-powerpoint", "ppt");
+ add("application/vnd.ms-powerpoint", "pot");
+ add("application/vnd.ms-powerpoint", "pps");
+ add("application/vnd.openxmlformats-officedocument.presentationml.presentation", "pptx");
+ add("application/vnd.openxmlformats-officedocument.presentationml.template", "potx");
+ add("application/vnd.openxmlformats-officedocument.presentationml.slideshow", "ppsx");
+ add("application/vnd.rim.cod", "cod");
+ add("application/vnd.smaf", "mmf");
+ add("application/vnd.stardivision.calc", "sdc");
+ add("application/vnd.stardivision.draw", "sda");
+ add("application/vnd.stardivision.impress", "sdd");
+ add("application/vnd.stardivision.impress", "sdp");
+ add("application/vnd.stardivision.math", "smf");
+ add("application/vnd.stardivision.writer", "sdw");
+ add("application/vnd.stardivision.writer", "vor");
+ add("application/vnd.stardivision.writer-global", "sgl");
+ add("application/vnd.sun.xml.calc", "sxc");
+ add("application/vnd.sun.xml.calc.template", "stc");
+ add("application/vnd.sun.xml.draw", "sxd");
+ add("application/vnd.sun.xml.draw.template", "std");
+ add("application/vnd.sun.xml.impress", "sxi");
+ add("application/vnd.sun.xml.impress.template", "sti");
+ add("application/vnd.sun.xml.math", "sxm");
+ add("application/vnd.sun.xml.writer", "sxw");
+ add("application/vnd.sun.xml.writer.global", "sxg");
+ add("application/vnd.sun.xml.writer.template", "stw");
+ add("application/vnd.visio", "vsd");
+ add("application/x-abiword", "abw");
+ add("application/x-apple-diskimage", "dmg");
+ add("application/x-bcpio", "bcpio");
+ add("application/x-bittorrent", "torrent");
+ add("application/x-cdf", "cdf");
+ add("application/x-cdlink", "vcd");
+ add("application/x-chess-pgn", "pgn");
+ add("application/x-cpio", "cpio");
+ add("application/x-debian-package", "deb");
+ add("application/x-debian-package", "udeb");
+ add("application/x-director", "dcr");
+ add("application/x-director", "dir");
+ add("application/x-director", "dxr");
+ add("application/x-dms", "dms");
+ add("application/x-doom", "wad");
+ add("application/x-dvi", "dvi");
+ add("application/x-font", "pfa");
+ add("application/x-font", "pfb");
+ add("application/x-font", "gsf");
+ add("application/x-font", "pcf");
+ add("application/x-font", "pcf.Z");
+ add("application/x-freemind", "mm");
+ // application/futuresplash isn't IANA, so application/x-futuresplash should come first.
+ add("application/x-futuresplash", "spl");
+ add("application/futuresplash", "spl");
+ add("application/x-gnumeric", "gnumeric");
+ add("application/x-go-sgf", "sgf");
+ add("application/x-graphing-calculator", "gcf");
+ add("application/x-gtar", "tgz");
+ add("application/x-gtar", "gtar");
+ add("application/x-gtar", "taz");
+ add("application/x-hdf", "hdf");
+ add("application/x-ica", "ica");
+ add("application/x-internet-signup", "ins");
+ add("application/x-internet-signup", "isp");
+ add("application/x-iphone", "iii");
+ add("application/x-iso9660-image", "iso");
+ add("application/x-jmol", "jmz");
+ add("application/x-kchart", "chrt");
+ add("application/x-killustrator", "kil");
+ add("application/x-koan", "skp");
+ add("application/x-koan", "skd");
+ add("application/x-koan", "skt");
+ add("application/x-koan", "skm");
+ add("application/x-kpresenter", "kpr");
+ add("application/x-kpresenter", "kpt");
+ add("application/x-kspread", "ksp");
+ add("application/x-kword", "kwd");
+ add("application/x-kword", "kwt");
+ add("application/x-latex", "latex");
+ add("application/x-lha", "lha");
+ add("application/x-lzh", "lzh");
+ add("application/x-lzx", "lzx");
+ add("application/x-maker", "frm");
+ add("application/x-maker", "maker");
+ add("application/x-maker", "frame");
+ add("application/x-maker", "fb");
+ add("application/x-maker", "book");
+ add("application/x-maker", "fbdoc");
+ add("application/x-mif", "mif");
+ add("application/x-ms-wmd", "wmd");
+ add("application/x-ms-wmz", "wmz");
+ add("application/x-msi", "msi");
+ add("application/x-ns-proxy-autoconfig", "pac");
+ add("application/x-nwc", "nwc");
+ add("application/x-object", "o");
+ add("application/x-oz-application", "oza");
+ add("application/x-pem-file", "pem");
+ add("application/x-pkcs12", "p12");
+ add("application/x-pkcs12", "pfx");
+ add("application/x-pkcs7-certreqresp", "p7r");
+ add("application/x-pkcs7-crl", "crl");
+ add("application/x-quicktimeplayer", "qtl");
+ add("application/x-shar", "shar");
+ add("application/x-shockwave-flash", "swf");
+ add("application/x-stuffit", "sit");
+ add("application/x-sv4cpio", "sv4cpio");
+ add("application/x-sv4crc", "sv4crc");
+ add("application/x-tar", "tar");
+ add("application/x-texinfo", "texinfo");
+ add("application/x-texinfo", "texi");
+ add("application/x-troff", "t");
+ add("application/x-troff", "roff");
+ add("application/x-troff-man", "man");
+ add("application/x-ustar", "ustar");
+ add("application/x-wais-source", "src");
+ add("application/x-wingz", "wz");
+ add("application/x-webarchive", "webarchive");
+ add("application/x-webarchive-xml", "webarchivexml");
+ add("application/x-x509-ca-cert", "crt");
+ add("application/x-x509-user-cert", "crt");
+ add("application/x-x509-server-cert", "crt");
+ add("application/x-xcf", "xcf");
+ add("application/x-xfig", "fig");
+ add("application/xhtml+xml", "xhtml");
+ add("audio/3gpp", "3gpp");
+ add("audio/aac", "aac");
+ add("audio/aac-adts", "aac");
+ add("audio/amr", "amr");
+ add("audio/amr-wb", "awb");
+ add("audio/basic", "snd");
+ add("audio/flac", "flac");
+ add("application/x-flac", "flac");
+ add("audio/imelody", "imy");
+ add("audio/midi", "mid");
+ add("audio/midi", "midi");
+ add("audio/midi", "ota");
+ add("audio/midi", "kar");
+ add("audio/midi", "rtttl");
+ add("audio/midi", "xmf");
+ add("audio/mobile-xmf", "mxmf");
+ // add ".mp3" first so it will be the default for guessExtensionFromMimeType
+ add("audio/mpeg", "mp3");
+ add("audio/mpeg", "mpga");
+ add("audio/mpeg", "mpega");
+ add("audio/mpeg", "mp2");
+ add("audio/mpeg", "m4a");
+ add("audio/mpegurl", "m3u");
+ add("audio/prs.sid", "sid");
+ add("audio/x-aiff", "aif");
+ add("audio/x-aiff", "aiff");
+ add("audio/x-aiff", "aifc");
+ add("audio/x-gsm", "gsm");
+ add("audio/x-matroska", "mka");
+ add("audio/x-mpegurl", "m3u");
+ add("audio/x-ms-wma", "wma");
+ add("audio/x-ms-wax", "wax");
+ add("audio/x-pn-realaudio", "ra");
+ add("audio/x-pn-realaudio", "rm");
+ add("audio/x-pn-realaudio", "ram");
+ add("audio/x-realaudio", "ra");
+ add("audio/x-scpls", "pls");
+ add("audio/x-sd2", "sd2");
+ add("audio/x-wav", "wav");
+ // image/bmp isn't IANA, so image/x-ms-bmp should come first.
+ add("image/x-ms-bmp", "bmp");
+ add("image/bmp", "bmp");
+ add("image/gif", "gif");
+ // image/ico isn't IANA, so image/x-icon should come first.
+ add("image/x-icon", "ico");
+ add("image/ico", "cur");
+ add("image/ico", "ico");
+ add("image/ief", "ief");
+ // add ".jpg" first so it will be the default for guessExtensionFromMimeType
+ add("image/jpeg", "jpg");
+ add("image/jpeg", "jpeg");
+ add("image/jpeg", "jpe");
+ add("image/pcx", "pcx");
+ add("image/png", "png");
+ add("image/svg+xml", "svg");
+ add("image/svg+xml", "svgz");
+ add("image/tiff", "tiff");
+ add("image/tiff", "tif");
+ add("image/vnd.djvu", "djvu");
+ add("image/vnd.djvu", "djv");
+ add("image/vnd.wap.wbmp", "wbmp");
+ add("image/webp", "webp");
+ add("image/x-cmu-raster", "ras");
+ add("image/x-coreldraw", "cdr");
+ add("image/x-coreldrawpattern", "pat");
+ add("image/x-coreldrawtemplate", "cdt");
+ add("image/x-corelphotopaint", "cpt");
+ add("image/x-jg", "art");
+ add("image/x-jng", "jng");
+ add("image/x-photoshop", "psd");
+ add("image/x-portable-anymap", "pnm");
+ add("image/x-portable-bitmap", "pbm");
+ add("image/x-portable-graymap", "pgm");
+ add("image/x-portable-pixmap", "ppm");
+ add("image/x-rgb", "rgb");
+ add("image/x-xbitmap", "xbm");
+ add("image/x-xpixmap", "xpm");
+ add("image/x-xwindowdump", "xwd");
+ add("model/iges", "igs");
+ add("model/iges", "iges");
+ add("model/mesh", "msh");
+ add("model/mesh", "mesh");
+ add("model/mesh", "silo");
+ add("text/calendar", "ics");
+ add("text/calendar", "icz");
+ add("text/comma-separated-values", "csv");
+ add("text/css", "css");
+ add("text/html", "htm");
+ add("text/html", "html");
+ add("text/h323", "323");
+ add("text/iuls", "uls");
+ add("text/mathml", "mml");
+ // add ".txt" first so it will be the default for guessExtensionFromMimeType
+ add("text/plain", "txt");
+ add("text/plain", "asc");
+ add("text/plain", "text");
+ add("text/plain", "diff");
+ add("text/plain", "po"); // reserve "pot" for vnd.ms-powerpoint
+ add("text/richtext", "rtx");
+ add("text/rtf", "rtf");
+ add("text/text", "phps");
+ add("text/tab-separated-values", "tsv");
+ add("text/xml", "xml");
+ add("text/x-bibtex", "bib");
+ add("text/x-boo", "boo");
+ add("text/x-c++hdr", "hpp");
+ add("text/x-c++hdr", "h++");
+ add("text/x-c++hdr", "hxx");
+ add("text/x-c++hdr", "hh");
+ add("text/x-c++src", "cpp");
+ add("text/x-c++src", "c++");
+ add("text/x-c++src", "cc");
+ add("text/x-c++src", "cxx");
+ add("text/x-chdr", "h");
+ add("text/x-component", "htc");
+ add("text/x-csh", "csh");
+ add("text/x-csrc", "c");
+ add("text/x-dsrc", "d");
+ add("text/x-haskell", "hs");
+ add("text/x-java", "java");
+ add("text/x-literate-haskell", "lhs");
+ add("text/x-moc", "moc");
+ add("text/x-pascal", "p");
+ add("text/x-pascal", "pas");
+ add("text/x-pcs-gcd", "gcd");
+ add("text/x-setext", "etx");
+ add("text/x-tcl", "tcl");
+ add("text/x-tex", "tex");
+ add("text/x-tex", "ltx");
+ add("text/x-tex", "sty");
+ add("text/x-tex", "cls");
+ add("text/x-vcalendar", "vcs");
+ add("text/x-vcard", "vcf");
+ add("video/3gpp", "3gpp");
+ add("video/3gpp", "3gp");
+ add("video/3gpp2", "3gpp2");
+ add("video/3gpp2", "3g2");
+ add("video/avi", "avi");
+ add("video/dl", "dl");
+ add("video/dv", "dif");
+ add("video/dv", "dv");
+ add("video/fli", "fli");
+ add("video/m4v", "m4v");
+ add("video/mp2ts", "ts");
+ add("video/mpeg", "mpeg");
+ add("video/mpeg", "mpg");
+ add("video/mpeg", "mpe");
+ add("video/mp4", "mp4");
+ add("video/mpeg", "VOB");
+ add("video/quicktime", "qt");
+ add("video/quicktime", "mov");
+ add("video/vnd.mpegurl", "mxu");
+ add("video/webm", "webm");
+ add("video/x-la-asf", "lsf");
+ add("video/x-la-asf", "lsx");
+ add("video/x-matroska", "mkv");
+ add("video/x-mng", "mng");
+ add("video/x-ms-asf", "asf");
+ add("video/x-ms-asf", "asx");
+ add("video/x-ms-wm", "wm");
+ add("video/x-ms-wmv", "wmv");
+ add("video/x-ms-wmx", "wmx");
+ add("video/x-ms-wvx", "wvx");
+ add("video/x-sgi-movie", "movie");
+ add("video/x-webex", "wrf");
+ add("x-conference/x-cooltalk", "ice");
+ add("x-epoc/x-sisx-app", "sisx");
+ applyOverrides();
+ }
+ private static void add(String mimeType, String extension) {
+ // If we have an existing x -> y mapping, we do not want to
+ // override it with another mapping x -> y2.
+ // If a mime type maps to several extensions
+ // the first extension added is considered the most popular
+ // so we do not want to overwrite it later.
+ if (!mimeTypeToExtensionMap.containsKey(mimeType)) {
+ mimeTypeToExtensionMap.put(mimeType, extension);
+ }
+ if (!extensionToMimeTypeMap.containsKey(extension)) {
+ extensionToMimeTypeMap.put(extension, mimeType);
+ }
+ }
+ private static InputStream getContentTypesPropertiesStream() {
+ // User override?
+ String userTable = System.getProperty("content.types.user.table");
+ if (userTable != null) {
+ File f = new File(userTable);
+ if (f.exists()) {
+ try {
+ return new FileInputStream(f);
+ } catch (IOException ignored) {
+ }
+ }
+ }
+ // Standard location?
+ File f = new File(System.getProperty("java.home"), "lib" + File.separator + "content-types.properties");
+ if (f.exists()) {
+ try {
+ return new FileInputStream(f);
+ } catch (IOException ignored) {
+ }
+ }
+ return null;
+ }
+ /**
+ * This isn't what the RI does. The RI doesn't have hard-coded defaults, so supplying your
+ * own "content.types.user.table" means you don't get any of the built-ins, and the built-ins
+ * come from "$JAVA_HOME/lib/content-types.properties".
+ */
+ private static void applyOverrides() {
+ // Get the appropriate InputStream to read overrides from, if any.
+ InputStream stream = getContentTypesPropertiesStream();
+ if (stream == null) {
+ return;
+ }
+ try {
+ try {
+ // Read the properties file...
+ Properties overrides = new Properties();
+ overrides.load(stream);
+ // And translate its mapping to ours...
+ for (Map.Entry<Object, Object> entry : overrides.entrySet()) {
+ String extension = (String) entry.getKey();
+ String mimeType = (String) entry.getValue();
+ add(mimeType, extension);
+ }
+ } finally {
+ stream.close();
+ }
+ } catch (IOException ignored) {
+ }
+ }
+ private MimeUtils() {
+ }
+ /**
+ * Returns true if the given MIME type has an entry in the map.
+ * @param mimeType A MIME type (i.e. text/plain)
+ * @return True iff there is a mimeType entry in the map.
+ */
+ public static boolean hasMimeType(String mimeType) {
+ if (mimeType == null || mimeType.isEmpty()) {
+ return false;
+ }
+ return mimeTypeToExtensionMap.containsKey(mimeType);
+ }
+ /**
+ * Returns the MIME type for the given extension.
+ * @param extension A file extension without the leading '.'
+ * @return The MIME type for the given extension or null iff there is none.
+ */
+ public static String guessMimeTypeFromExtension(String extension) {
+ if (extension == null || extension.isEmpty()) {
+ return null;
+ }
+ return extensionToMimeTypeMap.get(extension);
+ }
+ /**
+ * Returns true if the given extension has a registered MIME type.
+ * @param extension A file extension without the leading '.'
+ * @return True iff there is an extension entry in the map.
+ */
+ public static boolean hasExtension(String extension) {
+ if (extension == null || extension.isEmpty()) {
+ return false;
+ }
+ return extensionToMimeTypeMap.containsKey(extension);
+ }
+ /**
+ * Returns the registered extension for the given MIME type. Note that some
+ * MIME types map to multiple extensions. This call will return the most
+ * common extension for the given MIME type.
+ * @param mimeType A MIME type (i.e. text/plain)
+ * @return The extension for the given MIME type or null iff there is none.
+ */
+ public static String guessExtensionFromMimeType(String mimeType) {
+ if (mimeType == null || mimeType.isEmpty()) {
+ return null;
+ }
+ return mimeTypeToExtensionMap.get(mimeType);
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/utils/SerialSingleThreadExecutor.java b/src/main/java/eu/siacs/conversations/utils/SerialSingleThreadExecutor.java
new file mode 100644
index 00000000..bfb4668d
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/utils/SerialSingleThreadExecutor.java
@@ -0,0 +1,34 @@
+package eu.siacs.conversations.utils;
+
+import java.util.ArrayDeque;
+import java.util.Queue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+public class SerialSingleThreadExecutor implements Executor {
+
+ final Executor executor = Executors.newSingleThreadExecutor();
+ final Queue<Runnable> tasks = new ArrayDeque();
+ Runnable active;
+
+ public synchronized void execute(final Runnable r) {
+ tasks.offer(new Runnable() {
+ public void run() {
+ try {
+ r.run();
+ } finally {
+ scheduleNext();
+ }
+ }
+ });
+ if (active == null) {
+ scheduleNext();
+ }
+ }
+
+ protected synchronized void scheduleNext() {
+ if ((active = tasks.poll()) != null) {
+ executor.execute(active);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java
index 82e950ad..7984c56f 100644
--- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java
@@ -1,6 +1,7 @@
package eu.siacs.conversations.utils;
import java.util.ArrayList;
+import java.util.Arrays;
import java.net.URLConnection;
import java.util.Calendar;
import java.util.Date;
@@ -16,7 +17,7 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
-import eu.siacs.conversations.entities.Downloadable;
+import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.xmpp.jid.Jid;
@@ -29,6 +30,33 @@ import android.text.Spannable;
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
@@ -253,24 +281,25 @@ public class UIHelper {
}
public static Pair<String,Boolean> getMessagePreview(final Context context, final Message message) {
- final Downloadable d = message.getDownloadable();
+ final Transferable d = message.getTransferable();
if (d != null ) {
switch (d.getStatus()) {
- case Downloadable.STATUS_CHECKING:
- return new Pair<>(context.getString(R.string.checking_image),true);
- case Downloadable.STATUS_DOWNLOADING:
+ case Transferable.STATUS_CHECKING:
+ return new Pair<>(context.getString(R.string.checking_x,
+ getFileDescriptionString(context,message)),true);
+ case Transferable.STATUS_DOWNLOADING:
return new Pair<>(context.getString(R.string.receiving_x_file,
getFileDescriptionString(context,message),
d.getProgress()),true);
- case Downloadable.STATUS_OFFER:
- case Downloadable.STATUS_OFFER_CHECK_FILESIZE:
+ case Transferable.STATUS_OFFER:
+ case Transferable.STATUS_OFFER_CHECK_FILESIZE:
return new Pair<>(context.getString(R.string.x_file_offered_for_download,
getFileDescriptionString(context,message)),true);
- case Downloadable.STATUS_DELETED:
+ case Transferable.STATUS_DELETED:
return new Pair<>(context.getString(R.string.file_deleted),true);
- case Downloadable.STATUS_FAILED:
+ case Transferable.STATUS_FAILED:
return new Pair<>(context.getString(R.string.file_transmission_failed),true);
- case Downloadable.STATUS_UPLOADING:
+ case Transferable.STATUS_UPLOADING:
if (message.getStatus() == Message.STATUS_OFFERED) {
return new Pair<>(context.getString(R.string.offering_x_file,
getFileDescriptionString(context, message)), true);
@@ -310,16 +339,7 @@ public class UIHelper {
if (message.getType() == Message.TYPE_IMAGE) {
return context.getString(R.string.image);
}
- final String path = message.getRelativeFilePath();
- if (path == null) {
- return "";
- }
- final String mime;
- try {
- mime = URLConnection.guessContentTypeFromName(path.replace("#",""));
- } catch (final StringIndexOutOfBoundsException ignored) {
- return context.getString(R.string.file);
- }
+ final String mime = message.getMimeType();
if (mime == null) {
return context.getString(R.string.file);
} else if (mime.startsWith("audio/")) {
@@ -341,10 +361,14 @@ public class UIHelper {
public static String getMessageDisplayName(final Message message) {
if (message.getStatus() == Message.STATUS_RECEIVED) {
+ final Contact contact = message.getContact();
if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
- return getDisplayedMucCounterpart(message.getCounterpart());
+ if (contact != null) {
+ return contact.getDisplayName();
+ } else {
+ return getDisplayedMucCounterpart(message.getCounterpart());
+ }
} else {
- final Contact contact = message.getContact();
return contact != null ? contact.getDisplayName() : "";
}
} else {
@@ -380,4 +404,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/utils/Xmlns.java b/src/main/java/eu/siacs/conversations/utils/Xmlns.java
index 17fd2d26..868566d9 100644
--- a/src/main/java/eu/siacs/conversations/utils/Xmlns.java
+++ b/src/main/java/eu/siacs/conversations/utils/Xmlns.java
@@ -5,4 +5,5 @@ public final class Xmlns {
public static final String ROSTER = "jabber:iq:roster";
public static final String REGISTER = "jabber:iq:register";
public static final String BYTE_STREAMS = "http://jabber.org/protocol/bytestreams";
+ public static final String HTTP_UPLOAD = "eu:siacs:conversations:http:upload";
}
diff --git a/src/main/java/eu/siacs/conversations/xml/Element.java b/src/main/java/eu/siacs/conversations/xml/Element.java
index 51708759..32657c66 100644
--- a/src/main/java/eu/siacs/conversations/xml/Element.java
+++ b/src/main/java/eu/siacs/conversations/xml/Element.java
@@ -57,6 +57,11 @@ public class Element {
return null;
}
+ public String findChildContent(String name) {
+ Element element = findChild(name);
+ return element == null ? null : element.getContent();
+ }
+
public Element findChild(String name, String xmlns) {
for (Element child : this.children) {
if (child.getName().equals(name)
@@ -67,6 +72,11 @@ public class Element {
return null;
}
+ public String findChildContent(String name, String xmlns) {
+ Element element = findChild(name,xmlns);
+ return element == null ? null : element.getContent();
+ }
+
public boolean hasChild(final String name) {
return findChild(name) != null;
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
index 48dc2150..35c89b45 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
@@ -26,6 +26,7 @@ import java.net.IDN;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
+import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
@@ -90,7 +91,7 @@ public class XmppConnection implements Runnable {
private boolean shouldBind = true;
private boolean shouldAuthenticate = true;
private Element streamFeatures;
- private final HashMap<String, List<String>> disco = new HashMap<>();
+ private final HashMap<Jid, Info> disco = new HashMap<>();
private String streamId = null;
private int smVersion = 3;
@@ -155,56 +156,62 @@ public class XmppConnection implements Runnable {
tagWriter = new TagWriter();
packetCallbacks.clear();
this.changeStatus(Account.State.CONNECTING);
- final Bundle result = DNSHelper.getSRVRecord(account.getServer());
- final ArrayList<Parcelable> values = result.getParcelableArrayList("values");
- if ("timeout".equals(result.getString("error"))) {
- throw new IOException("timeout in dns");
- } else if (values != null) {
- int i = 0;
- boolean socketError = true;
- while (socketError && values.size() > i) {
- final Bundle namePort = (Bundle) values.get(i);
- try {
- String srvRecordServer;
+ if (DNSHelper.isIp(account.getServer().toString())) {
+ socket = new Socket();
+ try {
+ socket.connect(new InetSocketAddress(account.getServer().toString(), 5222), Config.SOCKET_TIMEOUT * 1000);
+ } catch (IOException e) {
+ throw new UnknownHostException();
+ }
+ } else {
+ final Bundle result = DNSHelper.getSRVRecord(account.getServer());
+ final ArrayList<Parcelable> values = result.getParcelableArrayList("values");
+ if ("timeout".equals(result.getString("error"))) {
+ throw new IOException("timeout in dns");
+ } else if (values != null) {
+ int i = 0;
+ boolean socketError = true;
+ while (socketError && values.size() > i) {
+ final Bundle namePort = (Bundle) values.get(i);
try {
- srvRecordServer=IDN.toASCII(namePort.getString("name"));
- } catch (final IllegalArgumentException e) {
- // TODO: Handle me?`
- srvRecordServer = "";
- }
- final int srvRecordPort = namePort.getInt("port");
- final String srvIpServer = namePort.getString("ip");
- final InetSocketAddress addr;
- if (srvIpServer != null) {
- addr = new InetSocketAddress(srvIpServer, srvRecordPort);
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
- + ": using values from dns " + srvRecordServer
- + "[" + srvIpServer + "]:" + srvRecordPort);
- } else {
- addr = new InetSocketAddress(srvRecordServer, srvRecordPort);
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
- + ": using values from dns "
- + srvRecordServer + ":" + srvRecordPort);
+ String srvRecordServer;
+ try {
+ srvRecordServer = IDN.toASCII(namePort.getString("name"));
+ } catch (final IllegalArgumentException e) {
+ // TODO: Handle me?`
+ srvRecordServer = "";
+ }
+ final int srvRecordPort = namePort.getInt("port");
+ final String srvIpServer = namePort.getString("ip");
+ final InetSocketAddress addr;
+ if (srvIpServer != null) {
+ addr = new InetSocketAddress(srvIpServer, srvRecordPort);
+ Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
+ + ": using values from dns " + srvRecordServer
+ + "[" + srvIpServer + "]:" + srvRecordPort);
+ } else {
+ addr = new InetSocketAddress(srvRecordServer, srvRecordPort);
+ Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
+ + ": using values from dns "
+ + srvRecordServer + ":" + srvRecordPort);
+ }
+ socket = new Socket();
+ socket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
+ socketError = false;
+ } catch (final UnknownHostException e) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage());
+ i++;
+ } catch (final IOException e) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage());
+ i++;
}
- socket = new Socket();
- socket.connect(addr, 20000);
- socketError = false;
- } catch (final UnknownHostException e) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage());
- i++;
- } catch (final IOException e) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage());
- i++;
}
+ if (socketError) {
+ throw new UnknownHostException();
+ }
+ } else {
+ throw new IOException("unhandled exception in DNS resolver");
}
- if (socketError) {
- throw new UnknownHostException();
- }
- } else if (result.containsKey("error")
- && "nosrv".equals(result.getString("error", null))) {
- socket = new Socket(account.getServer().getDomainpart(), 5222);
- } else {
- throw new IOException("timeout in dns");
}
final OutputStream out = socket.getOutputStream();
tagWriter.setOutputStream(out);
@@ -224,6 +231,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 +244,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 +299,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");
@@ -334,23 +353,34 @@ public class XmppConnection implements Runnable {
} catch (final NumberFormatException ignored) {
}
sendServiceDiscoveryInfo(account.getServer());
+ sendServiceDiscoveryInfo(account.getJid().toBareJid());
sendServiceDiscoveryItems(account.getServer());
sendInitialPing();
} else if (nextTag.isStart("r")) {
tagReader.readElement(nextTag);
+ if (Config.EXTENDED_SM_LOGGING) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": acknowledging stanza #" + this.stanzasReceived);
+ }
final AckPacket ack = new AckPacket(this.stanzasReceived, smVersion);
tagWriter.writeStanzaAsync(ack);
} else if (nextTag.isStart("a")) {
final Element ack = tagReader.readElement(nextTag);
lastPacketReceived = SystemClock.elapsedRealtime();
- final int serverSequence = Integer.parseInt(ack.getAttribute("h"));
- final String msgId = this.messageReceipts.get(serverSequence);
- if (msgId != null) {
- if (this.acknowledgedListener != null) {
- this.acknowledgedListener.onMessageAcknowledged(
- account, msgId);
+ try {
+ final int serverSequence = Integer.parseInt(ack.getAttribute("h"));
+ if (Config.EXTENDED_SM_LOGGING) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server acknowledged stanza #" + serverSequence);
}
- this.messageReceipts.remove(serverSequence);
+ final String msgId = this.messageReceipts.get(serverSequence);
+ if (msgId != null) {
+ if (this.acknowledgedListener != null) {
+ this.acknowledgedListener.onMessageAcknowledged(
+ account, msgId);
+ }
+ this.messageReceipts.remove(serverSequence);
+ }
+ } catch (NumberFormatException e) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": server send ack without sequence number");
}
} else if (nextTag.isStart("failed")) {
tagReader.readElement(nextTag);
@@ -430,6 +460,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;
@@ -531,8 +565,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());
@@ -543,8 +576,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();
}
}
@@ -574,31 +606,36 @@ public class XmppConnection implements Runnable {
} else if (mechanisms.contains("DIGEST-MD5")) {
saslMechanism = new DigestMd5(tagWriter, account, mXmppConnectionService.getRNG());
}
- final JSONObject keys = account.getKeys();
- try {
- if (keys.has(Account.PINNED_MECHANISM_KEY) &&
- keys.getInt(Account.PINNED_MECHANISM_KEY) > saslMechanism.getPriority() ) {
- Log.e(Config.LOGTAG, "Auth failed. Authentication mechanism " + saslMechanism.getMechanism() +
- " 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);
- }
- } catch (final JSONException e) {
- Log.d(Config.LOGTAG, "Parse error while checking pinned auth mechanism");
- }
- Log.d(Config.LOGTAG,account.getJid().toString()+": Authenticating with " + saslMechanism.getMechanism());
- auth.setAttribute("mechanism", saslMechanism.getMechanism());
- if (!saslMechanism.getClientFirstMessage().isEmpty()) {
- auth.setContent(saslMechanism.getClientFirstMessage());
+ if (saslMechanism != null) {
+ final JSONObject keys = account.getKeys();
+ try {
+ if (keys.has(Account.PINNED_MECHANISM_KEY) &&
+ keys.getInt(Account.PINNED_MECHANISM_KEY) > saslMechanism.getPriority()) {
+ Log.e(Config.LOGTAG, "Auth failed. Authentication mechanism " + saslMechanism.getMechanism() +
+ " has lower priority (" + String.valueOf(saslMechanism.getPriority()) +
+ ") than pinned priority (" + keys.getInt(Account.PINNED_MECHANISM_KEY) +
+ "). Possible downgrade attack?");
+ throw new SecurityException();
+ }
+ } catch (final JSONException e) {
+ Log.d(Config.LOGTAG, "Parse error while checking pinned auth mechanism");
+ }
+ Log.d(Config.LOGTAG, account.getJid().toString() + ": Authenticating with " + saslMechanism.getMechanism());
+ auth.setAttribute("mechanism", saslMechanism.getMechanism());
+ if (!saslMechanism.getClientFirstMessage().isEmpty()) {
+ auth.setContent(saslMechanism.getClientFirstMessage());
+ }
+ tagWriter.writeElement(auth);
+ } else {
+ throw new IncompatibleServerException();
}
- tagWriter.writeElement(auth);
} else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:"
+ smVersion)
&& streamId != null) {
- final ResumePacket resume = new ResumePacket(this.streamId,
- stanzasReceived, smVersion);
+ if (Config.EXTENDED_SM_LOGGING) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": resuming after stanza #"+stanzasReceived);
+ }
+ final ResumePacket resume = new ResumePacket(this.streamId, stanzasReceived, smVersion);
this.tagWriter.writeStanzaAsync(resume);
} else if (this.streamFeatures.hasChild("bind") && shouldBind) {
sendBindRequest();
@@ -629,10 +666,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() {
@@ -645,7 +680,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);
@@ -659,7 +694,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() : ""));
}
}
});
@@ -674,7 +709,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) {
@@ -734,6 +769,7 @@ public class XmppConnection implements Runnable {
features.blockListRequested = false;
disco.clear();
sendServiceDiscoveryInfo(account.getServer());
+ sendServiceDiscoveryInfo(account.getJid().toBareJid());
sendServiceDiscoveryItems(account.getServer());
if (bindListener != null) {
bindListener.onBind(account);
@@ -741,34 +777,35 @@ public class XmppConnection implements Runnable {
sendInitialPing();
}
- private void sendServiceDiscoveryInfo(final Jid server) {
- if (disco.containsKey(server.toDomainJid().toString())) {
- if (account.getServer().equals(server.toDomainJid())) {
+ private void sendServiceDiscoveryInfo(final Jid jid) {
+ if (disco.containsKey(jid)) {
+ if (account.getServer().equals(jid)) {
enableAdvancedStreamFeatures();
}
} else {
final IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
- iq.setTo(server.toDomainJid());
+ iq.setTo(jid);
iq.query("http://jabber.org/protocol/disco#info");
this.sendIqPacket(iq, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(final Account account, final IqPacket packet) {
final List<Element> elements = packet.query().getChildren();
- final List<String> features = new ArrayList<>();
+ final Info info = new Info();
for (final Element element : elements) {
if (element.getName().equals("identity")) {
- if ("irc".equals(element.getAttribute("type"))) {
- //add fake feature to not confuse irc and real muc
- features.add("siacs:no:muc");
+ String type = element.getAttribute("type");
+ String category = element.getAttribute("category");
+ if (type != null && category != null) {
+ info.identities.add(new Pair<>(category,type));
}
} else if (element.getName().equals("feature")) {
- features.add(element.getAttribute("var"));
+ info.features.add(element.getAttribute("var"));
}
}
- disco.put(server.toDomainJid().toString(), features);
+ disco.put(jid, info);
- if (account.getServer().equals(server.toDomainJid())) {
+ if (account.getServer().equals(jid)) {
enableAdvancedStreamFeatures();
for (final OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) {
listener.onAdvancedStreamFeaturesAvailable(account);
@@ -784,7 +821,7 @@ public class XmppConnection implements Runnable {
sendEnableCarbons();
}
if (getFeatures().blocking() && !features.blockListRequested) {
- Log.d(Config.LOGTAG, "Requesting block list");
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": Requesting block list");
this.sendIqPacket(getIqGenerator().generateGetBlockList(), mXmppConnectionService.getIqParser());
}
}
@@ -885,13 +922,20 @@ 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;
}
tagWriter.writeStanzaAsync(packet);
- if (packet instanceof MessagePacket && packet.getId() != null && this.streamId != null) {
- Log.d(Config.LOGTAG, "request delivery report for stanza " + stanzasSent);
+ if (packet instanceof MessagePacket && packet.getId() != null && getFeatures().sm()) {
+ if (Config.EXTENDED_SM_LOGGING) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": requesting ack for message stanza #" + stanzasSent);
+ }
this.messageReceipts.put(stanzasSent, packet.getId());
tagWriter.writeStanzaAsync(new RequestPacket(this.smVersion));
}
@@ -981,18 +1025,22 @@ public class XmppConnection implements Runnable {
}
}
- public List<String> findDiscoItemsByFeature(final String feature) {
- final List<String> items = new ArrayList<>();
- for (final Entry<String, List<String>> cursor : disco.entrySet()) {
- if (cursor.getValue().contains(feature)) {
+ public void resetStreamId() {
+ this.streamId = null;
+ }
+
+ public List<Jid> findDiscoItemsByFeature(final String feature) {
+ final List<Jid> items = new ArrayList<>();
+ for (final Entry<Jid, Info> cursor : disco.entrySet()) {
+ if (cursor.getValue().features.contains(feature)) {
items.add(cursor.getKey());
}
}
return items;
}
- public String findDiscoItemByFeature(final String feature) {
- final List<String> items = findDiscoItemsByFeature(feature);
+ public Jid findDiscoItemByFeature(final String feature) {
+ final List<Jid> items = findDiscoItemsByFeature(feature);
if (items.size() >= 1) {
return items.get(0);
}
@@ -1004,10 +1052,12 @@ public class XmppConnection implements Runnable {
}
public String getMucServer() {
- for (final Entry<String, List<String>> cursor : disco.entrySet()) {
- final List<String> value = cursor.getValue();
- if (value.contains("http://jabber.org/protocol/muc") && !value.contains("jabber:iq:gateway") && !value.contains("siacs:no:muc")) {
- return cursor.getKey();
+ for (final Entry<Jid, Info> cursor : disco.entrySet()) {
+ final Info value = cursor.getValue();
+ if (value.features.contains("http://jabber.org/protocol/muc")
+ && !value.features.contains("jabber:iq:gateway")
+ && !value.identities.contains(new Pair<>("conference","irc"))) {
+ return cursor.getKey().toString();
}
}
return null;
@@ -1062,6 +1112,23 @@ public class XmppConnection implements Runnable {
this.lastConnect = 0;
}
+ private class Info {
+ public final ArrayList<String> features = new ArrayList<>();
+ 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;
@@ -1073,8 +1140,8 @@ public class XmppConnection implements Runnable {
}
private boolean hasDiscoFeature(final Jid server, final String feature) {
- return connection.disco.containsKey(server.toDomainJid().toString()) &&
- connection.disco.get(server.toDomainJid().toString()).contains(feature);
+ return connection.disco.containsKey(server) &&
+ connection.disco.get(server).features.contains(feature);
}
public boolean carbons() {
@@ -1090,24 +1157,35 @@ public class XmppConnection implements Runnable {
}
public boolean sm() {
- return streamId != null;
+ return streamId != null
+ || (connection.streamFeatures != null && connection.streamFeatures.hasChild("sm"));
}
public boolean csi() {
return connection.streamFeatures != null && connection.streamFeatures.hasChild("csi", "urn:xmpp:csi:0");
}
- public boolean pubsub() {
- return hasDiscoFeature(account.getServer(),
- "http://jabber.org/protocol/pubsub#publish");
+ public boolean pep() {
+ final Pair<String,String> needle = new Pair<>("pubsub","pep");
+ Info info = disco.get(account.getServer());
+ if (info != null && info.identities.contains(needle)) {
+ return true;
+ } else {
+ info = disco.get(account.getJid().toBareJid());
+ return info != null && info.identities.contains(needle);
+ }
}
public boolean mam() {
- return hasDiscoFeature(account.getServer(), "urn:xmpp:mam:0");
+ if (hasDiscoFeature(account.getJid().toBareJid(), "urn:xmpp:mam:0")) {
+ return true;
+ } else {
+ return hasDiscoFeature(account.getServer(), "urn:xmpp:mam:0");
+ }
}
public boolean advancedStreamFeaturesLoaded() {
- return disco.containsKey(account.getServer().toString());
+ return disco.containsKey(account.getServer());
}
public boolean rosterVersioning() {
@@ -1117,6 +1195,10 @@ public class XmppConnection implements Runnable {
public void setBlockListRequested(boolean value) {
this.blockListRequested = value;
}
+
+ public boolean httpUpload() {
+ return findDiscoItemsByFeature(Xmlns.HTTP_UPLOAD).size() > 0;
+ }
}
private IqGenerator getIqGenerator() {
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 0db0deee..2c56488d 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
@@ -1,6 +1,5 @@
package eu.siacs.conversations.xmpp.jingle;
-import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
@@ -16,9 +15,9 @@ import android.util.Log;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
-import eu.siacs.conversations.entities.Downloadable;
+import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.entities.DownloadableFile;
-import eu.siacs.conversations.entities.DownloadablePlaceholder;
+import eu.siacs.conversations.entities.TransferablePlaceholder;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Element;
@@ -29,7 +28,7 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
-public class JingleConnection implements Downloadable {
+public class JingleConnection implements Transferable {
private JingleConnectionManager mJingleConnectionManager;
private XmppConnectionService mXmppConnectionService;
@@ -40,10 +39,10 @@ public class JingleConnection implements Downloadable {
protected static final int JINGLE_STATUS_TRANSMITTING = 5;
protected static final int JINGLE_STATUS_FAILED = 99;
- private int ibbBlockSize = 4096;
+ private int ibbBlockSize = 8192;
private int mJingleStatus = -1;
- private int mStatus = Downloadable.STATUS_UNKNOWN;
+ private int mStatus = Transferable.STATUS_UNKNOWN;
private Message message;
private String sessionId;
private Account account;
@@ -99,7 +98,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));
@@ -192,15 +191,15 @@ public class JingleConnection implements Downloadable {
} else {
response = packet.generateResponse(IqPacket.TYPE.ERROR);
}
- account.getXmppConnection().sendIqPacket(response, null);
+ mXmppConnectionService.sendIqPacket(account,response,null);
}
public void init(Message message) {
this.contentCreator = "initiator";
this.contentName = this.mJingleConnectionManager.nextRandomId();
this.message = message;
- this.message.setDownloadable(this);
- this.mStatus = Downloadable.STATUS_UPLOADING;
+ this.message.setTransferable(this);
+ this.mStatus = Transferable.STATUS_UPLOADING;
this.account = message.getConversation().getAccount();
this.initiator = this.account.getJid();
this.responder = this.message.getCounterpart();
@@ -213,7 +212,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);
@@ -256,8 +255,8 @@ public class JingleConnection implements Downloadable {
packet.getFrom().toBareJid(), false);
this.message = new Message(conversation, "", Message.ENCRYPTION_NONE);
this.message.setStatus(Message.STATUS_RECEIVED);
- this.mStatus = Downloadable.STATUS_OFFER;
- this.message.setDownloadable(this);
+ this.mStatus = Transferable.STATUS_OFFER;
+ this.message.setTransferable(this);
final Jid from = packet.getFrom();
this.message.setCounterpart(from);
this.account = account;
@@ -271,6 +270,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");
@@ -366,7 +368,10 @@ public class JingleConnection implements Downloadable {
message, false);
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
Conversation conversation = this.message.getConversation();
- this.mXmppConnectionService.renewSymmetricKey(conversation);
+ if (!this.mXmppConnectionService.renewSymmetricKey(conversation)) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not set symmetric key");
+ cancel();
+ }
content.setFileOffer(this.file, true);
this.file.setKey(conversation.getSymmetricKey());
} else {
@@ -381,6 +386,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,14 +401,16 @@ 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;
}
private void sendAccept() {
mJingleStatus = JINGLE_STATUS_ACCEPTED;
- this.mStatus = Downloadable.STATUS_DOWNLOADING;
+ this.mStatus = Transferable.STATUS_DOWNLOADING;
mXmppConnectionService.updateConversationUi();
this.mJingleConnectionManager.getPrimaryCandidate(this.account, new OnPrimaryCandidateFound() {
@Override
@@ -459,11 +467,11 @@ public class JingleConnection implements Downloadable {
}
private void sendJinglePacket(JinglePacket packet) {
- account.getXmppConnection().sendIqPacket(packet, responseListener);
+ mXmppConnectionService.sendIqPacket(account,packet,responseListener);
}
private void sendJinglePacket(JinglePacket packet, OnIqPacketReceived callback) {
- account.getXmppConnection().sendIqPacket(packet,callback);
+ mXmppConnectionService.sendIqPacket(account,packet,callback);
}
private boolean receiveAccept(JinglePacket packet) {
@@ -556,7 +564,7 @@ public class JingleConnection implements Downloadable {
.setAttribute("sid", this.getSessionId());
activation.query().addChild("activate")
.setContent(this.getCounterPart().toString());
- this.account.getXmppConnection().sendIqPacket(activation,
+ mXmppConnectionService.sendIqPacket(account,activation,
new OnIqPacketReceived() {
@Override
@@ -633,7 +641,7 @@ public class JingleConnection implements Downloadable {
this.disconnectSocks5Connections();
this.mJingleStatus = JINGLE_STATUS_FINISHED;
this.message.setStatus(Message.STATUS_RECEIVED);
- this.message.setDownloadable(null);
+ this.message.setTransferable(null);
this.mXmppConnectionService.updateMessage(message);
this.mJingleConnectionManager.finishConnection(this);
}
@@ -710,7 +718,7 @@ public class JingleConnection implements Downloadable {
if (this.transport != null && this.transport instanceof JingleInbandTransport) {
this.transport.disconnect();
}
- this.message.setDownloadable(null);
+ this.message.setTransferable(null);
this.mJingleConnectionManager.finishConnection(this);
}
@@ -722,7 +730,7 @@ public class JingleConnection implements Downloadable {
this.sendCancel();
this.mJingleConnectionManager.finishConnection(this);
if (this.responder.equals(account.getJid())) {
- this.message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_FAILED));
+ this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED));
if (this.file!=null) {
file.delete();
}
@@ -730,7 +738,7 @@ public class JingleConnection implements Downloadable {
} else {
this.mXmppConnectionService.markMessage(this.message,
Message.STATUS_SEND_FAILED);
- this.message.setDownloadable(null);
+ this.message.setTransferable(null);
}
}
@@ -742,7 +750,7 @@ public class JingleConnection implements Downloadable {
}
if (this.message != null) {
if (this.responder.equals(account.getJid())) {
- this.message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_FAILED));
+ this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED));
if (this.file!=null) {
file.delete();
}
@@ -750,7 +758,7 @@ public class JingleConnection implements Downloadable {
} else {
this.mXmppConnectionService.markMessage(this.message,
Message.STATUS_SEND_FAILED);
- this.message.setDownloadable(null);
+ this.message.setTransferable(null);
}
}
this.mJingleConnectionManager.finishConnection(this);
@@ -948,24 +956,4 @@ public class JingleConnection implements Downloadable {
public int getProgress() {
return this.mProgress;
}
-
- @Override
- public String getMimeType() {
- if (this.message.getType() == Message.TYPE_FILE) {
- String mime = null;
- String path = this.message.getRelativeFilePath();
- if (path != null && !this.message.getRelativeFilePath().isEmpty()) {
- mime = URLConnection.guessContentTypeFromName(this.message.getRelativeFilePath());
- if (mime!=null) {
- return mime;
- } else {
- return "";
- }
- } else {
- return "";
- }
- } else {
- return "image/webp";
- }
- }
}
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..cadf9df3 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java
@@ -9,13 +9,13 @@ import android.annotation.SuppressLint;
import android.util.Log;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.AbstractConnectionManager;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.Xmlns;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
-import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
@@ -58,7 +58,12 @@ public class JingleConnectionManager extends AbstractConnectionManager {
}
public JingleConnection createNewConnection(Message message) {
+ Transferable old = message.getTransferable();
+ 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;
@@ -81,10 +86,10 @@ public class JingleConnectionManager extends AbstractConnectionManager {
return;
}
if (!this.primaryCandidates.containsKey(account.getJid().toBareJid())) {
- final String proxy = account.getXmppConnection().findDiscoItemByFeature(Xmlns.BYTE_STREAMS);
+ final Jid proxy = account.getXmppConnection().findDiscoItemByFeature(Xmlns.BYTE_STREAMS);
if (proxy != null) {
IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
- iq.setAttribute("to", proxy);
+ iq.setTo(proxy);
iq.query(Xmlns.BYTE_STREAMS);
account.getXmppConnection().sendIqPacket(iq,new OnIqPacketReceived() {
@@ -99,11 +104,11 @@ public class JingleConnectionManager extends AbstractConnectionManager {
candidate.setHost(host);
candidate.setPort(Integer.parseInt(port));
candidate.setType(JingleCandidate.TYPE_PROXY);
- candidate.setJid(Jid.fromString(proxy));
+ candidate.setJid(proxy);
candidate.setPriority(655360 + 65535);
primaryCandidates.put(account.getJid().toBareJid(),candidate);
listener.onPrimaryCandidateFound(true,candidate);
- } catch (final NumberFormatException | InvalidJidException e) {
+ } catch (final NumberFormatException e) {
listener.onPrimaryCandidateFound(false,null);
return;
}
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 174f70fa..9a02ee7a 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java
@@ -8,9 +8,12 @@ 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;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
@@ -22,7 +25,6 @@ public class JingleInbandTransport extends JingleTransport {
private Account account;
private Jid counterpart;
private int blockSize;
- private int bufferSize;
private int seq = 0;
private String sessionId;
@@ -55,7 +57,6 @@ public class JingleInbandTransport extends JingleTransport {
this.account = connection.getAccount();
this.counterpart = connection.getCounterPart();
this.blockSize = blocksize;
- this.bufferSize = blocksize / 4;
this.sessionId = sid;
}
@@ -94,11 +95,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();
}
}
@@ -109,12 +112,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;
}
@@ -123,6 +131,7 @@ public class JingleInbandTransport extends JingleTransport {
}
} catch (NoSuchAlgorithmException e) {
callback.onFileTransferAborted();
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage());
}
}
@@ -146,32 +155,42 @@ public class JingleInbandTransport extends JingleTransport {
}
private void sendNextBlock() {
- byte[] buffer = new byte[this.bufferSize];
+ byte[] buffer = new byte[this.blockSize];
try {
int count = fileInputStream.read(buffer);
if (count == -1) {
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++;
+ fileInputStream.close();
+ return;
+ } else if (count != buffer.length) {
+ int rem = fileInputStream.read(buffer,count,buffer.length-count);
+ if (rem > 0) {
+ count += rem;
+ }
+ }
+ this.remainingSize -= count;
+ 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()));
+ this.onFileTransmissionStatusChanged.onFileTransmitted(file);
+ fileInputStream.close();
}
} catch (IOException e) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage());
+ FileBackend.close(fileInputStream);
this.onFileTransmissionStatusChanged.onFileTransferAborted();
}
}
@@ -180,14 +199,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()));
@@ -198,6 +213,8 @@ 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();
}
}
@@ -207,6 +224,7 @@ public class JingleInbandTransport extends JingleTransport {
if (!established) {
established = true;
connected = true;
+ this.receiveNextBlock("");
this.account.getXmppConnection().sendIqPacket(
packet.generateResponse(IqPacket.TYPE.RESULT), null);
} else {
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 c3419580..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,16 +1,22 @@
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;
public class JingleSocks5Transport extends JingleTransport {
@@ -52,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 };
@@ -101,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;
}
@@ -120,31 +128,28 @@ 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 {
- try {
- if (fileInputStream != null) {
- fileInputStream.close();
- }
- } catch (IOException e) {
- callback.onFileTransferAborted();
- }
+ FileBackend.close(fileInputStream);
}
}
}).start();
}
- public void receive(final DownloadableFile file,
- final OnFileTransmissionStatusChanged callback) {
+ public void receive(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
new Thread(new Runnable() {
@Override
public void run() {
+ OutputStream fileOutputStream = null;
try {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.reset();
@@ -152,9 +157,10 @@ public class JingleSocks5Transport extends JingleTransport {
socket.setSoTimeout(30000);
file.getParentFile().mkdirs();
file.createNewFile();
- OutputStream fileOutputStream = file.createOutputStream();
+ 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();
@@ -165,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);
@@ -178,11 +185,16 @@ 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);
}
}
}).start();
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..74da6a9b 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,43 @@ 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) {
+ String hash = x == null ? null : x.findChildContent("photo");
+ 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}");
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java
index 55256ece..bd706b57 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java
@@ -51,4 +51,8 @@ public class AbstractStanza extends Element {
|| getTo().equals(account.getJid().toBareJid())
|| getTo().equals(account.getJid());
}
+
+ public boolean fromAccount(final Account account) {
+ return getFrom() != null && getFrom().toBareJid().equals(account.getJid().toBareJid());
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java
index 93aaa68c..e32811af 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java
@@ -1,5 +1,10 @@
package eu.siacs.conversations.xmpp.stanzas;
+import android.util.Pair;
+
+import java.text.ParseException;
+
+import eu.siacs.conversations.parser.AbstractParser;
import eu.siacs.conversations.xml.Element;
public class MessagePacket extends AbstractStanza {
@@ -14,12 +19,7 @@ public class MessagePacket extends AbstractStanza {
}
public String getBody() {
- Element body = this.findChild("body");
- if (body != null) {
- return body.getContent();
- } else {
- return null;
- }
+ return findChildContent("body");
}
public void setBody(String text) {
@@ -66,4 +66,31 @@ public class MessagePacket extends AbstractStanza {
return TYPE_NORMAL;
}
}
+
+ public Pair<MessagePacket,Long> getForwardedMessagePacket(String name, String namespace) {
+ Element wrapper = findChild(name, namespace);
+ if (wrapper == null) {
+ return null;
+ }
+ Element forwarded = wrapper.findChild("forwarded", "urn:xmpp:forward:0");
+ if (forwarded == null) {
+ return null;
+ }
+ MessagePacket packet = create(forwarded.findChild("message"));
+ if (packet == null) {
+ return null;
+ }
+ Long timestamp = AbstractParser.getTimestamp(forwarded,null);
+ return new Pair(packet,timestamp);
+ }
+
+ public static MessagePacket create(Element element) {
+ if (element == null) {
+ return null;
+ }
+ MessagePacket packet = new MessagePacket();
+ packet.setAttributes(element.getAttributes());
+ packet.setChildren(element.getChildren());
+ return packet;
+ }
}