aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/eu/siacs
diff options
context:
space:
mode:
authorlookshe <github@lookshe.org>2015-04-11 14:15:07 +0200
committerlookshe <github@lookshe.org>2015-04-11 14:15:07 +0200
commitdf63c14617bc8d665bfeac62cde0b29e6aa98873 (patch)
treeb825c0c87bcd9833ecfad85ecc5c0901ea4d77eb /src/main/java/eu/siacs
parent87e56b534ad41199380c97e16ad25acbd077878c (diff)
parente17b75511e7c9d8206939b742972021c5c9d6b86 (diff)
Merge branch 'trz/master' of ssh://git.fucktheforce.de/conversations into trz/master
Diffstat (limited to 'src/main/java/eu/siacs')
-rw-r--r--src/main/java/eu/siacs/conversations/Config.java22
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/OtrEngine.java32
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Account.java2
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Contact.java2
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Conversation.java60
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Message.java33
-rw-r--r--src/main/java/eu/siacs/conversations/entities/MucOptions.java17
-rw-r--r--src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java25
-rw-r--r--src/main/java/eu/siacs/conversations/generator/IqGenerator.java13
-rw-r--r--src/main/java/eu/siacs/conversations/generator/MessageGenerator.java36
-rw-r--r--src/main/java/eu/siacs/conversations/http/HttpConnection.java2
-rw-r--r--src/main/java/eu/siacs/conversations/parser/AbstractParser.java30
-rw-r--r--src/main/java/eu/siacs/conversations/parser/IqParser.java8
-rw-r--r--src/main/java/eu/siacs/conversations/parser/MessageParser.java59
-rw-r--r--src/main/java/eu/siacs/conversations/parser/PresenceParser.java8
-rw-r--r--src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java2
-rw-r--r--src/main/java/eu/siacs/conversations/persistance/FileBackend.java60
-rw-r--r--src/main/java/eu/siacs/conversations/services/AvatarService.java40
-rw-r--r--src/main/java/eu/siacs/conversations/services/NotificationService.java64
-rw-r--r--src/main/java/eu/siacs/conversations/services/XmppConnectionService.java178
-rw-r--r--src/main/java/eu/siacs/conversations/ui/AboutPreference.java14
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java94
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java34
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java22
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationActivity.java221
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationFragment.java343
-rw-r--r--src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java23
-rw-r--r--src/main/java/eu/siacs/conversations/ui/EditMessage.java56
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java15
-rw-r--r--src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java12
-rw-r--r--src/main/java/eu/siacs/conversations/ui/SettingsActivity.java2
-rw-r--r--src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java35
-rw-r--r--src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java12
-rw-r--r--src/main/java/eu/siacs/conversations/ui/XmppActivity.java62
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java99
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java96
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java46
-rw-r--r--src/main/java/eu/siacs/conversations/utils/CryptoHelper.java28
-rw-r--r--src/main/java/eu/siacs/conversations/utils/DNSHelper.java7
-rw-r--r--src/main/java/eu/siacs/conversations/utils/GeoHelper.java71
-rw-r--r--src/main/java/eu/siacs/conversations/utils/PhoneHelper.java14
-rw-r--r--src/main/java/eu/siacs/conversations/utils/UIHelper.java10
-rw-r--r--src/main/java/eu/siacs/conversations/utils/XmppUri.java6
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java89
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/chatstate/ChatState.java32
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jid/InvalidJidException.java1
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java32
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java5
48 files changed, 1646 insertions, 528 deletions
diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java
index 81a68008..fa9345a2 100644
--- a/src/main/java/eu/siacs/conversations/Config.java
+++ b/src/main/java/eu/siacs/conversations/Config.java
@@ -2,6 +2,8 @@ package eu.siacs.conversations;
import android.graphics.Bitmap;
+import eu.siacs.conversations.xmpp.chatstate.ChatState;
+
public final class Config {
public static final String LOGTAG = "conversations";
@@ -23,21 +25,26 @@ public final class Config {
public static final int PAGE_SIZE = 50;
public static final int MAX_NUM_PAGES = 3;
- public static final int PROGRESS_UI_UPDATE_INTERVAL = 750;
+ public static final int PROGRESS_UI_UPDATE_INTERVAL = 750;
+ public static final int REFRESH_UI_INTERVAL = 500;
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 long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY / 2;
public static final int MAM_MAX_MESSAGES = 500;
+ public static final ChatState DEFAULT_CHATSTATE = ChatState.ACTIVE;
+ public static final int TYPING_TIMEOUT = 8;
+
public static final String ENABLED_CIPHERS[] = {
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
- "TLS_ECDHE_RSA_AES_128_SHA",
- "TLS_ECDHE_RSA_AES_256_SHA",
+ "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
+ "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA384",
@@ -59,6 +66,15 @@ public final class Config {
"TLS_RSA_WITH_AES_256_CBC_SHA",
};
+ public static final String WEAK_CIPHER_PATTERNS[] = {
+ "_NULL_",
+ "_EXPORT_",
+ "_anon_",
+ "_RC4_",
+ "_DES_",
+ "_MD5",
+ };
+
private Config() {
}
diff --git a/src/main/java/eu/siacs/conversations/crypto/OtrEngine.java b/src/main/java/eu/siacs/conversations/crypto/OtrEngine.java
index d5c45465..20427d7b 100644
--- a/src/main/java/eu/siacs/conversations/crypto/OtrEngine.java
+++ b/src/main/java/eu/siacs/conversations/crypto/OtrEngine.java
@@ -21,6 +21,7 @@ import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.CryptoHelper;
+import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
@@ -182,14 +183,39 @@ public class OtrEngine extends OtrCryptoEngineImpl implements OtrEngineHost {
packet.addChild("private", "urn:xmpp:carbons:2");
packet.addChild("no-copy", "urn:xmpp:hints");
packet.addChild("no-store", "urn:xmpp:hints");
+
+ try {
+ Jid jid = Jid.fromSessionID(session);
+ Conversation conversation = mXmppConnectionService.find(account,jid);
+ if (conversation != null && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
+ if (mXmppConnectionService.sendChatStates()) {
+ packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
+ }
+ }
+ } catch (final InvalidJidException ignored) {
+
+ }
+
packet.setType(MessagePacket.TYPE_CHAT);
account.getXmppConnection().sendMessagePacket(packet);
}
@Override
- public void messageFromAnotherInstanceReceived(SessionID id) {
- Log.d(Config.LOGTAG,
- "unreadable message received from " + id.getAccountID());
+ 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;
+ }
}
@Override
diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java
index b0cde62c..2bc2c954 100644
--- a/src/main/java/eu/siacs/conversations/entities/Account.java
+++ b/src/main/java/eu/siacs/conversations/entities/Account.java
@@ -148,7 +148,7 @@ public class Account extends AbstractEntity {
try {
this.keys = new JSONObject(keys);
} catch (final JSONException ignored) {
-
+ this.keys = new JSONObject();
}
this.avatar = avatar;
}
diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java
index 698e0322..cef03ebe 100644
--- a/src/main/java/eu/siacs/conversations/entities/Contact.java
+++ b/src/main/java/eu/siacs/conversations/entities/Contact.java
@@ -80,7 +80,7 @@ public class Contact implements ListItem, Blockable {
cursor.getLong(cursor.getColumnIndex(LAST_TIME)));
final Jid jid;
try {
- jid = Jid.fromString(cursor.getString(cursor.getColumnIndex(JID)));
+ jid = Jid.fromString(cursor.getString(cursor.getColumnIndex(JID)), true);
} catch (final InvalidJidException e) {
// TODO: Borked DB... handle this somehow?
return null;
diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java
index 036acf63..bfee5007 100644
--- a/src/main/java/eu/siacs/conversations/entities/Conversation.java
+++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java
@@ -2,10 +2,8 @@ package eu.siacs.conversations.entities;
import android.content.ContentValues;
import android.database.Cursor;
-import android.os.SystemClock;
import net.java.otr4j.OtrException;
-import net.java.otr4j.crypto.OtrCryptoEngineImpl;
import net.java.otr4j.crypto.OtrCryptoException;
import net.java.otr4j.session.SessionID;
import net.java.otr4j.session.SessionImpl;
@@ -21,6 +19,7 @@ import java.util.Comparator;
import java.util.List;
import eu.siacs.conversations.Config;
+import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
@@ -77,6 +76,9 @@ public class Conversation extends AbstractEntity implements Blockable {
private Bookmark bookmark;
private boolean messagesLeftOnServer = true;
+ private ChatState mOutgoingChatState = Config.DEFAULT_CHATSTATE;
+ private ChatState mIncomingChatState = Config.DEFAULT_CHATSTATE;
+ private String mLastReceivedOtrMessageId = null;
public boolean hasMessagesLeftOnServer() {
return messagesLeftOnServer;
@@ -138,6 +140,34 @@ public class Conversation extends AbstractEntity implements Blockable {
}
}
+ public boolean setIncomingChatState(ChatState state) {
+ if (this.mIncomingChatState == state) {
+ return false;
+ }
+ this.mIncomingChatState = state;
+ return true;
+ }
+
+ public ChatState getIncomingChatState() {
+ return this.mIncomingChatState;
+ }
+
+ public boolean setOutgoingChatState(ChatState state) {
+ if (mode == MODE_MULTI) {
+ return false;
+ }
+ if (this.mOutgoingChatState != state) {
+ this.mOutgoingChatState = state;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public ChatState getOutgoingChatState() {
+ return this.mOutgoingChatState;
+ }
+
public void trim() {
synchronized (this.messages) {
final int size = messages.size();
@@ -205,6 +235,14 @@ public class Conversation extends AbstractEntity implements Blockable {
return getContact().getBlockedJid();
}
+ public String getLastReceivedOtrMessageId() {
+ return this.mLastReceivedOtrMessageId;
+ }
+
+ public void setLastReceivedOtrMessageId(String id) {
+ this.mLastReceivedOtrMessageId = id;
+ }
+
public interface OnMessageFound {
public void onMessageFound(final Message message);
@@ -340,7 +378,7 @@ public class Conversation extends AbstractEntity implements Blockable {
public static Conversation fromCursor(Cursor cursor) {
Jid jid;
try {
- jid = Jid.fromString(cursor.getString(cursor.getColumnIndex(CONTACTJID)));
+ jid = Jid.fromString(cursor.getString(cursor.getColumnIndex(CONTACTJID)), true);
} catch (final InvalidJidException e) {
// Borked DB..
jid = null;
@@ -626,8 +664,7 @@ public class Conversation extends AbstractEntity implements Blockable {
}
public boolean isMuted() {
- return SystemClock.elapsedRealtime() < this.getLongAttribute(
- ATTRIBUTE_MUTED_TILL, 0);
+ return System.currentTimeMillis() < this.getLongAttribute(ATTRIBUTE_MUTED_TILL, 0);
}
public boolean setAttribute(String key, String value) {
@@ -706,6 +743,19 @@ public class Conversation extends AbstractEntity implements Blockable {
}
}
+ public int unreadCount() {
+ synchronized (this.messages) {
+ int count = 0;
+ for(int i = this.messages.size() - 1; i >= 0; --i) {
+ if (this.messages.get(i).isRead()) {
+ return count;
+ }
+ ++count;
+ }
+ return count;
+ }
+ }
+
public class Smp {
public static final int STATUS_NONE = 0;
public static final int STATUS_CONTACT_REQUESTED = 1;
diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java
index 8112f5de..8015eead 100644
--- a/src/main/java/eu/siacs/conversations/entities/Message.java
+++ b/src/main/java/eu/siacs/conversations/entities/Message.java
@@ -8,6 +8,7 @@ import java.net.URL;
import java.util.Arrays;
import eu.siacs.conversations.Config;
+import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
@@ -49,6 +50,7 @@ public class Message extends AbstractEntity {
public static final String RELATIVE_FILE_PATH = "relativeFilePath";
public static final String ME_COMMAND = "/me ";
+
public boolean markable = false;
protected String conversationUuid;
protected Jid counterpart;
@@ -115,7 +117,7 @@ public class Message extends AbstractEntity {
try {
String value = cursor.getString(cursor.getColumnIndex(COUNTERPART));
if (value != null) {
- jid = Jid.fromString(value);
+ jid = Jid.fromString(value, true);
} else {
jid = null;
}
@@ -126,7 +128,7 @@ public class Message extends AbstractEntity {
try {
String value = cursor.getString(cursor.getColumnIndex(TRUE_COUNTERPART));
if (value != null) {
- trueCounterpart = Jid.fromString(value);
+ trueCounterpart = Jid.fromString(value, true);
} else {
trueCounterpart = null;
}
@@ -147,10 +149,11 @@ public class Message extends AbstractEntity {
cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID)));
}
- public static Message createStatusMessage(Conversation conversation) {
+ public static Message createStatusMessage(Conversation conversation, String body) {
Message message = new Message();
message.setType(Message.TYPE_STATUS);
message.setConversation(conversation);
+ message.setBody(body);
return message;
}
@@ -361,11 +364,14 @@ public class Message extends AbstractEntity {
message.getDownloadable() == null &&
message.getEncryption() != Message.ENCRYPTION_PGP &&
this.getType() == message.getType() &&
- this.getStatus() == message.getStatus() &&
+ //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) &&
@@ -373,12 +379,23 @@ public class Message extends AbstractEntity {
);
}
+ 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)
+ );
+ }
+
public String getMergedBody() {
final Message next = this.next();
if (this.mergeable(next)) {
- return getBody() + '\n' + next.getMergedBody();
+ return getBody().trim() + '\n' + next.getMergedBody();
}
- return getBody();
+ return getBody().trim();
}
public boolean hasMeCommand() {
@@ -386,6 +403,10 @@ public class Message extends AbstractEntity {
}
public int getMergedStatus() {
+ final Message next = this.next();
+ if (this.mergeable(next)) {
+ return next.getStatus();
+ }
return getStatus();
}
diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java
index 27821c65..addee8db 100644
--- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java
+++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java
@@ -150,6 +150,21 @@ public class MucOptions {
}
}
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ } else if (!(other instanceof User)) {
+ return false;
+ } else {
+ User o = (User) other;
+ return name != null && name.equals(o.name)
+ && jid != null && jid.equals(o.jid)
+ && affiliation == o.affiliation
+ && role == o.role;
+ }
+ }
+
public Affiliation getAffiliation() {
return this.affiliation;
}
@@ -260,7 +275,7 @@ public class MucOptions {
User user = new User();
if (x != null) {
Element item = x.findChild("item");
- if (item != null) {
+ if (item != null && name != null) {
user.setName(name);
user.setAffiliation(item.getAttribute("affiliation"));
user.setRole(item.getAttribute("role"));
diff --git a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java
index 526e5b19..79626511 100644
--- a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java
@@ -13,6 +13,7 @@ import java.util.Locale;
import java.util.TimeZone;
import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.utils.PhoneHelper;
public abstract class AbstractGenerator {
private final String[] FEATURES = {
@@ -25,15 +26,18 @@ public abstract class AbstractGenerator {
"http://jabber.org/protocol/caps",
"http://jabber.org/protocol/disco#info",
"urn:xmpp:avatar:metadata+notify",
- "urn:xmpp:ping"};
+ "urn:xmpp:ping",
+ "jabber:iq:version",
+ "http://jabber.org/protocol/chatstates"};
private final String[] MESSAGE_CONFIRMATION_FEATURES = {
"urn:xmpp:chat-markers:0",
"urn:xmpp:receipts"
};
- public final String IDENTITY_NAME = "Conversations 1.0";
+ private String mVersion = null;
+ public final String IDENTITY_NAME = "Conversations";
public final String IDENTITY_TYPE = "phone";
- private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
+ private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
protected XmppConnectionService mXmppConnectionService;
@@ -41,10 +45,21 @@ public abstract class AbstractGenerator {
this.mXmppConnectionService = service;
}
+ protected String getIdentityVersion() {
+ if (mVersion == null) {
+ this.mVersion = PhoneHelper.getVersionName(mXmppConnectionService);
+ }
+ return this.mVersion;
+ }
+
+ protected String getIdentityName() {
+ return IDENTITY_NAME + " " + getIdentityVersion();
+ }
+
public String getCapHash() {
StringBuilder s = new StringBuilder();
- s.append("client/" + IDENTITY_TYPE + "//" + IDENTITY_NAME + "<");
- MessageDigest md = null;
+ s.append("client/" + IDENTITY_TYPE + "//" + getIdentityName() + "<");
+ MessageDigest md;
try {
md = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e) {
diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
index 161e6f89..6bc629b5 100644
--- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
@@ -8,6 +8,7 @@ import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.services.MessageArchiveService;
import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.utils.PhoneHelper;
import eu.siacs.conversations.utils.Xmlns;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.forms.Data;
@@ -30,14 +31,22 @@ public class IqGenerator extends AbstractGenerator {
query.setAttribute("node", request.query().getAttribute("node"));
final Element identity = query.addChild("identity");
identity.setAttribute("category", "client");
- identity.setAttribute("type", this.IDENTITY_TYPE);
- identity.setAttribute("name", IDENTITY_NAME);
+ identity.setAttribute("type", IDENTITY_TYPE);
+ identity.setAttribute("name", getIdentityName());
for (final String feature : getFeatures()) {
query.addChild("feature").setAttribute("var", feature);
}
return packet;
}
+ public IqPacket versionResponse(final IqPacket request) {
+ final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT);
+ Element query = packet.query("jabber:iq:version");
+ query.addChild("name").setContent(IDENTITY_NAME);
+ query.addChild("version").setContent(getIdentityVersion());
+ return packet;
+ }
+
protected IqPacket publish(final String node, final Element item) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
final Element pubsub = packet.addChild("pubsub",
diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java
index 2ee636b5..8f6a90b9 100644
--- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java
@@ -12,6 +12,7 @@ import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.jid.Jid;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
@@ -34,6 +35,9 @@ public class MessageGenerator extends AbstractGenerator {
} else if (message.getType() == Message.TYPE_PRIVATE) {
packet.setTo(message.getCounterpart());
packet.setType(MessagePacket.TYPE_CHAT);
+ if (this.mXmppConnectionService.indicateReceived()) {
+ packet.addChild("request", "urn:xmpp:receipts");
+ }
} else {
packet.setTo(message.getCounterpart().toBareJid());
packet.setType(MessagePacket.TYPE_GROUPCHAT);
@@ -102,21 +106,12 @@ public class MessageGenerator extends AbstractGenerator {
return packet;
}
- public MessagePacket generateNotAcceptable(MessagePacket origin) {
- MessagePacket packet = generateError(origin);
- Element error = packet.addChild("error");
- error.setAttribute("type", "modify");
- error.setAttribute("code", "406");
- error.addChild("not-acceptable");
- return packet;
- }
-
- private MessagePacket generateError(MessagePacket origin) {
+ public MessagePacket generateChatState(Conversation conversation) {
+ final Account account = conversation.getAccount();
MessagePacket packet = new MessagePacket();
- packet.setId(origin.getId());
- packet.setTo(origin.getFrom());
- packet.setBody(origin.getBody());
- packet.setType(MessagePacket.TYPE_ERROR);
+ packet.setTo(conversation.getJid().toBareJid());
+ packet.setFrom(account.getJid());
+ packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
return packet;
}
@@ -176,4 +171,17 @@ public class MessageGenerator extends AbstractGenerator {
received.setAttribute("id", originalMessage.getId());
return receivedPacket;
}
+
+ public MessagePacket generateOtrError(Jid to, String id) {
+ MessagePacket packet = new MessagePacket();
+ packet.setType(MessagePacket.TYPE_ERROR);
+ packet.setAttribute("id",id);
+ packet.setTo(to);
+ Element error = packet.addChild("error");
+ 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");
+ return packet;
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/http/HttpConnection.java b/src/main/java/eu/siacs/conversations/http/HttpConnection.java
index 4bff5251..e7d30919 100644
--- a/src/main/java/eu/siacs/conversations/http/HttpConnection.java
+++ b/src/main/java/eu/siacs/conversations/http/HttpConnection.java
@@ -148,7 +148,7 @@ public class HttpConnection implements Downloadable {
mXmppConnectionService.getRNG());
final SSLSocketFactory sf = sc.getSocketFactory();
- final String[] cipherSuites = CryptoHelper.getSupportedCipherSuites(
+ final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites(
sf.getSupportedCipherSuites());
if (cipherSuites.length > 0) {
sc.getDefaultSSLParameters().setCipherSuites(cipherSuites);
diff --git a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java
index 9b3e239c..473195bd 100644
--- a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java
@@ -1,5 +1,7 @@
package eu.siacs.conversations.parser;
+import android.util.Log;
+
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -41,8 +43,14 @@ public abstract class AbstractParser {
if (stamp == null) {
return now;
}
- long time = parseTimestamp(stamp).getTime();
- return now < time ? now : time;
+ /*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;
+ }
}
/**
@@ -53,17 +61,29 @@ public abstract class AbstractParser {
* @return Date
* @throws ParseException
*/
- public static Date parseTimestamp(String timestamp) {
- try {
+ public static Date parseTimestamp(String timestamp) throws ParseException {
+ /*try {
+ Log.d("TIMESTAMP", timestamp);
return DatatypeFactory.newInstance().newXMLGregorianCalendar(timestamp).toGregorianCalendar().getTime();
} catch (DatatypeConfigurationException e) {
+ Log.d("TIMESTAMP", e.getMessage());
return new Date();
- }
+ }*/
+ timestamp = timestamp.replace("Z", "+0000");
+ SimpleDateFormat dateFormat;
+ timestamp = timestamp.substring(0,19)+timestamp.substring(timestamp.length() -5,timestamp.length());
+ dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ",Locale.US);
+ return dateFormat.parse(timestamp);
}
protected void updateLastseen(final Element packet, final Account account,
final boolean presenceOverwrite) {
final Jid from = packet.getAttributeAsJid("from");
+ updateLastseen(packet, account, from, 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);
diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java
index 6430c296..6039d395 100644
--- a/src/main/java/eu/siacs/conversations/parser/IqParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java
@@ -134,9 +134,11 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
mXmppConnectionService.getJingleConnectionManager()
.deliverIbbPacket(account, packet);
} else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) {
- final IqPacket response = mXmppConnectionService.getIqGenerator()
- .discoResponse(packet);
- account.getXmppConnection().sendIqPacket(response, null);
+ final IqPacket response = mXmppConnectionService.getIqGenerator().discoResponse(packet);
+ mXmppConnectionService.sendIqPacket(account, response, null);
+ } else if (packet.hasChild("query","jabber:iq:version")) {
+ final IqPacket response = mXmppConnectionService.getIqGenerator().versionResponse(packet);
+ mXmppConnectionService.sendIqPacket(account,response,null);
} else if (packet.hasChild("ping", "urn:xmpp:ping")) {
final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT);
mXmppConnectionService.sendIqPacket(account, response, null);
diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
index 49efb004..8ae9b642 100644
--- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
@@ -14,6 +14,7 @@ import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnMessagePacketReceived;
+import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.jid.Jid;
import eu.siacs.conversations.xmpp.pep.Avatar;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
@@ -24,13 +25,27 @@ public class MessageParser extends AbstractParser implements
super(service);
}
+ private boolean extractChatState(Conversation conversation, final Element element) {
+ ChatState state = ChatState.parse(element);
+ if (state != null && conversation != null) {
+ final Account account = conversation.getAccount();
+ Jid from = element.getAttributeAsJid("from");
+ if (from != null && from.toBareJid().equals(account.getJid().toBareJid())) {
+ conversation.setOutgoingChatState(state);
+ return false;
+ } else {
+ return conversation.setIncomingChatState(state);
+ }
+ }
+ return false;
+ }
+
private Message parseChat(MessagePacket packet, Account account) {
- final Jid jid = packet.getFrom();
+ final Jid jid = packet.getFrom();
if (jid == null) {
return null;
}
Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, jid.toBareJid(), false);
- updateLastseen(packet, account, true);
String pgpBody = getPgpBody(packet);
Message finishedMessage;
if (pgpBody != null) {
@@ -45,16 +60,22 @@ public class MessageParser extends AbstractParser implements
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(conversation.getMucOptions()
- .getTrueCounterpart(jid.getResourcepart()));
+ 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;
}
@@ -69,10 +90,11 @@ public class MessageParser extends AbstractParser implements
.findOrCreateConversation(account, from.toBareJid(), false);
String presence;
if (from.isBareJid()) {
- presence = "";
+ presence = "";
} else {
presence = from.getResourcepart();
}
+ extractChatState(conversation, packet);
updateLastseen(packet, account, true);
String body = packet.getBody();
if (body.matches("^\\?OTRv\\d{1,2}\\?.*")) {
@@ -97,6 +119,7 @@ public class MessageParser extends AbstractParser implements
}
}
try {
+ conversation.setLastReceivedOtrMessageId(packet.getId());
Session otrSession = conversation.getOtrSession();
SessionStatus before = otrSession.getSessionStatus();
body = otrSession.transformReceiving(body);
@@ -123,6 +146,7 @@ public class MessageParser extends AbstractParser implements
finishedMessage.setRemoteMsgId(packet.getId());
finishedMessage.markable = isMarkable(packet);
finishedMessage.setCounterpart(from);
+ conversation.setLastReceivedOtrMessageId(null);
return finishedMessage;
} catch (Exception e) {
conversation.resetOtrSession();
@@ -132,7 +156,7 @@ public class MessageParser extends AbstractParser implements
private Message parseGroupchat(MessagePacket packet, Account account) {
int status;
- final Jid from = packet.getFrom();
+ final Jid from = packet.getFrom();
if (from == null) {
return null;
}
@@ -142,6 +166,10 @@ public class MessageParser extends AbstractParser implements
}
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());
@@ -275,6 +303,7 @@ public class MessageParser extends AbstractParser implements
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);
@@ -362,6 +391,9 @@ public class MessageParser extends AbstractParser implements
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);
@@ -377,14 +409,19 @@ public class MessageParser extends AbstractParser implements
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")) {
+ } 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);
- mXmppConnectionService.markMessage(account, from.toBareJid(),
- id, Message.STATUS_SEND_DISPLAYED);
+ 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")
diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java
index accb56ea..7505b091 100644
--- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java
@@ -1,5 +1,7 @@
package eu.siacs.conversations.parser;
+import java.util.ArrayList;
+
import eu.siacs.conversations.crypto.PgpEngine;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
@@ -27,8 +29,12 @@ public class PresenceParser extends AbstractParser implements
final MucOptions mucOptions = conversation.getMucOptions();
boolean before = mucOptions.online();
int count = mucOptions.getUsers().size();
+ final ArrayList<MucOptions.User> tileUserBefore = new ArrayList<>(mucOptions.getUsers().subList(0,Math.min(mucOptions.getUsers().size(),5)));
mucOptions.processPacket(packet, mPgpEngine);
- mXmppConnectionService.getAvatarService().clear(conversation);
+ final ArrayList<MucOptions.User> tileUserAfter = new ArrayList<>(mucOptions.getUsers().subList(0,Math.min(mucOptions.getUsers().size(),5)));
+ if (!tileUserAfter.equals(tileUserBefore)) {
+ mXmppConnectionService.getAvatarService().clear(conversation);
+ }
if (before != mucOptions.online() || (mucOptions.online() && count != mucOptions.getUsers().size())) {
mXmppConnectionService.updateConversationUi();
} else if (mucOptions.online()) {
diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
index 3ae3356d..28e1c47e 100644
--- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
+++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
@@ -278,6 +278,8 @@ public class DatabaseBackend extends SQLiteOpenHelper {
return (count > 0);
} catch (SQLiteCantOpenDatabaseException e) {
return true; // better safe than sorry
+ } catch (RuntimeException e) {
+ return true; // better safe than sorry
}
}
diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
index 62987aaa..c499d499 100644
--- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
+++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
@@ -318,39 +318,41 @@ public class FileBackend {
}
public boolean save(Avatar avatar) {
+ File file;
if (isAvatarCached(avatar)) {
- return true;
- }
- String filename = getAvatarPath(avatar.getFilename());
- File file = new File(filename + ".tmp");
- file.getParentFile().mkdirs();
- try {
- file.createNewFile();
- FileOutputStream mFileOutputStream = new FileOutputStream(file);
- MessageDigest digest = MessageDigest.getInstance("SHA-1");
- digest.reset();
- DigestOutputStream mDigestOutputStream = new DigestOutputStream(
- mFileOutputStream, digest);
- mDigestOutputStream.write(avatar.getImageAsBytes());
- mDigestOutputStream.flush();
- mDigestOutputStream.close();
- avatar.size = file.length();
- String sha1sum = CryptoHelper.bytesToHex(digest.digest());
- if (sha1sum.equals(avatar.sha1sum)) {
- file.renameTo(new File(filename));
- return true;
- } else {
- Log.d(Config.LOGTAG, "sha1sum mismatch for " + avatar.owner);
- file.delete();
+ file = new File(getAvatarPath(avatar.getFilename()));
+ } else {
+ String filename = getAvatarPath(avatar.getFilename());
+ file = new File(filename + ".tmp");
+ file.getParentFile().mkdirs();
+ try {
+ file.createNewFile();
+ FileOutputStream mFileOutputStream = new FileOutputStream(file);
+ MessageDigest digest = MessageDigest.getInstance("SHA-1");
+ digest.reset();
+ DigestOutputStream mDigestOutputStream = new DigestOutputStream(
+ mFileOutputStream, digest);
+ mDigestOutputStream.write(avatar.getImageAsBytes());
+ mDigestOutputStream.flush();
+ mDigestOutputStream.close();
+ String sha1sum = CryptoHelper.bytesToHex(digest.digest());
+ if (sha1sum.equals(avatar.sha1sum)) {
+ file.renameTo(new File(filename));
+ } else {
+ Log.d(Config.LOGTAG, "sha1sum mismatch for " + avatar.owner);
+ file.delete();
+ return false;
+ }
+ } catch (FileNotFoundException e) {
+ return false;
+ } catch (IOException e) {
+ return false;
+ } catch (NoSuchAlgorithmException e) {
return false;
}
- } catch (FileNotFoundException e) {
- return false;
- } catch (IOException e) {
- return false;
- } catch (NoSuchAlgorithmException e) {
- return false;
}
+ avatar.size = file.length();
+ return true;
}
public String getAvatarPath(String avatar) {
diff --git a/src/main/java/eu/siacs/conversations/services/AvatarService.java b/src/main/java/eu/siacs/conversations/services/AvatarService.java
index f28dc24e..7412eb93 100644
--- a/src/main/java/eu/siacs/conversations/services/AvatarService.java
+++ b/src/main/java/eu/siacs/conversations/services/AvatarService.java
@@ -38,10 +38,10 @@ public class AvatarService {
this.mXmppConnectionService = service;
}
- public Bitmap get(final Contact contact, final int size) {
+ private Bitmap get(final Contact contact, final int size, boolean cachedOnly) {
final String KEY = key(contact, size);
Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY);
- if (avatar != null) {
+ if (avatar != null || cachedOnly) {
return avatar;
}
if (contact.getProfilePhoto() != null) {
@@ -51,7 +51,7 @@ public class AvatarService {
avatar = mXmppConnectionService.getFileBackend().getAvatar(contact.getAvatar(), size);
}
if (avatar == null) {
- avatar = get(contact.getDisplayName(), size);
+ avatar = get(contact.getDisplayName(), size, cachedOnly);
}
this.mXmppConnectionService.getBitmapCache().put(KEY, avatar);
return avatar;
@@ -77,25 +77,33 @@ public class AvatarService {
}
public Bitmap get(ListItem item, int size) {
+ return get(item,size,false);
+ }
+
+ public Bitmap get(ListItem item, int size, boolean cachedOnly) {
if (item instanceof Contact) {
- return get((Contact) item, size);
+ return get((Contact) item, size,cachedOnly);
} else if (item instanceof Bookmark) {
Bookmark bookmark = (Bookmark) item;
if (bookmark.getConversation() != null) {
- return get(bookmark.getConversation(), size);
+ return get(bookmark.getConversation(), size, cachedOnly);
} else {
- return get(bookmark.getDisplayName(), size);
+ return get(bookmark.getDisplayName(), size, cachedOnly);
}
} else {
- return get(item.getDisplayName(), size);
+ return get(item.getDisplayName(), size, cachedOnly);
}
}
public Bitmap get(Conversation conversation, int size) {
+ return get(conversation,size,false);
+ }
+
+ public Bitmap get(Conversation conversation, int size, boolean cachedOnly) {
if (conversation.getMode() == Conversation.MODE_SINGLE) {
- return get(conversation.getContact(), size);
+ return get(conversation.getContact(), size, cachedOnly);
} else {
- return get(conversation.getMucOptions(), size);
+ return get(conversation.getMucOptions(), size, cachedOnly);
}
}
@@ -107,10 +115,10 @@ public class AvatarService {
}
}
- public Bitmap get(MucOptions mucOptions, int size) {
+ private Bitmap get(MucOptions mucOptions, int size, boolean cachedOnly) {
final String KEY = key(mucOptions, size);
Bitmap bitmap = this.mXmppConnectionService.getBitmapCache().get(KEY);
- if (bitmap != null) {
+ if (bitmap != null || cachedOnly) {
return bitmap;
}
final List<MucOptions.User> users = new ArrayList<>(mucOptions.getUsers());
@@ -179,7 +187,7 @@ public class AvatarService {
avatar = mXmppConnectionService.getFileBackend().getAvatar(
account.getAvatar(), size);
if (avatar == null) {
- avatar = get(account.getJid().toBareJid().toString(), size);
+ avatar = get(account.getJid().toBareJid().toString(), size,false);
}
mXmppConnectionService.getBitmapCache().put(KEY, avatar);
return avatar;
@@ -204,10 +212,14 @@ public class AvatarService {
+ String.valueOf(size);
}
- public Bitmap get(final String name, final int size) {
+ public Bitmap get(String name, int size) {
+ return get(name,size,false);
+ }
+
+ public Bitmap get(final String name, final int size, boolean cachedOnly) {
final String KEY = key(name, size);
Bitmap bitmap = mXmppConnectionService.getBitmapCache().get(KEY);
- if (bitmap != null) {
+ if (bitmap != null || cachedOnly) {
return bitmap;
}
bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java
index 2ea0904f..fa079829 100644
--- a/src/main/java/eu/siacs/conversations/services/NotificationService.java
+++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java
@@ -40,7 +40,9 @@ import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.ui.ConversationActivity;
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 {
@@ -210,14 +212,24 @@ public class NotificationService {
mBuilder.setCategory(Notification.CATEGORY_MESSAGE);
}
setNotificationColor(mBuilder);
+ mBuilder.setDefaults(0);
mBuilder.setSmallIcon(R.drawable.ic_notification);
mBuilder.setDeleteIntent(createDeleteIntent());
- mBuilder.setLights(0xffffffff, 2000, 4000);
+ mBuilder.setLights(this.getLedNotificationColor(preferences), 2000, 4000);
final Notification notification = mBuilder.build();
notificationManager.notify(NOTIFICATION_ID, notification);
}
}
+ private int getLedNotificationColor(SharedPreferences preferences) {
+ String ledColorString = preferences.getString("led_notification_color", "0xffffffff");
+ try {
+ return Integer.valueOf(ledColorString);
+ } catch (NumberFormatException e) {
+ return 0xffffffff;
+ }
+ }
+
private Builder buildMultipleConversation() {
final Builder mBuilder = new NotificationCompat.Builder(
mXmppConnectionService);
@@ -277,6 +289,11 @@ public class NotificationService {
createDownloadIntent(message)
);
}
+ if ((message = getFirstLocationMessage(messages)) != null) {
+ mBuilder.addAction(R.drawable.ic_room_white_24dp,
+ mXmppConnectionService.getString(R.string.show_location),
+ createShowLocationIntent(message));
+ }
mBuilder.setContentIntent(createContentIntent(conversation));
}
return mBuilder;
@@ -340,6 +357,15 @@ public class NotificationService {
return null;
}
+ private Message getFirstLocationMessage(final Iterable<Message> messages) {
+ for(final Message message : messages) {
+ if (GeoHelper.isGeoUri(message.getBody())) {
+ return message;
+ }
+ }
+ return null;
+ }
+
private CharSequence getMergedBodies(final ArrayList<Message> messages) {
final StringBuilder text = new StringBuilder();
for (int i = 0; i < messages.size(); ++i) {
@@ -351,6 +377,16 @@ public class NotificationService {
return text.toString();
}
+ private PendingIntent createShowLocationIntent(final Message message) {
+ Iterable<Intent> intents = GeoHelper.createGeoIntentsFromMessage(message);
+ for(Intent intent : intents) {
+ if (intent.resolveActivity(mXmppConnectionService.getPackageManager()) != null) {
+ return PendingIntent.getActivity(mXmppConnectionService,18,intent,PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+ }
+ return createOpenConversationsIntent();
+ }
+
private PendingIntent createContentIntent(final String conversationUuid, final String downloadMessageUuid) {
final TaskStackBuilder stackBuilder = TaskStackBuilder
.create(mXmppConnectionService);
@@ -395,7 +431,20 @@ public class NotificationService {
final Intent intent = new Intent(mXmppConnectionService,
XmppConnectionService.class);
intent.setAction(XmppConnectionService.ACTION_DISABLE_FOREGROUND);
- return PendingIntent.getService(mXmppConnectionService, 0, intent, 0);
+ return PendingIntent.getService(mXmppConnectionService, 34, intent, 0);
+ }
+
+ private PendingIntent createTryAgainIntent() {
+ final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
+ intent.setAction(XmppConnectionService.ACTION_TRY_AGAIN);
+ return PendingIntent.getService(mXmppConnectionService, 45, intent, 0);
+ }
+
+ private PendingIntent createDisableAccountIntent(final Account account) {
+ final Intent intent = new Intent(mXmppConnectionService,XmppConnectionService.class);
+ intent.setAction(XmppConnectionService.ACTION_DISABLE_ACCOUNT);
+ intent.putExtra("account",account.getJid().toBareJid().toString());
+ return PendingIntent.getService(mXmppConnectionService,0,intent,PendingIntent.FLAG_UPDATE_CURRENT);
}
private boolean wasHighlightedOrPrivate(final Message message) {
@@ -423,9 +472,6 @@ public class NotificationService {
}
public void setIsInForeground(final boolean foreground) {
- if (foreground != this.mIsInForeground) {
- Log.d(Config.LOGTAG,"setIsInForeground("+Boolean.toString(foreground)+")");
- }
this.mIsInForeground = foreground;
}
@@ -492,6 +538,14 @@ public class NotificationService {
mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.problem_connecting_to_accounts));
mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_fix));
}
+ mBuilder.addAction(R.drawable.ic_autorenew_white_24dp,
+ mXmppConnectionService.getString(R.string.try_again),
+ createTryAgainIntent());
+ if (errors.size() == 1) {
+ mBuilder.addAction(R.drawable.ic_block_white_24dp,
+ mXmppConnectionService.getString(R.string.disable_account),
+ createDisableAccountIntent(errors.get(0)));
+ }
mBuilder.setOngoing(true);
//mBuilder.setLights(0xffffffff, 2000, 4000);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index e34f9bd7..ca182867 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -16,6 +16,7 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.FileObserver;
import android.os.IBinder;
+import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.SystemClock;
@@ -85,6 +86,7 @@ import eu.siacs.conversations.xmpp.OnPresencePacketReceived;
import eu.siacs.conversations.xmpp.OnStatusChanged;
import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
import eu.siacs.conversations.xmpp.XmppConnection;
+import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.forms.Data;
import eu.siacs.conversations.xmpp.forms.Field;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
@@ -102,6 +104,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public static final String ACTION_CLEAR_NOTIFICATION = "clear_notification";
public static final String ACTION_DISABLE_FOREGROUND = "disable_foreground";
private static final String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts";
+ public static final String ACTION_TRY_AGAIN = "try_again";
+ public static final String ACTION_DISABLE_ACCOUNT = "disable_account";
private ContentObserver contactObserver = new ContentObserver(null) {
@Override
public void onChange(boolean selfChange) {
@@ -275,6 +279,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
private LruCache<String, Bitmap> mBitmapCache;
private Thread mPhoneContactMergerThread;
+ private boolean mRestoredFromDatabase = false;
+ public boolean areMessagesInitialized() {
+ return this.mRestoredFromDatabase;
+ }
+
public PgpEngine getPgpEngine() {
if (pgpServiceConnection.isBound()) {
if (this.mPgpEngine == null) {
@@ -297,6 +306,24 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return this.mAvatarService;
}
+ public void attachLocationToConversation(final Conversation conversation,
+ final Uri uri,
+ final UiCallback<Message> callback) {
+ int encryption = conversation.getNextEncryption(forceEncryption());
+ if (encryption == Message.ENCRYPTION_PGP) {
+ encryption = Message.ENCRYPTION_DECRYPTED;
+ }
+ Message message = new Message(conversation,uri.toString(),encryption);
+ if (conversation.getNextCounterpart() != null) {
+ message.setCounterpart(conversation.getNextCounterpart());
+ }
+ if (encryption == Message.ENCRYPTION_DECRYPTED) {
+ getPgpEngine().encrypt(message,callback);
+ } else {
+ callback.success(message);
+ }
+ }
+
public void attachFileToConversation(final Conversation conversation,
final Uri uri,
final UiCallback<Message> callback) {
@@ -386,7 +413,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (action != null) {
switch (action) {
case ACTION_MERGE_PHONE_CONTACTS:
- PhoneHelper.loadPhoneContacts(getApplicationContext(), new CopyOnWriteArrayList<Bundle>(), this);
+ if (mRestoredFromDatabase) {
+ PhoneHelper.loadPhoneContacts(getApplicationContext(),
+ new CopyOnWriteArrayList<Bundle>(),
+ this);
+ }
return START_STICKY;
case Intent.ACTION_SHUTDOWN:
logoutAndSave();
@@ -398,6 +429,28 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
getPreferences().edit().putBoolean("keep_foreground_service",false).commit();
toggleForegroundService();
break;
+ case ACTION_TRY_AGAIN:
+ for(Account account : accounts) {
+ if (account.hasErrorStatus()) {
+ final XmppConnection connection = account.getXmppConnection();
+ if (connection != null) {
+ connection.resetAttemptCount();
+ }
+ }
+ }
+ break;
+ case ACTION_DISABLE_ACCOUNT:
+ try {
+ String jid = intent.getStringExtra("account");
+ Account account = jid == null ? null : findAccountByJid(Jid.fromString(jid));
+ if (account != null) {
+ account.setOption(Account.OPTION_DISABLED,true);
+ updateAccount(account);
+ }
+ } catch (final InvalidJidException ignored) {
+ break;
+ }
+ break;
}
}
this.wakeLock.acquire();
@@ -432,10 +485,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
this.scheduleWakeUpCall((int) (msToNextPing / 1000), account.getUuid().hashCode());
}
} else if (account.getStatus() == Account.State.OFFLINE) {
- if (account.getXmppConnection() == null) {
- account.setXmppConnection(this.createConnection(account));
- }
- new Thread(account.getXmppConnection()).start();
+ reconnectAccount(account,true);
} else if (account.getStatus() == Account.State.CONNECTING) {
long timeout = Config.CONNECT_TIMEOUT - ((SystemClock.elapsedRealtime() - account.getXmppConnection().getLastConnect()) / 1000);
if (timeout < 0) {
@@ -499,10 +549,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
for (final Account account : this.accounts) {
account.initOtrEngine(this);
- this.databaseBackend.readRoster(account.getRoster());
}
- initConversations();
- PhoneHelper.loadPhoneContacts(getApplicationContext(),new CopyOnWriteArrayList<Bundle>(), this);
+ restoreFromDatabase();
getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
this.fileObserver.startWatching();
@@ -574,6 +622,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return connection;
}
+ public void sendChatState(Conversation conversation) {
+ if (sendChatStates()) {
+ MessagePacket packet = mMessageGenerator.generateChatState(conversation);
+ sendMessagePacket(conversation.getAccount(), packet);
+ }
+ }
+
public void sendMessage(final Message message) {
final Account account = message.getConversation().getAccount();
account.deactivateGracePeriod();
@@ -674,6 +729,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
if ((send) && (packet != null)) {
+ if (conv.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
+ if (this.sendChatStates()) {
+ packet.addChild(ChatState.toElement(conv.getOutgoingChatState()));
+ }
+ }
sendMessagePacket(account, packet);
}
updateConversationUi();
@@ -755,6 +815,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
} 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);
}
}
@@ -855,7 +920,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
mPhoneContactMergerThread.start();
}
- private void initConversations() {
+ private void restoreFromDatabase() {
synchronized (this.conversations) {
final Map<String, Account> accountLookupTable = new Hashtable<>();
for (Account account : this.accounts) {
@@ -865,9 +930,29 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
for (Conversation conversation : this.conversations) {
Account account = accountLookupTable.get(conversation.getAccountUuid());
conversation.setAccount(account);
- conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE));
- checkDeletedFiles(conversation);
}
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ Log.d(Config.LOGTAG,"restoring roster");
+ for(Account account : accounts) {
+ databaseBackend.readRoster(account.getRoster());
+ }
+ getBitmapCache().evictAll();
+ Looper.prepare();
+ PhoneHelper.loadPhoneContacts(getApplicationContext(),
+ new CopyOnWriteArrayList<Bundle>(),
+ XmppConnectionService.this);
+ Log.d(Config.LOGTAG,"restoring messages");
+ for (Conversation conversation : conversations) {
+ conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE));
+ checkDeletedFiles(conversation);
+ }
+ mRestoredFromDatabase = true;
+ Log.d(Config.LOGTAG,"restored all messages");
+ updateConversationUi();
+ }
+ }).start();
}
}
@@ -1069,7 +1154,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
account.initOtrEngine(this);
databaseBackend.createAccount(account);
this.accounts.add(account);
- this.reconnectAccount(account, false);
+ this.reconnectAccountInBackground(account);
updateAccountUi();
}
@@ -1274,6 +1359,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
}
+ for(Conversation conversation : getConversations()) {
+ conversation.setIncomingChatState(ChatState.ACTIVE);
+ }
this.mNotificationService.setIsInForeground(false);
Log.d(Config.LOGTAG, "app switched into background");
}
@@ -1902,24 +1990,29 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void reconnectAccount(final Account account, final boolean force) {
- new Thread(new Runnable() {
+ synchronized (account) {
+ if (account.getXmppConnection() != null) {
+ disconnect(account, force);
+ }
+ if (!account.isOptionSet(Account.OPTION_DISABLED)) {
+ if (account.getXmppConnection() == null) {
+ account.setXmppConnection(createConnection(account));
+ }
+ Thread thread = new Thread(account.getXmppConnection());
+ thread.start();
+ scheduleWakeUpCall(Config.CONNECT_TIMEOUT, account.getUuid().hashCode());
+ } else {
+ account.getRoster().clearPresences();
+ account.setXmppConnection(null);
+ }
+ }
+ }
+ public void reconnectAccountInBackground(final Account account) {
+ new Thread(new Runnable() {
@Override
public void run() {
- if (account.getXmppConnection() != null) {
- disconnect(account, force);
- }
- if (!account.isOptionSet(Account.OPTION_DISABLED)) {
- if (account.getXmppConnection() == null) {
- account.setXmppConnection(createConnection(account));
- }
- Thread thread = new Thread(account.getXmppConnection());
- thread.start();
- scheduleWakeUpCall(Config.CONNECT_TIMEOUT, account.getUuid().hashCode());
- } else {
- account.getRoster().clearPresences();
- account.setXmppConnection(null);
- }
+ reconnectAccount(account,false);
}
}).start();
}
@@ -1943,19 +2036,20 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
- public boolean markMessage(final Account account, final Jid recipient, final String uuid,
- final int status) {
+ public Message markMessage(final Account account, final Jid recipient, final String uuid, final int status) {
if (uuid == null) {
- return false;
- } else {
- for (Conversation conversation : getConversations()) {
- if (conversation.getJid().equals(recipient)
- && conversation.getAccount().equals(account)) {
- return markMessage(conversation, uuid, status);
+ return null;
+ }
+ for (Conversation conversation : getConversations()) {
+ if (conversation.getJid().toBareJid().equals(recipient) && conversation.getAccount() == account) {
+ final Message message = conversation.findSentMessageWithUuid(uuid);
+ if (message != null) {
+ markMessage(message, status);
}
+ return message;
}
- return false;
}
+ return null;
}
public boolean markMessage(Conversation conversation, String uuid,
@@ -1997,6 +2091,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return getPreferences().getBoolean("confirm_messages", true);
}
+ public boolean sendChatStates() {
+ return getPreferences().getBoolean("chat_states", false);
+ }
+
public boolean saveEncryptedMessages() {
return !getPreferences().getBoolean("dont_save_encrypted", false);
}
@@ -2005,6 +2103,14 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return getPreferences().getBoolean("indicate_received", false);
}
+ public int unreadCount() {
+ int count = 0;
+ for(Conversation conversation : getConversations()) {
+ count += conversation.unreadCount();
+ }
+ return count;
+ }
+
public void updateConversationUi() {
if (mOnConversationUpdate != null) {
mOnConversationUpdate.onConversationUpdate();
diff --git a/src/main/java/eu/siacs/conversations/ui/AboutPreference.java b/src/main/java/eu/siacs/conversations/ui/AboutPreference.java
index 804b4e23..a57e1b89 100644
--- a/src/main/java/eu/siacs/conversations/ui/AboutPreference.java
+++ b/src/main/java/eu/siacs/conversations/ui/AboutPreference.java
@@ -6,6 +6,8 @@ import android.content.pm.PackageManager;
import android.preference.Preference;
import android.util.AttributeSet;
+import eu.siacs.conversations.utils.PhoneHelper;
+
public class AboutPreference extends Preference {
public AboutPreference(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
@@ -25,17 +27,7 @@ public class AboutPreference extends Preference {
}
private void setSummary() {
- if (getContext() != null && getContext().getPackageManager() != null) {
- final String packageName = getContext().getPackageName();
- final String versionName;
- try {
- versionName = getContext().getPackageManager().getPackageInfo(packageName, 0).versionName;
- setSummary("Conversations " + versionName);
- } catch (final PackageManager.NameNotFoundException e) {
- // Using try/catch as part of the logic is sort of like this:
- // https://xkcd.com/292/
- }
- }
+ setSummary("Conversations " + PhoneHelper.getVersionName(getContext()));
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java
index 70b353c6..c9e99ce5 100644
--- a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java
@@ -3,20 +3,100 @@ package eu.siacs.conversations.ui;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
+import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView;
+import android.widget.ListView;
+import java.util.Set;
+import java.util.HashSet;
import java.util.Collections;
+import java.util.List;
+import java.util.ArrayList;
+import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.ListItem;
public class ChooseContactActivity extends AbstractSearchableListItemActivity {
+
+ private Set<Contact> selected;
+ private Set<String> filterContacts;
+
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ filterContacts = new HashSet<>();
+ String[] contacts = getIntent().getStringArrayExtra("filter_contacts");
+ if (contacts != null) {
+ Collections.addAll(filterContacts, contacts);
+ }
+
+ if (getIntent().getBooleanExtra("multiple", false)) {
+ getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
+ getListView().setMultiChoiceModeListener(new MultiChoiceModeListener() {
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(getSearchEditText().getWindowToken(),
+ InputMethodManager.HIDE_IMPLICIT_ONLY);
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.select_multiple, menu);
+ selected = new HashSet<Contact>();
+ return true;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ switch(item.getItemId()) {
+ case R.id.selection_submit:
+ final Intent request = getIntent();
+ final Intent data = new Intent();
+ data.putExtra("conversation",
+ request.getStringExtra("conversation"));
+ String[] selection = getSelectedContactJids();
+ data.putExtra("contacts", selection);
+ data.putExtra("multiple", true);
+ setResult(RESULT_OK, data);
+ finish();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
+ Contact item = (Contact) getListItems().get(position);
+ if (checked) {
+ selected.add(item);
+ } else {
+ selected.remove(item);
+ }
+ int numSelected = selected.size();
+ MenuItem selectButton = mode.getMenu().findItem(R.id.selection_submit);
+ String buttonText = getResources().getQuantityString(R.plurals.select_contact,
+ numSelected, numSelected);
+ selectButton.setTitle(buttonText);
+ }
+ });
+ }
+
getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
@@ -36,6 +116,7 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity {
data.putExtra("account", account);
data.putExtra("conversation",
request.getStringExtra("conversation"));
+ data.putExtra("multiple", false);
setResult(RESULT_OK, data);
finish();
}
@@ -48,7 +129,9 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity {
for (final Account account : xmppConnectionService.getAccounts()) {
if (account.getStatus() != Account.State.DISABLED) {
for (final Contact contact : account.getRoster().getContacts()) {
- if (contact.showInRoster() && contact.match(needle)) {
+ if (contact.showInRoster() &&
+ !filterContacts.contains(contact.getJid().toBareJid().toString())
+ && contact.match(needle)) {
getListItems().add(contact);
}
}
@@ -57,4 +140,13 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity {
Collections.sort(getListItems());
getListItemAdapter().notifyDataSetChanged();
}
+
+ private String[] getSelectedContactJids() {
+ List<String> result = new ArrayList<>();
+ for (Contact contact : selected) {
+ result.add(contact.getJid().toString());
+ }
+ return result.toArray(new String[result.size()]);
+ }
+
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
index 399d9fdf..e4bfd6ff 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
@@ -25,6 +25,8 @@ import android.widget.Toast;
import org.openintents.openpgp.util.OpenPgpUtils;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import eu.siacs.conversations.R;
@@ -142,24 +144,17 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
@Override
public void onConversationUpdate() {
- runOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- updateView();
- }
- });
+ refreshUi();
}
@Override
public void onMucRosterUpdate() {
- runOnUiThread(new Runnable() {
+ refreshUi();
+ }
- @Override
- public void run() {
- updateView();
- }
- });
+ @Override
+ protected void refreshUiReal() {
+ updateView();
}
@Override
@@ -431,9 +426,16 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
}
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
membersView.removeAllViews();
- for (final User user : mConversation.getMucOptions().getUsers()) {
- View view = inflater.inflate(R.layout.contact, membersView,
- false);
+ final ArrayList<User> users = new ArrayList<>();
+ users.addAll(mConversation.getMucOptions().getUsers());
+ Collections.sort(users,new Comparator<User>() {
+ @Override
+ public int compare(User lhs, User rhs) {
+ return lhs.getName().compareToIgnoreCase(rhs.getName());
+ }
+ });
+ for (final User user : users) {
+ View view = inflater.inflate(R.layout.contact, membersView,false);
this.setListItemBackgroundOnView(view);
view.setOnClickListener(new OnClickListener() {
@Override
diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
index fda0c617..40a4587c 100644
--- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
@@ -139,26 +139,18 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
@Override
public void onRosterUpdate() {
- runOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- invalidateOptionsMenu();
- populateView();
- }
- });
+ refreshUi();
}
@Override
public void onAccountUpdate() {
- runOnUiThread(new Runnable() {
+ refreshUi();
+ }
- @Override
- public void run() {
- invalidateOptionsMenu();
- populateView();
- }
- });
+ @Override
+ protected void refreshUiReal() {
+ invalidateOptionsMenu();
+ populateView();
}
@Override
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
index 0a55c6b5..82afda07 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
@@ -12,7 +12,6 @@ import android.content.IntentSender.SendIntentException;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.os.SystemClock;
import android.provider.MediaStore;
import android.support.v4.widget.SlidingPaneLayout;
import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener;
@@ -34,7 +33,6 @@ import java.util.ArrayList;
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;
@@ -64,6 +62,7 @@ public class ConversationActivity extends XmppActivity
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;
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";
@@ -72,6 +71,7 @@ public class ConversationActivity extends XmppActivity
private boolean mPanelOpen = true;
private Uri mPendingImageUri = null;
private Uri mPendingFileUri = null;
+ private Uri mPendingGeoUri = null;
private View mContentView;
@@ -85,6 +85,7 @@ public class ConversationActivity extends XmppActivity
private Toast prepareFileToast;
private boolean mActivityPaused = false;
+ private boolean mRedirected = true;
public Conversation getSelectedConversation() {
return this.mSelectedConversation;
@@ -313,7 +314,6 @@ public class ConversationActivity extends XmppActivity
menuInviteContact.setVisible(getSelectedConversation().getMucOptions().canInvite());
} else {
menuMucDetails.setVisible(false);
- final Account account = this.getSelectedConversation().getAccount();
}
if (this.getSelectedConversation().isMuted()) {
menuMute.setVisible(false);
@@ -325,50 +325,60 @@ public class ConversationActivity extends XmppActivity
return true;
}
- private void selectPresenceToAttachFile(final int attachmentChoice) {
- selectPresence(getSelectedConversation(), new OnPresenceSelected() {
+ 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() {
- @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;
- }
- 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;
+ 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);
+ }
}
}
- }
- });
+ });
+ }
}
private void attachFile(final int attachmentChoice) {
final Conversation conversation = getSelectedConversation();
- if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
+ final int encryption = conversation.getNextEncryption(forceEncryption());
+ if (encryption == Message.ENCRYPTION_PGP) {
if (hasPgp()) {
if (conversation.getContact().getPgpKeyId() != 0) {
xmppConnectionService.getPgpEngine().hasKey(
@@ -378,13 +388,12 @@ public class ConversationActivity extends XmppActivity
@Override
public void userInputRequried(PendingIntent pi,
Contact contact) {
- ConversationActivity.this.runIntent(pi,
- attachmentChoice);
+ ConversationActivity.this.runIntent(pi,attachmentChoice);
}
@Override
public void success(Contact contact) {
- selectPresenceToAttachFile(attachmentChoice);
+ selectPresenceToAttachFile(attachmentChoice,encryption);
}
@Override
@@ -406,7 +415,7 @@ public class ConversationActivity extends XmppActivity
.setNextEncryption(Message.ENCRYPTION_NONE);
xmppConnectionService.databaseBackend
.updateConversation(conversation);
- selectPresenceToAttachFile(attachmentChoice);
+ selectPresenceToAttachFile(attachmentChoice,Message.ENCRYPTION_NONE);
}
});
}
@@ -414,11 +423,8 @@ public class ConversationActivity extends XmppActivity
} else {
showInstallPgpDialog();
}
- } else if (getSelectedConversation().getNextEncryption(
- forceEncryption()) == Message.ENCRYPTION_NONE) {
- selectPresenceToAttachFile(attachmentChoice);
} else {
- selectPresenceToAttachFile(attachmentChoice);
+ selectPresenceToAttachFile(attachmentChoice,encryption);
}
}
@@ -526,6 +532,9 @@ public class ConversationActivity extends XmppActivity
if (new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION).resolveActivity(getPackageManager()) == null) {
attachFilePopup.getMenu().findItem(R.id.attach_record_voice).setVisible(false);
}
+ if (new Intent("eu.siacs.conversations.location.request").resolveActivity(getPackageManager()) == null) {
+ attachFilePopup.getMenu().findItem(R.id.attach_location).setVisible(false);
+ }
attachFilePopup.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
@@ -543,6 +552,9 @@ public class ConversationActivity extends XmppActivity
case R.id.attach_record_voice:
attachFile(ATTACHMENT_CHOICE_RECORD_VOICE);
break;
+ case R.id.attach_location:
+ attachFile(ATTACHMENT_CHOICE_LOCATION);
+ break;
}
return false;
}
@@ -677,8 +689,7 @@ public class ConversationActivity extends XmppActivity
if (durations[which] == -1) {
till = Long.MAX_VALUE;
} else {
- till = SystemClock.elapsedRealtime()
- + (durations[which] * 1000);
+ till = System.currentTimeMillis() + (durations[which] * 1000);
}
conversation.setMutedTill(till);
ConversationActivity.this.xmppConnectionService.databaseBackend
@@ -722,6 +733,7 @@ public class ConversationActivity extends XmppActivity
@Override
public void onStart() {
super.onStart();
+ this.mRedirected = false;
if (this.xmppConnectionServiceBound) {
this.onBackendConnected();
}
@@ -778,10 +790,19 @@ public class ConversationActivity extends XmppActivity
this.xmppConnectionService.getNotificationService().setIsInForeground(true);
updateConversationList();
if (xmppConnectionService.getAccounts().size() == 0) {
- startActivity(new Intent(this, EditAccountActivity.class));
+ if (!mRedirected) {
+ this.mRedirected = true;
+ startActivity(new Intent(this, EditAccountActivity.class));
+ finish();
+ }
} else if (conversationList.size() <= 0) {
- startActivity(new Intent(this, StartConversationActivity.class));
- finish();
+ if (!mRedirected) {
+ this.mRedirected = true;
+ Intent intent = new Intent(this, StartConversationActivity.class);
+ intent.putExtra("init",true);
+ startActivity(intent);
+ finish();
+ }
} else if (getIntent() != null && VIEW_CONVERSATION.equals(getIntent().getType())) {
handleViewConversationIntent(getIntent());
} else if (selectConversationByUuid(mOpenConverstaion)) {
@@ -795,11 +816,12 @@ public class ConversationActivity extends XmppActivity
this.mConversationFragment.reInit(getSelectedConversation());
mOpenConverstaion = null;
} else if (getSelectedConversation() != null) {
- this.mConversationFragment.updateMessages();
+ this.mConversationFragment.reInit(getSelectedConversation());
} else {
showConversationsOverview();
mPendingImageUri = null;
mPendingFileUri = null;
+ mPendingGeoUri = null;
setSelectedConversation(conversationList.get(0));
this.mConversationFragment.reInit(getSelectedConversation());
}
@@ -810,6 +832,9 @@ public class ConversationActivity extends XmppActivity
} else if (mPendingFileUri != null) {
attachFileToConversation(getSelectedConversation(),mPendingFileUri);
mPendingFileUri = null;
+ } else if (mPendingGeoUri != null) {
+ attachLocationToConversation(getSelectedConversation(),mPendingGeoUri);
+ mPendingGeoUri = null;
}
ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
setIntent(new Intent());
@@ -888,6 +913,14 @@ public class ConversationActivity extends XmppActivity
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);
+ this.mPendingGeoUri = Uri.parse("geo:"+String.valueOf(latitude)+","+String.valueOf(longitude));
+ if (xmppConnectionServiceBound) {
+ attachLocationToConversation(getSelectedConversation(), mPendingGeoUri);
+ this.mPendingGeoUri = null;
+ }
}
} else {
if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO) {
@@ -896,6 +929,26 @@ public class ConversationActivity extends XmppActivity
}
}
+ private void attachLocationToConversation(Conversation conversation, Uri uri) {
+ xmppConnectionService.attachLocationToConversation(conversation,uri, new UiCallback<Message>() {
+
+ @Override
+ public void success(Message message) {
+ xmppConnectionService.sendMessage(message);
+ }
+
+ @Override
+ public void error(int errorCode, Message object) {
+
+ }
+
+ @Override
+ public void userInputRequried(PendingIntent pi, Message object) {
+
+ }
+ });
+ }
+
private void attachFileToConversation(Conversation conversation, Uri uri) {
prepareFileToast = Toast.makeText(getApplicationContext(),
getText(R.string.preparing_file), Toast.LENGTH_LONG);
@@ -1008,56 +1061,50 @@ public class ConversationActivity extends XmppActivity
}
@Override
- public void onAccountUpdate() {
- runOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- updateConversationList();
- ConversationActivity.this.mConversationFragment.updateMessages();
- updateActionBarTitle();
+ protected void refreshUiReal() {
+ updateConversationList();
+ if (xmppConnectionService != null && xmppConnectionService.getAccounts().size() == 0) {
+ if (!mRedirected) {
+ this.mRedirected = true;
+ startActivity(new Intent(this, EditAccountActivity.class));
+ finish();
}
- });
+ } else if (conversationList.size() == 0) {
+ if (!mRedirected) {
+ this.mRedirected = true;
+ Intent intent = new Intent(this, StartConversationActivity.class);
+ intent.putExtra("init",true);
+ startActivity(intent);
+ finish();
+ }
+ } else {
+ ConversationActivity.this.mConversationFragment.updateMessages();
+ updateActionBarTitle();
+ }
}
@Override
- public void onConversationUpdate() {
- runOnUiThread(new Runnable() {
+ public void onAccountUpdate() {
+ this.refreshUi();
+ }
- @Override
- public void run() {
- updateConversationList();
- if (conversationList.size() == 0) {
- startActivity(new Intent(getApplicationContext(),
- StartConversationActivity.class));
- finish();
- }
- ConversationActivity.this.mConversationFragment.updateMessages();
- updateActionBarTitle();
- }
- });
+ @Override
+ public void onConversationUpdate() {
+ this.refreshUi();
}
@Override
public void onRosterUpdate() {
- runOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- updateConversationList();
- ConversationActivity.this.mConversationFragment.updateMessages();
- updateActionBarTitle();
- }
- });
+ this.refreshUi();
}
@Override
public void OnUpdateBlocklist(Status status) {
+ this.refreshUi();
runOnUiThread(new Runnable() {
@Override
public void run() {
invalidateOptionsMenu();
- ConversationActivity.this.mConversationFragment.updateMessages();
}
});
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
index 80ac9da1..d5f20e41 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
@@ -8,6 +8,7 @@ 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;
@@ -40,6 +41,7 @@ import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentLinkedQueue;
+import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.PgpEngine;
import eu.siacs.conversations.entities.Account;
@@ -52,15 +54,16 @@ import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.ui.EditMessage.OnEnterPressed;
import eu.siacs.conversations.ui.XmppActivity.OnPresenceSelected;
import eu.siacs.conversations.ui.XmppActivity.OnValueEdited;
import eu.siacs.conversations.ui.adapter.MessageAdapter;
import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureClicked;
import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureLongClicked;
+import eu.siacs.conversations.utils.GeoHelper;
+import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.jid.Jid;
-public class ConversationFragment extends Fragment {
+public class ConversationFragment extends Fragment implements EditMessage.KeyboardListener {
protected Conversation conversation;
private OnClickListener leaveMuc = new OnClickListener() {
@@ -99,7 +102,6 @@ public class ConversationFragment extends Fragment {
protected ListView messagesView;
final protected List<Message> messageList = new ArrayList<>();
protected MessageAdapter messageListAdapter;
- protected Contact contact;
private EditMessage mEditMessage;
private ImageButton mSendButton;
private RelativeLayout snackbar;
@@ -196,6 +198,7 @@ public class ConversationFragment extends Fragment {
askForPassphraseIntent,
ConversationActivity.REQUEST_DECRYPT_PGP, null, 0,
0, 0);
+ askForPassphraseIntent = null;
} catch (SendIntentException e) {
//
}
@@ -321,22 +324,12 @@ public class ConversationFragment extends Fragment {
@Override
public void onClick(View v) {
- activity.hideConversationsOverview();
- }
- });
- mEditMessage.setOnEditorActionListener(mEditorActionListener);
- mEditMessage.setOnEnterPressedListener(new OnEnterPressed() {
-
- @Override
- public boolean onEnterPressed() {
- if (activity.enterIsSend()) {
- sendMessage();
- return true;
- } else {
- return false;
+ if (activity != null) {
+ activity.hideConversationsOverview();
}
}
});
+ mEditMessage.setOnEditorActionListener(mEditorActionListener);
mSendButton = (ImageButton) view.findViewById(R.id.textSendButton);
mSendButton.setOnClickListener(this.mSendButtonListener);
@@ -418,19 +411,21 @@ public class ConversationFragment extends Fragment {
MenuItem copyUrl = menu.findItem(R.id.copy_url);
MenuItem downloadImage = menu.findItem(R.id.download_image);
MenuItem cancelTransmission = menu.findItem(R.id.cancel_transmission);
- if (m.getType() != Message.TYPE_TEXT || m.getDownloadable() != null) {
+ 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
+ if ((m.getType() == Message.TYPE_TEXT
|| m.getType() == Message.TYPE_PRIVATE
- || m.getDownloadable() != null) {
+ || m.getDownloadable() != null)
+ && (!GeoHelper.isGeoUri(m.getBody()))) {
shareWith.setVisible(false);
- }
+ }
if (m.getStatus() != Message.STATUS_SEND_FAILED) {
sendAgain.setVisible(false);
}
- if ((m.getType() != Message.TYPE_IMAGE && m.getDownloadable() == null)
- || m.getImageParams().url == null) {
+ 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
@@ -475,16 +470,21 @@ public class ConversationFragment extends Fragment {
private void shareWith(Message message) {
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
- shareIntent.putExtra(Intent.EXTRA_STREAM,
- 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);
- if (mime == null) {
- mime = "image/webp";
- }
- shareIntent.setType(mime);
+ if (GeoHelper.isGeoUri(message.getBody())) {
+ shareIntent.putExtra(Intent.EXTRA_TEXT, message.getBody());
+ shareIntent.setType("text/plain");
+ } else {
+ shareIntent.putExtra(Intent.EXTRA_STREAM,
+ 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);
+ if (mime == null) {
+ mime = "image/webp";
+ }
+ shareIntent.setType(mime);
+ }
activity.startActivity(Intent.createChooser(shareIntent,getText(R.string.share_with)));
}
@@ -509,8 +509,16 @@ public class ConversationFragment extends Fragment {
}
private void copyUrl(Message message) {
- if (activity.copyTextToClipboard(
- message.getImageParams().url.toString(), R.string.image_url)) {
+ final String url;
+ final int resId;
+ if (GeoHelper.isGeoUri(message.getBody())) {
+ resId = R.string.location;
+ url = message.getBody();
+ } else {
+ resId = R.string.image_url;
+ url = message.getImageParams().url.toString();
+ }
+ if (activity.copyTextToClipboard(url, resId)) {
Toast.makeText(activity, R.string.url_copied_to_clipboard,
Toast.LENGTH_SHORT).show();
}
@@ -555,7 +563,17 @@ public class ConversationFragment extends Fragment {
mDecryptJobRunning = false;
super.onStop();
if (this.conversation != null) {
- this.conversation.setNextMessage(mEditMessage.getText().toString());
+ final String msg = mEditMessage.getText().toString();
+ this.conversation.setNextMessage(msg);
+ updateChatState(this.conversation,msg);
+ }
+ }
+
+ private void updateChatState(final Conversation conversation, final String msg) {
+ ChatState state = msg.length() == 0 ? Config.DEFAULT_CHATSTATE : ChatState.PAUSED;
+ Account.State status = conversation.getAccount().getStatus();
+ if (status == Account.State.ONLINE && conversation.setOutgoingChatState(state)) {
+ activity.xmppConnectionService.sendChatState(conversation);
}
}
@@ -563,20 +581,30 @@ public class ConversationFragment extends Fragment {
if (conversation == null) {
return;
}
+
+ this.activity = (ConversationActivity) getActivity();
+
if (this.conversation != null) {
- this.conversation.setNextMessage(mEditMessage.getText().toString());
+ final String msg = mEditMessage.getText().toString();
+ this.conversation.setNextMessage(msg);
+ if (this.conversation != conversation) {
+ updateChatState(this.conversation,msg);
+ }
this.conversation.trim();
}
- this.activity = (ConversationActivity) getActivity();
+
+ this.askForPassphraseIntent = null;
this.conversation = conversation;
this.mDecryptJobRunning = false;
this.mEncryptedMessages.clear();
if (this.conversation.getMode() == Conversation.MODE_MULTI) {
this.conversation.setNextCounterpart(null);
}
+ this.mEditMessage.setKeyboardListener(null);
this.mEditMessage.setText("");
this.mEditMessage.append(this.conversation.getNextMessage());
- this.messagesView.invalidateViews();
+ this.mEditMessage.setKeyboardListener(this);
+ this.messagesView.setAdapter(messageListAdapter);
updateMessages();
this.messagesLoaded = true;
int size = this.messageList.size();
@@ -585,88 +613,127 @@ public class ConversationFragment extends Fragment {
}
}
+ private OnClickListener mUnblockClickListener = new OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ v.post(new Runnable() {
+ @Override
+ public void run() {
+ v.setVisibility(View.INVISIBLE);
+ }
+ });
+ if (conversation.isDomainBlocked()) {
+ BlockContactDialog.show(activity, activity.xmppConnectionService, conversation);
+ } else {
+ activity.unblockConversation(conversation);
+ }
+ }
+ };
+
+ private OnClickListener mAddBackClickListener = new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ final Contact contact = conversation == null ? null :conversation.getContact();
+ if (contact != null) {
+ activity.xmppConnectionService.createContact(contact);
+ activity.switchToContactDetails(contact);
+ }
+ }
+ };
+
+ private OnClickListener mUnmuteClickListener = new OnClickListener() {
+
+ @Override
+ public void onClick(final View v) {
+ activity.unmuteConversation(conversation);
+ }
+ };
+
+ private OnClickListener mAnswerSmpClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(activity, VerifyOTRActivity.class);
+ 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);
+ startActivity(intent);
+ }
+ };
+
+ private void updateSnackBar(final Conversation conversation) {
+ final Account account = conversation.getAccount();
+ final Contact contact = conversation.getContact();
+ final int mode = conversation.getMode();
+ if (conversation.isBlocked()) {
+ 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);
+ } else if (mode == Conversation.MODE_MULTI
+ &&!conversation.getMucOptions().online()
+ && account.getStatus() == Account.State.ONLINE) {
+ switch (conversation.getMucOptions().getError()) {
+ case MucOptions.ERROR_NICK_IN_USE:
+ showSnackbar(R.string.nick_in_use, R.string.edit, clickToMuc);
+ break;
+ case MucOptions.ERROR_UNKNOWN:
+ showSnackbar(R.string.conference_not_found, R.string.leave, leaveMuc);
+ break;
+ case MucOptions.ERROR_PASSWORD_REQUIRED:
+ showSnackbar(R.string.conference_requires_password, R.string.enter_password, enterPassword);
+ break;
+ case MucOptions.ERROR_BANNED:
+ showSnackbar(R.string.conference_banned, R.string.leave, leaveMuc);
+ break;
+ case MucOptions.ERROR_MEMBERS_ONLY:
+ showSnackbar(R.string.conference_members_only, R.string.leave, leaveMuc);
+ break;
+ case MucOptions.KICKED_FROM_ROOM:
+ showSnackbar(R.string.conference_kicked, R.string.join, joinMuc);
+ break;
+ default:
+ break;
+ }
+ } 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);
+ } else if (mode == Conversation.MODE_SINGLE
+ &&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);
+ } else {
+ hideSnackbar();
+ }
+ }
+
public void updateMessages() {
synchronized (this.messageList) {
if (getView() == null) {
return;
}
- hideSnackbar();
final ConversationActivity activity = (ConversationActivity) getActivity();
if (this.conversation != null) {
+ updateSnackBar(this.conversation);
final Contact contact = this.conversation.getContact();
if (this.conversation.isBlocked()) {
- showSnackbar(R.string.contact_blocked, R.string.unblock,
- new OnClickListener() {
- @Override
- public void onClick(final View v) {
- v.post(new Runnable() {
- @Override
- public void run() {
- v.setVisibility(View.INVISIBLE);
- }
- });
- if (conversation.isDomainBlocked()) {
- BlockContactDialog.show(getActivity(), ((ConversationActivity) getActivity()).xmppConnectionService, conversation);
- } else {
- ((ConversationActivity) getActivity()).unblockConversation(conversation);
- }
- }
- });
+
} else if (!contact.showInRoster()
&& contact
.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
- showSnackbar(R.string.contact_added_you, R.string.add_back,
- new OnClickListener() {
- @Override
- public void onClick(View v) {
- activity.xmppConnectionService
- .createContact(contact);
- activity.switchToContactDetails(contact);
- }
- });
} else if (conversation.getMode() == Conversation.MODE_SINGLE) {
makeFingerprintWarning();
} else if (!conversation.getMucOptions().online()
&& conversation.getAccount().getStatus() == Account.State.ONLINE) {
- int error = conversation.getMucOptions().getError();
- switch (error) {
- case MucOptions.ERROR_NICK_IN_USE:
- showSnackbar(R.string.nick_in_use, R.string.edit,
- clickToMuc);
- break;
- case MucOptions.ERROR_UNKNOWN:
- showSnackbar(R.string.conference_not_found,
- R.string.leave, leaveMuc);
- break;
- case MucOptions.ERROR_PASSWORD_REQUIRED:
- showSnackbar(R.string.conference_requires_password,
- R.string.enter_password, enterPassword);
- break;
- case MucOptions.ERROR_BANNED:
- showSnackbar(R.string.conference_banned,
- R.string.leave, leaveMuc);
- break;
- case MucOptions.ERROR_MEMBERS_ONLY:
- showSnackbar(R.string.conference_members_only,
- R.string.leave, leaveMuc);
- break;
- case MucOptions.KICKED_FROM_ROOM:
- showSnackbar(R.string.conference_kicked, R.string.join,
- joinMuc);
- break;
- default:
- break;
- }
+
} else if (this.conversation.isMuted()) {
- showSnackbar(R.string.notifications_disabled, R.string.enable,
- new OnClickListener() {
- @Override
- public void onClick(final View v) {
- activity.unmuteConversation(conversation);
- }
- });
}
conversation.populateWithMessages(ConversationFragment.this.messageList);
for (final Message message : this.messageList) {
@@ -703,8 +770,7 @@ public class ConversationFragment extends Fragment {
public void userInputRequried(PendingIntent pi, Message message) {
mDecryptJobRunning = false;
askForPassphraseIntent = pi.getIntentSender();
- showSnackbar(R.string.openpgp_messages_found,
- R.string.decrypt, clickToDecryptListener);
+ updateSnackBar(conversation);
}
@Override
@@ -792,13 +858,21 @@ public class ConversationFragment extends Fragment {
protected void updateStatusMessages() {
synchronized (this.messageList) {
if (conversation.getMode() == Conversation.MODE_SINGLE) {
- for (int i = this.messageList.size() - 1; i >= 0; --i) {
- if (this.messageList.get(i).getStatus() == Message.STATUS_RECEIVED) {
- return;
- } else {
- if (this.messageList.get(i).getStatus() == Message.STATUS_SEND_DISPLAYED) {
- this.messageList.add(i + 1,Message.createStatusMessage(conversation));
+ ChatState state = conversation.getIncomingChatState();
+ if (state == ChatState.COMPOSING) {
+ this.messageList.add(Message.createStatusMessage(conversation, getString(R.string.contact_is_typing, conversation.getName())));
+ } else if (state == ChatState.PAUSED) {
+ this.messageList.add(Message.createStatusMessage(conversation, getString(R.string.contact_has_stopped_typing, conversation.getName())));
+ } else {
+ for (int i = this.messageList.size() - 1; i >= 0; --i) {
+ if (this.messageList.get(i).getStatus() == Message.STATUS_RECEIVED) {
return;
+ } else {
+ if (this.messageList.get(i).getStatus() == Message.STATUS_SEND_DISPLAYED) {
+ this.messageList.add(i + 1,
+ Message.createStatusMessage(conversation, getString(R.string.contact_has_read_up_to_this_point, conversation.getName())));
+ return;
+ }
}
}
}
@@ -807,22 +881,7 @@ public class ConversationFragment extends Fragment {
}
protected void makeFingerprintWarning() {
- if (conversation.smpRequested()) {
- showSnackbar(R.string.smp_requested, R.string.verify, new OnClickListener() {
- @Override
- public void onClick(View view) {
- Intent intent = new Intent(activity, VerifyOTRActivity.class);
- 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);
- startActivity(intent);
- }
- });
- } else if (conversation.hasValidOtrSession() && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED)
- && (!conversation.isOtrFingerprintVerified())) {
- showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify, clickToVerify);
- }
+
}
protected void showSnackbar(final int message, final int action,
@@ -968,4 +1027,38 @@ public class ConversationFragment extends Fragment {
this.mEditMessage.append(text);
}
+ @Override
+ public boolean onEnterPressed() {
+ if (activity.enterIsSend()) {
+ sendMessage();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onTypingStarted() {
+ Account.State status = conversation.getAccount().getStatus();
+ if (status == Account.State.ONLINE && conversation.setOutgoingChatState(ChatState.COMPOSING)) {
+ activity.xmppConnectionService.sendChatState(conversation);
+ }
+ }
+
+ @Override
+ public void onTypingStopped() {
+ Account.State status = conversation.getAccount().getStatus();
+ if (status == Account.State.ONLINE && conversation.setOutgoingChatState(ChatState.PAUSED)) {
+ activity.xmppConnectionService.sendChatState(conversation);
+ }
+ }
+
+ @Override
+ public void onTextDeleted() {
+ Account.State status = conversation.getAccount().getStatus();
+ if (status == Account.State.ONLINE && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
+ activity.xmppConnectionService.sendChatState(conversation);
+ }
+ }
+
}
diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
index ea45b75e..27dfc492 100644
--- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
@@ -102,6 +102,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
} catch (final InvalidJidException ignored) {
return;
}
+ mAccountJid.setError(null);
+ mPasswordConfirm.setError(null);
mAccount.setPassword(password);
mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
xmppConnectionService.updateAccount(mAccount);
@@ -221,6 +223,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
if (avatar != null) {
intent = new Intent(getApplicationContext(),
StartConversationActivity.class);
+ intent.putExtra("init",true);
} else {
intent = new Intent(getApplicationContext(),
PublishProfilePictureActivity.class);
@@ -234,7 +237,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
}
protected void updateSaveButton() {
- if (mAccount != null && mAccount.getStatus() == Account.State.CONNECTING) {
+ 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);
@@ -329,17 +332,18 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
final MenuItem showBlocklist = menu.findItem(R.id.action_show_block_list);
final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more);
final MenuItem changePassword = menu.findItem(R.id.action_change_password_on_server);
- if (mAccount == null) {
+ if (mAccount != null && mAccount.isOnlineAndConnected()) {
+ if (!mAccount.getXmppConnection().getFeatures().blocking()) {
+ showBlocklist.setVisible(false);
+ }
+ if (!mAccount.getXmppConnection().getFeatures().register()) {
+ changePassword.setVisible(false);
+ }
+ } else {
showQrCode.setVisible(false);
showBlocklist.setVisible(false);
showMoreInfo.setVisible(false);
changePassword.setVisible(false);
- } else if (mAccount.getStatus() != Account.State.ONLINE) {
- showBlocklist.setVisible(false);
- showMoreInfo.setVisible(false);
- changePassword.setVisible(false);
- } else if (!mAccount.getXmppConnection().getFeatures().blocking()) {
- showBlocklist.setVisible(false);
}
return true;
}
@@ -379,6 +383,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
if (getActionBar() != null) {
getActionBar().setDisplayHomeAsUpEnabled(false);
getActionBar().setDisplayShowHomeEnabled(false);
+ getActionBar().setHomeButtonEnabled(false);
}
this.mCancelButton.setEnabled(false);
this.mCancelButton.setTextColor(getSecondaryTextColor());
@@ -491,6 +496,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
if (this.mAccount.errorStatus()) {
this.mAccountJid.setError(getString(this.mAccount.getStatus().getReadableId()));
this.mAccountJid.requestFocus();
+ } else {
+ this.mAccountJid.setError(null);
}
this.mStats.setVisibility(View.GONE);
}
diff --git a/src/main/java/eu/siacs/conversations/ui/EditMessage.java b/src/main/java/eu/siacs/conversations/ui/EditMessage.java
index 5090bbf5..a58cf2b8 100644
--- a/src/main/java/eu/siacs/conversations/ui/EditMessage.java
+++ b/src/main/java/eu/siacs/conversations/ui/EditMessage.java
@@ -1,10 +1,13 @@
package eu.siacs.conversations.ui;
import android.content.Context;
+import android.os.Handler;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.widget.EditText;
+import eu.siacs.conversations.Config;
+
public class EditMessage extends EditText {
public EditMessage(Context context, AttributeSet attrs) {
@@ -15,28 +18,61 @@ public class EditMessage extends EditText {
super(context);
}
- protected OnEnterPressed mOnEnterPressed;
+ protected Handler mTypingHandler = new Handler();
+
+ protected Runnable mTypingTimeout = new Runnable() {
+ @Override
+ public void run() {
+ if (isUserTyping && keyboardListener != null) {
+ keyboardListener.onTypingStopped();
+ isUserTyping = false;
+ }
+ }
+ };
+
+ private boolean isUserTyping = false;
+
+ protected KeyboardListener keyboardListener;
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_ENTER) {
- if (mOnEnterPressed != null) {
- if (mOnEnterPressed.onEnterPressed()) {
- return true;
- } else {
- return super.onKeyDown(keyCode, event);
- }
+ if (keyboardListener != null && keyboardListener.onEnterPressed()) {
+ return true;
}
}
return super.onKeyDown(keyCode, event);
}
- public void setOnEnterPressedListener(OnEnterPressed listener) {
- this.mOnEnterPressed = listener;
+ @Override
+ public void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
+ super.onTextChanged(text,start,lengthBefore,lengthAfter);
+ if (this.mTypingHandler != null && this.keyboardListener != null) {
+ this.mTypingHandler.removeCallbacks(mTypingTimeout);
+ this.mTypingHandler.postDelayed(mTypingTimeout, Config.TYPING_TIMEOUT * 1000);
+ final int length = text.length();
+ if (!isUserTyping && length > 0) {
+ this.isUserTyping = true;
+ this.keyboardListener.onTypingStarted();
+ } else if (length == 0) {
+ this.isUserTyping = false;
+ this.keyboardListener.onTextDeleted();
+ }
+ }
+ }
+
+ public void setKeyboardListener(KeyboardListener listener) {
+ this.keyboardListener = listener;
+ if (listener != null) {
+ this.isUserTyping = false;
+ }
}
- public interface OnEnterPressed {
+ public interface KeyboardListener {
public boolean onEnterPressed();
+ public void onTypingStarted();
+ public void onTypingStopped();
+ public void onTextDeleted();
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java
index b3ab5ee6..b2d5ddfd 100644
--- a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java
@@ -32,18 +32,17 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
@Override
public void onAccountUpdate() {
+ refreshUi();
+ }
+
+ @Override
+ protected void refreshUiReal() {
synchronized (this.accountList) {
accountList.clear();
accountList.addAll(xmppConnectionService.getAccounts());
}
- runOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- invalidateOptionsMenu();
- mAccountAdapter.notifyDataSetChanged();
- }
- });
+ invalidateOptionsMenu();
+ mAccountAdapter.notifyDataSetChanged();
}
@Override
diff --git a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
index 2ba0b090..3f72b723 100644
--- a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
@@ -54,8 +54,10 @@ public class PublishProfilePictureActivity extends XmppActivity {
@Override
public void run() {
if (mInitialAccountSetup) {
- startActivity(new Intent(getApplicationContext(),
- StartConversationActivity.class));
+ Intent intent = new Intent(getApplicationContext(),
+ StartConversationActivity.class);
+ intent.putExtra("init",true);
+ startActivity(intent);
}
Toast.makeText(PublishProfilePictureActivity.this,
R.string.avatar_has_been_published,
@@ -112,8 +114,10 @@ public class PublishProfilePictureActivity extends XmppActivity {
@Override
public void onClick(View v) {
if (mInitialAccountSetup) {
- startActivity(new Intent(getApplicationContext(),
- StartConversationActivity.class));
+ Intent intent = new Intent(getApplicationContext(),
+ StartConversationActivity.class);
+ intent.putExtra("init",true);
+ startActivity(intent);
}
finish();
}
diff --git a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
index 136108ef..1c1ff3b9 100644
--- a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
@@ -67,7 +67,7 @@ public class SettingsActivity extends XmppActivity implements
for (Account account : xmppConnectionService.getAccounts()) {
account.setResource(resource);
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
- xmppConnectionService.reconnectAccount(account, false);
+ xmppConnectionService.reconnectAccountInBackground(account);
}
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
index ff46ffd8..a556b8b7 100644
--- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
@@ -176,15 +176,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
@Override
public void onRosterUpdate() {
- runOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- if (mSearchEditText != null) {
- filter(mSearchEditText.getText().toString());
- }
- }
- });
+ this.refreshUi();
}
@Override
@@ -582,9 +574,15 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
this.mActivatedAccounts.add(account.getJid().toBareJid().toString());
}
}
+ final Intent intent = getIntent();
+ final ActionBar ab = getActionBar();
+ if (intent != null && intent.getBooleanExtra("init",false) && ab != null) {
+ ab.setDisplayShowHomeEnabled(false);
+ ab.setDisplayHomeAsUpEnabled(false);
+ ab.setHomeButtonEnabled(false);
+ }
this.mKnownHosts = xmppConnectionService.getKnownHosts();
- this.mKnownConferenceHosts = xmppConnectionService
- .getKnownConferenceHosts();
+ this.mKnownConferenceHosts = xmppConnectionService.getKnownConferenceHosts();
if (this.mPendingInvite != null) {
mPendingInvite.invite();
this.mPendingInvite = null;
@@ -711,15 +709,14 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
@Override
public void OnUpdateBlocklist(final Status status) {
- runOnUiThread(new Runnable() {
+ refreshUi();
+ }
- @Override
- public void run() {
- if (mSearchEditText != null) {
- filter(mSearchEditText.getText().toString());
- }
- }
- });
+ @Override
+ protected void refreshUiReal() {
+ if (mSearchEditText != null) {
+ filter(mSearchEditText.getText().toString());
+ }
}
public static class MyListFragment extends ListFragment {
diff --git a/src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java b/src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java
index c33decd8..ec9d59e1 100644
--- a/src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java
@@ -436,11 +436,11 @@ public class VerifyOTRActivity extends XmppActivity implements XmppConnectionSer
}
public void onConversationUpdate() {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- updateView();
- }
- });
+ refreshUi();
+ }
+
+ @Override
+ protected void refreshUiReal() {
+ updateView();
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
index 1f1af09c..62f62b9a 100644
--- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
@@ -33,7 +33,9 @@ import android.nfc.NfcEvent;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.text.InputType;
import android.util.DisplayMetrics;
@@ -68,6 +70,7 @@ 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.MucOptions;
import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.services.XmppConnectionService;
@@ -100,6 +103,33 @@ public abstract class XmppActivity extends Activity {
protected int mTheme;
protected boolean mUsingEnterKey = false;
+ private long mLastUiRefresh = 0;
+ private Handler mRefreshUiHandler = new Handler();
+ private Runnable mRefreshUiRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mLastUiRefresh = SystemClock.elapsedRealtime();
+ refreshUiReal();
+ }
+ };
+
+
+ protected void refreshUi() {
+ final long diff = SystemClock.elapsedRealtime() - mLastUiRefresh;
+ if (diff > Config.REFRESH_UI_INTERVAL) {
+ mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable);
+ runOnUiThread(mRefreshUiRunnable);
+ } else {
+ final long next = Config.REFRESH_UI_INTERVAL - diff;
+ mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable);
+ mRefreshUiHandler.postDelayed(mRefreshUiRunnable,next);
+ }
+ }
+
+ protected void refreshUiReal() {
+
+ };
+
protected interface OnValueEdited {
public void onValueEdited(String value);
}
@@ -384,7 +414,20 @@ public abstract class XmppActivity extends Activity {
protected void inviteToConversation(Conversation conversation) {
Intent intent = new Intent(getApplicationContext(),
ChooseContactActivity.class);
+ List<String> contacts = new ArrayList<>();
+ if (conversation.getMode() == Conversation.MODE_MULTI) {
+ for (MucOptions.User user : conversation.getMucOptions().getUsers()) {
+ Jid jid = user.getJid();
+ if (jid != null) {
+ contacts.add(jid.toBareJid().toString());
+ }
+ }
+ } else {
+ contacts.add(conversation.getJid().toBareJid().toString());
+ }
+ intent.putExtra("filter_contacts", contacts.toArray(new String[contacts.size()]));
intent.putExtra("conversation", conversation.getUuid());
+ intent.putExtra("multiple", true);
startActivityForResult(intent, REQUEST_INVITE_TO_CONVERSATION);
}
@@ -627,22 +670,31 @@ public abstract class XmppActivity extends Activity {
if (requestCode == REQUEST_INVITE_TO_CONVERSATION
&& resultCode == RESULT_OK) {
try {
- Jid jid = Jid.fromString(data.getStringExtra("contact"));
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) {
- xmppConnectionService.invite(conversation, jid);
+ for (Jid jid : jids) {
+ xmppConnectionService.invite(conversation, jid);
+ }
} else {
- List<Jid> jids = new ArrayList<Jid>();
jids.add(conversation.getJid().toBareJid());
- jids.add(jid);
xmppConnectionService.createAdhocConference(conversation.getAccount(), jids, adhocCallback);
}
} catch (final InvalidJidException ignored) {
}
- }
+ }
}
private UiCallback<Conversation> adhocCallback = new UiCallback<Conversation>() {
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 e62aaf96..fac4cc91 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java
@@ -1,8 +1,13 @@
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;
+import android.os.AsyncTask;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
@@ -11,14 +16,15 @@ import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
+import java.lang.ref.WeakReference;
import java.util.List;
+import java.util.concurrent.RejectedExecutionException;
import de.tzur.conversations.Settings;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Downloadable;
-import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.ui.ConversationActivity;
@@ -114,7 +120,9 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
Pair<String,Boolean> preview = UIHelper.getMessagePreview(activity,message);
mLastMessage.setVisibility(View.VISIBLE);
imagePreview.setVisibility(View.GONE);
- mLastMessage.setText(preview.first);
+ boolean parseEmoticons = Settings.PARSE_EMOTICONS;
+ CharSequence msgText = parseEmoticons ? UIHelper.transformAsciiEmoticons(getContext(), preview.first) : preview.first;
+ mLastMessage.setText(msgText);
if (preview.second) {
if (conversation.isRead()) {
mLastMessage.setTypeface(null, Typeface.ITALIC);
@@ -132,8 +140,91 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
mTimestamp.setText(UIHelper.readableTimeDifference(activity,conversation.getLatestMessage().getTimeSent()));
ImageView profilePicture = (ImageView) view.findViewById(R.id.conversation_image);
- profilePicture.setImageBitmap(activity.avatarService().get(conversation, activity.getPixel(56)));
+ loadAvatar(conversation,profilePicture);
return view;
}
-}
+
+ class BitmapWorkerTask extends AsyncTask<Conversation, Void, Bitmap> {
+ private final WeakReference<ImageView> imageViewReference;
+ private Conversation conversation = null;
+
+ public BitmapWorkerTask(ImageView imageView) {
+ imageViewReference = new WeakReference<>(imageView);
+ }
+
+ @Override
+ protected Bitmap doInBackground(Conversation... params) {
+ return activity.avatarService().get(params[0], activity.getPixel(56));
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (bitmap != null) {
+ final ImageView imageView = imageViewReference.get();
+ if (imageView != null) {
+ imageView.setImageBitmap(bitmap);
+ imageView.setBackgroundColor(0x00000000);
+ }
+ }
+ }
+ }
+
+ public void loadAvatar(Conversation conversation, ImageView imageView) {
+ if (cancelPotentialWork(conversation, imageView)) {
+ final Bitmap bm = activity.avatarService().get(conversation, activity.getPixel(56), true);
+ if (bm != null) {
+ imageView.setImageBitmap(bm);
+ imageView.setBackgroundColor(0x00000000);
+ } else {
+ imageView.setBackgroundColor(UIHelper.getColorForName(conversation.getName()));
+ imageView.setImageDrawable(null);
+ final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
+ final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task);
+ imageView.setImageDrawable(asyncDrawable);
+ try {
+ task.execute(conversation);
+ } catch (final RejectedExecutionException ignored) {
+ }
+ }
+ }
+ }
+
+ public static boolean cancelPotentialWork(Conversation conversation, ImageView imageView) {
+ final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+ if (bitmapWorkerTask != null) {
+ final Conversation oldConversation = bitmapWorkerTask.conversation;
+ if (oldConversation == null || conversation != oldConversation) {
+ bitmapWorkerTask.cancel(true);
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
+ if (imageView != null) {
+ final Drawable drawable = imageView.getDrawable();
+ if (drawable instanceof AsyncDrawable) {
+ final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
+ return asyncDrawable.getBitmapWorkerTask();
+ }
+ }
+ return null;
+ }
+
+ static class AsyncDrawable extends BitmapDrawable {
+ private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
+
+ public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
+ super(res, bitmap);
+ bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
+ }
+
+ public BitmapWorkerTask getBitmapWorkerTask() {
+ return bitmapWorkerTaskReference.get();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
index 91fb021c..7b20b55f 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
@@ -1,17 +1,23 @@
package eu.siacs.conversations.ui.adapter;
+import java.lang.ref.WeakReference;
import java.util.List;
+import java.util.concurrent.RejectedExecutionException;
-import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.ListItem;
import eu.siacs.conversations.ui.XmppActivity;
+import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.jid.Jid;
import android.content.Context;
import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
import android.preference.PreferenceManager;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -77,8 +83,7 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
tvJid.setText("");
}
tvName.setText(item.getDisplayName());
- picture.setImageBitmap(activity.avatarService().get(item,
- activity.getPixel(48)));
+ loadAvatar(item,picture);
return view;
}
@@ -90,4 +95,87 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
public void onTagClicked(String tag);
}
+ class BitmapWorkerTask extends AsyncTask<ListItem, Void, Bitmap> {
+ private final WeakReference<ImageView> imageViewReference;
+ private ListItem item = null;
+
+ public BitmapWorkerTask(ImageView imageView) {
+ imageViewReference = new WeakReference<>(imageView);
+ }
+
+ @Override
+ protected Bitmap doInBackground(ListItem... params) {
+ return activity.avatarService().get(params[0], activity.getPixel(48));
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (bitmap != null) {
+ final ImageView imageView = imageViewReference.get();
+ if (imageView != null) {
+ imageView.setImageBitmap(bitmap);
+ imageView.setBackgroundColor(0x00000000);
+ }
+ }
+ }
+ }
+
+ public void loadAvatar(ListItem item, ImageView imageView) {
+ if (cancelPotentialWork(item, imageView)) {
+ final Bitmap bm = activity.avatarService().get(item,activity.getPixel(48),true);
+ if (bm != null) {
+ imageView.setImageBitmap(bm);
+ imageView.setBackgroundColor(0x00000000);
+ } else {
+ imageView.setBackgroundColor(UIHelper.getColorForName(item.getDisplayName()));
+ imageView.setImageDrawable(null);
+ final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
+ final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task);
+ imageView.setImageDrawable(asyncDrawable);
+ try {
+ task.execute(item);
+ } catch (final RejectedExecutionException ignored) {
+ }
+ }
+ }
+ }
+
+ public static boolean cancelPotentialWork(ListItem item, ImageView imageView) {
+ final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+ if (bitmapWorkerTask != null) {
+ final ListItem oldItem = bitmapWorkerTask.item;
+ if (oldItem == null || item != oldItem) {
+ bitmapWorkerTask.cancel(true);
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
+ if (imageView != null) {
+ final Drawable drawable = imageView.getDrawable();
+ if (drawable instanceof AsyncDrawable) {
+ final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
+ return asyncDrawable.getBitmapWorkerTask();
+ }
+ }
+ return null;
+ }
+
+ static class AsyncDrawable extends BitmapDrawable {
+ private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
+
+ public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
+ super(res, bitmap);
+ bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
+ }
+
+ public BitmapWorkerTask getBitmapWorkerTask() {
+ return bitmapWorkerTaskReference.get();
+ }
+ }
+
}
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 7fa05050..c98dbdd0 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
@@ -36,6 +36,7 @@ import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.Message.ImageParams;
import eu.siacs.conversations.ui.ConversationActivity;
+import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.utils.UIHelper;
public class MessageAdapter extends ArrayAdapter<Message> {
@@ -225,17 +226,17 @@ public class MessageAdapter extends ArrayAdapter<Message> {
final String formattedBody = message.getMergedBody().replaceAll("^" + Message.ME_COMMAND,
nick + " ");
if (message.getType() != Message.TYPE_PRIVATE) {
- boolean parseEmoticons = Settings.PARSE_EMOTICONS;
- viewHolder.messageBody.setText(parseEmoticons ? UIHelper
- .transformAsciiEmoticons(getContext(), message.getMergedBody())
- : message.getMergedBody());
+
if (message.hasMeCommand()) {
final Spannable span = new SpannableString(formattedBody);
span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
viewHolder.messageBody.setText(span);
} else {
- viewHolder.messageBody.setText(message.getMergedBody());
+ boolean parseEmoticons = Settings.PARSE_EMOTICONS;
+ viewHolder.messageBody.setText(parseEmoticons ? UIHelper
+ .transformAsciiEmoticons(getContext(), message.getMergedBody())
+ : message.getMergedBody());
}
} else {
String privateMarker;
@@ -305,6 +306,21 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.download_button.setOnLongClickListener(openContextMenu);
}
+ private void displayLocationMessage(ViewHolder viewHolder, final Message message) {
+ viewHolder.image.setVisibility(View.GONE);
+ viewHolder.messageBody.setVisibility(View.GONE);
+ viewHolder.download_button.setVisibility(View.VISIBLE);
+ viewHolder.download_button.setText(R.string.show_location);
+ viewHolder.download_button.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ showLocation(message);
+ }
+ });
+ viewHolder.download_button.setOnLongClickListener(openContextMenu);
+ }
+
private void displayImageMessage(ViewHolder viewHolder,
final Message message) {
if (viewHolder.download_button != null) {
@@ -416,9 +432,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
.avatarService().get(conversation.getContact(),
activity.getPixel(32)));
viewHolder.contact_picture.setAlpha(0.5f);
- viewHolder.status_message.setText(
- activity.getString(R.string.contact_has_read_up_to_this_point, conversation.getName()));
-
+ viewHolder.status_message.setText(message.getBody());
}
return view;
} else if (type == NULL) {
@@ -517,7 +531,11 @@ public class MessageAdapter extends ArrayAdapter<Message> {
} else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
displayDecryptionFailed(viewHolder);
} else {
- displayTextMessage(viewHolder, message);
+ if (GeoHelper.isGeoUri(message.getBody())) {
+ displayLocationMessage(viewHolder,message);
+ } else {
+ displayTextMessage(viewHolder, message);
+ }
}
displayStatus(viewHolder, message);
@@ -552,6 +570,16 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
}
+ public void showLocation(Message message) {
+ for(Intent intent : GeoHelper.createGeoIntentsFromMessage(message)) {
+ if (intent.resolveActivity(getContext().getPackageManager()) != null) {
+ getContext().startActivity(intent);
+ return;
+ }
+ }
+ Toast.makeText(activity,R.string.no_application_found_to_display_location,Toast.LENGTH_SHORT).show();
+ }
+
public interface OnContactPictureClicked {
public void onContactPictureClicked(Message message);
}
diff --git a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java
index fc21acbc..eb7e2c3c 100644
--- a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java
@@ -4,7 +4,9 @@ import java.security.SecureRandom;
import java.text.Normalizer;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Iterator;
import java.util.LinkedHashSet;
+import java.util.List;
import eu.siacs.conversations.Config;
@@ -97,10 +99,26 @@ public final class CryptoHelper {
return builder.toString();
}
- public static String[] getSupportedCipherSuites(final String[] platformSupportedCipherSuites) {
- //final Collection<String> cipherSuites = new LinkedHashSet<>(Arrays.asList(Config.ENABLED_CIPHERS));
- //cipherSuites.retainAll(Arrays.asList(platformSupportedCipherSuites));
- //return cipherSuites.toArray(new String[cipherSuites.size()]);
- return platformSupportedCipherSuites;
+ public static String[] getOrderedCipherSuites(final String[] platformSupportedCipherSuites) {
+ final Collection<String> cipherSuites = new LinkedHashSet<>(Arrays.asList(Config.ENABLED_CIPHERS));
+ final List<String> platformCiphers = Arrays.asList(platformSupportedCipherSuites);
+ cipherSuites.retainAll(platformCiphers);
+ cipherSuites.addAll(platformCiphers);
+ filterWeakCipherSuites(cipherSuites);
+ return cipherSuites.toArray(new String[cipherSuites.size()]);
+ }
+
+ private static void filterWeakCipherSuites(final Collection<String> cipherSuites) {
+ final Iterator<String> it = cipherSuites.iterator();
+ while (it.hasNext()) {
+ String cipherName = it.next();
+ // remove all ciphers with no or very weak encryption or no authentication
+ for (String weakCipherPattern : Config.WEAK_CIPHER_PATTERNS) {
+ if (cipherName.contains(weakCipherPattern)) {
+ it.remove();
+ break;
+ }
+ }
+ }
}
}
diff --git a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
index a09b4d0f..bcb2ca44 100644
--- a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
@@ -140,18 +140,13 @@ public class DNSHelper {
}
ArrayList<Bundle> values = new ArrayList<>();
for (SRV srv : result) {
- boolean added = false;
if (ips6.containsKey(srv.getName())) {
values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips6));
- added = true;
}
if (ips4.containsKey(srv.getName())) {
values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips4));
- added = true;
- }
- if (!added) {
- values.add(createNamePortBundle(srv.getName(),srv.getPort(),null));
}
+ values.add(createNamePortBundle(srv.getName(),srv.getPort(),null));
}
bundle.putParcelableArrayList("values", values);
} catch (SocketTimeoutException e) {
diff --git a/src/main/java/eu/siacs/conversations/utils/GeoHelper.java b/src/main/java/eu/siacs/conversations/utils/GeoHelper.java
new file mode 100644
index 00000000..f7dda936
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/utils/GeoHelper.java
@@ -0,0 +1,71 @@
+package eu.siacs.conversations.utils;
+
+import android.content.Intent;
+import android.net.Uri;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.Message;
+
+public class GeoHelper {
+ private static Pattern GEO_URI = Pattern.compile("geo:([\\-0-9.]+),([\\-0-9.]+)(?:,([\\-0-9.]+))?(?:\\?(.*))?", Pattern.CASE_INSENSITIVE);
+
+ public static boolean isGeoUri(String body) {
+ return body != null && GEO_URI.matcher(body).matches();
+ }
+
+ public static ArrayList<Intent> createGeoIntentsFromMessage(Message message) {
+ final ArrayList<Intent> intents = new ArrayList();
+ Matcher matcher = GEO_URI.matcher(message.getBody());
+ if (!matcher.matches()) {
+ return intents;
+ }
+ double latitude;
+ double longitude;
+ try {
+ latitude = Double.parseDouble(matcher.group(1));
+ if (latitude > 90.0 || latitude < -90.0) {
+ return intents;
+ }
+ longitude = Double.parseDouble(matcher.group(2));
+ if (longitude > 180.0 || longitude < -180.0) {
+ return intents;
+ }
+ } catch (NumberFormatException nfe) {
+ return intents;
+ }
+ final Conversation conversation = message.getConversation();
+ String label;
+ if (conversation.getMode() == Conversation.MODE_SINGLE && message.getStatus() == Message.STATUS_RECEIVED) {
+ try {
+ label = "(" + URLEncoder.encode(message.getConversation().getName(), "UTF-8") + ")";
+ } catch (UnsupportedEncodingException e) {
+ label = "";
+ }
+ } else {
+ label = "";
+ }
+
+ 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());
+ }
+ intents.add(locationPluginIntent);
+
+ Intent geoIntent = new Intent(Intent.ACTION_VIEW);
+ geoIntent.setData(Uri.parse("geo:" + String.valueOf(latitude) + "," + String.valueOf(longitude) + "?q=" + String.valueOf(latitude) + "," + String.valueOf(longitude) + label));
+ intents.add(geoIntent);
+
+ Intent httpIntent = new Intent(Intent.ACTION_VIEW);
+ httpIntent.setData(Uri.parse("https://maps.google.com/maps?q=loc:"+String.valueOf(latitude) + "," + String.valueOf(longitude) +label));
+ intents.add(httpIntent);
+ return intents;
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java b/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java
index 9a5cbaaf..99e8ebb8 100644
--- a/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java
@@ -8,6 +8,7 @@ import android.content.Context;
import android.content.CursorLoader;
import android.content.Loader;
import android.content.Loader.OnLoadCompleteListener;
+import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
@@ -91,4 +92,17 @@ public class PhoneHelper {
}
}
}
+
+ public static String getVersionName(Context context) {
+ final String packageName = context == null ? null : context.getPackageName();
+ if (packageName != null) {
+ try {
+ return context.getPackageManager().getPackageInfo(packageName, 0).versionName;
+ } catch (final PackageManager.NameNotFoundException e) {
+ return "unknown";
+ }
+ } else {
+ return "unknown";
+ }
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java
index 333f6e27..0ddf606f 100644
--- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java
@@ -295,8 +295,14 @@ public class UIHelper {
if (message.getBody().startsWith(Message.ME_COMMAND)) {
return new Pair<>(message.getBody().replaceAll("^" + Message.ME_COMMAND,
UIHelper.getMessageDisplayName(message) + " "), false);
- } else {
- return new Pair<>(message.getBody(), false);
+ } else if (GeoHelper.isGeoUri(message.getBody())) {
+ if (message.getStatus() == Message.STATUS_RECEIVED) {
+ return new Pair<>(context.getString(R.string.received_location),true);
+ } else {
+ return new Pair<>(context.getString(R.string.location), true);
+ }
+ } else{
+ return new Pair<>(message.getBody().trim(), false);
}
}
}
diff --git a/src/main/java/eu/siacs/conversations/utils/XmppUri.java b/src/main/java/eu/siacs/conversations/utils/XmppUri.java
index 0f1b18c3..92c0241e 100644
--- a/src/main/java/eu/siacs/conversations/utils/XmppUri.java
+++ b/src/main/java/eu/siacs/conversations/utils/XmppUri.java
@@ -32,7 +32,7 @@ public class XmppUri {
protected void parse(Uri uri) {
String scheme = uri.getScheme();
- if ("xmpp".equals(scheme)) {
+ if ("xmpp".equalsIgnoreCase(scheme)) {
// sample: xmpp:jid@foo.com
muc = "join".equalsIgnoreCase(uri.getQuery());
if (uri.getAuthority() != null) {
@@ -41,7 +41,7 @@ public class XmppUri {
jid = uri.getSchemeSpecificPart().split("\\?")[0];
}
fingerprint = parseFingerprint(uri.getQuery());
- } else if ("imto".equals(scheme)) {
+ } else if ("imto".equalsIgnoreCase(scheme)) {
// sample: imto://xmpp/jid@foo.com
try {
jid = URLDecoder.decode(uri.getEncodedPath(), "UTF-8").split("/")[1];
@@ -73,7 +73,7 @@ public class XmppUri {
public Jid getJid() {
try {
- return this.jid == null ? null :Jid.fromString(this.jid);
+ return this.jid == null ? null :Jid.fromString(this.jid.toLowerCase());
} catch (InvalidJidException e) {
return null;
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
index 19e271b2..48dc2150 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
@@ -242,6 +242,13 @@ public class XmppConnection implements Runnable {
@Override
public void run() {
+ try {
+ if (socket != null) {
+ socket.close();
+ }
+ } catch (final IOException ignored) {
+
+ }
connect();
}
@@ -515,8 +522,9 @@ public class XmppConnection implements Runnable {
sslSocket.setEnabledProtocols(supportProtocols);
- final String[] cipherSuites = CryptoHelper.getSupportedCipherSuites(
+ final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites(
sslSocket.getSupportedCipherSuites());
+ //Log.d(Config.LOGTAG, "Using ciphers: " + Arrays.toString(cipherSuites));
if (cipherSuites.length > 0) {
sslSocket.setEnabledCipherSuites(cipherSuites);
}
@@ -658,6 +666,12 @@ public class XmppConnection implements Runnable {
}
private void sendBindRequest() {
+ while(!mXmppConnectionService.areMessagesInitialized()) {
+ try {
+ Thread.sleep(500);
+ } catch (final InterruptedException ignored) {
+ }
+ }
final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
iq.addChild("bind", "urn:ietf:params:xml:ns:xmpp-bind")
.addChild("resource").setContent(account.getResource());
@@ -673,28 +687,11 @@ public class XmppConnection implements Runnable {
} catch (final InvalidJidException e) {
// TODO: Handle the case where an external JID is technically invalid?
}
- if (streamFeatures.hasChild("sm", "urn:xmpp:sm:3")) {
- smVersion = 3;
- final EnablePacket enable = new EnablePacket(smVersion);
- tagWriter.writeStanzaAsync(enable);
- stanzasSent = 0;
- messageReceipts.clear();
- } else if (streamFeatures.hasChild("sm", "urn:xmpp:sm:2")) {
- smVersion = 2;
- final EnablePacket enable = new EnablePacket(smVersion);
- tagWriter.writeStanzaAsync(enable);
- stanzasSent = 0;
- messageReceipts.clear();
- }
- features.carbonsEnabled = false;
- features.blockListRequested = false;
- disco.clear();
- sendServiceDiscoveryInfo(account.getServer());
- sendServiceDiscoveryItems(account.getServer());
- if (bindListener != null) {
- bindListener.onBind(account);
+ if (streamFeatures.hasChild("session")) {
+ sendStartSession();
+ } else {
+ sendPostBindInitialization();
}
- sendInitialPing();
} else {
disconnect(true);
}
@@ -703,12 +700,45 @@ public class XmppConnection implements Runnable {
}
}
});
- if (this.streamFeatures.hasChild("session")) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": sending deprecated session");
- final IqPacket startSession = new IqPacket(IqPacket.TYPE.SET);
- startSession.addChild("session","urn:ietf:params:xml:ns:xmpp-session");
- this.sendUnmodifiedIqPacket(startSession, null);
+ }
+
+ private void sendStartSession() {
+ final IqPacket startSession = new IqPacket(IqPacket.TYPE.SET);
+ startSession.addChild("session","urn:ietf:params:xml:ns:xmpp-session");
+ this.sendUnmodifiedIqPacket(startSession, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
+ sendPostBindInitialization();
+ } else {
+ disconnect(true);
+ }
+ }
+ });
+ }
+
+ private void sendPostBindInitialization() {
+ smVersion = 0;
+ if (streamFeatures.hasChild("sm", "urn:xmpp:sm:3")) {
+ smVersion = 3;
+ } else if (streamFeatures.hasChild("sm", "urn:xmpp:sm:2")) {
+ smVersion = 2;
+ }
+ if (smVersion != 0) {
+ final EnablePacket enable = new EnablePacket(smVersion);
+ tagWriter.writeStanzaAsync(enable);
+ stanzasSent = 0;
+ messageReceipts.clear();
}
+ features.carbonsEnabled = false;
+ features.blockListRequested = false;
+ disco.clear();
+ sendServiceDiscoveryInfo(account.getServer());
+ sendServiceDiscoveryItems(account.getServer());
+ if (bindListener != null) {
+ bindListener.onBind(account);
+ }
+ sendInitialPing();
}
private void sendServiceDiscoveryInfo(final Jid server) {
@@ -1027,6 +1057,11 @@ public class XmppConnection implements Runnable {
this.sendPacket(new InactivePacket());
}
+ public void resetAttemptCount() {
+ this.attempt = 0;
+ this.lastConnect = 0;
+ }
+
public class Features {
XmppConnection connection;
private boolean carbonsEnabled = false;
diff --git a/src/main/java/eu/siacs/conversations/xmpp/chatstate/ChatState.java b/src/main/java/eu/siacs/conversations/xmpp/chatstate/ChatState.java
new file mode 100644
index 00000000..f85efbdb
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/xmpp/chatstate/ChatState.java
@@ -0,0 +1,32 @@
+package eu.siacs.conversations.xmpp.chatstate;
+
+import eu.siacs.conversations.xml.Element;
+
+public enum ChatState {
+
+ ACTIVE, INACTIVE, GONE, COMPOSING, PAUSED, mIncomingChatState;
+
+ public static ChatState parse(Element element) {
+ final String NAMESPACE = "http://jabber.org/protocol/chatstates";
+ if (element.hasChild("active",NAMESPACE)) {
+ return ACTIVE;
+ } else if (element.hasChild("inactive",NAMESPACE)) {
+ return INACTIVE;
+ } else if (element.hasChild("composing",NAMESPACE)) {
+ return COMPOSING;
+ } else if (element.hasChild("gone",NAMESPACE)) {
+ return GONE;
+ } else if (element.hasChild("paused",NAMESPACE)) {
+ return PAUSED;
+ } else {
+ return null;
+ }
+ }
+
+ public static Element toElement(ChatState state) {
+ final String NAMESPACE = "http://jabber.org/protocol/chatstates";
+ final Element element = new Element(state.toString().toLowerCase());
+ element.setAttribute("xmlns",NAMESPACE);
+ return element;
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jid/InvalidJidException.java b/src/main/java/eu/siacs/conversations/xmpp/jid/InvalidJidException.java
index f1855263..164e8849 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jid/InvalidJidException.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jid/InvalidJidException.java
@@ -8,6 +8,7 @@ public class InvalidJidException extends Exception {
public final static String INVALID_PART_LENGTH = "JID part must be between 0 and 1023 characters";
public final static String INVALID_CHARACTER = "JID contains an invalid character";
public final static String STRINGPREP_FAIL = "The STRINGPREP operation has failed for the given JID";
+ public final static String IS_NULL = "JID can not be NULL";
/**
* Constructs a new {@code Exception} that includes the current stack trace.
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 a35ea37c..295e067a 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java
@@ -1,9 +1,12 @@
package eu.siacs.conversations.xmpp.jid;
+import android.util.LruCache;
+
import net.java.otr4j.session.SessionID;
import java.net.IDN;
+import eu.siacs.conversations.Config;
import gnu.inet.encoding.Stringprep;
import gnu.inet.encoding.StringprepException;
@@ -12,6 +15,8 @@ import gnu.inet.encoding.StringprepException;
*/
public final class Jid {
+ private static LruCache<String,Jid> cache = new LruCache<>(1024);
+
private final String localpart;
private final String domainpart;
private final String resourcepart;
@@ -41,7 +46,11 @@ public final class Jid {
}
public static Jid fromString(final String jid) throws InvalidJidException {
- return new Jid(jid);
+ return Jid.fromString(jid, false);
+ }
+
+ public static Jid fromString(final String jid, final boolean safe) throws InvalidJidException {
+ return new Jid(jid, safe);
}
public static Jid fromParts(final String localpart,
@@ -56,10 +65,21 @@ public final class Jid {
if (resourcepart != null && !resourcepart.isEmpty()) {
out = out + "/" + resourcepart;
}
- return new Jid(out);
+ return new Jid(out, false);
}
- private Jid(final String jid) throws InvalidJidException {
+ private Jid(final String jid, final boolean safe) throws InvalidJidException {
+ if (jid == null) throw new InvalidJidException(InvalidJidException.IS_NULL);
+
+ Jid fromCache = Jid.cache.get(jid);
+ if (fromCache != null) {
+ displayjid = fromCache.displayjid;
+ localpart = fromCache.localpart;
+ domainpart = fromCache.domainpart;
+ resourcepart = fromCache.resourcepart;
+ return;
+ }
+
// Hackish Android way to count the number of chars in a string... should work everywhere.
final int atCount = jid.length() - jid.replace("@", "").length();
final int slashCount = jid.length() - jid.replace("/", "").length();
@@ -88,7 +108,7 @@ public final class Jid {
} else {
final String lp = jid.substring(0, atLoc);
try {
- localpart = Stringprep.nodeprep(lp);
+ localpart = Config.DISABLE_STRING_PREP || safe ? lp : Stringprep.nodeprep(lp);
} catch (final StringprepException e) {
throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e);
}
@@ -103,7 +123,7 @@ public final class Jid {
if (slashCount > 0) {
final String rp = jid.substring(slashLoc + 1, jid.length());
try {
- resourcepart = Stringprep.resourceprep(rp);
+ resourcepart = Config.DISABLE_STRING_PREP || safe ? rp : Stringprep.resourceprep(rp);
} catch (final StringprepException e) {
throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e);
}
@@ -139,6 +159,8 @@ public final class Jid {
throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH);
}
+ Jid.cache.put(jid, this);
+
this.displayjid = finaljid;
}
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 4e7b532b..93aaa68c 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java
@@ -26,7 +26,7 @@ public class MessagePacket extends AbstractStanza {
this.children.remove(findChild("body"));
Element body = new Element("body");
body.setContent(text);
- this.children.add(body);
+ this.children.add(0, body);
}
public void setType(int type) {
@@ -39,6 +39,9 @@ public class MessagePacket extends AbstractStanza {
break;
case TYPE_NORMAL:
break;
+ case TYPE_ERROR:
+ this.setAttribute("type","error");
+ break;
default:
this.setAttribute("type", "chat");
break;