aboutsummaryrefslogtreecommitdiffstats
path: root/src/de/gultsch
diff options
context:
space:
mode:
authorDaniel Gultsch <daniel.gultsch@rwth-aachen.de>2014-02-13 23:40:08 +0100
committerDaniel Gultsch <daniel.gultsch@rwth-aachen.de>2014-02-13 23:40:08 +0100
commit42c4c1789a75c87b67c38ef9ca3f57ddd10f0548 (patch)
treec5bf69cb73c3e4c3be0439c175774561f8b0a5e7 /src/de/gultsch
parente63109215e9dda9152f0bc92bf230b652413a677 (diff)
basic otr support
Diffstat (limited to 'src/de/gultsch')
-rw-r--r--src/de/gultsch/chat/crypto/OtrEngine.java229
-rw-r--r--src/de/gultsch/chat/entities/Account.java53
-rw-r--r--src/de/gultsch/chat/entities/Conversation.java54
-rw-r--r--src/de/gultsch/chat/persistance/DatabaseBackend.java2
-rw-r--r--src/de/gultsch/chat/services/XmppConnectionService.java305
-rw-r--r--src/de/gultsch/chat/ui/ConversationActivity.java18
-rw-r--r--src/de/gultsch/chat/ui/ConversationFragment.java35
-rw-r--r--src/de/gultsch/chat/utils/UIHelper.java2
-rw-r--r--src/de/gultsch/chat/xml/TagWriter.java5
-rw-r--r--src/de/gultsch/chat/xmpp/MessagePacket.java1
-rw-r--r--src/de/gultsch/chat/xmpp/XmppConnection.java3
11 files changed, 602 insertions, 105 deletions
diff --git a/src/de/gultsch/chat/crypto/OtrEngine.java b/src/de/gultsch/chat/crypto/OtrEngine.java
new file mode 100644
index 00000000..d994f0f9
--- /dev/null
+++ b/src/de/gultsch/chat/crypto/OtrEngine.java
@@ -0,0 +1,229 @@
+package de.gultsch.chat.crypto;
+
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.DSAPrivateKeySpec;
+import java.security.spec.DSAPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.content.Context;
+import android.util.Log;
+
+import de.gultsch.chat.entities.Account;
+import de.gultsch.chat.persistance.DatabaseBackend;
+import de.gultsch.chat.xml.Element;
+import de.gultsch.chat.xmpp.MessagePacket;
+
+import net.java.otr4j.OtrEngineHost;
+import net.java.otr4j.OtrException;
+import net.java.otr4j.OtrPolicy;
+import net.java.otr4j.OtrPolicyImpl;
+import net.java.otr4j.session.InstanceTag;
+import net.java.otr4j.session.SessionID;
+
+public class OtrEngine implements OtrEngineHost {
+
+ private static final String LOGTAG = "xmppService";
+
+ private Account account;
+ private OtrPolicy otrPolicy;
+ private KeyPair keyPair;
+ private Context context;
+
+ public OtrEngine(Context context, Account account) {
+ this.account = account;
+ this.otrPolicy = new OtrPolicyImpl();
+ this.otrPolicy.setAllowV1(false);
+ this.otrPolicy.setAllowV2(true);
+ this.otrPolicy.setAllowV3(true);
+ this.keyPair = loadKey(account.getKeys());
+ }
+
+ private KeyPair loadKey(JSONObject keys) {
+ if (keys == null) {
+ return null;
+ }
+ try {
+ BigInteger x = new BigInteger(keys.getString("otr_x"),16);
+ BigInteger y = new BigInteger(keys.getString("otr_y"),16);
+ BigInteger p = new BigInteger(keys.getString("otr_p"),16);
+ BigInteger q = new BigInteger(keys.getString("otr_q"),16);
+ BigInteger g = new BigInteger(keys.getString("otr_g"),16);
+ KeyFactory keyFactory = KeyFactory.getInstance("DSA");
+ DSAPublicKeySpec pubKeySpec = new DSAPublicKeySpec(y, p, q, g);
+ DSAPrivateKeySpec privateKeySpec = new DSAPrivateKeySpec(x, p, q, g);
+ PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);
+ PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
+ return new KeyPair(publicKey, privateKey);
+ } catch (JSONException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (NoSuchAlgorithmException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (InvalidKeySpecException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ private void saveKey() {
+ PublicKey publicKey = keyPair.getPublic();
+ PrivateKey privateKey = keyPair.getPrivate();
+ KeyFactory keyFactory;
+ try {
+ keyFactory = KeyFactory.getInstance("DSA");
+ DSAPrivateKeySpec privateKeySpec = keyFactory.getKeySpec(privateKey, DSAPrivateKeySpec.class);
+ DSAPublicKeySpec publicKeySpec = keyFactory.getKeySpec(publicKey, DSAPublicKeySpec.class);
+ this.account.setKey("otr_x",privateKeySpec.getX().toString(16));
+ this.account.setKey("otr_g",privateKeySpec.getG().toString(16));
+ this.account.setKey("otr_p",privateKeySpec.getP().toString(16));
+ this.account.setKey("otr_q",privateKeySpec.getQ().toString(16));
+ this.account.setKey("otr_y",publicKeySpec.getY().toString(16));
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ } catch (InvalidKeySpecException e) {
+ e.printStackTrace();
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ @Override
+ public void askForSecret(SessionID arg0, InstanceTag arg1, String arg2) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void finishedSessionMessage(SessionID arg0, String arg1)
+ throws OtrException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public String getFallbackMessage(SessionID arg0) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public byte[] getLocalFingerprintRaw(SessionID arg0) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public KeyPair getLocalKeyPair(SessionID arg0) throws OtrException {
+ if (this.keyPair==null) {
+ KeyPairGenerator kg;
+ try {
+ kg = KeyPairGenerator.getInstance("DSA");
+ this.keyPair = kg.genKeyPair();
+ this.saveKey();
+ DatabaseBackend.getInstance(context).updateAccount(account);
+ } catch (NoSuchAlgorithmException e) {
+ Log.d(LOGTAG,"error generating key pair "+e.getMessage());
+ }
+ }
+ return this.keyPair;
+ }
+
+ @Override
+ public String getReplyForUnreadableMessage(SessionID arg0) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public OtrPolicy getSessionPolicy(SessionID arg0) {
+ return otrPolicy;
+ }
+
+ @Override
+ public void injectMessage(SessionID session, String body) throws OtrException {
+ MessagePacket packet = new MessagePacket();
+ packet.setFrom(account.getFullJid()); //sender
+ packet.setTo(session.getAccountID()+"/"+session.getUserID()); //reciepient
+ packet.setBody(body);
+ Element privateTag = new Element("private");
+ privateTag.setAttribute("xmlns","urn:xmpp:carbons:2");
+ packet.addChild(privateTag);
+ account.getXmppConnection().sendMessagePacket(packet);
+ }
+
+ @Override
+ public void messageFromAnotherInstanceReceived(SessionID arg0) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void multipleInstancesDetected(SessionID arg0) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void requireEncryptedMessage(SessionID arg0, String arg1)
+ throws OtrException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void showError(SessionID arg0, String arg1) throws OtrException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void smpAborted(SessionID arg0) throws OtrException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void smpError(SessionID arg0, int arg1, boolean arg2)
+ throws OtrException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void unencryptedMessageReceived(SessionID arg0, String arg1)
+ throws OtrException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void unreadableMessageReceived(SessionID arg0) throws OtrException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void unverify(SessionID arg0, String arg1) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void verify(SessionID arg0, String arg1, boolean arg2) {
+ // TODO Auto-generated method stub
+
+ }
+
+}
diff --git a/src/de/gultsch/chat/entities/Account.java b/src/de/gultsch/chat/entities/Account.java
index 0f7bfd52..7f14b090 100644
--- a/src/de/gultsch/chat/entities/Account.java
+++ b/src/de/gultsch/chat/entities/Account.java
@@ -1,7 +1,14 @@
package de.gultsch.chat.entities;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import de.gultsch.chat.crypto.OtrEngine;
+import de.gultsch.chat.xmpp.XmppConnection;
import android.content.ContentValues;
+import android.content.Context;
import android.database.Cursor;
+import android.util.JsonReader;
import android.util.Log;
public class Account extends AbstractEntity{
@@ -15,6 +22,7 @@ public class Account extends AbstractEntity{
public static final String PASSWORD = "password";
public static final String OPTIONS = "options";
public static final String ROSTERVERSION = "rosterversion";
+ public static final String KEYS = "keys";
public static final int OPTION_USETLS = 0;
public static final int OPTION_DISABLED = 1;
@@ -34,23 +42,32 @@ public class Account extends AbstractEntity{
protected String rosterVersion;
protected String resource;
protected int status = 0;
+ protected JSONObject keys = new JSONObject();
protected boolean online = false;
+ transient OtrEngine otrEngine = null;
+ transient XmppConnection xmppConnection = null;
+
public Account() {
this.uuid = "0";
}
public Account(String username, String server, String password) {
- this(java.util.UUID.randomUUID().toString(),username,server,password,0,null);
+ this(java.util.UUID.randomUUID().toString(),username,server,password,0,null,"");
}
- public Account(String uuid, String username, String server,String password, int options, String rosterVersion) {
+ public Account(String uuid, String username, String server,String password, int options, String rosterVersion, String keys) {
this.uuid = uuid;
this.username = username;
this.server = server;
this.password = password;
this.options = options;
this.rosterVersion = rosterVersion;
+ try {
+ this.keys = new JSONObject(keys);
+ } catch (JSONException e) {
+
+ }
}
public boolean isOptionSet(int option) {
@@ -108,6 +125,14 @@ public class Account extends AbstractEntity{
public String getJid() {
return username+"@"+server;
}
+
+ public JSONObject getKeys() {
+ return keys;
+ }
+
+ public void setKey(String keyName, String keyValue) throws JSONException {
+ this.keys.put(keyName, keyValue);
+ }
@Override
public ContentValues getContentValues() {
@@ -117,6 +142,8 @@ public class Account extends AbstractEntity{
values.put(SERVER, server);
values.put(PASSWORD, password);
values.put(OPTIONS,options);
+ values.put(KEYS,this.keys.toString());
+ values.put(ROSTERVERSION,rosterVersion);
return values;
}
@@ -126,8 +153,28 @@ public class Account extends AbstractEntity{
cursor.getString(cursor.getColumnIndex(SERVER)),
cursor.getString(cursor.getColumnIndex(PASSWORD)),
cursor.getInt(cursor.getColumnIndex(OPTIONS)),
- cursor.getString(cursor.getColumnIndex(ROSTERVERSION))
+ cursor.getString(cursor.getColumnIndex(ROSTERVERSION)),
+ cursor.getString(cursor.getColumnIndex(KEYS))
);
}
+
+ public OtrEngine getOtrEngine(Context context) {
+ if (otrEngine==null) {
+ otrEngine = new OtrEngine(context,this);
+ }
+ return this.otrEngine;
+ }
+
+ public XmppConnection getXmppConnection() {
+ return this.xmppConnection;
+ }
+
+ public void setXmppConnection(XmppConnection connection) {
+ this.xmppConnection = connection;
+ }
+
+ public String getFullJid() {
+ return this.getJid()+"/"+this.resource;
+ }
}
diff --git a/src/de/gultsch/chat/entities/Conversation.java b/src/de/gultsch/chat/entities/Conversation.java
index fba5464a..7339aadb 100644
--- a/src/de/gultsch/chat/entities/Conversation.java
+++ b/src/de/gultsch/chat/entities/Conversation.java
@@ -3,7 +3,16 @@ package de.gultsch.chat.entities;
import java.util.ArrayList;
import java.util.List;
+import de.gultsch.chat.crypto.OtrEngine;
+import de.gultsch.chat.xmpp.XmppConnection;
+
+import net.java.otr4j.OtrException;
+import net.java.otr4j.session.SessionID;
+import net.java.otr4j.session.SessionImpl;
+import net.java.otr4j.session.SessionStatus;
+
import android.content.ContentValues;
+import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
@@ -40,6 +49,9 @@ public class Conversation extends AbstractEntity {
private transient List<Message> messages = null;
private transient Account account = null;
private transient Contact contact;
+
+ private transient SessionImpl otrSession;
+ private transient String foreignOtrPresence;
public Conversation(String name, Account account,
String contactJid, int mode) {
@@ -85,19 +97,13 @@ public class Conversation extends AbstractEntity {
}
}
- public String getLatestMessage() {
- if ((this.messages == null)||(this.messages.size()==0)) {
- return null;
- } else {
- return this.messages.get(this.messages.size() - 1).getBody();
- }
- }
-
- public long getLatestMessageDate() {
+ public Message getLatestMessage() {
if ((this.messages == null)||(this.messages.size()==0)) {
- return this.getCreated();
+ Message message = new Message(this,"",Message.ENCRYPTION_NONE);
+ message.setTime(0);
+ return message;
} else {
- return this.messages.get(this.messages.size() - 1).getTimeSent();
+ return this.messages.get(this.messages.size() - 1);
}
}
@@ -198,4 +204,30 @@ public class Conversation extends AbstractEntity {
public void setMode(int mode) {
this.mode = mode;
}
+
+ public void startOtrSession(Context context, String presence) {
+ Log.d("xmppService","starting otr session with "+presence);
+ SessionID sessionId = new SessionID(this.getContactJid(),presence,"xmpp");
+ this.otrSession = new SessionImpl(sessionId, getAccount().getOtrEngine(context));
+ }
+
+ public SessionImpl getOtrSession() {
+ return this.otrSession;
+ }
+
+ public void resetOtrSession() {
+ this.otrSession = null;
+ }
+
+ public void endOtrIfNeeded() throws OtrException {
+ if (this.otrSession!=null) {
+ if (this.otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) {
+ this.otrSession.endSession();
+ }
+ }
+ }
+
+ public boolean hasOtrSession() {
+ return (this.otrSession!=null);
+ }
}
diff --git a/src/de/gultsch/chat/persistance/DatabaseBackend.java b/src/de/gultsch/chat/persistance/DatabaseBackend.java
index 25a96f46..df919f72 100644
--- a/src/de/gultsch/chat/persistance/DatabaseBackend.java
+++ b/src/de/gultsch/chat/persistance/DatabaseBackend.java
@@ -35,7 +35,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ " TEXT PRIMARY KEY," + Account.USERNAME + " TEXT,"
+ Account.SERVER + " TEXT," + Account.PASSWORD + " TEXT,"
+ Account.ROSTERVERSION + " TEXT," + Account.OPTIONS
- + " NUMBER)");
+ + " NUMBER, "+Account.KEYS+" TEXT)");
db.execSQL("create table " + Conversation.TABLENAME + " ("
+ Conversation.UUID + " TEXT PRIMARY KEY, " + Conversation.NAME
+ " TEXT, " + Conversation.CONTACT + " TEXT, "
diff --git a/src/de/gultsch/chat/services/XmppConnectionService.java b/src/de/gultsch/chat/services/XmppConnectionService.java
index 567c851f..b1c4eca5 100644
--- a/src/de/gultsch/chat/services/XmppConnectionService.java
+++ b/src/de/gultsch/chat/services/XmppConnectionService.java
@@ -6,6 +6,12 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;
+import java.util.Set;
+
+import net.java.otr4j.OtrException;
+import net.java.otr4j.session.Session;
+import net.java.otr4j.session.SessionImpl;
+import net.java.otr4j.session.SessionStatus;
import de.gultsch.chat.entities.Account;
import de.gultsch.chat.entities.Contact;
@@ -50,8 +56,6 @@ public class XmppConnectionService extends Service {
private List<Account> accounts;
private List<Conversation> conversations = null;
- private Hashtable<Account, XmppConnection> connections = new Hashtable<Account, XmppConnection>();
-
private OnConversationListChangedListener convChangedListener = null;
private OnAccountListChangedListener accountChangedListener = null;
@@ -73,7 +77,9 @@ public class XmppConnectionService extends Service {
if ((packet.getType() == MessagePacket.TYPE_CHAT)
|| (packet.getType() == MessagePacket.TYPE_GROUPCHAT)) {
boolean notify = true;
+ boolean runOtrCheck = false;
int status = Message.STATUS_RECIEVED;
+ int encryption = Message.ENCRYPTION_NONE;
String body;
String fullJid;
if (!packet.hasChild("body")) {
@@ -106,6 +112,7 @@ public class XmppConnectionService extends Service {
} else {
fullJid = packet.getFrom();
body = packet.getBody();
+ runOtrCheck = true;
}
Conversation conversation = null;
String[] fromParts = fullJid.split("/");
@@ -124,9 +131,51 @@ public class XmppConnectionService extends Service {
}
} else {
counterPart = fullJid;
+ if ((runOtrCheck) && body.startsWith("?OTR")) {
+ if (!conversation.hasOtrSession()) {
+ conversation.startOtrSession(
+ getApplicationContext(), fromParts[1]);
+ }
+ try {
+ Session otrSession = conversation.getOtrSession();
+ SessionStatus before = otrSession
+ .getSessionStatus();
+ body = otrSession.transformReceiving(body);
+ SessionStatus after = otrSession.getSessionStatus();
+ if ((before != after)
+ && (after == SessionStatus.ENCRYPTED)) {
+ Log.d(LOGTAG, "otr session etablished");
+ List<Message> messages = conversation
+ .getMessages();
+ for (int i = 0; i < messages.size(); ++i) {
+ Message msg = messages.get(i);
+ if ((msg.getStatus() == Message.STATUS_UNSEND)
+ && (msg.getEncryption() == Message.ENCRYPTION_OTR)) {
+ MessagePacket outPacket = prepareMessagePacket(
+ account, msg, otrSession);
+ msg.setStatus(Message.STATUS_SEND);
+ databaseBackend.updateMessage(msg);
+ account.getXmppConnection()
+ .sendMessagePacket(outPacket);
+ }
+ }
+ if (convChangedListener!=null) {
+ convChangedListener.onConversationListChanged();
+ }
+ }
+ } catch (Exception e) {
+ Log.d(LOGTAG, "error receiving otr. resetting");
+ conversation.resetOtrSession();
+ return;
+ }
+ if (body == null) {
+ return;
+ }
+ encryption = Message.ENCRYPTION_OTR;
+ }
}
Message message = new Message(conversation, counterPart, body,
- Message.ENCRYPTION_NONE, status);
+ encryption, status);
if (packet.hasChild("delay")) {
try {
String stamp = packet.findChild("delay").getAttribute(
@@ -169,14 +218,14 @@ public class XmppConnectionService extends Service {
databaseBackend.clearPresences(account);
connectMultiModeConversations(account);
List<Conversation> conversations = getConversations();
- for(int i = 0; i < conversations.size(); ++i) {
- if (conversations.get(i).getAccount()==account) {
- sendUnsendMessages(conversations.get(i));
- }
- }
- if (convChangedListener!=null) {
- convChangedListener.onConversationListChanged();
- }
+ for (int i = 0; i < conversations.size(); ++i) {
+ if (conversations.get(i).getAccount() == account) {
+ sendUnsendMessages(conversations.get(i));
+ }
+ }
+ if (convChangedListener != null) {
+ convChangedListener.onConversationListChanged();
+ }
}
}
};
@@ -216,8 +265,19 @@ public class XmppConnectionService extends Service {
databaseBackend.updateContact(contact);
}
}
+ replaceContactInConversation(contact);
}
};
+
+ private void replaceContactInConversation(Contact contact) {
+ List<Conversation> conversations = getConversations();
+ for(int i = 0; i < conversations.size(); ++i) {
+ if (conversations.get(i).getContact().equals(contact)) {
+ conversations.get(i).setContact(contact);
+ break;
+ }
+ }
+ }
public class XmppConnectionBinder extends Binder {
public XmppConnectionService getService() {
@@ -228,13 +288,9 @@ public class XmppConnectionService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
for (Account account : accounts) {
- if (!connections.containsKey(account)) {
+ if (account.getXmppConnection() == null) {
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
- this.connections.put(account,
- this.createConnection(account));
- } else {
- Log.d(LOGTAG, account.getJid()
- + ": not starting because it's disabled");
+ account.setXmppConnection(this.createConnection(account));
}
}
}
@@ -250,6 +306,16 @@ public class XmppConnectionService extends Service {
ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
}
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ for (Account account : accounts) {
+ if (account.getXmppConnection() != null) {
+ disconnect(account);
+ }
+ }
+ }
+
public XmppConnection createConnection(Account account) {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
XmppConnection connection = new XmppConnection(account, pm);
@@ -261,40 +327,105 @@ public class XmppConnectionService extends Service {
return connection;
}
+ private void startOtrSession(Conversation conv) {
+ Set<String> presences = conv.getContact().getPresences()
+ .keySet();
+ if (presences.size() == 0) {
+ Log.d(LOGTAG, "counter part isnt online. cant use otr");
+ return;
+ } else if (presences.size() == 1) {
+ conv.startOtrSession(getApplicationContext(),
+ (String) presences.toArray()[0]);
+ try {
+ conv.getOtrSession().startSession();
+ } catch (OtrException e) {
+ Log.d(LOGTAG, "couldnt actually start");
+ }
+ } else {
+ String latestCounterpartPresence = null;
+ List<Message> messages = conv.getMessages();
+ for (int i = messages.size() - 1; i >= 0; --i) {
+ if (messages.get(i).getStatus() == Message.STATUS_RECIEVED) {
+ String[] parts = messages.get(i).getCounterpart()
+ .split("/");
+ if (parts.length == 2) {
+ latestCounterpartPresence = parts[1];
+ break;
+ }
+ }
+ }
+ if (presences.contains(latestCounterpartPresence)) {
+ conv.startOtrSession(getApplicationContext(),
+ latestCounterpartPresence);
+ try {
+ conv.getOtrSession().startSession();
+ } catch (OtrException e) {
+ // TODO Auto-generated catch block
+ Log.d(LOGTAG, "couldnt actually start");
+ }
+ } else {
+ Log.d(LOGTAG,
+ "could not decide where to send otr connection to");
+ }
+ }
+ }
+
public void sendMessage(Account account, Message message) {
-
+ Conversation conv = message.getConversation();
+ boolean saveInDb = false;
+ boolean addToConversation = false;
if (account.getStatus() == Account.STATUS_ONLINE) {
- MessagePacket packet = prepareMessagePacket(account, message);
- connections.get(account).sendMessagePacket(packet);
- if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
- message.setStatus(Message.STATUS_SEND);
+ MessagePacket packet;
+ if (message.getEncryption() == Message.ENCRYPTION_OTR) {
+ if (!conv.hasOtrSession()) {
+ //starting otr session. messages will be send later
+ startOtrSession(conv);
+ } else {
+ //otr session aleary exists, creating message packet accordingly
+ packet = prepareMessagePacket(account, message,
+ conv.getOtrSession());
+ account.getXmppConnection().sendMessagePacket(packet);
+ message.setStatus(Message.STATUS_SEND);
+ }
+ saveInDb = true;
+ addToConversation = true;
+ } else {
+ // don't encrypt
if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
- databaseBackend.createMessage(message);
- message.getConversation().getMessages().add(message);
- if (convChangedListener!=null) {
- convChangedListener.onConversationListChanged();
- }
+ message.setStatus(Message.STATUS_SEND);
+ saveInDb = true;
+ addToConversation = true;
}
+
+ packet = prepareMessagePacket(account, message, null);
+ account.getXmppConnection().sendMessagePacket(packet);
}
} else {
- message.getConversation().getMessages().add(message);
+ // account is offline
+ saveInDb = true;
+ addToConversation = true;
+
+ }
+ if (saveInDb) {
databaseBackend.createMessage(message);
- if (convChangedListener!=null) {
+ }
+ if (addToConversation) {
+ conv.getMessages().add(message);
+ if (convChangedListener != null) {
convChangedListener.onConversationListChanged();
}
}
-
+
}
private void sendUnsendMessages(Conversation conversation) {
for (int i = 0; i < conversation.getMessages().size(); ++i) {
if (conversation.getMessages().get(i).getStatus() == Message.STATUS_UNSEND) {
- Message message = conversation.getMessages()
- .get(i);
+ Message message = conversation.getMessages().get(i);
MessagePacket packet = prepareMessagePacket(
- conversation.getAccount(),message);
- connections.get(conversation.getAccount()).sendMessagePacket(
- packet);
+ conversation.getAccount(), message, null);
+ conversation.getAccount().getXmppConnection()
+ .sendMessagePacket(packet);
message.setStatus(Message.STATUS_SEND);
if (conversation.getMode() == Conversation.MODE_SINGLE) {
databaseBackend.updateMessage(message);
@@ -307,16 +438,37 @@ public class XmppConnectionService extends Service {
}
}
- private MessagePacket prepareMessagePacket(Account account, Message message) {
+ private MessagePacket prepareMessagePacket(Account account,
+ Message message, Session otrSession) {
MessagePacket packet = new MessagePacket();
if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
packet.setType(MessagePacket.TYPE_CHAT);
+ if (otrSession != null) {
+ try {
+ packet.setBody(otrSession.transformSending(message
+ .getBody()));
+ } catch (OtrException e) {
+ Log.d(LOGTAG,
+ account.getJid()
+ + ": could not encrypt message to "
+ + message.getCounterpart());
+ }
+ Element privateMarker = new Element("private");
+ privateMarker.setAttribute("xmlns", "urn:xmpp:carbons:2");
+ packet.addChild(privateMarker);
+ packet.setTo(otrSession.getSessionID().getAccountID()+"/"+otrSession.getSessionID().getUserID());
+ packet.setFrom(account.getFullJid());
+ } else {
+ packet.setBody(message.getBody());
+ packet.setTo(message.getCounterpart());
+ packet.setFrom(account.getJid());
+ }
} else if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
packet.setType(MessagePacket.TYPE_GROUPCHAT);
+ packet.setBody(message.getBody());
+ packet.setTo(message.getCounterpart());
+ packet.setFrom(account.getJid());
}
- packet.setTo(message.getCounterpart());
- packet.setFrom(account.getJid());
- packet.setBody(message.getBody());
return packet;
}
@@ -345,7 +497,7 @@ public class XmppConnectionService extends Service {
query.setAttribute("xmlns", "jabber:iq:roster");
query.setAttribute("ver", "");
iqPacket.addChild(query);
- connections.get(account).sendIqPacket(iqPacket,
+ account.getXmppConnection().sendIqPacket(iqPacket,
new OnIqPacketReceived() {
@Override
@@ -488,7 +640,7 @@ public class XmppConnectionService extends Service {
if (muc) {
conversation.setMode(Conversation.MODE_MULTI);
if (account.getStatus() == Account.STATUS_ONLINE) {
- joinMuc(account, conversation);
+ joinMuc(conversation);
}
} else {
conversation.setMode(Conversation.MODE_SINGLE);
@@ -506,7 +658,7 @@ public class XmppConnectionService extends Service {
conversation = new Conversation(conversationName, account, jid,
Conversation.MODE_MULTI);
if (account.getStatus() == Account.STATUS_ONLINE) {
- joinMuc(account, conversation);
+ joinMuc(conversation);
}
} else {
conversation = new Conversation(conversationName, account, jid,
@@ -523,6 +675,17 @@ public class XmppConnectionService extends Service {
}
public void archiveConversation(Conversation conversation) {
+ if (conversation.getMode() == Conversation.MODE_MULTI) {
+ leaveMuc(conversation);
+ } else {
+ try {
+ conversation.endOtrIfNeeded();
+ } catch (OtrException e) {
+ Log.d(LOGTAG,
+ "error ending otr session for "
+ + conversation.getName());
+ }
+ }
this.databaseBackend.updateConversation(conversation);
this.conversations.remove(conversation);
if (this.convChangedListener != null) {
@@ -537,23 +700,18 @@ public class XmppConnectionService extends Service {
public void createAccount(Account account) {
databaseBackend.createAccount(account);
this.accounts.add(account);
- this.connections.put(account, this.createConnection(account));
+ account.setXmppConnection(this.createConnection(account));
if (accountChangedListener != null)
accountChangedListener.onAccountListChangedListener();
}
public void updateAccount(Account account) {
databaseBackend.updateAccount(account);
- XmppConnection connection = this.connections.get(account);
- if (connection != null) {
- connection.disconnect();
- this.connections.remove(account);
+ if (account.getXmppConnection() != null) {
+ disconnect(account);
}
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
- this.connections.put(account, this.createConnection(account));
- } else {
- Log.d(LOGTAG, account.getJid()
- + ": not starting because it's disabled");
+ account.setXmppConnection(this.createConnection(account));
}
if (accountChangedListener != null)
accountChangedListener.onAccountListChangedListener();
@@ -561,10 +719,8 @@ public class XmppConnectionService extends Service {
public void deleteAccount(Account account) {
Log.d(LOGTAG, "called delete account");
- if (this.connections.containsKey(account)) {
- Log.d(LOGTAG, "found connection. disconnecting");
- this.connections.get(account).disconnect();
- this.connections.remove(account);
+ if (account.getXmppConnection() != null) {
+ this.disconnect(account);
}
databaseBackend.deleteAccount(account);
this.accounts.remove(account);
@@ -596,31 +752,54 @@ public class XmppConnectionService extends Service {
Conversation conversation = conversations.get(i);
if ((conversation.getMode() == Conversation.MODE_MULTI)
&& (conversation.getAccount() == account)) {
- joinMuc(account, conversation);
+ joinMuc(conversation);
}
}
}
- public void joinMuc(Account account, Conversation conversation) {
+ public void joinMuc(Conversation conversation) {
String muc = conversation.getContactJid();
PresencePacket packet = new PresencePacket();
- packet.setAttribute("to", muc + "/" + account.getUsername());
+ packet.setAttribute("to", muc + "/"
+ + conversation.getAccount().getUsername());
Element x = new Element("x");
x.setAttribute("xmlns", "http://jabber.org/protocol/muc");
if (conversation.getMessages().size() != 0) {
Element history = new Element("history");
- history.setAttribute(
- "seconds",
+ history.setAttribute("seconds",
(System.currentTimeMillis() - conversation
- .getLatestMessageDate()) / 1000 + "");
+ .getLatestMessage().getTimeSent() / 1000) + "");
x.addChild(history);
}
packet.addChild(x);
- connections.get(conversation.getAccount()).sendPresencePacket(packet);
+ conversation.getAccount().getXmppConnection()
+ .sendPresencePacket(packet);
}
- public void disconnectMultiModeConversations() {
+ public void leaveMuc(Conversation conversation) {
+
+ }
+ public void disconnect(Account account) {
+ List<Conversation> conversations = getConversations();
+ for (int i = 0; i < conversations.size(); i++) {
+ Conversation conversation = conversations.get(i);
+ if (conversation.getAccount() == account) {
+ if (conversation.getMode() == Conversation.MODE_MULTI) {
+ leaveMuc(conversation);
+ } else {
+ try {
+ conversation.endOtrIfNeeded();
+ } catch (OtrException e) {
+ Log.d(LOGTAG, "error ending otr session for "
+ + conversation.getName());
+ }
+ }
+ }
+ }
+ account.getXmppConnection().disconnect();
+ Log.d(LOGTAG, "disconnected account: " + account.getJid());
+ account.setXmppConnection(null);
}
@Override
diff --git a/src/de/gultsch/chat/ui/ConversationActivity.java b/src/de/gultsch/chat/ui/ConversationActivity.java
index 66cec2e7..011261e7 100644
--- a/src/de/gultsch/chat/ui/ConversationActivity.java
+++ b/src/de/gultsch/chat/ui/ConversationActivity.java
@@ -9,6 +9,7 @@ import de.gultsch.chat.R;
import de.gultsch.chat.R.id;
import de.gultsch.chat.entities.Contact;
import de.gultsch.chat.entities.Conversation;
+import de.gultsch.chat.entities.Message;
import de.gultsch.chat.utils.UIHelper;
import android.net.Uri;
import android.os.Bundle;
@@ -114,7 +115,7 @@ public class ConversationActivity extends XmppActivity {
Collections.sort(this.conversationList, new Comparator<Conversation>() {
@Override
public int compare(Conversation lhs, Conversation rhs) {
- return (int) (rhs.getLatestMessageDate() - lhs.getLatestMessageDate());
+ return (int) (rhs.getLatestMessage().getTimeSent() - lhs.getLatestMessage().getTimeSent());
}
});
}
@@ -143,7 +144,7 @@ public class ConversationActivity extends XmppActivity {
TextView convName = (TextView) view.findViewById(R.id.conversation_name);
convName.setText(conv.getName());
TextView convLastMsg = (TextView) view.findViewById(R.id.conversation_lastmsg);
- convLastMsg.setText(conv.getLatestMessage());
+ convLastMsg.setText(conv.getLatestMessage().getBody());
if(!conv.isRead()) {
convName.setTypeface(null,Typeface.BOLD);
@@ -154,7 +155,7 @@ public class ConversationActivity extends XmppActivity {
}
((TextView) view.findViewById(R.id.conversation_lastupdate))
- .setText(UIHelper.readableTimeDifference(getItem(position).getLatestMessageDate()));
+ .setText(UIHelper.readableTimeDifference(getItem(position).getLatestMessage().getTimeSent()));
Uri profilePhoto = getItem(position).getProfilePhotoUri();
ImageView imageView = (ImageView) view.findViewById(R.id.conversation_image);
@@ -238,18 +239,23 @@ public class ConversationActivity extends XmppActivity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.conversations, menu);
-
+ MenuItem menuSecure = (MenuItem) menu.findItem(R.id.action_security);
+
if (spl.isOpen()) {
((MenuItem) menu.findItem(R.id.action_archive)).setVisible(false);
((MenuItem) menu.findItem(R.id.action_details)).setVisible(false);
- ((MenuItem) menu.findItem(R.id.action_security)).setVisible(false);
+ menuSecure.setVisible(false);
} else {
((MenuItem) menu.findItem(R.id.action_add)).setVisible(false);
if (this.getSelectedConversation()!=null) {
if (this.getSelectedConversation().getMode() == Conversation.MODE_MULTI) {
((MenuItem) menu.findItem(R.id.action_security)).setVisible(false);
- ((MenuItem) menu.findItem(R.id.action_details)).setVisible(false);
+ menuSecure.setVisible(false);
((MenuItem) menu.findItem(R.id.action_archive)).setTitle("Leave conference");
+ } else {
+ if (this.getSelectedConversation().getLatestMessage().getEncryption() != Message.ENCRYPTION_NONE) {
+ menuSecure.setIcon(R.drawable.ic_action_secure);
+ }
}
}
}
diff --git a/src/de/gultsch/chat/ui/ConversationFragment.java b/src/de/gultsch/chat/ui/ConversationFragment.java
index f7513d08..8ec511fc 100644
--- a/src/de/gultsch/chat/ui/ConversationFragment.java
+++ b/src/de/gultsch/chat/ui/ConversationFragment.java
@@ -15,7 +15,6 @@ import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
@@ -36,38 +35,30 @@ public class ConversationFragment extends Fragment {
protected ArrayAdapter<Message> messageListAdapter;
protected Contact contact;
+ private EditText chatMsg;
+ private int nextMessageEncryption = Message.ENCRYPTION_NONE;
+
@Override
public View onCreateView(final LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
this.inflater = inflater;
-
final View view = inflater.inflate(R.layout.fragment_conversation,
container, false);
+ chatMsg = (EditText) view.findViewById(R.id.textinput);
((ImageButton) view.findViewById(R.id.textSendButton))
.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
ConversationActivity activity = (ConversationActivity) getActivity();
- EditText chatMsg = (EditText) view
- .findViewById(R.id.textinput);
if (chatMsg.getText().length() < 1)
return;
Message message = new Message(conversation, chatMsg
- .getText().toString(), Message.ENCRYPTION_NONE);
+ .getText().toString(), nextMessageEncryption);
activity.xmppConnectionService.sendMessage(conversation.getAccount(),message);
chatMsg.setText("");
-
- /*if (conversation.getMode()==Conversation.MODE_SINGLE) {
- conversation.getMessages().add(message);
- messageList.add(message);
- }*/
-
- //activity.updateConversationList();
-
- //messagesView.setSelection(messageList.size() - 1);
}
});
@@ -213,6 +204,22 @@ public class ConversationFragment extends Fragment {
this.messageList.clear();
this.messageList.addAll(this.conversation.getMessages());
this.messageListAdapter.notifyDataSetChanged();
+ if (messageList.size()>=1) {
+ nextMessageEncryption = this.conversation.getLatestMessage().getEncryption();
+ }
+ getActivity().invalidateOptionsMenu();
+ switch (nextMessageEncryption) {
+ case Message.ENCRYPTION_NONE:
+ chatMsg.setHint("Send plain text message");
+ break;
+ case Message.ENCRYPTION_OTR:
+ chatMsg.setHint("Send OTR encrypted message");
+ break;
+ case Message.ENCRYPTION_PGP:
+ chatMsg.setHint("Send openPGP encryted messeage");
+ default:
+ break;
+ }
int size = this.messageList.size();
if (size >= 1)
messagesView.setSelection(size - 1);
diff --git a/src/de/gultsch/chat/utils/UIHelper.java b/src/de/gultsch/chat/utils/UIHelper.java
index 8e6d8e5f..df3f9c1c 100644
--- a/src/de/gultsch/chat/utils/UIHelper.java
+++ b/src/de/gultsch/chat/utils/UIHelper.java
@@ -92,7 +92,7 @@ public class UIHelper {
.getName(), (int) res
.getDimension(android.R.dimen.notification_large_icon_width)));
mBuilder.setContentTitle(conversation.getName());
- mBuilder.setTicker(conversation.getLatestMessage().trim());
+ mBuilder.setTicker(conversation.getLatestMessage().getBody().trim());
StringBuilder bigText = new StringBuilder();
List<Message> messages = conversation.getMessages();
String firstLine = "";
diff --git a/src/de/gultsch/chat/xml/TagWriter.java b/src/de/gultsch/chat/xml/TagWriter.java
index 401a6eee..844f5253 100644
--- a/src/de/gultsch/chat/xml/TagWriter.java
+++ b/src/de/gultsch/chat/xml/TagWriter.java
@@ -3,12 +3,7 @@ package de.gultsch.chat.xml;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
import android.util.Log;
diff --git a/src/de/gultsch/chat/xmpp/MessagePacket.java b/src/de/gultsch/chat/xmpp/MessagePacket.java
index 5da76152..0d4d07d7 100644
--- a/src/de/gultsch/chat/xmpp/MessagePacket.java
+++ b/src/de/gultsch/chat/xmpp/MessagePacket.java
@@ -42,6 +42,7 @@ public class MessagePacket extends Element {
}
public void setBody(String text) {
+ this.children.remove(findChild("body"));
Element body = new Element("body");
body.setContent(text);
this.children.add(body);
diff --git a/src/de/gultsch/chat/xmpp/XmppConnection.java b/src/de/gultsch/chat/xmpp/XmppConnection.java
index 337d8c7d..1b9aa0c3 100644
--- a/src/de/gultsch/chat/xmpp/XmppConnection.java
+++ b/src/de/gultsch/chat/xmpp/XmppConnection.java
@@ -411,6 +411,7 @@ public class XmppConnection implements Runnable {
}
public void sendMessagePacket(MessagePacket packet) {
+ Log.d(LOGTAG,"sending message packet "+packet.toString());
tagWriter.writeElement(packet);
}
@@ -440,6 +441,6 @@ public class XmppConnection implements Runnable {
public void disconnect() {
shouldConnect = false;
- tagWriter.writeTag(Tag.end("stream"));
+ tagWriter.writeTag(Tag.end("stream:stream"));
}
}